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:
@@ -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) |
|
||||
|
||||
@@ -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
236
install.sh
Executable 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 ""
|
||||
Reference in New Issue
Block a user