feat: install.sh 원클릭 설치 + SQLite→MariaDB(Prisma) 전환

- install.sh: Node/pnpm 체크, git clone, 의존성, .env 생성, Prisma migrate, PM2 설정 자동화
  --dev / --dir / --repo 옵션 지원, 범용 사용 가능
- SPRINT-001 스펙: SQLite → MariaDB + Prisma ORM 으로 전환
  기존 Dev 서버 MariaDB(10.10.10.146:33006) 활용
- stack.md 규칙 갱신: better-sqlite3 → Prisma

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 13:32:24 +09:00
parent b2058129f4
commit 5a64e26005
3 changed files with 248 additions and 7 deletions

View File

@@ -6,7 +6,7 @@
| 패키지 | **pnpm** (npm/yarn 금지) |
| 상태 머신 | **XState v5** |
| 스키마 | **Zod** |
| 영속화 | **SQLite (better-sqlite3)** 단일 파일 |
| 영속화 | **MariaDB** (Prisma ORM, Dev 서버 10.10.10.146:33006) |
| 프로세스 | **execa + AbortController** |
| CLI | **citty** |
| 로그 | **pino** (구조화 JSON) |

View File

@@ -1,6 +1,6 @@
# SPRINT-001 — 스켈레톤: XState FSM + orchestrator + CLI
> **목표**: 결정론적 파이프라인의 뼈대를 세운다. XState 머신이 메모리에서 돌고, SQLite 에 상태가 저장되고, `rails status` CLI 로 조회 가능해야 한다.
> **목표**: 결정론적 파이프라인의 뼈대를 세운다. XState 머신이 메모리에서 돌고, MariaDB(Prisma) 에 상태가 저장되고, `rails status` CLI 로 조회 가능해야 한다.
## Type
`scaffold`
@@ -16,10 +16,13 @@
- Core 디렉토리 구조: `src/orchestrator/`, `src/contract/`, `src/cli/`, `tests/`
- XState v5 머신 정의 (`src/orchestrator/machine.ts`)
- Zod 이벤트/컨텍스트 스키마 (`src/orchestrator/events.ts`)
- SQLite 스키마 + migration (`src/orchestrator/store.ts`)
- Prisma schema + MariaDB migration (`prisma/schema.prisma`)
- citty CLI 뼈대 (`rails <subcommand>`)
- `rails start` — 빈 파이프라인 시작
- `rails status` — 현재 상태 표시
- `install.sh` — 원클릭 설치 스크립트 (Node/pnpm 체크 + clone + 빌드 + .env + PM2)
- `.env.example` — 환경변수 템플릿
- `ecosystem.config.cjs` — PM2 설정
- Vitest 테스트: 머신 상태 전이 단위 테스트 (성공 경로 1건 + 에러 경로 1건)
## Non-Goals
@@ -41,13 +44,15 @@
| 1.4 | `src/orchestrator/events.ts` — Zod 이벤트 스키마 | 타입체크 통과 | 1.2 | cc:TODO |
| 1.5 | `src/orchestrator/context.ts` — FSM context 타입 | 타입체크 통과 | 1.4 | cc:TODO |
| 1.6 | `src/orchestrator/machine.ts` — XState v5 머신 (stub actor) | `createActor` 성공 | 1.5 | cc:TODO |
| 1.7 | `src/orchestrator/store.ts` — better-sqlite3 + 스키마 + prepared statements | 테이블 4개 생성됨 | 1.1 | cc:TODO |
| 1.8 | `src/orchestrator/persist.ts` — FSM snapshot ↔ SQLite 변환 | snapshot round-trip 테스트 통과 | 1.6, 1.7 | cc:TODO |
| 1.7 | `prisma/schema.prisma` — MariaDB 스키마 (pipelines, state_transitions, actor_spawns, contracts) | `pnpm prisma migrate dev` 성공 | 1.1 | cc:TODO |
| 1.8 | `src/orchestrator/persist.ts` — FSM snapshot ↔ Prisma 변환 | snapshot round-trip 테스트 통과 | 1.6, 1.7 | cc:TODO |
| 1.9 | `src/cli/index.ts` — citty 진입점 | `pnpm rails --help` 출력 | 1.1 | cc:TODO |
| 1.10 | `src/cli/start.ts``rails start <projectName>` | 파이프라인 생성, ULID 반환 | 1.9, 1.8 | cc:TODO |
| 1.11 | `src/cli/status.ts``rails status [<id>]` | 현재 상태 표 출력 | 1.9, 1.8 | cc:TODO |
| 1.12 | `tests/machine.test.ts` — 성공 경로 + 에러 경로 | 2개 이상 pass | 1.6 | cc:TODO |
| 1.13 | `tests/store.test.ts` — snapshot round-trip | 1개 이상 pass | 1.8 | cc:TODO |
| 1.13 | `tests/store.test.ts` — snapshot round-trip (Prisma) | 1개 이상 pass | 1.8 | cc:TODO |
| 1.17 | `install.sh` — 원클릭 설치 스크립트 | bash install.sh --help 동작 | — | cc:완료 |
| 1.18 | `.env.example` + `ecosystem.config.cjs` | 파일 존재 | — | cc:TODO |
| 1.14 | `src/logger.ts` — pino 구조화 로거 + pipelineId 필드 | 로그 JSON 형식 확인 | 1.1 | cc:TODO |
| 1.15 | `src/env.ts` — Zod 검증 env | 환경변수 검증 실패 시 throw | 1.1 | cc:TODO |
| 1.16 | README 실행 방법 섹션 업데이트 | `## 실행 방법` 존재 | — | cc:TODO |
@@ -61,7 +66,7 @@
- `pnpm rails --help` exit 0 + 출력에 `start`, `status` 포함
- `pnpm rails start test-project` → ULID 출력
- `pnpm rails status <id>``idle` 또는 `planning` 상태 표시
- SQLite 파일 (`data/rails.db`) 생성 확인
- MariaDB `hanarang_rails` DB 에 테이블 4개 생성 확인
- `package.json` lockfile 이 `pnpm-lock.yaml` (yarn/npm 아님)
### 수동 검증 (다랑이 manual)

