fix(deploy): webhook deployer spawn 로그 redirect + base64 quoting
Deploy momo-erp / deploy (push) Failing after 14m17s
Deploy momo-erp / deploy (push) Failing after 14m17s
이전: spawn(docker, [...], { stdio: 'ignore' }) → 에러 보이지 않음
이제: sh -c 'docker run -d ... >> log 2>&1' → 컨테이너 ID/에러 모두 로그
+ 내부 스크립트 base64 인코딩으로 sh -c 중첩 quoting 안전화
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user