#!/usr/bin/env bash # # visionA — stage container 內的多 process 啟動腳本 # # 策略: # 1. 啟動四個 process 為 background: # - api-server :3721 # - remote-proxy :3800/3801 # - node server.js :3000 (Next.js standalone) # - nginx :80 (reverse proxy) # 2. `wait -n` 阻塞,任一 process 結束 → 整個 container 退出(讓 Docker 重啟) # 3. trap SIGTERM / SIGINT → 優雅關閉所有子 process # # 為什麼不用 supervisord: # - supervisord 預設會嘗試重啟死掉的子 process,會掩蓋真正的錯誤(healthcheck 看起來活著) # - 在 stage 階段,「任一 process 死 → container die → docker restart unless-stopped」更乾淨 # - 由 Docker 層處理重啟,比 supervisord 內層重啟更容易看 log 與診斷 # # 為什麼用 bash:alpine 預設 sh 不支援 `wait -n`(bash 4.3+ feature)。 # Dockerfile.stage 已 apk add bash。 set -euo pipefail # ──────────── 環境變數預設值 ──────────── # 這些都可由 .env.stage 覆蓋;這裡只是「即使沒注入也能起來」的 fallback。 : "${VISIONA_API_PORT:=3721}" : "${VISIONA_TUNNEL_PORT:=3800}" : "${VISIONA_PROXY_INTERNAL_PORT:=3801}" : "${VISIONA_PROXY_INTERNAL_URL:=http://127.0.0.1:${VISIONA_PROXY_INTERNAL_PORT}}" : "${VISIONA_HOST:=0.0.0.0}" # Next.js standalone server 預設讀 PORT / HOSTNAME : "${NEXT_PORT:=3000}" : "${NEXT_HOSTNAME:=127.0.0.1}" # ──────────── helpers ──────────── log() { # ISO8601 timestamp 方便 docker logs 排查 printf '[%s] [entrypoint] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*" } # 子 process pid 暫存 PIDS=() # 收到 SIGTERM/SIGINT → 廣播給子 process,等它們收尾 shutdown() { log "received signal — shutting down children: ${PIDS[*]:-}" for pid in "${PIDS[@]:-}"; do if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then kill -TERM "$pid" 2>/dev/null || true fi done # 給 30s 讓子 process 收尾,再強殺 local deadline=$(( $(date +%s) + 30 )) while [ "$(date +%s)" -lt "$deadline" ]; do local alive=0 for pid in "${PIDS[@]:-}"; do if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then alive=1 break fi done [ "$alive" -eq 0 ] && break sleep 1 done for pid in "${PIDS[@]:-}"; do if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then log "force killing pid=$pid" kill -KILL "$pid" 2>/dev/null || true fi done exit 0 } trap shutdown TERM INT # ──────────── 起 process ──────────── log "starting remote-proxy on tunnel:${VISIONA_TUNNEL_PORT} internal:${VISIONA_PROXY_INTERNAL_PORT}" /usr/local/bin/remote-proxy & PIDS+=("$!") # api-server 啟動會立即去探測 remote-proxy 的 internal HTTP(POC 假設)→ 給它一兩秒 sleep 1 log "starting api-server on :${VISIONA_API_PORT}" /usr/local/bin/api-server & PIDS+=("$!") log "starting next.js standalone server on ${NEXT_HOSTNAME}:${NEXT_PORT}" # Next.js 16 standalone 結構: # /var/www/visiona/standalone/server.js # /var/www/visiona/standalone/.next/static/ ← Dockerfile 已 COPY 進來 # /var/www/visiona/standalone/public/ ← Dockerfile 已 COPY 進來 # server.js 用環境變數 PORT / HOSTNAME 控制 listen address ( cd /var/www/visiona/standalone PORT="$NEXT_PORT" HOSTNAME="$NEXT_HOSTNAME" exec node server.js ) & PIDS+=("$!") log "starting nginx (foreground via daemon off)" nginx -g 'daemon off;' & PIDS+=("$!") log "all process started: pids=${PIDS[*]}" # ──────────── 等任一 process 退出 ──────────── # wait -n 在 bash 4.3+ 支援;alpine 用 apk add bash 後可用。 # 任一 process 結束 → 整個 container 跟著結束 → docker restart 重起整套 wait -n exit_code=$? log "a child process exited (code=${exit_code}) — terminating container" # 觸發 shutdown 把其餘 process 收尾 shutdown