fix: isolate child PM2 arguments

This commit is contained in:
2026-08-08 15:05:55 +00:00
parent 19d76d23de
commit 5e82fb52c6
6 changed files with 43 additions and 3 deletions
@@ -24,6 +24,7 @@ export interface ProcessManager {
const PM2_INTERNAL_ENV_KEYS = new Set([
'NODE_APP_INSTANCE',
'args',
'autorestart',
'autostart',
'created_at',
@@ -179,6 +179,7 @@ describe('buildProcessDefinitions', () => {
DATABASE_URL: 'postgresql://integration.invalid/sammo',
VITE_APP_BASE_PATH: '/gateway',
GATEWAY_ROLE: 'orchestrator',
args: 'daemon',
NODE_APP_INSTANCE: '2',
name: 'sammo:gateway-orchestrator',
pm_id: '2',
@@ -189,6 +190,7 @@ describe('buildProcessDefinitions', () => {
for (const definition of Object.values(definitions)) {
expect(definition.env).toMatchObject({ DATABASE_URL: 'postgresql://integration.invalid/sammo' });
expect(definition.env).not.toHaveProperty('pm_id');
expect(definition.env).not.toHaveProperty('args');
expect(definition.env).not.toHaveProperty('pm_exec_path');
expect(definition.env).not.toHaveProperty('name');
expect(definition.env).not.toHaveProperty('NODE_APP_INSTANCE');
@@ -206,6 +208,7 @@ describe('sanitizeManagedProcessEnv', () => {
PATH: '/usr/local/bin:/usr/bin',
GATEWAY_ROLE: 'orchestrator',
GAME_API_ROLE: 'server',
args: 'daemon',
NODE_APP_INSTANCE: '2',
name: 'sammo:gateway-orchestrator',
pm_id: '2',
@@ -11,6 +11,7 @@ describe('buildPm2StartOptions', () => {
env: {
DATABASE_URL: 'postgresql://integration.invalid/sammo',
GAME_API_ROLE: 'server',
args: 'daemon',
pm_id: '2',
pm_exec_path: '/srv/sammo/app/gateway-api/dist/index.js',
name: 'sammo:gateway-orchestrator',
@@ -31,9 +32,27 @@ describe('buildPm2StartOptions', () => {
},
});
expect(options.env).not.toHaveProperty('pm_id');
expect(options.env).not.toHaveProperty('args');
expect(options.env).not.toHaveProperty('pm_exec_path');
expect(options.env).not.toHaveProperty('name');
expect(options.env).not.toHaveProperty('NODE_APP_INSTANCE');
expect(options.env).toHaveProperty('GAME_API_ROLE', 'server');
});
it('keeps explicit child arguments when a PM2 parent exposes its own args in the environment', () => {
const options = buildPm2StartOptions({
name: 'sammo:gateway-frontend',
script: '/srv/sammo/app/gateway-frontend/node_modules/vite/bin/vite.js',
cwd: '/srv/sammo/app/gateway-frontend',
args: ['preview', '--host', '0.0.0.0', '--port', '15000'],
env: {
args: 'daemon',
name: 'sammo:release-controller',
pm_id: '3',
},
});
expect(options.args).toEqual(['preview', '--host', '0.0.0.0', '--port', '15000']);
expect(options.env).not.toHaveProperty('args');
});
});
+5
View File
@@ -13,6 +13,11 @@
5. 두 HTTP endpoint와 세 PM2 process가 모두 준비된 경우에만 현재·이전
릴리스 상태를 게시합니다. 실패하면 이전 세 프로세스를 복구합니다.
Controller는 PM2 자식으로 실행되지만 자신의 `args=daemon`과 PM2 identity를
Gateway process 환경에 전달하지 않습니다. 이 값이 frontend 정의를 덮으면 Vite가
의도한 preview port 대신 기본 개발 port로 실행될 수 있으므로, process `online`
여부뿐 아니라 Gateway API와 frontend HTTP readiness를 모두 확인합니다.
## 환경 변수
- `GATEWAY_DATABASE_URL`: Gateway PostgreSQL URL입니다. 필수입니다.
@@ -119,9 +119,7 @@ const gatewayNames = ['sammo:gateway-api', 'sammo:gateway-frontend', 'sammo:gate
it('runs Gateway preview from the frontend workspace dependency', () => {
const definitions = buildGatewayProcessDefinitions('/srv/sammo/release', config);
const frontend = definitions.find((definition) => definition.name === 'sammo:gateway-frontend');
expect(frontend?.script).toBe(
'/srv/sammo/release/app/gateway-frontend/node_modules/vite/bin/vite.js'
);
expect(frontend?.script).toBe('/srv/sammo/release/app/gateway-frontend/node_modules/vite/bin/vite.js');
});
it('does not forward release-controller PM2 identity to Gateway processes', () => {
@@ -130,6 +128,7 @@ it('does not forward release-controller PM2 identity to Gateway processes', () =
baseEnv: {
DATABASE_URL: 'postgresql://integration.invalid/sammo',
GATEWAY_ROLE: 'orchestrator',
args: 'daemon',
name: 'sammo:release-controller',
pm_id: '3',
pm_exec_path: '/srv/release-controller.js',
@@ -139,6 +138,7 @@ it('does not forward release-controller PM2 identity to Gateway processes', () =
for (const definition of definitions) {
expect(definition.env).toMatchObject({ DATABASE_URL: 'postgresql://integration.invalid/sammo' });
expect(definition.env).not.toHaveProperty('pm_id');
expect(definition.env).not.toHaveProperty('args');
expect(definition.env).not.toHaveProperty('pm_exec_path');
expect(definition.env).not.toHaveProperty('name');
}
@@ -250,6 +250,7 @@ describe('resolveReleaseControllerConfig', () => {
const resolved = resolveReleaseControllerConfig({
GATEWAY_DATABASE_URL: 'postgresql://user:pass@127.0.0.1:5432/sammo',
RELEASE_CONTROLLER_WORKSPACE_ROOT: '/srv/sammo/controller',
args: 'daemon',
pm_id: '3',
name: 'sammo:release-controller',
axm_monitor: '{}',
@@ -260,6 +261,7 @@ describe('resolveReleaseControllerConfig', () => {
RELEASE_CONTROLLER_WORKSPACE_ROOT: '/srv/sammo/controller',
});
expect(resolved.baseEnv).not.toHaveProperty('pm_id');
expect(resolved.baseEnv).not.toHaveProperty('args');
expect(resolved.baseEnv).not.toHaveProperty('name');
expect(resolved.baseEnv).not.toHaveProperty('axm_monitor');
});
+10
View File
@@ -107,6 +107,13 @@ Gateway는 자기 process를 직접 교체하지 않습니다. 관리자 화면
확인합니다.
6. 모두 준비된 경우에만 현재·이전 commit과 workspace를 게시합니다.
release-controller는 PM2 process 안에서 실행되므로 부모의 `args`, `pm_id`,
`pm_exec_path`, `name`, `NODE_APP_INSTANCE``axm_*` 같은 PM2 내부 값을 자식
환경으로 전달하지 않습니다. 특히 부모의 `args=daemon`이 frontend의
`vite preview --host 0.0.0.0 --port 15000` 인자를 덮으면 PM2 상태만 `online`이고
Caddy upstream port는 열리지 않을 수 있습니다. 배포 readiness는 process 상태와
두 HTTP endpoint를 함께 확인해야 합니다.
Gateway 전체에는 활성 릴리스 작업을 동시에 하나만 둘 수 있습니다. 화면의
릴리스 이력에서 요청 source, 고정 commit, 상태와 오류를 확인할 수 있습니다.
@@ -173,6 +180,9 @@ pnpm --filter @sammo-ts/release-controller self-upgrade -- COMMIT <full-sha>
- 작업이 `SUCCEEDED`이고 고정 commit이 요청한 commit과 같은지 확인합니다.
- PM2 process 이름별 항목이 정확히 하나이고 restart count가 0이며, cwd와
script가 게시된 worktree를 가리키는지 확인합니다.
- Gateway frontend의 실제 인자가 `preview --host 0.0.0.0 --port 15000`
유지하고 container 내부 `127.0.0.1:15000/gateway/`와 Caddy에서
`runtime:15000/gateway/`가 모두 응답하는지 확인합니다.
- `/gateway/` 또는 대상 profile prefix에 직접 접속하고 새로고침합니다.
- API health, tRPC, SSE와 정적 자산 경로를 확인합니다.
- DB 유지 배포에서는 현재 season/scenario와 핵심 게임 상태가 유지됐는지