cli v0.5.0: deploy가 도메인 리컨실 + 소스 405 액션형 에러 + --local 폴백/러너 프리플라이트 + 에러 힌트/pyyaml 가드; init에서 bin/install 제거; 문서(토큰권한·healthz·러너) 보강

This commit is contained in:
2026-08-27 18:43:37 +09:00
parent d0c27e349b
commit d19242029f
5 changed files with 173 additions and 80 deletions

View File

@ -18,7 +18,7 @@ set -uo pipefail # -e 미사용: 'test && action' 관용구가 값 없을 때
REPO="${YAKCLOUD_STARTER_REPO:-https://gitea.yakenator.io/yakenator/yakcloud-starter}"
BRANCH="${YAKCLOUD_STARTER_BRANCH:-main}"
VERSION="0.4.0"
VERSION="0.5.0"
CONFIG_DIR="${YAKCLOUD_CONFIG_DIR:-$HOME/.config/yakcloud}"
CONFIG_FILE="$CONFIG_DIR/config"
@ -147,6 +147,8 @@ cmd_project_init() {
rm -rf "$tmp/s/.git"
cp -R "$tmp/s/." .
rm -rf "$tmp"
# CLI 배포 파일(전역 yakcloud 사용)은 사용자 프로젝트에 불필요 → 스캐폴드에서 제거.
rm -rf bin install.sh
[ -n "$name" ] && [ -f yakcloud.yaml ] && { sed -i.bak "s/^project: .*/project: $name/" yakcloud.yaml && rm -f yakcloud.yaml.bak; }
git rev-parse --git-dir >/dev/null 2>&1 || git init -q
echo "✓ 스캐폴딩 완료 ($(pwd))"
@ -192,28 +194,77 @@ _maybe_login_prompt() {
}
cmd_project_deploy() {
local local_mode=0 ver=""
for a in "$@"; do case "$a" in --local) local_mode=1 ;; *) [ -z "$ver" ] && ver="$a" ;; esac; done
[ -f yakcloud.yaml ] || die "yakcloud.yaml 없음 — 'yakcloud project init' 먼저"
if [ "$local_mode" = 1 ]; then cmd_project_deploy_local "$ver"; return; fi
git rev-parse --git-dir >/dev/null 2>&1 || git init -q
git remote get-url origin >/dev/null 2>&1 \
|| die "git 원격(origin) 없음 — Gitea 레포 만들어 'git remote add origin <url>' + Secrets 설정 후 다시"
|| die "git 원격(origin) 없음 — Gitea 레포 만들어 'git remote add origin <url>' + Secrets 설정 후 다시 (또는 'yakcloud project deploy --local')"
git fetch -q --tags origin 2>/dev/null || true
# 버전: 인자 있으면 그것, 없으면 최신 v* 태그에서 패치 +1(없으면 v0.1.0) — 배포마다 자동 버전업.
local ver="${1:-}"
if [ -z "$ver" ]; then
local last; last="$(git tag -l 'v*' --sort=-v:refname 2>/dev/null | head -1)"
if [ -n "$last" ]; then
ver="$(printf '%s' "$last" | awk -F. 'BEGIN{OFS="."} {$NF=$NF+1; print}')"
else
ver="v0.1.0"
fi
if [ -n "$last" ]; then ver="$(printf '%s' "$last" | awk -F. 'BEGIN{OFS="."} {$NF=$NF+1; print}')"; else ver="v0.1.0"; fi
fi
case "$ver" in v*) : ;; *) ver="v$ver" ;; esac
_runner_preflight # 러너 없으면 경고(비차단)
info "배포 버전: $ver (커밋 + 태그 push)"
git add -A; git commit -q -m "deploy $ver" 2>/dev/null || true
git push -q origin HEAD 2>/dev/null || true
git tag "$ver" 2>/dev/null || die "태그 $ver 이미 존재 — 'yakcloud project deploy <다음버전>'"
git push -q origin "$ver"
echo "✓ $ver push — Gitea Actions 에서 빌드·배포. 콘솔 앱 탭/도메인 확인."
echo "✓ $ver push — Gitea Actions 에서 빌드·배포. Actions 탭에서 결과 확인."
echo " 실패/러너 없음이면: yakcloud project deploy --local (러너 없이 로컬 빌드/푸시/리컨실)"
}
# 러너 프리플라이트(best-effort) — origin 이 Gitea 면 레포 러너 유무 확인. 없으면 경고(비차단).
_runner_preflight() {
local url base owner_repo n
url="$(git remote get-url origin 2>/dev/null)" || return 0
base="${GITEA_URL:-https://gitea.yakenator.io}"
case "$url" in *"${base#https://}"*|*gitea.yakenator.io*) : ;; *) return 0 ;; esac
[ -n "${GITEA_TOKEN:-}" ] || return 0
owner_repo="$(printf '%s' "$url" | sed -E 's#^[a-z]+://[^@]*@?[^/]+/##; s#\.git$##')"
n="$(curl -fsS -H "Authorization: token $GITEA_TOKEN" "$base/api/v1/repos/$owner_repo/actions/runners" 2>/dev/null \
| python3 -c 'import sys,json
try:
d=json.load(sys.stdin); print(len(d if isinstance(d,list) else d.get("runners",[])))
except Exception: print(-1)' 2>/dev/null || echo -1)"
if [ "$n" = "0" ]; then
echo " ⚠ 이 레포에 등록된 Actions 러너가 없습니다 — 공유 러너가 라벨을 못 맞추면 태그 push 잡이 조용히 실패할 수 있습니다."
echo " 확실히 하려면: yakcloud project deploy --local"
fi
}
# CI 없이 로컬에서 build/push/리컨실 — 러너가 없거나 죽었을 때의 확실한 대안.
cmd_project_deploy_local() {
: "${YAKCLOUD_URL:?YAKCLOUD_URL 필요 — 'yakcloud login'}"; : "${YAKCLOUD_TOKEN:?YAKCLOUD_TOKEN 필요 — 'yakcloud login'}"
command -v docker >/dev/null 2>&1 || die "docker 필요(--local 로컬 빌드/푸시)"
ensure_pyyaml
local tag="${1:-}"; [ -z "$tag" ] && tag="dev-$(date +%Y%m%d-%H%M%S)"; case "$tag" in v*|dev-*) : ;; *) tag="v$tag" ;; esac
local reg="${GITEA_URL:-https://gitea.yakenator.io}"; reg="${reg#https://}"; reg="${reg#http://}"
if [ -n "${GITEA_USER:-}" ] && [ -n "${GITEA_TOKEN:-}" ]; then
printf '%s' "$GITEA_TOKEN" | docker login "$reg" -u "$GITEA_USER" --password-stdin >/dev/null 2>&1 \
|| info "docker login 경고($reg) — 레지스트리 push 실패 가능(yakcloud login 확인)"
else
info "Gitea 자격 없음 — 'yakcloud login' 후 --local 권장(레지스트리 push)"
fi
info "로컬 빌드/푸시 (tag=$tag)…"
TAG="$tag" python3 - <<'PY'
import os, subprocess, yaml
tag = os.environ["TAG"]; m = yaml.safe_load(open("yakcloud.yaml"))
for w in m.get("workloads", []) or []:
ctx = w.get("build")
if not ctx:
continue
img = w["image"].replace("${TAG}", tag)
print("▸ build %s: %s -> %s" % (w["name"], ctx, img), flush=True)
subprocess.check_call(["docker", "build", "-t", img, ctx])
subprocess.check_call(["docker", "push", img])
PY
info "리컨실 배포(콘솔 API)…"
TAG="$tag" python3 scripts/yakcloud_deploy.py yakcloud.yaml
echo "✓ 로컬 배포 완료(tag=$tag) — CI 없이 직접 리컨실."
}
cmd_project_check() {
@ -258,14 +309,16 @@ cmd_project_update() {
rm -rf .claude/skills/yakcloud-deploy
cp -R "$tmp/s/.claude/skills/yakcloud-deploy" .claude/skills/ 2>/dev/null || true
rm -rf "$tmp"
rm -rf bin install.sh # 옛 스캐폴드 잔재(CLI 배포 파일) 정리
echo "✓ 최신화 완료 — scripts/·.gitea/·.claude 스킬 갱신(app.py·yakcloud.yaml·Dockerfile 그대로)"
}
usage() {
cat <<EOF
yakcloud $VERSION — YakCloud 프로젝트 CLI
project init [name] 빈 폴더에 스타터 스캐폴딩(앱+CI+매니페스트+Claude 스킬)
project deploy [vX.Y.Z] 커밋 + 태그 push → Gitea Actions 자동 배포(버전 생략=자동 버전업)
project init [name] 빈 폴더에 스타터 스캐폴딩(대화형 마법사) + 자격 프롬프트
project deploy [vX.Y.Z] 커밋 + 태그 push → Gitea Actions(버전 생략=자동 버전업). 도메인도 리컨실.
project deploy --local 러너 없이 로컬 build/push/리컨실(CI 대안)
project info 프로젝트 개괄(매니페스트 + 라이브 상태)
project check dry-run(직접 API 계획만)
project update 이 프로젝트의 엔진·CI·스킬만 최신화(앱·매니페스트 보존)