236
install.sh Executable file
View File

@@ -0,0 +1,236 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════
# hanarang-rails installer
# 범용: 어떤 서버에서든 실행 가능
# 사용법: curl -fsSL <raw-url>/install.sh | bash
# 또는: bash install.sh [--dev]
# ═══════════════════════════════════════════════════════
set -euo pipefail
# ──────────────────────────────────────────────
# 색상 + 로그
# ──────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
log() { printf "${BLUE}[rails]${NC} %s\n" "$*"; }
ok() { printf "${GREEN}[ OK ]${NC} %s\n" "$*"; }
warn() { printf "${YELLOW}[WARN ]${NC} %s\n" "$*"; }
err() { printf "${RED}[ERROR]${NC} %s\n" "$*" >&2; }
die() { err "$*"; exit 1; }
# ──────────────────────────────────────────────
# 설정
# ──────────────────────────────────────────────
REPO_URL="${RAILS_REPO_URL:-https://git.nabomhalang.co.kr/hanarang/hanarang-rails.git}"
INSTALL_DIR="${RAILS_INSTALL_DIR:-$(pwd)/hanarang-rails}"
DEV_MODE=false
while [[ $# -gt 0 ]]; do
case "$1" in
--dev) DEV_MODE=true; shift ;;
--dir) INSTALL_DIR="$2"; shift 2 ;;
--repo) REPO_URL="$2"; shift 2 ;;
-h|--help)
echo "hanarang-rails installer"
echo ""
echo "Usage: bash install.sh [options]"
echo ""
echo "Options:"
echo " --dev Dev 모드 (소스 clone + 개발 의존성)"
echo " --dir <path> 설치 경로 (기본: ./hanarang-rails)"
echo " --repo <url> Git repo URL"
echo " -h, --help 도움말"
echo ""
echo "Environment:"
echo " RAILS_REPO_URL Git repo URL override"
echo " RAILS_INSTALL_DIR 설치 경로 override"
echo " DATABASE_URL MariaDB 연결 문자열"
echo " DISCORD_TOKEN Discord bot token"
echo " DISCORD_GUILD_ID Discord guild ID"
exit 0
;;
*) die "알 수 없는 옵션: $1" ;;
esac
done
# ──────────────────────────────────────────────
# 1. 사전 조건 확인
# ──────────────────────────────────────────────
log "사전 조건 확인..."
check_cmd() {
if command -v "$1" &>/dev/null; then
ok "$1 $(command -v "$1")"
return 0
else
return 1
fi
}
# Node.js ≥ 22
if check_cmd node; then
NODE_VER=$(node --version | sed 's/v//' | cut -d. -f1)
if [[ "$NODE_VER" -lt 22 ]]; then
die "Node.js 22 이상 필요 (현재: $(node --version))"
fi
else
die "Node.js 가 설치되지 않음. https://nodejs.org 에서 v22+ 설치"
fi
# pnpm
if ! check_cmd pnpm; then
log "pnpm 미설치. 자동 설치..."
npm install -g pnpm || die "pnpm 설치 실패"
ok "pnpm 설치 완료 ($(pnpm --version))"
fi
# git
check_cmd git || die "git 이 설치되지 않음"
# ──────────────────────────────────────────────
# 2. 소스 코드 받기
# ──────────────────────────────────────────────
if [[ -d "$INSTALL_DIR/.git" ]]; then
log "기존 설치 감지: $INSTALL_DIR"
log "git pull 로 업데이트..."
git -C "$INSTALL_DIR" pull --ff-only || warn "git pull 실패 — 수동 확인 필요"
else
log "repo clone: $REPO_URL$INSTALL_DIR"
git clone "$REPO_URL" "$INSTALL_DIR" || die "git clone 실패"
fi
cd "$INSTALL_DIR"
ok "소스: $(pwd) ($(git log --oneline -1))"
# ──────────────────────────────────────────────
# 3. 의존성 설치
# ──────────────────────────────────────────────
log "의존성 설치 (pnpm install)..."
if [[ "$DEV_MODE" == true ]]; then
pnpm install || die "pnpm install 실패"
else
pnpm install --prod || die "pnpm install --prod 실패"
fi
ok "의존성 설치 완료"
# ──────────────────────────────────────────────
# 4. .env 생성 (없을 때만)
# ──────────────────────────────────────────────
if [[ ! -f .env ]]; then
if [[ -f .env.example ]]; then
cp .env.example .env
warn ".env.example → .env 복사됨. 값을 채워주세요:"
else
cat > .env << 'ENVEOF'
# hanarang-rails 환경변수
# 아래 값을 채워주세요.
# ── Database (MariaDB) ──
DATABASE_URL="mysql://rails:CHANGE_ME@localhost:3306/hanarang_rails"
# ── Discord ──
DISCORD_TOKEN=""
DISCORD_GUILD_ID=""
# ── Gitea Webhook ──
GITEA_WEBHOOK_SECRET=""
# ── Rails ──
RAILS_PORT=18800
RAILS_LOG_LEVEL=info
NODE_ENV=production
ENVEOF
warn ".env 파일 생성됨. 값을 채워주세요:"
fi
echo ""
echo " 필수:"
echo " DATABASE_URL — MariaDB 연결 문자열"
echo " DISCORD_TOKEN — Discord bot token"
echo " DISCORD_GUILD_ID — Discord 서버 ID"
echo ""
echo " 선택:"
echo " GITEA_WEBHOOK_SECRET — Gitea webhook HMAC secret"
echo " RAILS_PORT — webhook + API 포트 (기본: 18800)"
echo ""
fi
# ──────────────────────────────────────────────
# 5. 빌드 (TypeScript → dist/)
# ──────────────────────────────────────────────
if [[ -f tsconfig.json ]]; then
log "TypeScript 빌드..."
pnpm build || die "빌드 실패"
ok "빌드 완료 (dist/)"
else
warn "tsconfig.json 없음 — 빌드 스킵 (아직 Sprint 001 미완?)"
fi
# ──────────────────────────────────────────────
# 6. DB 마이그레이션 (Prisma)
# ──────────────────────────────────────────────
if [[ -f prisma/schema.prisma ]]; then
if [[ -n "${DATABASE_URL:-}" ]] || grep -q "DATABASE_URL" .env 2>/dev/null; then
log "Prisma 마이그레이션..."
pnpm prisma migrate deploy 2>/dev/null || warn "DB 마이그레이션 실패 — DATABASE_URL 을 확인하세요"
pnpm prisma generate || warn "Prisma generate 실패"
else
warn "DATABASE_URL 미설정 — DB 마이그레이션 스킵"
fi
else
warn "prisma/schema.prisma 없음 — DB 마이그레이션 스킵"
fi
# ──────────────────────────────────────────────
# 7. PM2 설정 (선택)
# ──────────────────────────────────────────────
if command -v pm2 &>/dev/null; then
if [[ ! -f ecosystem.config.js ]] && [[ ! -f ecosystem.config.cjs ]]; then
log "PM2 ecosystem 설정 생성..."
cat > ecosystem.config.cjs << 'PM2EOF'
module.exports = {
apps: [{
name: 'hanarang-rails',
script: 'dist/cli/index.js',
args: 'serve',
cwd: __dirname,
env: {
NODE_ENV: 'production',
},
max_restarts: 10,
restart_delay: 5000,
error_file: './logs/error.log',
out_file: './logs/out.log',
merge_logs: true,
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
}]
}
PM2EOF
ok "ecosystem.config.cjs 생성"
fi
echo ""
echo " PM2 로 시작하려면:"
echo " pm2 start ecosystem.config.cjs"
echo " pm2 save"
echo ""
else
warn "PM2 미설치. 수동 실행: node dist/cli/index.js serve"
fi
# ──────────────────────────────────────────────
# 8. 완료
# ──────────────────────────────────────────────
echo ""
echo "═══════════════════════════════════════════════"
printf " ${GREEN}hanarang-rails 설치 완료${NC}\n"
echo "═══════════════════════════════════════════════"
echo ""
echo " 경로: $(pwd)"
echo " 버전: $(git describe --tags --always 2>/dev/null || git log --oneline -1)"
echo ""
echo " 다음 단계:"
echo " 1. .env 파일의 값을 채우세요 (DATABASE_URL, DISCORD_TOKEN 등)"
echo " 2. DB 마이그레이션: pnpm prisma migrate deploy"
echo " 3. 시작: pm2 start ecosystem.config.cjs (또는 pnpm rails serve)"
echo " 4. 확인: pnpm rails status"
echo ""
echo " 문서: https://git.nabomhalang.co.kr/hanarang/hanarang-rails"
echo ""