diff --git a/src/app/api/deploy/webhook/route.ts b/src/app/api/deploy/webhook/route.ts index 7b2a88d..96e9416 100644 --- a/src/app/api/deploy/webhook/route.ts +++ b/src/app/api/deploy/webhook/route.ts @@ -29,36 +29,46 @@ export async function POST(req: NextRequest) { // 배포가 도중에 끊겼던 문제를 해결. docker daemon 이 띄운 별도 컨테이너는 // momo-erp 의 PID/network 와 무관하게 살아남는다. // - // 임시 컨테이너: - // - root(--user 0:0) 로 실행 → .git permission 문제 없음 - // - 호스트 source 디렉토리와 docker.sock 마운트 - // - 종료 후 자동 삭제(--rm), 로그는 /tmp/momo-deploy.log 에 누적 - const deployCmd = [ - "docker", "run", "-d", "--rm", - "--name", `momo-deployer-${Date.now()}`, - "-v", "/home/chpark/momo-erp/source:/src", - "-w", "/src", - "-v", "/var/run/docker.sock:/var/run/docker.sock", - "--user", "0:0", - "docker:cli", - "sh", "-c", - [ - `echo "[$(date)] deployer 시작" >> ${logPath}`, - `apk add --no-cache git docker-cli-compose >/dev/null 2>&1`, - `git config --global --add safe.directory '*'`, - `git fetch origin >> ${logPath} 2>&1`, - `git reset --hard origin/main >> ${logPath} 2>&1`, - `git rev-parse HEAD > public/build-sha.txt`, - `echo "[$(date)] 배포 SHA: $(cat public/build-sha.txt)" >> ${logPath}`, - `docker compose -f docker-compose.prod.yml build momo-erp >> ${logPath} 2>&1`, - `docker compose -f docker-compose.prod.yml up -d --force-recreate momo-erp >> ${logPath} 2>&1`, - `docker restart traefik >> ${logPath} 2>&1`, - `docker image prune -f >> ${logPath} 2>&1`, - `echo "[$(date)] ✔ 배포 완료" >> ${logPath}`, - ].join(" && "), - ]; + // - root(--user 0:0) → .git permission 무관 + // - --rm 종료 후 자동 삭제 + // - docker run -d 결과(컨테이너 ID 또는 에러) 와 모든 진단 로그를 + // /tmp/momo-deploy.log 에 redirect → 디버깅 가능 + const deployerName = `momo-deployer-${Date.now()}`; + const innerScript = [ + `echo "[$(date)] deployer 내부 시작 ($(hostname))"`, + `apk add --no-cache git docker-cli-compose >/dev/null 2>&1 || echo "[WARN] apk add 실패 (이미 설치돼 있을 수 있음)"`, + `git config --global --add safe.directory '*'`, + `git fetch origin`, + `git reset --hard origin/main`, + `git rev-parse HEAD > public/build-sha.txt`, + `echo "[$(date)] 배포 SHA: $(cat public/build-sha.txt)"`, + `docker compose -f docker-compose.prod.yml build momo-erp`, + `docker compose -f docker-compose.prod.yml up -d --force-recreate momo-erp`, + `docker restart traefik || true`, + `docker image prune -f >/dev/null 2>&1 || true`, + `echo "[$(date)] ✔ 배포 완료"`, + ].join(" && "); - const child = spawn(deployCmd[0], deployCmd.slice(1), { detached: true, stdio: "ignore" }); + // 안전한 shell quoting: innerScript 를 base64 인코딩해서 컨테이너 안에서 디코드 실행 + // (sh -c 내부에 sh -c 가 중첩되며 따옴표 escaping 이 깨지는 위험을 피함) + const innerB64 = Buffer.from(innerScript).toString("base64"); + + // 호스트의 docker daemon 에 -d(detached) 컨테이너 spawn 요청. + // 그 자체 명령은 1초 안에 끝나고 컨테이너 ID 반환 → momo-erp 가 죽어도 deployer 는 계속 돔. + const outerScript = ` +echo "[$(date)] webhook 호출됨 — deployer spawn 시도: ${deployerName}" >> ${logPath} +docker run -d --rm \ + --name ${deployerName} \ + -v /home/chpark/momo-erp/source:/deploy/source \ + -w /deploy/source \ + -v /var/run/docker.sock:/var/run/docker.sock \ + --user 0:0 \ + docker:cli \ + sh -c "echo ${innerB64} | base64 -d | sh" >> ${logPath} 2>&1 +echo "[$(date)] deployer spawn 종료(exit=$?)" >> ${logPath} +`; + + const child = spawn("/bin/sh", ["-c", outerScript], { detached: true, stdio: "ignore" }); child.unref(); return NextResponse.json({