From fbdf8f68dfa8dffc23654ed54833f814e43e8a68 Mon Sep 17 00:00:00 2001 From: jungwoo choi Date: Sat, 29 Aug 2026 13:08:21 +0900 Subject: [PATCH] =?UTF-8?q?refactor(cli):=20=EC=97=94=EC=A7=84=20=EC=A0=84?= =?UTF-8?q?=EC=97=AD=ED=99=94(.yakcloud=20=EB=8B=A4=EC=9A=B4=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EC=86=8C=EC=8A=A4)=20+=20=EC=8A=A4=EC=BA=90?= =?UTF-8?q?=ED=8F=B4=EB=93=9C=20=EC=B5=9C=EC=86=8C=ED=99=94=20+=20shared?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20=E2=80=94=200.18.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 엔진(deploy/ctl/dev.py)은 install.sh 가 ~/.config/yakcloud/lib 로 설치, CLI 가 전역 실행. project init 스캐폴드 = 앱+매니페스트+얇은 .gitea CI 만. 데이터소스 기본 shared. Co-Authored-By: Claude Opus 4.8 --- .gitea/workflows/deploy.yml | 6 +- scripts/yakcloud_ctl.py => .yakcloud/ctl.py | 0 .../yakcloud_deploy.py => .yakcloud/deploy.py | 2 +- scripts/yakcloud_dev.py => .yakcloud/dev.py | 0 bin/yakcloud | 105 ++++++++++-------- install.sh | 27 +++-- yakcloud.yaml | 19 ++-- 7 files changed, 91 insertions(+), 68 deletions(-) rename scripts/yakcloud_ctl.py => .yakcloud/ctl.py (100%) rename scripts/yakcloud_deploy.py => .yakcloud/deploy.py (98%) rename scripts/yakcloud_dev.py => .yakcloud/dev.py (100%) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 6ee9eed..c64cd7f 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -54,5 +54,7 @@ jobs: subprocess.check_call(["docker", "push", img]) PY - # 5) YakCloud 리컨실 배포(콘솔 API, PAT 인증) - TAG="$TAG" python3 scripts/yakcloud_deploy.py yakcloud.yaml + # 5) YakCloud 리컨실 배포(콘솔 API, PAT 인증) — 엔진은 전역(프로젝트 미포함), + # 스타터에서 받아 실행(버전 파편화 없음). + curl -fsSL "https://gitea.yakenator.io/yakenator/yakcloud-starter/raw/branch/main/.yakcloud/deploy.py" -o /tmp/yc-deploy.py + TAG="$TAG" python3 /tmp/yc-deploy.py yakcloud.yaml diff --git a/scripts/yakcloud_ctl.py b/.yakcloud/ctl.py similarity index 100% rename from scripts/yakcloud_ctl.py rename to .yakcloud/ctl.py diff --git a/scripts/yakcloud_deploy.py b/.yakcloud/deploy.py similarity index 98% rename from scripts/yakcloud_deploy.py rename to .yakcloud/deploy.py index b8f7916..65b3ce1 100644 --- a/scripts/yakcloud_deploy.py +++ b/.yakcloud/deploy.py @@ -99,7 +99,7 @@ def reconcile_source(req: dict) -> str | None: if match and match.get("status") == "READY": log(f"source '{name}' ({stype}) 이미 READY → 스킵 (id={match['id']})") return match["id"] - mode = req.get("mode", "local") # local=인클러스터 프로비저닝 · shared=공유 외부(백엔드가 클러스터별 격리 DB 민팅) + mode = req.get("mode", "shared") # shared(기본)=공유 외부 서버에 격리 DB 민팅 · local=인클러스터 프로비저닝(옵션, size 사용) if DRY: if match: log(f"source '{name}' 상태={match.get('status')} — READY 대기 필요") diff --git a/scripts/yakcloud_dev.py b/.yakcloud/dev.py similarity index 100% rename from scripts/yakcloud_dev.py rename to .yakcloud/dev.py diff --git a/bin/yakcloud b/bin/yakcloud index d834d1c..b466da8 100755 --- a/bin/yakcloud +++ b/bin/yakcloud @@ -16,15 +16,27 @@ set -uo pipefail # -e 미사용: 'test && action' 관용구가 값 없을 때 REPO="${YAKCLOUD_STARTER_REPO:-https://gitea.yakenator.io/yakenator/yakcloud-starter}" BRANCH="${YAKCLOUD_STARTER_BRANCH:-main}" -VERSION="0.17.0" +VERSION="0.18.0" # 로컬 개발 클러스터(kind) — 개발 후 운영까지 '직선 배포'의 dev 구간. DEV_CTX="${YAK_DEV_CONTEXT:-kind-yak-dev}" DEV_CLUSTER="${YAK_DEV_CLUSTER:-yak-dev}" INGRESS_MANIFEST="${YAK_DEV_INGRESS:-https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/kind/deploy.yaml}" CONFIG_DIR="${YAKCLOUD_CONFIG_DIR:-$HOME/.config/yakcloud}" CONFIG_FILE="$CONFIG_DIR/config" +LIB_DIR="${YAKCLOUD_LIB_DIR:-$CONFIG_DIR/lib}" # 전역 엔진(deploy/ctl/dev.py) — 프로젝트에 복제하지 않음 die() { echo "✗ $*" >&2; exit 1; } + +# 전역 엔진 경로 반환 — 없으면 스타터에서 내려받는다(자가 치유). install.sh·upgrade 가 미리 깐다. +_engine() { + local f="$LIB_DIR/$1.py" + if [ ! -f "$f" ]; then + mkdir -p "$LIB_DIR" 2>/dev/null || true + curl -fsSL "$REPO/raw/branch/$BRANCH/.yakcloud/$1.py" -o "$f" 2>/dev/null \ + || echo " ⚠ 엔진 '$1' 다운로드 실패 — 네트워크 확인 또는 'yakcloud upgrade'" >&2 + fi + printf '%s' "$f" +} info() { echo "▸ $*"; } ensure_pyyaml() { @@ -36,9 +48,9 @@ ensure_pyyaml() { # 설정/조회 커맨드는 프로젝트의 ctl 엔진에 위임(현재 폴더 = 프로젝트). ctl() { - [ -f scripts/yakcloud_ctl.py ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 로 초기화하세요." + [ -f yakcloud.yaml ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 로 초기화하세요." ensure_pyyaml - python3 scripts/yakcloud_ctl.py "$@" + python3 "$(_engine ctl)" "$@" } # ── 자격 저장 ── 비밀(배포토큰·Gitea토큰)=OS 키체인(암호화), 그 외(URL·사용자)=config 파일(600) ── @@ -149,10 +161,13 @@ cmd_project_init() { rm -rf "$tmp/s/.git" cp -R "$tmp/s/." . rm -rf "$tmp" - # 사용자 프로젝트에 불필요한 배포/전역/문서 파일은 스캐폴드에서 제거 — 앱+엔진+CI+SKILL 만 깔끔히 남긴다. - # bin·install.sh=CLI 배포용 · .claude/commands=전역 슬래시명령(중복) · README/DATA-SOURCES=스타터 문서. - rm -rf bin install.sh .claude/commands README.md DATA-SOURCES.md - [ -n "$name" ] && [ -f yakcloud.yaml ] && { sed -i.bak "s/^project: .*/project: $name/" yakcloud.yaml && rm -f yakcloud.yaml.bak; } + # 스캐폴드는 '앱 + 매니페스트 + 얇은 CI'만 남긴다 — 엔진·스킬·명령은 전역(프로젝트에 미포함). + # bin·install.sh=CLI · .yakcloud=엔진(전역) · .claude=스킬·명령(전역) · README/DATA-SOURCES=스타터 문서. + rm -rf bin install.sh .yakcloud .claude README.md DATA-SOURCES.md + # 전역 엔진/스킬/명령 준비(최초 init 시에도 보장). + install_global_assets 2>/dev/null || true + # 프로젝트명을 project·워크로드(배포)명·image 에 일괄 반영(고정 'web' 대신 프로젝트명). + [ -n "$name" ] && [ -f yakcloud.yaml ] && { sed -i.bak "s/my-app/$name/g" yakcloud.yaml && rm -f yakcloud.yaml.bak; } git rev-parse --git-dir >/dev/null 2>&1 || git init -q echo "✓ 스캐폴딩 완료 ($(pwd))" # 대화형 마법사(TTY + not --yes): 클러스터·앱·포트·헬스·CPU·메모리·replicas·env 를 물어 매니페스트에 반영. @@ -178,21 +193,20 @@ _init_wizard() { printf "메모리 요청 [64Mi]: "; read -r mem; mem="${mem:-64Mi}" printf "replicas [2]: "; read -r reps; reps="${reps:-2}" printf "노출 경로(ingress path) [/]: "; read -r path; path="${path:-/}" - sed -i.bak "s/^project: .*/project: $app/" yakcloud.yaml && rm -f yakcloud.yaml.bak - sed -i.bak "s/yakcloud-starter-web/${app}-web/" yakcloud.yaml 2>/dev/null; rm -f yakcloud.yaml.bak - [ -n "${GITEA_USER:-}" ] && { sed -i.bak "s#/yakenator/#/${GITEA_USER}/#" yakcloud.yaml 2>/dev/null; rm -f yakcloud.yaml.bak; } + sed -i.bak "s/my-app/$app/g" yakcloud.yaml && rm -f yakcloud.yaml.bak # project·워크로드명·image = 앱(프로젝트)명 + [ -n "${GITEA_USER:-}" ] && { sed -i.bak "s#CHANGE_ME_GITEA_USER#${GITEA_USER}#" yakcloud.yaml 2>/dev/null; rm -f yakcloud.yaml.bak; } [ -n "$devc" ] && { sed -i.bak "s#my-dev-cluster#$devc#" yakcloud.yaml && rm -f yakcloud.yaml.bak; } [ -n "$prodc" ] && { sed -i.bak "s#my-prod-cluster#$prodc#" yakcloud.yaml && rm -f yakcloud.yaml.bak; } [ -n "$pdom" ] && { sed -i.bak "s#domains: \[\]#domains: [$pdom]#" yakcloud.yaml && rm -f yakcloud.yaml.bak; } if [ -n "$valc" ]; then sed -i.bak "s#my-val-cluster#$valc#" yakcloud.yaml && rm -f yakcloud.yaml.bak else sed -i.bak "/my-val-cluster/d" yakcloud.yaml && rm -f yakcloud.yaml.bak; fi # 검증 생략 시 val 줄 제거 ensure_pyyaml - YAKCLOUD_TOKEN= python3 scripts/yakcloud_ctl.py set web --port "$port" --health "$health" --cpu "$cpu" --mem "$mem" --path "$path" >/dev/null 2>&1 || true - YAKCLOUD_TOKEN= python3 scripts/yakcloud_ctl.py scale web "$reps" >/dev/null 2>&1 || true + YAKCLOUD_TOKEN= python3 "$(_engine ctl)" set "$app" --port "$port" --health "$health" --cpu "$cpu" --mem "$mem" --path "$path" >/dev/null 2>&1 || true + YAKCLOUD_TOKEN= python3 "$(_engine ctl)" scale "$app" "$reps" >/dev/null 2>&1 || true echo "환경변수(KEY=VALUE, 빈 줄=종료):" while :; do printf " env> "; read -r kv || break; [ -z "$kv" ] && break - YAKCLOUD_TOKEN= python3 scripts/yakcloud_ctl.py env web "$kv" >/dev/null 2>&1 || echo " (형식: KEY=VALUE)" + YAKCLOUD_TOKEN= python3 "$(_engine ctl)" env "$app" "$kv" >/dev/null 2>&1 || echo " (형식: KEY=VALUE)" done echo "✓ 매니페스트 반영: app=$app dev=${devc:-<미지정>} val=${valc:-<생략>} prod=${prodc:-<미지정>} port=$port path=$path cpu=$cpu mem=$mem replicas=$reps" } @@ -273,21 +287,21 @@ for w in m.get("workloads", []) or []: subprocess.check_call(["docker", "push", img]) PY info "리컨실 배포(콘솔 API)…" - TAG="$tag" python3 scripts/yakcloud_deploy.py yakcloud.yaml + TAG="$tag" python3 "$(_engine deploy)" yakcloud.yaml echo "✓ 로컬 배포 완료(tag=$tag) — CI 없이 직접 리컨실." } cmd_project_check() { - [ -f scripts/yakcloud_deploy.py ] || die "'yakcloud project init' 먼저" + [ -f yakcloud.yaml ] || die "'yakcloud project init' 먼저" : "${YAKCLOUD_URL:?YAKCLOUD_URL 필요}"; : "${YAKCLOUD_TOKEN:?YAKCLOUD_TOKEN 필요}" ensure_pyyaml local env=""; for a in "$@"; do case "$a" in --env=*) env="${a#--env=}" ;; --prod) env=prod ;; esac; done - YAKCLOUD_ENV="${env:-dev}" python3 scripts/yakcloud_deploy.py yakcloud.yaml --dry-run + YAKCLOUD_ENV="${env:-dev}" python3 "$(_engine deploy)" yakcloud.yaml --dry-run } # 운영 승격 — 개발에서 검증한 그 태그의 이미지를 **재빌드 없이** 운영 클러스터로 리컨실 + 운영 도메인 부착. cmd_project_promote() { - [ -f scripts/yakcloud_deploy.py ] || die "'yakcloud project init' 먼저" + [ -f yakcloud.yaml ] || die "'yakcloud project init' 먼저" : "${YAKCLOUD_URL:?YAKCLOUD_URL 필요 — 'yakcloud login'}"; : "${YAKCLOUD_TOKEN:?YAKCLOUD_TOKEN 필요 — 'yakcloud login'}" ensure_pyyaml local tag="" env="prod" @@ -305,7 +319,7 @@ cmd_project_promote() { [ -n "$tag" ] || die "승격할 태그가 없습니다 — 'yakcloud project promote vX.Y.Z' (개발 배포된 태그)" case "$tag" in v*) : ;; *) tag="v$tag" ;; esac info "'$env' 승격: $tag → '$env' 환경(재빌드 없이 개발에서 빌드된 그 이미지를 리컨실)" - YAKCLOUD_ENV="$env" TAG="$tag" python3 scripts/yakcloud_deploy.py yakcloud.yaml + YAKCLOUD_ENV="$env" TAG="$tag" python3 "$(_engine deploy)" yakcloud.yaml echo "✓ 승격 완료: $tag → '$env' 클러스터 + 도메인(기본 도메인은 자동)" } @@ -463,14 +477,14 @@ cmd_dev() { case "$sub" in up) cmd_dev_up "$@" ;; deploy|status|clean) - [ -f scripts/yakcloud_dev.py ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 먼저" + [ -f yakcloud.yaml ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 먼저" [ -f yakcloud.yaml ] || die "yakcloud.yaml 없음" ensure_pyyaml local rt tag=""; rt="$(_dev_runtime)" for a in "$@"; do case "$a" in -*) : ;; *) [ -z "$tag" ] && tag="$a" ;; esac; done local pysub="$sub"; [ "$sub" = clean ] && pysub="down" YAK_DEV_CONTEXT="$DEV_CTX" YAK_DEV_CLUSTER="$DEV_CLUSTER" YAK_DEV_RUNTIME="${rt:-docker}" \ - YAK_REG_NAME="$(_local_reg_name)" TAG="${tag:-${TAG:-dev}}" python3 scripts/yakcloud_dev.py "$pysub" yakcloud.yaml + YAK_REG_NAME="$(_local_reg_name)" TAG="${tag:-${TAG:-dev}}" python3 "$(_engine dev)" "$pysub" yakcloud.yaml [ "$sub" = deploy ] && _dev_register || true ;; down) @@ -484,20 +498,24 @@ cmd_dev() { esac } -# 전역 CLI 자기 갱신 — 버전이 같아도 항상 최신 파일을 강제로 다시 받아 덮어쓴다(캐시 우회). -# Claude Code 전역 슬래시 명령(/yakcloud) 설치/최신화 — 설치·업그레이드 시 함께 실행. -# opt-out: YAKCLOUD_NO_CLAUDE_CMD=1. 실패해도 CLI 동작엔 지장 없음(비치명적). -install_claude_command() { +# 전역 자산 설치/최신화 — 엔진(deploy/ctl/dev.py → $LIB_DIR) + Claude 스킬 + /yakcloud 슬래시명령. +# 프로젝트엔 복제하지 않는다(전역 1벌, 버전 파편화 없음). Claude 자산 opt-out: YAKCLOUD_NO_CLAUDE_CMD=1. +install_global_assets() { + local bust; bust="$(date +%s 2>/dev/null || echo 0)" n ok=1 + mkdir -p "$LIB_DIR" 2>/dev/null || true + for n in deploy ctl dev; do + curl -fsSL -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' \ + "$REPO/raw/branch/$BRANCH/.yakcloud/$n.py?nocache=$bust" -o "$LIB_DIR/$n.py" 2>/dev/null || ok=0 + done + [ "$ok" = 1 ] && echo "✓ 엔진 → $LIB_DIR (deploy/ctl/dev.py)" || echo " ⚠ 엔진 일부 다운로드 실패(무시 가능 — 필요 시 자동 재시도)" [ -z "${YAKCLOUD_NO_CLAUDE_CMD:-}" ] || return 0 - local dir="${CLAUDE_COMMANDS_DIR:-$HOME/.claude/commands}" - local bust; bust="$(date +%s 2>/dev/null || echo 0)" - if mkdir -p "$dir" 2>/dev/null && \ - curl -fsSL -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' \ - "$REPO/raw/branch/$BRANCH/.claude/commands/yakcloud.md?nocache=$bust" -o "$dir/yakcloud.md" 2>/dev/null; then - echo "✓ Claude Code 전역 슬래시 명령 → $dir/yakcloud.md (Claude Code에서 /yakcloud)" - else - echo " ⚠ /yakcloud 슬래시 명령 설치 건너뜀(무시 가능) — 터미널 yakcloud 는 정상" - fi + local sdir="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}/yakcloud-deploy" + local cdir="${CLAUDE_COMMANDS_DIR:-$HOME/.claude/commands}" + mkdir -p "$sdir" "$cdir" 2>/dev/null || true + curl -fsSL -H 'Cache-Control: no-cache' "$REPO/raw/branch/$BRANCH/.claude/skills/yakcloud-deploy/SKILL.md?nocache=$bust" -o "$sdir/SKILL.md" 2>/dev/null \ + && echo "✓ 배포 스킬 → $sdir/SKILL.md" || echo " ⚠ 스킬 설치 건너뜀(무시 가능)" + curl -fsSL -H 'Cache-Control: no-cache' "$REPO/raw/branch/$BRANCH/.claude/commands/yakcloud.md?nocache=$bust" -o "$cdir/yakcloud.md" 2>/dev/null \ + && echo "✓ /yakcloud 슬래시 명령 → $cdir/yakcloud.md" || echo " ⚠ 슬래시 명령 설치 건너뜀(무시 가능)" } cmd_upgrade() { @@ -517,28 +535,23 @@ cmd_upgrade() { mv "$tmp" "$self" 2>/dev/null || { rm -f "$tmp"; die "설치 실패(권한) — 'DEST= 재설치' 또는 sudo"; } hash -r 2>/dev/null || true # 셸 명령 해시 캐시 초기화(새 바이너리 즉시 반영) echo "✓ 업그레이드 완료: $before → $("$self" version 2>/dev/null || echo yakcloud)" - install_claude_command # /yakcloud 슬래시 명령도 최신화 + install_global_assets # 전역 엔진(deploy/ctl/dev) + 스킬 + /yakcloud 명령도 최신화 } # 기존 프로젝트의 플러밍(엔진·CI·스킬)만 최신화. 앱/매니페스트/Dockerfile 등 사용자 파일은 보존. cmd_project_update() { [ -f yakcloud.yaml ] || die "프로젝트 폴더가 아닙니다 — 'yakcloud project init' 먼저" command -v git >/dev/null 2>&1 || die "git 이 필요합니다." - info "프로젝트 플러밍 최신화(앱·매니페스트 보존)… ($REPO @ $BRANCH)" + info "CI(.gitea) 최신화 + 전역 엔진/스킬 갱신(앱·매니페스트 보존)… ($REPO @ $BRANCH)" local tmp; tmp="$(mktemp -d)" git clone -q --depth 1 -b "$BRANCH" "$REPO.git" "$tmp/s" || { rm -rf "$tmp"; die "클론 실패"; } - mkdir -p scripts .gitea/workflows .claude/skills - cp "$tmp/s/scripts/yakcloud_deploy.py" scripts/ 2>/dev/null || true - cp "$tmp/s/scripts/yakcloud_ctl.py" scripts/ 2>/dev/null || true - cp "$tmp/s/scripts/yakcloud_dev.py" scripts/ 2>/dev/null || true # 로컬 kind 리컨실 엔진(직선 dev) - rm -f scripts/yakcloud_migrate.py # 옛 migrate 잔재 제거 - cp "$tmp/s/.gitea/workflows/deploy.yml" .gitea/workflows/ 2>/dev/null || true - rm -rf .claude/skills/yakcloud-deploy - cp -R "$tmp/s/.claude/skills/yakcloud-deploy" .claude/skills/ 2>/dev/null || true + mkdir -p .gitea/workflows + cp "$tmp/s/.gitea/workflows/deploy.yml" .gitea/workflows/ 2>/dev/null || true # 얇은 CI(전역 엔진 받아 실행) rm -rf "$tmp" - # 비프로젝트 파일 정리(스캐폴드 최소화): CLI 배포용 + 전역 슬래시명령 + 스타터 문서. - rm -rf bin install.sh .claude/commands README.md DATA-SOURCES.md - echo "✓ 최신화 완료 — scripts/·.gitea/·.claude/skills 갱신(app.py·yakcloud.yaml·Dockerfile 그대로)" + # 엔진·스킬·명령은 전역(프로젝트에 복제 안 함). 옛 per-project 잔재 제거. + rm -rf scripts .yakcloud .claude bin install.sh README.md DATA-SOURCES.md + install_global_assets # 전역 엔진(deploy/ctl/dev) + 스킬 + /yakcloud 명령 최신화 + echo "✓ 최신화 완료 — .gitea CI + 전역 엔진/스킬 갱신(app.py·yakcloud.yaml·Dockerfile 그대로)" } usage() { diff --git a/install.sh b/install.sh index 497048c..db735e3 100755 --- a/install.sh +++ b/install.sh @@ -14,16 +14,27 @@ curl -fsSL -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' \ "$REPO/raw/branch/$BRANCH/bin/yakcloud?nocache=$BUST" -o "$DEST/yakcloud" chmod +x "$DEST/yakcloud" -# Claude Code 전역 슬래시 명령(/yakcloud) 설치 — opt-out: YAKCLOUD_NO_CLAUDE_CMD=1 +# 전역 엔진(deploy/ctl/dev.py) → ~/.config/yakcloud/lib — 프로젝트에 복제하지 않는다(전역 1벌). +LIB_DIR="${YAKCLOUD_LIB_DIR:-$HOME/.config/yakcloud/lib}" +mkdir -p "$LIB_DIR" +for n in deploy ctl dev; do + curl -fsSL -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' \ + "$REPO/raw/branch/$BRANCH/.yakcloud/$n.py?nocache=$BUST" -o "$LIB_DIR/$n.py" 2>/dev/null \ + || echo " ⚠ 엔진 $n 다운로드 실패(무시 가능 — CLI 가 자동 재시도)" +done +echo "✓ 엔진 설치 → $LIB_DIR (deploy/ctl/dev.py)" + +# Claude Code 전역 스킬 + 슬래시 명령(/yakcloud) 설치 — opt-out: YAKCLOUD_NO_CLAUDE_CMD=1 if [ -z "${YAKCLOUD_NO_CLAUDE_CMD:-}" ]; then + SKILL_DIR="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}/yakcloud-deploy" CMD_DIR="${CLAUDE_COMMANDS_DIR:-$HOME/.claude/commands}" - if mkdir -p "$CMD_DIR" 2>/dev/null && \ - curl -fsSL -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' \ - "$REPO/raw/branch/$BRANCH/.claude/commands/yakcloud.md?nocache=$BUST" -o "$CMD_DIR/yakcloud.md" 2>/dev/null; then - echo "✓ Claude Code 전역 슬래시 명령 설치 → $CMD_DIR/yakcloud.md" - else - echo " ⚠ /yakcloud 슬래시 명령 설치 건너뜀(무시 가능) — 터미널 yakcloud 는 정상" - fi + mkdir -p "$SKILL_DIR" "$CMD_DIR" 2>/dev/null || true + curl -fsSL -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' \ + "$REPO/raw/branch/$BRANCH/.claude/skills/yakcloud-deploy/SKILL.md?nocache=$BUST" -o "$SKILL_DIR/SKILL.md" 2>/dev/null \ + && echo "✓ 배포 스킬 설치 → $SKILL_DIR/SKILL.md" || echo " ⚠ 스킬 설치 건너뜀(무시 가능)" + curl -fsSL -H 'Cache-Control: no-cache' -H 'Pragma: no-cache' \ + "$REPO/raw/branch/$BRANCH/.claude/commands/yakcloud.md?nocache=$BUST" -o "$CMD_DIR/yakcloud.md" 2>/dev/null \ + && echo "✓ /yakcloud 슬래시 명령 설치 → $CMD_DIR/yakcloud.md" || echo " ⚠ 슬래시 명령 설치 건너뜀(무시 가능)" fi echo "✓ 완료. PATH 에 $DEST 가 있는지 확인하세요:" diff --git a/yakcloud.yaml b/yakcloud.yaml index 4710acc..abc879e 100644 --- a/yakcloud.yaml +++ b/yakcloud.yaml @@ -17,22 +17,19 @@ environments: val: { cluster: my-val-cluster } # promote --to val prod: { cluster: my-prod-cluster, domains: [] } # promote --to prod (domains 예: [app.example.com]) -# 필요한 데이터 소스(논리 이름). 없으면 [] 로 둬도 됨. -# type ∈ postgresql·mysql·mariadb·mongodb·redis·minio·rabbitmq·solr·oracle, plan ∈ small·medium·large -# ⚠ 이름이 클러스터의 기존 READY 소스와 일치하면 스킵·바인딩. -# - 인클러스터(local) 소스: 배포 토큰에 생성 권한이 있으면 자동 프로비저닝(없으면 405 → 콘솔에서 먼저 만들고 이 name 사용). -# - 공유 외부(shared) 소스(소용량 S 클러스터용): 아래처럼 mode: shared 로 선언(또는 'yakcloud source add mongodb --shared'). -# 배포 시 백엔드가 클러스터별 격리 DB/계정을 민팅(인클러스터 StatefulSet 불필요). 콘솔/API 등록도 가능. 예: -# - { name: appdb, type: mongodb, mode: shared } -# ('yakcloud project check' 로 미리 확인·리스팅) +# 필요한 데이터 소스(논리 이름). 없으면 [] 로. 기본 = **공유(shared)** — 이미 있는 공유 소스에 연결 + 격리 DB 민팅. +# type ∈ postgresql·mysql·mariadb·mongodb·redis·minio·rabbitmq·solr·oracle +# - 기본(shared, mode 생략): 공유 외부 서버에 클러스터/프로젝트별 **격리 DB·계정** 민팅(인클러스터 리소스 생성 없음). +# - 인클러스터(옵션): 정말 전용 인스턴스가 필요할 때만 `mode: local, plan: small|medium|large`. +# - 이름이 클러스터의 기존 READY 소스와 일치하면 스킵·바인딩. ('yakcloud project check' 로 미리 확인) requires: - - { name: appdb, type: postgresql, plan: small } + - { name: appdb, type: postgresql } # 기본 shared. 전용 인스턴스는 { …, mode: local, plan: small } # 워크로드(앱 컨테이너). CI 는 각 workload.build 를 도커 빌드해 image 로 push 한다(${TAG}=릴리스 태그). workloads: - - name: web + - name: my-app # 배포(워크로드)명 = 프로젝트명(권장). 'project init ' 이 프로젝트명으로 설정. build: ./ # 도커 빌드 컨텍스트(Dockerfile 위치). 예: ./ 또는 ./api - image: gitea.yakenator.io/CHANGE_ME_GITEA_USER/my-app-web:${TAG} + image: gitea.yakenator.io/CHANGE_ME_GITEA_USER/my-app:${TAG} port: 8080 replicas: 2 # ≥2 → 무중단 롤링(readiness 게이트) health: /healthz