Compare commits
7 Commits
2b1e7cefbe
...
70ef97dd62
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70ef97dd62 | ||
|
|
ab7b3b1e05 | ||
|
|
5dd2ec862d | ||
|
|
ab6cdcb501 | ||
|
|
fc58eca634 | ||
|
|
9b2b3921c8 | ||
|
|
47c873a962 |
208
docs/superpowers/plans/2026-03-20-galaxis-agent-cicd.md
Normal file
208
docs/superpowers/plans/2026-03-20-galaxis-agent-cicd.md
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
# galaxis-agent Gitea Actions CI/CD 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 리포에 Gitea Actions CI/CD를 구성한다 (PR 테스트 + main 배포).
|
||||||
|
|
||||||
|
**Architecture:** 2개 워크플로우 파일로 구성. `ci.yml`은 PR에서 pytest를 실행하고, `deploy.yml`은 main push 시 테스트 → Docker Compose 배포 → health check를 수행한다. galaxis-po의 기존 deploy.yml 패턴을 따른다.
|
||||||
|
|
||||||
|
**Tech Stack:** Gitea Actions, Docker Compose, uv, pytest
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-03-20-galaxis-agent-cicd-design.md`
|
||||||
|
|
||||||
|
**Working Directory:** `~/workspace/quant/galaxis-agent/`
|
||||||
|
|
||||||
|
**전제조건 (수동):** Gitea 리포 Settings > Secrets에 8개 시크릿 등록 필요 — `ANTHROPIC_API_KEY`, `GITEA_TOKEN`, `GITEA_WEBHOOK_SECRET`, `DISCORD_TOKEN`, `DISCORD_CHANNEL_ID`, `DISCORD_BOT_USER_ID`, `FERNET_KEY`, `AGENT_API_KEY`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### 신규 생성 파일
|
||||||
|
|
||||||
|
```
|
||||||
|
.gitea/workflows/ci.yml # PR 검증 워크플로우 (테스트만)
|
||||||
|
.gitea/workflows/deploy.yml # main push → 테스트 + 배포
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Task 의존성:** 2개 Task 모두 독립적 → 병렬 dispatch 가능 (sonnet 충분)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: ci.yml 작성
|
||||||
|
|
||||||
|
PR 생성/업데이트 시 테스트를 실행하는 워크플로우.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.gitea/workflows/ci.yml`
|
||||||
|
|
||||||
|
### Step 1a: ci.yml 작성
|
||||||
|
|
||||||
|
- [ ] **ci.yml 작성**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
run: |
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: uv sync --frozen
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: uv run python -m pytest --tb=short -q
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **커밋**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p .gitea/workflows
|
||||||
|
git add .gitea/workflows/ci.yml
|
||||||
|
git commit -m "ci: add PR test workflow"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: deploy.yml 작성
|
||||||
|
|
||||||
|
main push 시 테스트 → Docker Compose 배포 → health check를 수행하는 워크플로우.
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.gitea/workflows/deploy.yml`
|
||||||
|
|
||||||
|
**참고:** galaxis-po의 `.gitea/workflows/deploy.yml` 패턴을 따른다.
|
||||||
|
|
||||||
|
### Step 2a: deploy.yml 작성
|
||||||
|
|
||||||
|
- [ ] **deploy.yml 작성**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Deploy to Production
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
run: |
|
||||||
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: uv sync --frozen
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: uv run python -m pytest --tb=short -q
|
||||||
|
|
||||||
|
- name: Setup Docker CLI
|
||||||
|
run: |
|
||||||
|
apt-get update -qq && apt-get install -y -qq docker.io >/dev/null 2>&1
|
||||||
|
mkdir -p ~/.docker/cli-plugins
|
||||||
|
curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" \
|
||||||
|
-o ~/.docker/cli-plugins/docker-compose
|
||||||
|
chmod +x ~/.docker/cli-plugins/docker-compose
|
||||||
|
docker compose version
|
||||||
|
|
||||||
|
- name: Create .env from secrets
|
||||||
|
run: |
|
||||||
|
cat <<EOF > .env
|
||||||
|
ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
|
GITEA_URL=http://gitea:3000
|
||||||
|
GITEA_EXTERNAL_URL=https://ayuriel.duckdns.org
|
||||||
|
GITEA_TOKEN=${{ secrets.GITEA_TOKEN }}
|
||||||
|
GITEA_WEBHOOK_SECRET=${{ secrets.GITEA_WEBHOOK_SECRET }}
|
||||||
|
DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }}
|
||||||
|
DISCORD_CHANNEL_ID=${{ secrets.DISCORD_CHANNEL_ID }}
|
||||||
|
DISCORD_BOT_USER_ID=${{ secrets.DISCORD_BOT_USER_ID }}
|
||||||
|
LANGGRAPH_URL=http://langgraph-server:8123
|
||||||
|
AUTONOMY_LEVEL=conservative
|
||||||
|
DEFAULT_REPO_OWNER=quant
|
||||||
|
DEFAULT_REPO_NAME=galaxis-po
|
||||||
|
AGENT_API_KEY=${{ secrets.AGENT_API_KEY }}
|
||||||
|
SANDBOX_IMAGE=galaxis-sandbox:latest
|
||||||
|
SANDBOX_MEM_LIMIT=4g
|
||||||
|
SANDBOX_CPU_COUNT=2
|
||||||
|
SANDBOX_TIMEOUT=600
|
||||||
|
FERNET_KEY=${{ secrets.FERNET_KEY }}
|
||||||
|
LOG_FORMAT=json
|
||||||
|
DAILY_COST_LIMIT_USD=10.0
|
||||||
|
PER_TASK_COST_LIMIT_USD=3.0
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Ensure galaxis-net network
|
||||||
|
run: |
|
||||||
|
docker network inspect galaxis-net >/dev/null 2>&1 || docker network create galaxis-net
|
||||||
|
|
||||||
|
- name: Build sandbox image
|
||||||
|
run: ./build-sandbox.sh
|
||||||
|
|
||||||
|
- name: Deploy with Docker Compose
|
||||||
|
run: |
|
||||||
|
docker compose --project-name galaxis-agent down || true
|
||||||
|
docker compose --project-name galaxis-agent build
|
||||||
|
docker compose --project-name galaxis-agent up -d
|
||||||
|
|
||||||
|
- name: Health check
|
||||||
|
run: |
|
||||||
|
echo "Waiting for galaxis-agent to become healthy..."
|
||||||
|
for i in $(seq 1 12); do
|
||||||
|
if curl -sf http://localhost:8100/health | grep -q '"status".*"ok"'; then
|
||||||
|
echo "Main health check: OK"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "$i" -eq 12 ]; then
|
||||||
|
echo "Health check failed after 60 seconds"
|
||||||
|
docker compose --project-name galaxis-agent logs
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo " Waiting... (${i}/12, 5s interval)"
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
for ep in /health/gitea /health/discord /health/queue /health/costs; do
|
||||||
|
if curl -sf "http://localhost:8100${ep}" >/dev/null 2>&1; then
|
||||||
|
echo " ${ep} ... OK"
|
||||||
|
else
|
||||||
|
echo " ${ep} ... WARN (non-critical)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Deploy complete!"
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **커밋**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .gitea/workflows/deploy.yml
|
||||||
|
git commit -m "ci: add production deploy workflow with health checks"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 완료 기준
|
||||||
|
|
||||||
|
- [ ] PR 생성 시 `ci.yml`이 테스트 139개를 실행한다
|
||||||
|
- [ ] `main` push 시 `deploy.yml`이 테스트 → 배포 → health check를 수행한다
|
||||||
|
- [ ] 모든 기존 테스트 통과 (139개)
|
||||||
376
docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md
Normal file
376
docs/superpowers/plans/2026-03-20-galaxis-agent-phase5.md
Normal file
@ -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개)
|
||||||
131
docs/superpowers/specs/2026-03-20-galaxis-agent-cicd-design.md
Normal file
131
docs/superpowers/specs/2026-03-20-galaxis-agent-cicd-design.md
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
# galaxis-agent Gitea Actions CI/CD 설계 스펙
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
galaxis-agent 리포에 Gitea Actions 기반 CI/CD를 구성한다. PR에서는 테스트만 실행하고, `main` push 시 테스트 통과 후 Oracle VM에 자동 배포한다.
|
||||||
|
|
||||||
|
## Infrastructure
|
||||||
|
|
||||||
|
- **Gitea**: `ssh://git@152.69.231.161:7798/quant/galaxis-agent.git`
|
||||||
|
- **Oracle VM**: A1 (4코어 ARM64, 24GB) — Gitea + act_runner + Docker 운영
|
||||||
|
- **act_runner**: 설치/등록 완료 (galaxis-po에서 사용 중)
|
||||||
|
- **Runner label**: `ubuntu-latest`
|
||||||
|
|
||||||
|
## Workflow 구성
|
||||||
|
|
||||||
|
### 1. `.gitea/workflows/ci.yml` — PR 검증
|
||||||
|
|
||||||
|
**트리거:** PR 생성/업데이트 (target: `main`)
|
||||||
|
|
||||||
|
**파이프라인:**
|
||||||
|
```
|
||||||
|
checkout → uv 설치 → uv sync --frozen → pytest → 결과 리포트
|
||||||
|
```
|
||||||
|
|
||||||
|
**상세:**
|
||||||
|
- `runs-on: ubuntu-latest`
|
||||||
|
- Python 3.12-slim 환경
|
||||||
|
- `uv sync --frozen`으로 의존성 설치 (lock 파일 기반)
|
||||||
|
- `uv run python -m pytest --tb=short -q` 실행
|
||||||
|
- 외부 서비스 의존성 없음 (모든 테스트 mock 기반, SQLite tempfile)
|
||||||
|
|
||||||
|
### 2. `.gitea/workflows/deploy.yml` — 배포
|
||||||
|
|
||||||
|
**트리거:** `main` branch push
|
||||||
|
|
||||||
|
**파이프라인:**
|
||||||
|
```
|
||||||
|
checkout → uv 설치 → uv sync → pytest
|
||||||
|
→ Docker CLI + Compose 설치
|
||||||
|
→ .env 생성 (Gitea secrets)
|
||||||
|
→ galaxis-net 네트워크 확인/생성
|
||||||
|
→ build-sandbox.sh 실행
|
||||||
|
→ docker compose build → docker compose up -d
|
||||||
|
→ health check (메인 + 개별 엔드포인트)
|
||||||
|
```
|
||||||
|
|
||||||
|
**상세:**
|
||||||
|
|
||||||
|
#### Step: 테스트
|
||||||
|
- uv 설치 → `uv sync --frozen` → `uv run python -m pytest --tb=short -q`
|
||||||
|
- ci.yml과 동일한 환경/명령어
|
||||||
|
- 실패 시 배포 중단
|
||||||
|
|
||||||
|
#### Step: Docker CLI + Compose 설치
|
||||||
|
- galaxis-po 패턴과 동일
|
||||||
|
- `apt-get install docker.io` + compose plugin 설치
|
||||||
|
|
||||||
|
#### Step: .env 생성
|
||||||
|
- Gitea secrets에서 환경변수 조합
|
||||||
|
- 기본값이 있는 변수는 워크플로우에 하드코딩
|
||||||
|
|
||||||
|
#### Step: 네트워크 확인
|
||||||
|
- `docker network inspect galaxis-net` 실패 시 `docker network create galaxis-net`
|
||||||
|
|
||||||
|
#### Step: 샌드박스 이미지 빌드
|
||||||
|
- `./build-sandbox.sh` 실행
|
||||||
|
- 샌드박스 이미지는 에이전트 컨테이너가 runtime에 사용 (build-time 의존 없음)
|
||||||
|
- docker compose build 전에 실행하여 이미지 준비
|
||||||
|
|
||||||
|
#### Step: 배포
|
||||||
|
- `docker compose --project-name galaxis-agent build`
|
||||||
|
- `docker compose --project-name galaxis-agent up -d`
|
||||||
|
|
||||||
|
#### Step: Health check
|
||||||
|
- 메인: `curl -sf http://localhost:8100/health` (5초 간격, 최대 60초)
|
||||||
|
- 개별: `/health/gitea`, `/health/discord`, `/health/queue`, `/health/costs` (Phase 3에서 구현 완료)
|
||||||
|
- 실패 시 워크플로우 fail → 수동 대응
|
||||||
|
|
||||||
|
## Gitea Secrets
|
||||||
|
|
||||||
|
### 필수 (리포 Settings > Secrets에 등록)
|
||||||
|
|
||||||
|
| Secret | 용도 |
|
||||||
|
|--------|------|
|
||||||
|
| `ANTHROPIC_API_KEY` | Claude API |
|
||||||
|
| `GITEA_TOKEN` | Gitea API 접근 |
|
||||||
|
| `GITEA_WEBHOOK_SECRET` | Webhook 검증 |
|
||||||
|
| `DISCORD_TOKEN` | Discord Bot |
|
||||||
|
| `DISCORD_CHANNEL_ID` | Discord 채널 |
|
||||||
|
| `DISCORD_BOT_USER_ID` | Discord 봇 ID |
|
||||||
|
| `FERNET_KEY` | 암호화 키 |
|
||||||
|
| `AGENT_API_KEY` | Agent API 인증 |
|
||||||
|
|
||||||
|
### 하드코딩 (기본값)
|
||||||
|
|
||||||
|
| 변수 | 값 |
|
||||||
|
|------|-----|
|
||||||
|
| `GITEA_URL` | `http://gitea:3000` |
|
||||||
|
| `GITEA_EXTERNAL_URL` | `https://ayuriel.duckdns.org` |
|
||||||
|
| `LANGGRAPH_URL` | `http://langgraph-server:8123` |
|
||||||
|
| `AUTONOMY_LEVEL` | `conservative` |
|
||||||
|
| `DEFAULT_REPO_OWNER` | `quant` |
|
||||||
|
| `DEFAULT_REPO_NAME` | `galaxis-po` |
|
||||||
|
| `SANDBOX_IMAGE` | `galaxis-sandbox:latest` |
|
||||||
|
| `SANDBOX_MEM_LIMIT` | `4g` |
|
||||||
|
| `SANDBOX_CPU_COUNT` | `2` |
|
||||||
|
| `SANDBOX_TIMEOUT` | `600` |
|
||||||
|
| `LOG_FORMAT` | `json` |
|
||||||
|
| `DAILY_COST_LIMIT_USD` | `10.0` |
|
||||||
|
| `PER_TASK_COST_LIMIT_USD` | `3.0` |
|
||||||
|
|
||||||
|
## deploy.sh와의 관계
|
||||||
|
|
||||||
|
- `deploy.sh`는 수동 배포 도구로 유지 (SSH 접속 시 사용)
|
||||||
|
- 워크플로우는 `deploy.sh`를 호출하지 않고 step으로 직접 실행
|
||||||
|
- pre-flight 체크 중 Docker/Compose/네트워크는 워크플로우에서 처리
|
||||||
|
|
||||||
|
## 롤백
|
||||||
|
|
||||||
|
- 자동 롤백 없음
|
||||||
|
- health check 실패 시 워크플로우 fail 표시 → 수동 대응
|
||||||
|
- 수동 롤백: `docker compose down && git checkout <이전_커밋> && ./deploy.sh`
|
||||||
|
|
||||||
|
## 파일 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
.gitea/
|
||||||
|
workflows/
|
||||||
|
ci.yml # PR 검증 (테스트만)
|
||||||
|
deploy.yml # main push → 테스트 + 배포
|
||||||
|
```
|
||||||
@ -0,0 +1,155 @@
|
|||||||
|
# galaxis-agent Phase 5: Oracle VM 배포 자동화 설계 문서
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
galaxis-agent를 Oracle VM (A1, 4코어 ARM64, 24GB)에 배포하기 위한 자동화 스크립트를 작성한다. Docker Compose 기반으로 서비스를 시작하고, health check로 배포 성공을 검증한다.
|
||||||
|
|
||||||
|
## 범위
|
||||||
|
|
||||||
|
- `deploy.sh` — pre-flight 체크, 이미지 빌드, 서비스 시작, health check 검증
|
||||||
|
- `build-sandbox.sh` — 샌드박스 이미지 빌드 (배포와 분리)
|
||||||
|
- `.env.example` 업데이트 — Phase 4에서 추가된 환경변수 반영
|
||||||
|
|
||||||
|
## 범위 밖
|
||||||
|
|
||||||
|
- Ansible / CI/CD 파이프라인
|
||||||
|
- 모니터링 대시보드 (Grafana)
|
||||||
|
- 멀티 리포 지원
|
||||||
|
- autonomous 모드 전환
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. deploy.sh
|
||||||
|
|
||||||
|
SSH로 Oracle VM에 접속한 후 실행하는 배포 스크립트.
|
||||||
|
|
||||||
|
### Pre-flight 체크
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Docker 설치 확인 (docker --version)
|
||||||
|
2. Docker Compose 설치 확인 (docker compose version)
|
||||||
|
3. docker-compose.yml 유효성 검증 (docker compose config --quiet)
|
||||||
|
4. galaxis-net 네트워크 존재 확인 → 없으면 생성
|
||||||
|
5. .env 파일 존재 확인 → 없으면 에러 + .env.example 복사 안내
|
||||||
|
6. 필수 환경변수 검증:
|
||||||
|
- ANTHROPIC_API_KEY
|
||||||
|
- GITEA_TOKEN
|
||||||
|
- GITEA_WEBHOOK_SECRET
|
||||||
|
- DISCORD_TOKEN
|
||||||
|
- DISCORD_CHANNEL_ID
|
||||||
|
- FERNET_KEY
|
||||||
|
- AGENT_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
### 배포 단계
|
||||||
|
|
||||||
|
```
|
||||||
|
1. galaxis-agent 이미지 빌드 (docker compose build)
|
||||||
|
2. 서비스 시작 (docker compose up -d)
|
||||||
|
3. Health check 검증 (5초 간격, 최대 60초 retry):
|
||||||
|
- GET /health → status == "ok"
|
||||||
|
- GET /health/gitea → 연결 확인
|
||||||
|
- GET /health/discord → 연결 확인
|
||||||
|
- GET /health/queue → 큐 상태 확인
|
||||||
|
- GET /health/costs → 응답 확인
|
||||||
|
4. 결과 출력 (성공/실패 + 로그 안내)
|
||||||
|
```
|
||||||
|
|
||||||
|
### --dry-run 옵션
|
||||||
|
|
||||||
|
pre-flight 체크는 실제로 실행하되, Docker 명령(build, up, health check)은 echo만 출력한다. 로컬에서 `.env` 유효성 등을 검증할 수 있다.
|
||||||
|
|
||||||
|
### 에러 처리
|
||||||
|
|
||||||
|
- pre-flight 실패 시: 구체적 에러 메시지 + 해결 방법 안내 후 종료
|
||||||
|
- 빌드 실패 시: 에러 로그 출력 후 종료
|
||||||
|
- health check 실패 시: `docker compose logs` 안내 후 종료 (자동 롤백 없음)
|
||||||
|
|
||||||
|
### 수동 롤백
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
git checkout <이전_커밋>
|
||||||
|
./deploy.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. build-sandbox.sh
|
||||||
|
|
||||||
|
샌드박스 이미지를 빌드하는 독립 스크립트.
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Dockerfile.sandbox 존재 확인
|
||||||
|
2. docker build -f Dockerfile.sandbox -t galaxis-sandbox:latest .
|
||||||
|
(VM에서 직접 실행하므로 네이티브 ARM64 빌드, --platform 불필요)
|
||||||
|
3. 빌드 성공 확인 + 이미지 크기 출력
|
||||||
|
```
|
||||||
|
|
||||||
|
`--dry-run` 옵션: 명령 출력만 (실행 안 함).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. .env.example 업데이트
|
||||||
|
|
||||||
|
Phase 4에서 추가된 환경변수를 `.env.example`에 반영:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Logging
|
||||||
|
LOG_FORMAT=json
|
||||||
|
|
||||||
|
# Cost Guard
|
||||||
|
DAILY_COST_LIMIT_USD=10.0
|
||||||
|
PER_TASK_COST_LIMIT_USD=3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
`COST_GUARD_DB`와 `TASK_HISTORY_DB`는 Docker volume `/data/` 내 기본 경로를 사용하므로 `.env.example`에 포함하지 않는다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 파일 구조
|
||||||
|
|
||||||
|
```
|
||||||
|
galaxis-agent/
|
||||||
|
├── deploy.sh # 신규: 배포 스크립트
|
||||||
|
├── build-sandbox.sh # 신규: 샌드박스 이미지 빌드
|
||||||
|
├── .env.example # 수정: Phase 4 변수 추가
|
||||||
|
├── docker-compose.yml # 기존 (변경 없음)
|
||||||
|
├── Dockerfile # 기존 (변경 없음)
|
||||||
|
└── Dockerfile.sandbox # 기존 (변경 없음)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 배포 절차 (사용자 관점)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Oracle VM에 SSH 접속
|
||||||
|
ssh oracle-vm
|
||||||
|
|
||||||
|
# 2. 리포 클론 (최초) 또는 pull
|
||||||
|
cd ~/galaxis-agent && git pull
|
||||||
|
|
||||||
|
# 3. 환경변수 설정 (최초만)
|
||||||
|
cp .env.example .env
|
||||||
|
# .env 편집
|
||||||
|
|
||||||
|
# 4. 샌드박스 이미지 빌드 (최초 또는 Dockerfile.sandbox 변경 시)
|
||||||
|
./build-sandbox.sh
|
||||||
|
|
||||||
|
# 5. 배포
|
||||||
|
./deploy.sh
|
||||||
|
|
||||||
|
# 6. Gitea webhook 등록 (최초만, 웹 UI)
|
||||||
|
# URL: http://galaxis-agent:8000/webhooks/gitea
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 테스트 전략
|
||||||
|
|
||||||
|
셸 스크립트이므로 Python 테스트 체계와 분리한다:
|
||||||
|
|
||||||
|
- `deploy.sh --dry-run` — pre-flight 체크 + 명령 출력만 (실행 안 함)
|
||||||
|
- `build-sandbox.sh --dry-run` — 명령 출력만
|
||||||
|
- 실제 배포 검증은 VM에서 수동 실행으로 확인
|
||||||
Loading…
x
Reference in New Issue
Block a user