# 릴리스 운영 매뉴얼 이 문서는 Core2026의 profile 서버와 Gateway를 Git commit 단위로 배포하고 되돌리는 현재 운영 경계를 설명합니다. Profile은 Gateway orchestrator가, Gateway 전체는 별도 release-controller가 처리합니다. ## 구성과 권한 | 대상 | 요청 경로 | 실행자 | 영속 상태 | | --------------------------------- | ------------------- | ----------------------- | ------------------------------------------------ | | `che`, `hwe` 등 profile | Gateway 관리자 화면 | Gateway orchestrator | `GatewayOperation`, `GatewayProfile` | | Gateway API·frontend·orchestrator | Gateway 관리자 화면 | 외부 release-controller | `GatewayReleaseOperation`, `GatewayReleaseState` | | release-controller 자체 | 별도 CLI process | self-upgrade CLI | PM2 `sammo:release-controller` | Profile 화면은 `/gateway/admin/servers/:profileName/version`과 `/gateway/admin/servers/:profileName/scenario`, Gateway 화면은 `/gateway/admin/releases`입니다. 이전 `/gateway/admin/server-operations`는 호환성을 위해 서버 목록으로 이동합니다. Profile 작업은 runtime/settings/deploy/reset capability로 분리되며 포괄 운영 권한은 사용하지 않습니다. Gateway 전체 릴리스에는 profile 범위 권한과 별개인 전역 `admin.releases.manage` 권한이 필요합니다. 일반 사용자와 권한이 없는 관리자는 Gateway 릴리스 영역을 사용할 수 없습니다. 운영 전에 다음을 확인해 주세요. - 대상 branch 또는 전체 commit SHA가 Core2026 저장소에 존재합니다. - 대상 commit의 `release-manifest.json`에 필요한 component와 현재 migration head가 들어 있습니다. - Gateway PostgreSQL, profile PostgreSQL, Redis와 PM2가 준비되어 있습니다. - controller가 `GATEWAY_DATABASE_URL`, `GATEWAY_DB_SCHEMA`, workspace와 worktree 경로를 올바르게 읽습니다. - 공개 hostname을 쉼표로 구분한 `VITE_PREVIEW_ALLOWED_HOSTS`가 Gateway와 profile frontend build 환경에 전달됩니다. 신뢰된 reverse proxy 뒤가 아니라면 `*`를 사용하지 않습니다. - PM2 definition은 Gateway에 `GATEWAY_ROLE=api|orchestrator`, game API에 `GAME_API_ROLE=server|*-worker`, turn daemon에 `GAME_ENGINE_ROLE=turn-daemon`을 명시합니다. library import나 PM2 wrapper의 argv만으로 실행 역할을 추론하지 않습니다. - PM2 child에서 상속된 `pm_id`, `name`, `pm_exec_path`, `NODE_APP_INSTANCE`와 `axm_*` 메타데이터는 새 definition에 전달하지 않습니다. 동일 process 이름은 시작 전에 제거하며 PM2 start는 남은 동일 이름을 거부합니다. - Runtime process는 10초 이전의 불안정 종료에 대해 최대 5회, 2초 간격으로만 자동 재시작합니다. Readiness는 예상 process 수가 정확하고 모든 restart count가 0일 때만 성공합니다. - Root와 server package의 `tsdown`은 0.22.14 계열로 통일합니다. Docker runtime의 Node heap/Rayon 상한을 상속한 동일 toolchain으로 초기 Gateway와 profile worktree를 빌드하여 구형 Rolldown의 과도한 native thread 생성을 피합니다. - Profile, Gateway와 controller self-upgrade의 server package build는 Turbo DAG를 사용합니다. 실행 중인 game/Gateway process와 4 GiB runtime을 공유하는 기본 동시성은 1이며, 더 큰 격리 build host에서는 `RELEASE_TURBO_CONCURRENCY`를 측정 후 2 이상으로 올릴 수 있습니다. 기본 local cache는 원래 Core checkout의 `.turbo/release-cache`이므로 commit별 worktree가 달라도 재사용됩니다. 별도 persistent 경로가 필요하면 controller/orchestrator 환경에 `TURBO_CACHE_DIR`을 설정합니다. Cache는 재생성 가능한 build artifact이며 DB/Redis backup이 아닙니다. - Server build 뒤 frontend release는 `typecheck:release`와 Vite bundle을 별도 Turbo task로 실행합니다. 정적 운영 모드의 profile은 profile별 base/API 값을 build env에서 제거하고 `VITE_ASSET_BASE_PATH=./`인 공용 bundle을 commit당 한 번 생성합니다. 타입검사 hash에는 frontend 자체와 직접 해석하는 common/infra/ logic/game-engine/game-api/gateway-api source, Prisma schema와 기본 navigation resource가 들어가며, bundle hash에는 `NODE_ENV`와 모든 `VITE_*`가 포함됩니다. 따라서 내부 type source와 공개 build-time 값이 다른 frontend artifact를 cache hit로 잘못 복원하지 않습니다. Profile base path와 API/SSE URL은 공용 bundle 입력이 아니라 게시 시점의 runtime JSON config이므로 profile 사이에서 의도적으로 같은 cache key를 사용합니다. 일반 개발용 `pnpm --filter build`의 typecheck 계약은 그대로입니다. `NODE_OPTIONS`와 `RAYON_NUM_THREADS`는 출력에는 영향을 주지 않는 resource 제한으로 build child에 전달됩니다. - `TURN_DAEMON_NODE_OPTIONS`가 설정되어 있으면 Gateway orchestrator는 그 값을 turn-daemon PM2 process의 `NODE_OPTIONS`로만 덮어씁니다. API·frontend·worker는 공용 `NODE_OPTIONS`를 계속 사용합니다. Profile의 `vue-tsc`와 Vite build만 더 큰 heap이 필요하면 `PROFILE_FRONTEND_BUILD_NODE_OPTIONS`를 지정합니다. 이 값은 profile frontend release task의 `NODE_OPTIONS`만 덮어쓰며 server package Turbo build와 배포 후 PM2 process의 heap은 바꾸지 않습니다. 전용 heap을 늘릴 때는 runtime container hard limit과 전체 process RSS를 먼저 확인합니다. - migration 이후 이전 애플리케이션으로 돌아갈 때 schema 하위 호환성이 유지됩니다. ## Commit worktree 자동 정리 Profile orchestrator와 Gateway release-controller는 서로 다른 worktree root를 사용하지만 같은 보존 정책을 적용합니다. 각 daemon은 시작 시 한 번, 이후 24시간마다 자신이 소유한 commit worktree를 점검합니다. - `GatewayProfile.buildWorkspace`, `RUNNING`/`QUEUED` profile 빌드 대상, `GatewayReleaseState`의 active/previous workspace는 기간과 무관하게 보호합니다. - 활성 PM2 process의 cwd 또는 script 아래에 있는 worktree도 보호합니다. 여기에는 self-upgrade된 release-controller worktree도 포함됩니다. - 보호 대상이 아닌 worktree는 마지막 prepare 이후 최소 24시간을 유예하고, 그중 최신 2개는 재시도 cache로 더 남깁니다. 나머지는 Git worktree로 제거하고 `git worktree prune --expire now`로 사라진 metadata를 정리합니다. - tracked 또는 untracked 변경이 있으면 자동 삭제하지 않습니다. Git 제거 실패를 raw directory 삭제로 우회하지 않으며 다음 주기까지 보존합니다. - 정리는 commit checkout과 재생성 가능한 build artifact만 대상으로 합니다. Gateway/profile PostgreSQL, Redis, image, runtime data volume에는 접근하지 않습니다. 따라서 하루 안에 매우 많은 commit을 연속 배포하면 유예 구간만큼 일시적으로 늘 수 있지만, active/rollback/current profile 경로 외의 장기 누적은 다음 정리 주기에 제거됩니다. Profile 관리자 API의 `admin.profiles.cleanupWorkspaces`는 같은 보호 규칙을 사용하므로 진행 중인 build/operation이 있으면 전체 정리를 보류합니다. PM2의 Node client는 한 process 안에서 공유 connection을 사용합니다. Profile orchestrator가 시작될 때 reconcile과 worktree 정리가 동시에 `connect/list/disconnect`를 호출해 한 callback이 사라지면, PM2에는 orchestrator가 `online`으로 보이면서도 정리 flag가 풀리지 않아 profile operation poll이 계속 대기할 수 있습니다. 제품 경계에서는 PM2 session을 직렬화하고 `connect/list`를 5초, start/stop/delete를 30초로 제한합니다. Timeout은 scheduled task 오류로 끝나 정리 flag를 해제하고 다음 5초 operation poll이 queue를 다시 claim하게 합니다. PM2 mutation이 timeout된 경우에는 같은 mutation을 즉시 직접 반복하지 않고 실제 process 목록과 operation terminal 상태를 먼저 재조회합니다. ## Profile 배포 버전 업데이트 화면에서 profile의 branch 또는 commit을 선택합니다. Branch는 worker가 작업을 claim할 때 commit으로 해석하며, commit 입력은 전체 SHA로 고정됩니다. 같은 profile에는 `QUEUED` 또는 `RUNNING` 작업을 동시에 하나만 둘 수 있습니다. Profile 작업과 Gateway 전체 릴리스 claim도 같은 PostgreSQL advisory lock으로 직렬화합니다. 따라서 profile `RESET`/`DEPLOY`가 실행 중이면 Gateway 릴리스는 queue에서 기다리고, Gateway 릴리스가 실행 중이면 새 profile 작업이 기다립니다. Gateway process 전환이 진행 중인 profile migration·seed 실행자를 중단하지 않는 운영 계약입니다. 상대 Gateway 릴리스가 terminal인데도 profile 작업이 두 번의 poll 주기 이상 `QUEUED`, `attempts=0`이면 단순 build 지연이 아닙니다. Gateway orchestrator의 PM2 상태뿐 아니라 최근 started 로그, 활성 release row와 profile operation row를 함께 확인합니다. DB에 활성 release가 없고 API/frontend/profile runtime이 정상인 경우에만 현재 definition의 `sammo:gateway-orchestrator` 한 process를 재시작해 queue poll을 복구할 수 있습니다. Container, release-controller와 game daemon은 함께 재시작하지 않습니다. ### DB 유지 배포 `DB 유지 배포`는 현재 시즌을 계속 운영하면서 코드를 교체할 때 사용합니다. 1. 대상 commit의 game API target과 그 transitive engine/worker artifact를 빌드한 뒤, profile frontend typecheck와 bundle을 공유 Turbo cache에서 복원하거나 생성합니다. 2. 기존 profile PM2 process를 정지합니다. 3. profile game schema에 `prisma migrate deploy`를 실행합니다. 4. Scenario seed를 실행하지 않고 frontend, API, daemon과 worker를 시작합니다. 5. HTTP와 모든 PM2 role의 readiness가 확인된 뒤 build commit을 게시합니다. 이 모드는 현재 scenario, status와 인게임 DB를 유지합니다. Migration이 데이터를 변환할 수 있으므로 대상 migration의 운영 데이터 영향은 배포 전에 별도로 검토해 주세요. Profile frontend bundle은 package의 `.release-build`에 상대 asset URL로 한 번 생성되어 profile과 무관한 Turbo cache에 저장됩니다. Orchestrator는 이를 `game-assets/releases/` 불변 release로 한 번 stage하고, 각 profile에는 base path, API/SSE URL과 Gateway URL을 `