diff --git a/docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md b/docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md new file mode 100644 index 0000000..1d74392 --- /dev/null +++ b/docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md @@ -0,0 +1,376 @@ +# galaxis-agent Phase 5: Oracle VM 배포 자동화 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** galaxis-agent를 Oracle VM에 Docker Compose 기반으로 배포하는 자동화 스크립트를 작성한다. + +**Architecture:** `deploy.sh`가 pre-flight 체크(Docker, 네트워크, 환경변수) → 이미지 빌드 → 서비스 시작 → health check retry 검증을 수행한다. `build-sandbox.sh`가 샌드박스 이미지를 별도로 빌드한다. `.env.example`에 Phase 4 변수를 추가한다. + +**Tech Stack:** Bash, Docker Compose, curl + +**Spec:** `docs/superpowers/specs/2026-03-20-galaxis-agent-phase5-deploy-design.md` + +**Working Directory:** `~/workspace/quant/galaxis-agent/` + +--- + +## File Structure + +### 신규 생성 파일 + +``` +deploy.sh # 배포 스크립트 (pre-flight, build, up, health check) +build-sandbox.sh # 샌드박스 이미지 빌드 스크립트 +``` + +### 수정할 파일 + +``` +.env.example # Phase 4 환경변수 추가 (LOG_FORMAT, DAILY_COST_LIMIT_USD, PER_TASK_COST_LIMIT_USD) +``` + +--- + +**Task 의존성:** 3개 Task 모두 독립적 → 병렬 dispatch 가능 (sonnet 충분) + +--- + +## Task 1: deploy.sh 작성 + +배포 스크립트. pre-flight 체크 → 이미지 빌드 → 서비스 시작 → health check 검증. + +**Files:** +- Create: `deploy.sh` + +### Step 1a: deploy.sh 작성 + +- [ ] **deploy.sh 작성** + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# galaxis-agent 배포 스크립트 +# Usage: ./deploy.sh [--dry-run] + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +DRY_RUN=false +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=true + echo "[DRY-RUN] Docker 명령은 실행하지 않고 출력만 합니다." + echo "" +fi + +HEALTH_URL="${HEALTH_URL:-http://localhost:8100}" +HEALTH_RETRY_INTERVAL=5 +HEALTH_MAX_WAIT=60 + +# 색상 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +# ── Pre-flight 체크 ──────────────────────────────────── + +info "Pre-flight 체크 시작..." + +# 1. Docker +if ! command -v docker &>/dev/null; then + error "Docker가 설치되어 있지 않습니다." + echo " 설치: https://docs.docker.com/engine/install/" + exit 1 +fi +info "Docker: $(docker --version)" + +# 2. Docker Compose +if ! docker compose version &>/dev/null; then + error "Docker Compose가 설치되어 있지 않습니다." + echo " 설치: https://docs.docker.com/compose/install/" + exit 1 +fi +info "Docker Compose: $(docker compose version --short)" + +# 3. .env 파일 (docker compose config보다 먼저 확인 — env_file 의존) +if [[ ! -f .env ]]; then + error ".env 파일이 없습니다." + echo " cp .env.example .env 후 값을 설정하세요." + exit 1 +fi +info ".env 파일 OK" + +# 4. 필수 환경변수 검증 (grep 기반 — 셸 환경 오염 방지) +REQUIRED_VARS=( + ANTHROPIC_API_KEY + GITEA_TOKEN + GITEA_WEBHOOK_SECRET + DISCORD_TOKEN + DISCORD_CHANNEL_ID + FERNET_KEY + AGENT_API_KEY +) + +MISSING=() +for var in "${REQUIRED_VARS[@]}"; do + if ! grep -qE "^${var}=.+" .env; then + MISSING+=("$var") + fi +done + +if [[ ${#MISSING[@]} -gt 0 ]]; then + error "필수 환경변수가 설정되지 않았습니다:" + for var in "${MISSING[@]}"; do + echo " - $var" + done + echo " .env 파일을 확인하세요." + exit 1 +fi +info "필수 환경변수 OK (${#REQUIRED_VARS[@]}개 확인)" + +# 5. docker-compose.yml 유효성 +if ! docker compose config --quiet 2>/dev/null; then + error "docker-compose.yml 유효성 검증 실패." + echo " docker compose config 으로 상세 에러를 확인하세요." + exit 1 +fi +info "docker-compose.yml 유효성 OK" + +# 6. galaxis-net 네트워크 +if ! docker network inspect galaxis-net &>/dev/null; then + warn "galaxis-net 네트워크가 없습니다. 생성합니다." + if [[ "$DRY_RUN" == true ]]; then + echo " [DRY-RUN] docker network create galaxis-net" + else + docker network create galaxis-net + fi +fi +info "galaxis-net 네트워크 OK" + +echo "" +info "Pre-flight 체크 완료!" +echo "" + +# ── 배포 ──────────────────────────────────────────────── + +if [[ "$DRY_RUN" == true ]]; then + echo "[DRY-RUN] 실행될 명령:" + echo " docker compose build" + echo " docker compose up -d" + echo " Health check: ${HEALTH_URL}/health (${HEALTH_RETRY_INTERVAL}초 간격, 최대 ${HEALTH_MAX_WAIT}초)" + echo "" + info "Dry-run 완료. 실제 배포는 --dry-run 없이 실행하세요." + exit 0 +fi + +info "galaxis-agent 이미지 빌드 중..." +docker compose build + +info "서비스 시작 중..." +docker compose up -d + +# ── Health check ──────────────────────────────────────── + +info "Health check 시작 (${HEALTH_RETRY_INTERVAL}초 간격, 최대 ${HEALTH_MAX_WAIT}초)..." + +ELAPSED=0 +HEALTHY=false + +while [[ $ELAPSED -lt $HEALTH_MAX_WAIT ]]; do + if curl -sf "${HEALTH_URL}/health" | grep -q '"status".*"ok"' 2>/dev/null; then + HEALTHY=true + break + fi + sleep "$HEALTH_RETRY_INTERVAL" + ELAPSED=$((ELAPSED + HEALTH_RETRY_INTERVAL)) + echo " 대기 중... (${ELAPSED}초)" +done + +if [[ "$HEALTHY" != true ]]; then + error "Health check 실패: ${HEALTH_URL}/health 가 ${HEALTH_MAX_WAIT}초 내에 응답하지 않았습니다." + echo "" + echo " 로그 확인: docker compose logs" + echo " 수동 롤백:" + echo " docker compose down" + echo " git checkout <이전_커밋>" + echo " ./deploy.sh" + exit 1 +fi + +# 개별 health check +ENDPOINTS=("/health" "/health/gitea" "/health/discord" "/health/queue" "/health/costs") +ALL_OK=true + +echo "" +for ep in "${ENDPOINTS[@]}"; do + if curl -sf "${HEALTH_URL}${ep}" >/dev/null 2>&1; then + info " ${ep} ... OK" + else + warn " ${ep} ... FAIL" + ALL_OK=false + fi +done + +echo "" +if [[ "$ALL_OK" == true ]]; then + info "배포 성공! 모든 health check 통과." +else + warn "배포 완료. 일부 health check가 실패했습니다." + echo " 로그 확인: docker compose logs" +fi +``` + +- [ ] **실행 권한 부여** + +```bash +chmod +x deploy.sh +``` + +- [ ] **dry-run으로 검증** + +```bash +# .env가 없는 상태에서 에러 메시지 확인 +./deploy.sh --dry-run +``` + +Expected: `.env` 파일이 없으면 에러 메시지 출력, 있으면 pre-flight 체크 후 dry-run 명령 출력 + +- [ ] **커밋** + +```bash +git add deploy.sh +git commit -m "feat: add deploy.sh for Oracle VM deployment automation" +``` + +--- + +## Task 2: build-sandbox.sh 작성 + +샌드박스 이미지를 빌드하는 독립 스크립트. + +**Files:** +- Create: `build-sandbox.sh` + +### Step 2a: build-sandbox.sh 작성 + +- [ ] **build-sandbox.sh 작성** + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# galaxis-sandbox 이미지 빌드 스크립트 +# Usage: ./build-sandbox.sh [--dry-run] + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +DRY_RUN=false +if [[ "${1:-}" == "--dry-run" ]]; then + DRY_RUN=true +fi + +IMAGE_NAME="galaxis-sandbox:latest" +DOCKERFILE="Dockerfile.sandbox" + +# 색상 +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $*"; } +error() { echo -e "${RED}[ERROR]${NC} $*"; } + +# Docker 설치 확인 +if ! command -v docker &>/dev/null; then + error "Docker가 설치되어 있지 않습니다." + exit 1 +fi + +# Dockerfile.sandbox 존재 확인 +if [[ ! -f "$DOCKERFILE" ]]; then + error "${DOCKERFILE}이 없습니다." + exit 1 +fi + +if [[ "$DRY_RUN" == true ]]; then + echo "[DRY-RUN] 실행될 명령:" + echo " docker build -f ${DOCKERFILE} -t ${IMAGE_NAME} ." + exit 0 +fi + +info "${IMAGE_NAME} 이미지 빌드 중..." +docker build -f "$DOCKERFILE" -t "$IMAGE_NAME" . + +# 이미지 크기 출력 +SIZE=$(docker image inspect "$IMAGE_NAME" --format='{{.Size}}' | awk '{printf "%.0f", $1/1024/1024}') +info "빌드 완료! 이미지 크기: ${SIZE}MB" +``` + +- [ ] **실행 권한 부여** + +```bash +chmod +x build-sandbox.sh +``` + +- [ ] **dry-run으로 검증** + +```bash +./build-sandbox.sh --dry-run +``` + +Expected: `[DRY-RUN] 실행될 명령: docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest .` + +- [ ] **커밋** + +```bash +git add build-sandbox.sh +git commit -m "feat: add build-sandbox.sh for sandbox image building" +``` + +--- + +## Task 3: .env.example 업데이트 + +Phase 4에서 추가된 환경변수를 `.env.example`에 반영한다. + +**Files:** +- Modify: `~/workspace/quant/galaxis-agent/.env.example` + +### Step 3a: .env.example 수정 + +- [ ] **.env.example에 Phase 4 변수 추가** + +파일 끝(`# Encryption` 섹션 다음)에 추가: + +```bash +# Logging +LOG_FORMAT=json + +# Cost Guard +DAILY_COST_LIMIT_USD=10.0 +PER_TASK_COST_LIMIT_USD=3.0 +``` + +- [ ] **커밋** + +```bash +git add .env.example +git commit -m "docs: add Phase 4 environment variables to .env.example" +``` + +--- + +## Phase 5 완료 기준 + +- [ ] `deploy.sh`가 pre-flight 체크 → 이미지 빌드 → 서비스 시작 → health check retry 검증을 수행한다 +- [ ] `deploy.sh --dry-run`이 Docker 명령 없이 pre-flight 체크와 명령 출력만 수행한다 +- [ ] `build-sandbox.sh`가 샌드박스 이미지를 빌드하고 크기를 출력한다 +- [ ] `build-sandbox.sh --dry-run`이 명령 출력만 수행한다 +- [ ] `.env.example`에 Phase 4 환경변수(LOG_FORMAT, DAILY_COST_LIMIT_USD, PER_TASK_COST_LIMIT_USD)가 포함되어 있다 +- [ ] 모든 기존 테스트 통과 (139개)