# designpaca 배포 운영 계획 이 문서는 designpaca를 git.chanpaca.net(Forgejo)에서 빌드·배포·릴리스하기 위한 전제와 절차를 적는다. 한 번 읽고 그대로 따라 하면 첫 릴리스까지 갈 수 있어야 한다. ## 0. 전제 — 러너 환경 CI는 self-hosted **`linux-builder`** 러너(host 모드)에서 돈다. host 모드라 러너 머신에 있는 것만 쓸 수 있다. | 필요 | 확인 | 없을 때 | |---|---|---| | Node 20 이상 | `node -v` | `fnm install 22 && fnm default 22` 후 러너 재시작 | | pnpm 10 | `pnpm -v` | `corepack enable && corepack prepare pnpm@10.5.2 --activate` | | git, curl, tar, python3 | `command -v` | 배포판 패키지로 설치 | `build/ci/verify-node.sh`가 매 잡 첫 스텝에서 이걸 검사하고, 없으면 **명확한 메시지로 즉시 실패**시킨다. nvm/fnm/volta의 흔한 설치 경로는 자동으로 PATH에 올려본다. > host 러너에는 Node가 없을 수 있으므로 워크플로에서 `uses:` 액션을 쓰지 않는다. > 체크아웃도 `run:` 스텝의 git 명령으로 처리한다 (videodownloader 파이프라인과 동일한 패턴). ## 1. 저장소 ``` git.chanpaca.net//designpaca ``` - 소스 공개 여부는 자유. 단 **npm 배포는 npmjs 공개 레지스트리**이므로 코드는 어차피 tarball로 공개된다 - 기본 브랜치: `main` - 태그 규칙: `v` (예: `v0.1.0`) — 태그 푸시가 릴리스 트리거 ## 2. 시크릿 등록 저장소 → Settings → Actions → Secrets 에 등록한다. | 이름 | 용도 | 발급처 | |---|---|---| | `NPM_TOKEN` | npmjs 배포 | npmjs.com → Access Tokens → **Automation** 타입 | | `FORGEJO_NPM_TOKEN` | 사설 레지스트리 미러 | Forgejo → 설정 → 애플리케이션 → 액세스 토큰 (`write:package` 권한) | | `RELEASE_TOKEN` | 릴리스 생성·자산 업로드 | Forgejo 액세스 토큰 (`write:repository`) | | `CF_API_TOKEN` | Cloudflare Pages 배포 | Cloudflare → API Tokens → **Edit Cloudflare Workers** 템플릿 | | `CF_ACCOUNT_ID` | Cloudflare 계정 식별 | Cloudflare 대시보드 우측 하단 Account ID | 변수(Variables)에 선택적으로: | 이름 | 기본값 | 설명 | |---|---|---| | `FORGEJO_NPM_OWNER` | `yunchan` | 사설 npm 패키지 소유자 | > **시크릿이 없어도 파이프라인은 죽지 않는다.** `publish-npm.sh`는 `FORGEJO_NPM_TOKEN`이 없으면 미러를 건너뛰고, > 릴리스 스텝은 `CF_API_TOKEN`이 없으면 페이지 배포를 건너뛴다. 하나씩 붙여가며 굴릴 수 있다. ## 3. 릴리스 절차 ### 3.1 변경 기록 ```bash pnpm changeset # 무엇이 바뀌었는지, patch/minor/major 중 무엇인지 기록 git add .changeset && git commit -m "changeset: <요약>" ``` ### 3.2 버전 확정 ```bash pnpm version # changeset version + lockfile 갱신 # packages/*/package.json 과 CHANGELOG.md 가 갱신된다 git add -A && git commit -m "release: v<새 버전>" ``` `designpaca` · `@designpaca/core` · `@designpaca/skill`은 changesets의 **fixed 그룹**이라 항상 같은 버전으로 움직인다. 스킬 내용과 CLI 버전이 어긋나면 업그레이드 판정(`.designpaca_version` 비교)이 깨지기 때문이다. ### 3.3 태그 푸시 = 릴리스 발동 ```bash git tag v0.1.0 && git push origin main --tags ``` 파이프라인이 순서대로 수행한다: 1. `verify-node.sh` → 의존성 설치 2. **태그와 `packages/cli/package.json` 버전 일치 확인** (다르면 즉시 실패) 3. 스킬 문서 검사 → 타입 검사 → 테스트 → 빌드 → 배포물 점검 4. `npm publish` (npmjs) → Forgejo 레지스트리 미러. **이미 배포된 버전이면 조용히 건너뛴다**(재실행 안전) 5. Forgejo **draft 릴리스** 생성 + tarball 첨부 6. Cloudflare Pages 배포 ### 3.4 승격 draft 릴리스를 확인하고 수동으로 **Publish**한다. draft는 익명에게 보이지 않으므로 QA 시간을 벌 수 있다. ### 3.5 리허설 태그 없이 돌려볼 수 있다. Actions → release → **Run workflow**. `ci-rehearsal` prerelease 릴리스로 전 과정을 검증하되 **npm 배포는 건너뛴다**(`DO_PUBLISH=0`). ## 4. 사용자 설치 경로 ```bash npx designpaca # 온보딩 TUI npx designpaca install -t claude-code,codex -s user -y ``` 사설 레지스트리에서 직접 받으려면: ```bash npx --registry=https://git.chanpaca.net/api/packages//npm/ designpaca ``` ## 5. 소개 페이지 - 배포 대상: Cloudflare Pages 프로젝트 **`designpaca`** - 도메인: `designpaca.chanpaca.net` (Cloudflare DNS에 CNAME → Pages) - 최초 1회만 수동: Cloudflare 대시보드에서 Pages 프로젝트 생성(빈 프로젝트) → 커스텀 도메인 연결. 이후에는 릴리스 워크플로의 `wrangler pages deploy`가 알아서 올린다 ## 6. 장애 대응 | 증상 | 원인 | 조치 | |---|---|---| | `Node 를 찾을 수 없다` | host 러너에 Node 미설치 | 러너 머신에 fnm으로 Node 22 설치 후 러너 재시작 | | `태그와 package.json 이 다르다` | `pnpm version` 없이 태그를 밀었다 | 태그 삭제 → 3.2부터 다시 | | npm publish 403 | 토큰 만료 / 이름 선점 | `NPM_TOKEN` 재발급, `npm view designpaca` 로 소유 확인 | | Forgejo 미러 401 | 토큰에 `write:package` 없음 | 토큰 권한 재발급 | | 릴리스 자산 업로드 실패 | `RELEASE_TOKEN` 권한 부족 | `write:repository` 부여 | | Pages 배포 스킵 | `CF_API_TOKEN` 미등록 | 시크릿 등록 (없어도 나머지는 성공한다) | ## 7. 롤백 npm은 배포 취소가 사실상 불가능하다(72시간 이내 unpublish만 가능, 같은 버전 재사용 금지). 따라서 **되돌리기가 아니라 앞으로 감는다**: ```bash # 문제 버전을 latest 에서 내린다 npm dist-tag add designpaca@<직전 정상 버전> latest # 수정 후 새 패치 릴리스 ``` 사용자 쪽 복구는 `npx designpaca@<정상버전> update --force`. --- ## 8. 실제 배포에서 걸린 것 (2026-08-20) 문서대로 되지 않은 지점들이다. 다음 사람이 같은 데서 막히지 않게 남긴다. ### git push 는 도메인으로 못 한다 `git.chanpaca.net` 은 Cloudflare 뒤에 있어(104.21.x) **22·2222 포트가 둘 다 막혀 있다.** 오리진을 감추는 정상 구성이다. push 는 Tailscale 주소로 한다. ``` ssh://git@100.116.83.60:2222/yunchan/designpaca.git ``` SSH 키는 이미 Forgejo 에 등록돼 있어(`encep-win`) 토큰도 비밀번호도 필요 없다. ### CI 가 네 번 실패했고 전부 다른 이유였다 | # | 증상 | 원인 | |---|---|---| | 1 | `exit 126` / 허가 거부 | Windows 에서 만든 `.sh` 가 mode 100644 로 커밋됐다. `git update-index --chmod=+x` + 워크플로에서 `bash script` 로 호출 | | 2 | `Node 20 이상이 필요하다 (현재 v18)` | 러너에 Node 18 뿐. fnm 으로 22 설치. **그리고 `verify-node.sh` 가 Node 가 *없을 때만* 대체 경로를 훑고 있었다** — 낡았을 때도 찾도록 고침 | | 3 | `ERR_MODULE_NOT_FOUND: picocolors` | tarball 을 풀어 `node dist/index.js` 를 직접 불렀다. 그건 npx 경로가 아니다. 임시 프로젝트에 `npm i` 후 실행하도록 교체 | | 4 | 초록불인데 `# pass 0` | 빌드보다 테스트가 먼저라 CLI 테스트 6개가 전부 SKIP. 순서를 바꾸고 **SKIP 이 있으면 실패시키는 단계**를 넣었다 | **4번이 가장 위험했다.** 통과한 게 아니라 건너뛴 것이었고, 그래도 초록이었다. ### Cloudflare 는 계정 ID 가 문제였다 `403 Authentication error` 가 계속 났다. 토큰 권한을 의심했지만 실제로는 **`.env` 의 `CF_ACCOUNT_ID` 가 토큰이 볼 수 없는 계정을 가리키고 있었다.** 진단법: `/accounts` 목록을 부르면 **토큰이 실제로 접근 가능한 계정**이 나온다. 그 id 로 `/accounts/{id}/pages/projects` 를 부르면 권한 유무가 갈린다. - `/accounts` 목록이 200 인 것은 권한 증거가 아니다. 유효한 토큰이면 누구나 200 이다 - `/user/tokens/verify` 는 **User 토큰 전용**이다. 계정 소유 토큰(`cfat…`)은 여기서 거부당한다. 그걸 보고 "토큰이 잘못됐다"고 판단하면 틀린다 — `wrangler whoami` 로 교차 확인해라 ### Pages 커스텀 도메인은 DNS 레코드를 직접 만들 필요가 없다 `designpaca.chanpaca.net` 을 Pages 프로젝트에 등록하면 **Cloudflare 가 CNAME 을 알아서 만든다.** 그런데 그 레코드는 **DNS 레코드 목록에도, DNS API 조회에도 나오지 않는다.** Pages 가 관리하기 때문이다. 그래서 "레코드가 없네" 하고 손으로 추가하려 하면 이 오류를 만난다. ``` An A, AAAA, or CNAME record with that host already exists. ``` **이 오류가 곧 성공 신호다.** 레코드는 이미 있고, 상태만 `pending` → `active` 로 넘어가길 기다리면 된다. 확인은 DNS API 가 아니라 **Pages 도메인 API** 로 한다. ``` GET /accounts/{acc}/pages/projects/{proj}/domains → status: pending | active ``` 실측: 등록 직후 `pending`, 몇 분 뒤 `active`(검증 http, 인증서 google). 그때부터 200 이 뜬다. ### 아직 남은 것 - `NPM_TOKEN` — 이름 선점이라 사람 승인 후에만 태그를 민다