From ea46fe78179c1277a494b81bf968e1fd4991e08f Mon Sep 17 00:00:00 2001 From: ttipo Date: Thu, 17 Sep 2026 22:23:37 +0900 Subject: [PATCH] =?UTF-8?q?ci:=20Gitea=20Actions=20=EB=9F=AC=EB=84=88=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI 워크플로는 저장소에 있지만 실행 주체(러너)가 없어 동작하지 않는 상태였다. 러너가 없으면 워크플로는 "Waiting for a runner" 로 대기만 한다. - ci/docker-compose.runner.yml — act_runner 를 별도 스택으로 분리. 앱 스택은 개발할 때만 띄우지만 러너는 항상 켜져 있어야 하므로 생명주기가 다르다. - ci/.env.example — 등록 토큰은 ci/.env 에만 두며 커밋되지 않는다. - ci/README.md — 설치 절차, 증상별 문제 해결, 잡별 리소스 특성. - 라벨 기본값이 워크플로의 runs-on(ubuntu-latest)과 일치하는지 교차 확인했다. 불일치는 잡이 영원히 대기하는 가장 흔한 원인이다. 러너가 도커 소켓에 접근해 사실상 호스트 관리자 권한을 갖는다는 점도 문서에 명시했다. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/docker/SKILL.md | 19 +++++++++++ .gitignore | 2 ++ README.md | 1 + ci/.env.example | 20 ++++++++++++ ci/README.md | 60 ++++++++++++++++++++++++++++++++++ ci/docker-compose.runner.yml | 26 +++++++++++++++ 6 files changed, 128 insertions(+) create mode 100644 ci/.env.example create mode 100644 ci/README.md create mode 100644 ci/docker-compose.runner.yml diff --git a/.claude/skills/docker/SKILL.md b/.claude/skills/docker/SKILL.md index 76d24f5..07306c1 100644 --- a/.claude/skills/docker/SKILL.md +++ b/.claude/skills/docker/SKILL.md @@ -76,6 +76,25 @@ dc exec redis redis-cli -a --- +## CI 러너 (별도 스택) + +`.github/workflows/ci.yml` 을 실행하는 Gitea Actions 러너는 **앱 스택과 생명주기가 다르다.** +앱은 개발할 때만 띄우지만 러너는 항상 켜져 있어야 하므로 `ci/` 에 별도 compose 로 분리했다. + +```bash +cd ci +cp .env.example .env # 등록 토큰 입력 (커밋되지 않음) +docker compose -f docker-compose.runner.yml --env-file .env up -d +``` + +- 러너가 없으면 워크플로는 **"Waiting for a runner"** 로 대기만 한다. +- `RUNNER_LABELS` 의 라벨이 워크플로의 `runs-on` 과 **일치해야** 잡을 집는다. +- 러너는 도커 소켓에 접근하므로 사실상 호스트 관리자 권한을 갖는다 — 신뢰할 수 있는 워크플로만 실행되게 한다. + +설치·문제 해결은 [`ci/README.md`](../../../ci/README.md) 참조. + +--- + ## 선택적 서비스 (Redis) **Redis 는 기본적으로 기동하지 않는다.** 단일 인스턴스에서는 in-memory 캐시로 충분하고, diff --git a/.gitignore b/.gitignore index 01ad4af..df744a5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,8 @@ !/data/.gitkeep /db-data/ /redis-data/ +# CI 러너 등록 정보 +ci/runner-data/ # 업로드 파일 (로컬 저장) — 코드가 아니다 /data/uploads/ backend/uploads/ diff --git a/README.md b/README.md index a8a553e..d48947a 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,7 @@ lint 는 `--max-warnings 0` 이라 경고 하나만 있어도 실패합니다. ├── .github/ │ ├── workflows/ci.yml # lint·test·build + 에러 코드 동기화 검사 │ └── pull_request_template.md +├── ci/ # CI 러너 (Gitea Actions) — 앱 스택과 분리 └── .claude/ ├── settings.json # Claude Code 권한·환경 설정 ├── rules/ # 공통 규칙 (코딩·응답 포맷·에러 코드·API 설계·로깅·보안·환경변수·테스트) diff --git a/ci/.env.example b/ci/.env.example new file mode 100644 index 0000000..c245e69 --- /dev/null +++ b/ci/.env.example @@ -0,0 +1,20 @@ +# ========================================== +# Gitea Actions 러너 설정 +# +# 사용법: cp .env.example .env 후 토큰을 채운다. +# ⚠️ .env 는 커밋되지 않는다 (루트 .gitignore). 토큰을 이 예시 파일에 적지 말 것. +# ========================================== + +# Gitea 인스턴스 주소 (끝에 / 없이) +GITEA_INSTANCE_URL=https://git.wandergo.net + +# 등록 토큰 — 저장소 Settings → Actions → Runners → Create new Runner 에서 발급 +# (여러 저장소에서 공용으로 쓰려면 관리자 화면의 인스턴스 레벨 토큰을 사용) +GITEA_RUNNER_REGISTRATION_TOKEN= + +# 러너 식별 이름 — 여러 대를 띄운다면 구분되게 짓는다 +RUNNER_NAME=gitea-runner + +# 라벨: <워크플로의 runs-on>:docker://<잡을 실행할 이미지> +# ci.yml 이 ubuntu-latest 를 쓰므로 그대로 두면 된다. +RUNNER_LABELS=ubuntu-latest:docker://gitea/runner-images:ubuntu-latest diff --git a/ci/README.md b/ci/README.md new file mode 100644 index 0000000..549e4a7 --- /dev/null +++ b/ci/README.md @@ -0,0 +1,60 @@ +# CI 러너 설정 + +`.github/workflows/ci.yml` 을 실제로 실행하는 Gitea Actions 러너 설정입니다. +**러너가 없으면 워크플로는 "Waiting for a runner" 상태로 대기만 합니다.** + +## 어디에 띄울 것인가 + +| 위치 | 장점 | 단점 | +| --- | --- | --- | +| **Gitea 서버** (권장) | 항상 켜져 있어 CI 가 늘 동작 | 서버에 Docker 필요 | +| 개발 PC | 바로 시작 가능 | PC 가 꺼지면 CI 도 멈춤 | + +CI 의 목적이 "내가 없어도 검사되는 것"이므로 서버 쪽을 권장합니다. + +## 설치 + +```bash +# 1) 저장소 Settings → Actions → Runners → Create new Runner 에서 토큰 발급 +# 2) 설정 파일 준비 +cd ci +cp .env.example .env # 편집기로 열어 GITEA_RUNNER_REGISTRATION_TOKEN 채우기 + +# 3) 기동 +docker compose -f docker-compose.runner.yml --env-file .env up -d + +# 4) 등록 확인 +docker compose -f docker-compose.runner.yml --env-file .env logs -f runner +# "Runner registered successfully" 가 보이면 성공 +``` + +저장소 **Settings → Actions → Runners** 에서 러너가 **Idle** 로 표시되면 준비 완료입니다. +이후 푸시하면 Actions 탭에서 실행이 시작됩니다. + +## 잘 안 될 때 + +| 증상 | 원인 | 해결 | +| --- | --- | --- | +| 잡이 계속 대기 중 | 라벨 불일치 | `RUNNER_LABELS` 의 `ubuntu-latest` 가 워크플로 `runs-on` 과 같은지 확인 | +| `uses:` 액션 다운로드 실패 | 서버에서 github.com 접근 불가 | 방화벽 확인. 폐쇄망이면 액션 미러 필요 | +| 등록 실패 (401/404) | 토큰 만료·오타, URL 끝 `/` | 토큰 재발급, `GITEA_INSTANCE_URL` 확인 | +| 잡 시작 직후 실패 | 도커 소켓 마운트 누락 | `/var/run/docker.sock` 마운트 확인 (Windows 는 `//var/run/docker.sock`) | + +## 리소스 참고 + +`ci.yml` 은 네 개의 잡을 실행합니다. + +- `backend` — postgres 서비스 컨테이너를 띄워 단위 테스트와 e2e 를 실행 +- `frontend` — lint·type-check·테스트 +- `flutter` — **매 실행마다 Flutter SDK 를 내려받아 수 분 소요** +- `error-codes-sync` — 에러 코드 생성 파일 드리프트 검사 + +서버 사양이 넉넉하지 않다면 처음에는 `flutter` 잡을 빼고 시작해도 됩니다. +나머지 세 개만으로도 대부분의 회귀는 걸립니다. + +## 주의 + +- **토큰은 `ci/.env` 에만** 둡니다. 이 파일은 루트 `.gitignore` 로 커밋되지 않습니다. +- `runner-data/` 에 등록 정보가 저장됩니다. 지우면 토큰으로 재등록해야 합니다. +- 러너는 도커 소켓에 접근하므로 **호스트에 대한 사실상의 관리자 권한**을 갖습니다. + 신뢰할 수 있는 저장소의 워크플로만 실행되도록 하세요 (공개 저장소의 PR 실행 정책 확인). diff --git a/ci/docker-compose.runner.yml b/ci/docker-compose.runner.yml new file mode 100644 index 0000000..50f16c2 --- /dev/null +++ b/ci/docker-compose.runner.yml @@ -0,0 +1,26 @@ +# Gitea Actions 러너 — CI 를 실제로 실행하는 주체 +# +# 이 파일은 애플리케이션 스택(루트 docker-compose.yml)과 **생명주기가 다르다.** +# 앱은 개발할 때만 띄우지만 러너는 항상 켜져 있어야 CI 가 동작한다. +# +# 사용법은 ci/README.md 참조. + +services: + runner: + image: gitea/act_runner:latest + container_name: ${RUNNER_NAME:-gitea-runner} + restart: always + environment: + GITEA_INSTANCE_URL: ${GITEA_INSTANCE_URL} + # ⚠️ 등록 토큰 — ci/.env 에만 두고 절대 커밋하지 않는다 + GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN} + GITEA_RUNNER_NAME: ${RUNNER_NAME:-gitea-runner} + # ⚠️ 워크플로의 runs-on 과 일치해야 잡을 집는다. + # .github/workflows/ci.yml 은 모든 잡이 ubuntu-latest 다. + GITEA_RUNNER_LABELS: ${RUNNER_LABELS:-ubuntu-latest:docker://gitea/runner-images:ubuntu-latest} + volumes: + # 잡을 컨테이너로 띄우므로 도커 소켓이 필요하다. + # (Windows Docker Desktop 에서 Git Bash 를 쓰면 경로 변환 때문에 //var/run/... 로 적어야 할 수 있다) + - /var/run/docker.sock:/var/run/docker.sock + # 등록 정보를 보존한다 — 지우면 토큰으로 재등록해야 한다 + - ./runner-data:/data