All checks were successful
ci / 정본·보안·린트·타입·테스트 (push) Successful in 49s
ci / 모바일 린트·타입·Jest (push) Successful in 45s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 22s
ci / .NET API 서버 테스트 (push) Successful in 14s
deploy-site / deploy (push) Successful in 22s
ci / 워크스페이스 빌드 검증 (push) Successful in 31s
173 lines
7.2 KiB
Bash
173 lines
7.2 KiB
Bash
#!/bin/bash
|
|
# ============================================================================
|
|
# scripts/deploy-nas.sh
|
|
# D3RO Voice — Automated NAS Docker Deployment & Packaging Script (Bash)
|
|
# ============================================================================
|
|
|
|
set -e
|
|
|
|
: "${JWT_SECRET:?JWT_SECRET is required}"
|
|
: "${JWT_ISSUER:?JWT_ISSUER is required}"
|
|
: "${JWT_AUDIENCE:?JWT_AUDIENCE is required}"
|
|
: "${ADMIN_BOOTSTRAP_TOKEN:?ADMIN_BOOTSTRAP_TOKEN is required}"
|
|
: "${D3RO_API_TOKEN:?D3RO_API_TOKEN is required}"
|
|
: "${ADMIN_SESSION_SECRET:?ADMIN_SESSION_SECRET is required}"
|
|
: "${API_SERVER_URL:?API_SERVER_URL is required}"
|
|
: "${CORS_ALLOWED_ORIGINS:?CORS_ALLOWED_ORIGINS is required}"
|
|
: "${ALLOWED_HOSTS:?ALLOWED_HOSTS is required}"
|
|
: "${SUPABASE_URL:?SUPABASE_URL is required}"
|
|
: "${SUPABASE_SERVICE_ROLE_KEY:?SUPABASE_SERVICE_ROLE_KEY is required}"
|
|
# Web app (@d3ro/web) build args — NEXT_PUBLIC_* values are baked into the bundle at build time.
|
|
: "${NEXT_PUBLIC_SUPABASE_URL:?NEXT_PUBLIC_SUPABASE_URL is required}"
|
|
: "${NEXT_PUBLIC_SUPABASE_ANON_KEY:?NEXT_PUBLIC_SUPABASE_ANON_KEY is required}"
|
|
|
|
for value in \
|
|
"$JWT_SECRET" \
|
|
"$JWT_ISSUER" \
|
|
"$JWT_AUDIENCE" \
|
|
"$ADMIN_BOOTSTRAP_TOKEN" \
|
|
"$D3RO_API_TOKEN" \
|
|
"$ADMIN_SESSION_SECRET" \
|
|
"$API_SERVER_URL" \
|
|
"$CORS_ALLOWED_ORIGINS" \
|
|
"$ALLOWED_HOSTS" \
|
|
"$SUPABASE_URL" \
|
|
"$SUPABASE_SERVICE_ROLE_KEY" \
|
|
"$NEXT_PUBLIC_SUPABASE_URL" \
|
|
"$NEXT_PUBLIC_SUPABASE_ANON_KEY" \
|
|
"${NEXT_PUBLIC_PAYPLE_CLIENT_KEY:-}"; do
|
|
case "$value" in
|
|
*$'\n'*|*$'\r'*) echo "Deployment values must be single-line" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [ "${#D3RO_API_TOKEN}" -lt 32 ]; then
|
|
echo "D3RO_API_TOKEN must contain at least 32 characters" >&2
|
|
exit 1
|
|
fi
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
NAS_HOST="${1:-}"
|
|
NAS_USER="${2:-yunchan}"
|
|
NAS_PATH="${3:-/volume1/docker/d3ro}"
|
|
PORT="${4:-5000}"
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
echo -e "${CYAN}========================================================${NC}"
|
|
echo -e "${CYAN} D3RO Voice — NAS Docker Deployment & Packaging Tool ${NC}"
|
|
echo -e "${CYAN}========================================================${NC}"
|
|
|
|
OUT_DIR="$ROOT_DIR/out/nas-package"
|
|
mkdir -p "$OUT_DIR"
|
|
|
|
echo -e "\n${YELLOW}[1/4] Building D3RO Voice API & BackOffice Docker Image...${NC}"
|
|
docker build -t d3ro-voice-api:latest -f "$ROOT_DIR/apps/api-server/Dockerfile" "$ROOT_DIR/apps/api-server"
|
|
echo -e "${GREEN}Docker image built: d3ro-voice-api:latest${NC}"
|
|
|
|
echo -e "\n${YELLOW}[1.5/4] Building D3RO Voice Next.js Admin CRM Image...${NC}"
|
|
docker build -t d3ro-voice-admin:latest -f "$ROOT_DIR/apps/admin/Dockerfile" "$ROOT_DIR"
|
|
echo -e "${GREEN}Docker image built: d3ro-voice-admin:latest${NC}"
|
|
|
|
echo -e "\n${YELLOW}[1.6/4] Building D3RO Voice Next.js Web App Image (/app)...${NC}"
|
|
if [ -z "${NEXT_PUBLIC_PAYPLE_CLIENT_KEY:-}" ]; then
|
|
echo -e "${YELLOW}NEXT_PUBLIC_PAYPLE_CLIENT_KEY is empty: Payple checkout will be disabled in the web app.${NC}"
|
|
fi
|
|
docker build -t d3ro-voice-web:latest -f "$ROOT_DIR/apps/web/Dockerfile" \
|
|
--build-arg "NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL" \
|
|
--build-arg "NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY" \
|
|
--build-arg "NEXT_PUBLIC_PAYPLE_CLIENT_KEY=${NEXT_PUBLIC_PAYPLE_CLIENT_KEY:-}" \
|
|
"$ROOT_DIR"
|
|
echo -e "${GREEN}Docker image built: d3ro-voice-web:latest${NC}"
|
|
|
|
TAR_PATH="$OUT_DIR/d3ro-voice-api.tar"
|
|
ADMIN_TAR_PATH="$OUT_DIR/d3ro-voice-admin.tar"
|
|
WEB_TAR_PATH="$OUT_DIR/d3ro-voice-web.tar"
|
|
echo -e "\n${YELLOW}[2/4] Exporting Docker Images to Archives...${NC}"
|
|
docker save -o "$TAR_PATH" d3ro-voice-api:latest
|
|
docker save -o "$ADMIN_TAR_PATH" d3ro-voice-admin:latest
|
|
docker save -o "$WEB_TAR_PATH" d3ro-voice-web:latest
|
|
echo -e "${GREEN}Image archives exported: $TAR_PATH, $ADMIN_TAR_PATH, $WEB_TAR_PATH${NC}"
|
|
|
|
echo -e "\n${YELLOW}[3/4] Packaging NAS deployment files...${NC}"
|
|
cp "$ROOT_DIR/docker-compose.nas.yml" "$OUT_DIR/docker-compose.yml"
|
|
cp "$ROOT_DIR/scripts/nas-control.sh" "$OUT_DIR/nas-control.sh"
|
|
chmod +x "$OUT_DIR/nas-control.sh"
|
|
|
|
cat <<EOF > "$OUT_DIR/.env"
|
|
PORT=$PORT
|
|
DATA_PATH=./data
|
|
JWT_SECRET=$JWT_SECRET
|
|
JWT_ISSUER=$JWT_ISSUER
|
|
JWT_AUDIENCE=$JWT_AUDIENCE
|
|
ADMIN_BOOTSTRAP_TOKEN=$ADMIN_BOOTSTRAP_TOKEN
|
|
D3RO_API_TOKEN=$D3RO_API_TOKEN
|
|
ADMIN_SESSION_SECRET=$ADMIN_SESSION_SECRET
|
|
API_SERVER_URL=$API_SERVER_URL
|
|
CORS_ALLOWED_ORIGINS=$CORS_ALLOWED_ORIGINS
|
|
ALLOWED_HOSTS=$ALLOWED_HOSTS
|
|
SUPABASE_URL=$SUPABASE_URL
|
|
SUPABASE_SERVICE_ROLE_KEY=$SUPABASE_SERVICE_ROLE_KEY
|
|
TZ=Asia/Seoul
|
|
EOF
|
|
chmod 600 "$OUT_DIR/.env"
|
|
|
|
cat <<EOF > "$OUT_DIR/README.txt"
|
|
========================================================================
|
|
D3RO Voice — Standalone NAS Deployment Package
|
|
========================================================================
|
|
|
|
[Synology / QNAP / Linux NAS 배포 방법]
|
|
|
|
1. 이 폴더의 모든 파일 (d3ro-voice-api.tar, d3ro-voice-admin.tar, d3ro-voice-web.tar,
|
|
docker-compose.yml, .env, nas-control.sh)을 NAS의 Docker 작업 폴더 (예: /volume1/docker/d3ro)에 업로드합니다.
|
|
|
|
2. NAS SSH 터미널에 접속하여 해당 폴더로 이동합니다:
|
|
cd /volume1/docker/d3ro
|
|
|
|
3. Docker 이미지를 로드합니다:
|
|
docker load < d3ro-voice-api.tar
|
|
docker load < d3ro-voice-admin.tar
|
|
docker load < d3ro-voice-web.tar
|
|
|
|
4. 서비스를 시작합니다:
|
|
./nas-control.sh start (또는 docker compose up -d)
|
|
|
|
5. 브라우저에서 접속 확인:
|
|
- 관리자: http://<NAS_IP>:3001 (공개 주소 https://d3ro-admin.chanpaca.net)
|
|
- API Swagger: http://<NAS_IP>:$PORT/swagger
|
|
- 헬스체크: http://<NAS_IP>:$PORT/health
|
|
- 웹앱: http://<NAS_IP>:3002/app/login (공개 주소 https://d3ro.chanpaca.net/app 는 사이트 브리지 워커 경유)
|
|
EOF
|
|
|
|
echo -e "${GREEN}NAS Deployment Package ready at: $OUT_DIR${NC}"
|
|
|
|
if [ -n "$NAS_HOST" ]; then
|
|
echo -e "\n${YELLOW}[4/4] Deploying to Remote NAS ($NAS_USER@$NAS_HOST:$NAS_PATH)...${NC}"
|
|
ssh "$NAS_USER@$NAS_HOST" "mkdir -p $NAS_PATH/data"
|
|
scp -O "$TAR_PATH" "$ADMIN_TAR_PATH" "$WEB_TAR_PATH" "$OUT_DIR/docker-compose.yml" "$OUT_DIR/.env" "$OUT_DIR/nas-control.sh" "$NAS_USER@$NAS_HOST:$NAS_PATH/"
|
|
ssh "$NAS_USER@$NAS_HOST" "cd $NAS_PATH && chmod +x nas-control.sh && docker load < d3ro-voice-api.tar && docker load < d3ro-voice-admin.tar && docker load < d3ro-voice-web.tar && docker compose down 2>/dev/null || true; docker compose up -d"
|
|
|
|
echo -e "\n${GREEN}========================================================${NC}"
|
|
echo -e "${GREEN} NAS Deployment Completed Successfully!${NC}"
|
|
echo -e "${CYAN} Access your D3RO Cloud Service at:${NC}"
|
|
echo -e " - Portal: http://$NAS_HOST:$PORT/"
|
|
echo -e " - Admin: http://$NAS_HOST:$PORT/admin"
|
|
echo -e " - Swagger: http://$NAS_HOST:$PORT/swagger"
|
|
echo -e " - Health: http://$NAS_HOST:$PORT/health"
|
|
echo -e " - Web App: http://$NAS_HOST:3002/app/login"
|
|
echo -e "${GREEN}========================================================${NC}"
|
|
else
|
|
echo -e "\n${GREEN}========================================================${NC}"
|
|
echo -e "${GREEN} NAS Package Build Complete!${NC}"
|
|
echo -e " Package location: $OUT_DIR"
|
|
echo -e " To deploy automatically via SSH, run:"
|
|
echo -e " ./scripts/deploy-nas.sh <NAS_IP> <NAS_USER> <NAS_PATH> <PORT>"
|
|
echo -e "${GREEN}========================================================${NC}"
|
|
fi
|