Skip to content

Latest commit

 

History

History
292 lines (197 loc) · 18.3 KB

File metadata and controls

292 lines (197 loc) · 18.3 KB

공개 릴리즈 보조 스크립트

이 디렉터리의 스크립트는 private ReadMates 작업 tree에서 공개 가능한 릴리즈 후보를 만들고 검사합니다. 이 스크립트들은 GitHub에 게시하지 않고, 저장소 공개 설정을 바꾸지 않고, secret을 교체하지 않고, commit을 만들지 않습니다.

공개 저장소 전환과 secret 관리 기준은 공개 저장소 보안을 함께 확인합니다.

스크립트 작업은 후보 생성 또는 검사 결과가 명확히 출력되고, 발견된 finding을 공개 가능/수정 필요/active secret 가능성으로 분류했을 때 완료입니다. 실패한 scanner 결과를 단순 경고처럼 취급하지 않습니다.

gitleaks 미설치 fallback이나 current-tree historical finding처럼 검증 강도가 낮거나 해석이 필요한 경우에는 그 한계를 결과에 남깁니다. 이 스크립트 통과만으로 secret rotation, GitHub 공개 전환, branch protection 설정, production 배포가 끝났다고 판단하지 않습니다.

server-ci-check.sh

서버 코드를 수정한 뒤 GitHub Actions Backend job과 같은 품질 게이트를 로컬에서 먼저 확인합니다.

./scripts/server-ci-check.sh

실행 순서는 다음과 같습니다.

  • ./server/gradlew -p server check
  • ./server/gradlew -p server architectureTest

check는 ktlint baseline, detekt baseline, 서버 테스트, JaCoCo coverage를 함께 실행합니다. architectureTest는 CI처럼 별도 단계로 실행해 clean architecture boundary 회귀를 확인합니다.

스크립트 자체나 문서 변경만 빠르게 검증할 때는 dry-run으로 실행 명령만 확인할 수 있습니다.

READMATES_SERVER_CI_CHECK_DRY_RUN=true ./scripts/server-ci-check.sh

pre-push-check.sh

푸시 전에 CI에서 자주 실패하던 게이트를 로컬에서 먼저 실행합니다.

./scripts/pre-push-check.sh

기본 실행 범위는 다음과 같습니다.

  • git diff --check
  • pnpm --dir front lint
  • pnpm --dir front test:coverage
  • pnpm --dir front build
  • pnpm --dir front zod:export-fixtures
  • git diff --exit-code front/tests/unit/__fixtures__/zod-schemas/
  • ./server/gradlew -p server check

docs/, scripts/, deploy/, .github/, 공개 release 설정 파일처럼 공개 후보에 영향을 주는 경로가 바뀌면 clean 후보를 만들고 public-release scanner도 실행합니다. Historical 작업 기록인 docs/superpowers/ 하위 문서는 현재 동작의 source of truth가 아니므로 whitespace gate에서 제외합니다.

./scripts/build-public-release-candidate.sh
./scripts/public-release-check.sh .tmp/public-release-candidate

릴리즈 또는 태그 배포 직전에는 더 무거운 검증까지 포함합니다.

./scripts/pre-push-check.sh --full --release

--full./server/gradlew -p server integrationTest, pnpm --dir front test:e2e, 그리고 관측 설정 검증(validate-prometheus-rules.sh, validate-prometheus-config.sh, validate-alertmanager-config.sh)을 추가로 실행합니다. Docker, MySQL client, Playwright browser 의존성이 준비되지 않은 환경에서는 기본 pre-push hook보다 수동 릴리즈 점검으로 실행합니다.

Release-mode CHANGELOG guard

--release(또는 환경 변수 READMATES_PRE_PUSH_RELEASE=true)로 release 모드를 강제하면 CHANGELOG.md## Unreleased 섹션이 placeholder 상태인지 확인하는 가드가 먼저 실행됩니다. v1.11.0에서 Unreleased 섹션이 수동 정리에 의존했던 회귀를 막기 위한 안전장치입니다.

  • 차단 조건: ### Added, ### Changed, ### Fixed, ### Engineering, ### Engineering Proof Portfolio, ### Deployment Notes, ### Verification, ### Removed, ### Security 같은 구체적 카테고리 헤더가 남아 있거나, bullet이 두 개 이상이거나, bullet에 markdown bold(**) 마커가 포함된 경우.
  • 통과 조건: 비어 있는 placeholder (_No unreleased changes._, <!-- placeholder -->) 또는 ReadMates의 기존 convention인 ### Highlights 아래 단일 meta-placeholder bullet (예: 다음 릴리즈 후보 변경을 이 섹션에 기록합니다.).
  • Emergency override: --no-changelog-check로 가드를 건너뛸 수 있습니다. 이 경우 release-management Branch protection bypass policy 절에 따라 bypass 사유를 ledger에 기록해야 합니다.
# Release tag push 직전 표준 실행
./scripts/pre-push-check.sh --release

# CI 또는 자동화 컨텍스트에서 환경 변수로 release 모드 강제
READMATES_PRE_PUSH_RELEASE=true ./scripts/pre-push-check.sh

# CHANGELOG 경로를 override해서 fixture로 테스트
READMATES_PRE_PUSH_CHANGELOG=/tmp/test-changelog-stale.md \
  ./scripts/pre-push-check.sh --release

# Emergency 우회 (사유 ledger 기록 필수)
./scripts/pre-push-check.sh --release --no-changelog-check

Branch protection bypass 정책 전반은 release-management.md#branch-protection-bypass-policy를 참조합니다.

로컬 Git hook은 .git/hooks/pre-push에서 이 스크립트를 호출하도록 설치할 수 있습니다. Hook은 로컬 설정이므로 --no-verify로 우회할 수 있고 다른 clone에는 자동 전파되지 않습니다.

lint-grafana-dashboards.sh

ops/grafana/dashboards/*.json JSON 유효성과 필수 필드(title, schemaVersion, panels)를 검사합니다. CI backend job에서 실행됩니다.

./scripts/lint-grafana-dashboards.sh

validate-prometheus-rules.sh / validate-prometheus-config.sh / validate-alertmanager-config.sh

관측 설정 파일의 구조 유효성을 Docker 기반 promtool/amtool로 검사합니다. 로컬에 promtool/amtool을 설치하지 않아도 되도록 컨테이너 이미지(prom/prometheus, prom/alertmanager)로 실행하며, pre-push-check.sh --full이 릴리즈 직전에 함께 실행합니다.

./scripts/validate-prometheus-rules.sh    # ops/prometheus/alerts/*.yml rule 검사
./scripts/validate-prometheus-config.sh   # deploy/oci/prometheus/prometheus.yml 검사
./scripts/validate-alertmanager-config.sh # deploy/oci/alertmanager/alertmanager.yml 구조 검사

validate-alertmanager-config.sh${READMATES_ALERT_*} 환경 placeholder를 dummy 값으로 치환한 임시 파일을 lint하므로 실제 SMTP credential 없이 구조만 검증합니다. 치환 결과는 .tmp 아래 임시 디렉터리에 만들고 종료 시 삭제합니다.

observability-local-smoke.sh

로컬 Prometheus/Grafana stack을 띄워 alert rule load, readmates-server target 등록, Grafana datasource/dashboard provisioning을 확인합니다. 실제 운영 domain, receiver, credential은 사용하지 않습니다.

./scripts/lint-grafana-dashboards.sh
./scripts/validate-prometheus-rules.sh
./scripts/observability-local-smoke.sh

로컬 Spring Boot 서버가 8081 management port로 /actuator/prometheus를 노출 중이면 target health까지 함께 확인할 수 있습니다. 서버가 떠 있지 않으면 이 smoke는 target presence와 provisioning 확인까지만 로컬 증거로 사용하고, scrape health는 운영 bring-up에서 확인합니다.

generate-slo-report.py

server/src/main/resources/slo/slos.yaml의 6개 Prometheus query를 실행해 월간 SLO markdown 초안을 출력합니다. 운영자는 승인된 tunnel 또는 port-forward로 Prometheus를 로컬 주소에 열고, CHECK 행을 incident/deploy 맥락과 함께 검토합니다.

python3 scripts/generate-slo-report.py \
  --prometheus-url http://localhost:9090 \
  --month 2026-06 > docs/operations/slo-reports/2026-06.md

보고서에는 실제 운영 도메인, 수신자 이메일, 토큰, private endpoint를 쓰지 않습니다.

aigen-pii-check.sh

In-app AI 세션 생성 경로가 transcript 본문을 durable store, Kafka message, metric tag, Flyway column으로 흘리지 않는지 확인합니다. Redis aigen:job:<jobId>:transcript는 worker handoff용 short-lived key로만 허용됩니다.

bash scripts/aigen-pii-check.sh

CI scripts job이 PR마다 실행합니다. 실패하면 출력의 checkN 메시지와 AI session generation runbook을 기준으로 어느 invariant가 깨졌는지 확인합니다.

aigen-smoke-{claude,openai,gemini}.sh

Provider별 라이브 API key가 있는 운영 또는 staging 노드에서 AI generation multipart start/polling smoke를 수동 확인합니다. 이 스크립트는 live key를 요구하므로 공개 CI에서는 실행하지 않습니다.

./scripts/aigen-smoke-claude.sh
./scripts/aigen-smoke-openai.sh
./scripts/aigen-smoke-gemini.sh

Provider key, transcript, 응답 전문, 운영 domain은 Git에 남기지 않습니다. 모델 allowlist, cap, key 회전, kill switch 절차는 AI session generation runbook을 기준으로 합니다.

build-public-release-candidate.sh

아래 명령은 저장소 루트에서 실행하는 것을 기준으로 합니다. 스크립트 자체는 저장소 내부 어디에서든 실행할 수 있지만, 이 문서의 ./scripts/... 경로는 저장소 루트에서 그대로 복사해 실행할 수 있습니다.

./scripts/build-public-release-candidate.sh

출력 위치는 .tmp/public-release-candidate로 고정되어 있습니다. 임의 destination 인자는 지원하지 않습니다.

스크립트는 먼저 .tmp가 저장소 안의 .tmp로 해석되는지 확인합니다. 그 다음 .tmp/public-release-candidate.staging.* 아래에 staging tree를 만들고, 검증을 통과한 뒤에만 기존 .tmp/public-release-candidate를 교체합니다. 빌드가 실패하면 이전에 성공한 후보는 그대로 남습니다.

복사 전 preflight에서는 승인된 source root의 .envrc* loader 파일과 symlink를 거부합니다. .envrc는 secret을 직접 담지 않더라도 로컬 환경을 자동으로 불러올 수 있으므로 공개 후보 manifest에 포함하지 않습니다.

공개 릴리즈 후보 manifest는 명시적으로 관리합니다. 주요 포함 범위는 다음과 같습니다.

  • .github/workflows/ci.yml
  • .github/workflows/deploy-front.yml
  • .github/workflows/deploy-server.yml
  • .github/CODEOWNERS, 파일이 있을 때만 포함
  • .gitignore
  • .gitleaks.toml, 파일이 있을 때만 포함
  • .env.example
  • README.md
  • compose.yml
  • front/
  • server/
  • deploy/oci/: compose 배포 script, read-only diagnostics collector, post-deploy watch helper를 포함합니다. 공개 후보 scanner 대상이므로 운영 출력, deploy state, secret-bearing env, provider state를 넣지 않습니다.
  • docs/development/
  • docs/deploy/
  • docs/operations/README.mddocs/operations/runbooks/
  • 공개 릴리즈 보조 스크립트와 운영 증거 helper: scripts/build-public-release-candidate.sh, scripts/README.md, scripts/generate-slo-report.py, scripts/lint-grafana-dashboards.sh, scripts/observability-local-smoke.sh, scripts/public-release-check.sh, scripts/validate-prometheus-rules.sh, scripts/verify-public-release-fixtures.sh
  • 서버 CI 사전 점검 스크립트: scripts/server-ci-check.sh
  • 푸시 전 CI 사전 점검 스크립트: scripts/pre-push-check.sh
  • 배포 후 공개 연동 smoke script: scripts/smoke-production-integrations.sh

디렉터리를 복사할 때 copy_dir 공통 exclude는 .env*, *.env, key material, dump, .DS_Store를 제외합니다. manifest별 exclude는 front/output, front/node_modules, front/dist, front/test-results, front/playwright-report, front/coverage, front/.nyc_output, server/build, server/.gradle, server/.kotlin, deploy/oci/.deploy-state, deploy/oci/*.state를 복사하지 않습니다. docs/superpowers/ 하위 문서는 private historical 작업 기록으로 간주해 공개 후보에 포함하지 않습니다. provider state, screenshot, design, .gstack, .superpowers, .idea, .playwright-cli, .tmp, recode처럼 공개 후보 금지 경로로 분류되는 항목은 복사 중 조용히 제외된다고 가정하지 않고, staging 후보 검증에서 발견되면 거부되어 빌드가 실패합니다.

루트 .env.example만 의도적으로 포함되는 environment file입니다. 필수 파일과 디렉터리 root는 symlink일 수 없고, 승인된 source root 안에서 발견되는 symlink도 복사 전에 거부합니다. staging 후보 검증 단계에서도 승인된 manifest 밖의 경로, 금지 경로, .envrc*, symlink가 남아 있으면 실패합니다.

성공하면 후보 경로와 후속 확인 명령을 출력합니다. 루트 .gitleaks.toml이 있으면 후보에 함께 포함되어, 공개 전 같은 custom scanner rule을 사용할 수 있습니다.

.github/CODEOWNERS가 후보에 포함되더라도 Code Owner review enforcement는 GitHub branch protection 설정과 protected base branch에 병합된 CODEOWNERS 파일이 함께 있어야 적용됩니다. 후보 검증은 파일 포함과 scanner 통과를 확인하고, GitHub 설정은 별도 API나 Settings 화면에서 확인합니다.

public-release-check.sh

clean 공개 릴리즈 후보를 검사합니다.

./scripts/public-release-check.sh .tmp/public-release-candidate

인자 없이 실행하면 현재 private 작업 tree를 검사합니다.

./scripts/public-release-check.sh

현재 tree 모드는 git ls-files를 기준으로 tracked 금지 경로와 tracked symlink를 확인합니다. 후보 모드는 전달한 디렉터리를 find로 순회하며, 후보 안의 모든 symlink와 금지 경로를 거부합니다.

no-argument current-tree mode는 private 작업 tree의 ignored 파일까지 gitleaks dir .로 읽을 수 있습니다. .server-config/, .wrangler/, .gstack/, .tmp/, docs/private/, .claude/, .orchestrator/처럼 ignored local 운영/도구 파일을 일부러 검증 범위에서 제외하는 작업에서는 current-tree mode를 pass/fail gate로 삼지 말고, clean 후보와 tracked archive를 검사합니다.

./scripts/build-public-release-candidate.sh
./scripts/public-release-check.sh .tmp/public-release-candidate

tmp="$(mktemp -d)"
git archive HEAD | tar -x -C "$tmp"
gitleaks dir "$tmp" --config "$tmp/.gitleaks.toml" --no-banner --redact=100 --verbose
rm -rf "$tmp"

docs/superpowers/는 private historical 작업 기록으로 간주해 공개 후보에 포함하지 않습니다. 현재 동작이나 운영 절차로 승격된 내용은 docs/development/, docs/deploy/, docs/operations/ 중 적절한 source-of-truth 문서로 옮긴 뒤 공개 후보 scanner 대상에 둡니다.

checker가 차단하는 주요 항목은 다음과 같습니다.

  • private keys
  • OCI OCIDs
  • GitHub tokens
  • OpenAI/API-key-shaped tokens
  • 실제처럼 보이는 DB/BFF/OAuth secret assignment
  • Gmail addresses
  • private club domains
  • local workstation paths
  • private 또는 generated path로 분류된 금지 경로

gitleaks가 설치되어 있으면 repository의 .gitleaks.toml 설정으로 gitleaks dir <path>를 실행합니다. 설치된 gitleaksdir subcommand를 지원하지 않는 구버전이면 gitleaks detect --source <path>로 compatibility fallback을 실행하고, 그 사실을 출력합니다.

현재 filesystem을 보는 gitleaks dir와 달리 gitleaks detect --source .는 Git history까지 검사합니다. 과거 commit의 redacted example이나 fixture finding은 active secret 여부와 별도로 분류하고, active 또는 active 가능 secret이 확인되지 않으면 history rewrite와 force-push를 기본 처리로 삼지 않습니다.

gitleaks가 없더라도 targeted path/content check는 계속 실행합니다. 다만 fallback check는 좁은 guardrail입니다. 통과했다고 해서 전문적이거나 완전한 secret scan을 통과한 것은 아니며, 공개 전 명백한 실수를 줄이기 위한 로컬 안전장치로 봐야 합니다.

verify-public-release-fixtures.sh

scanner pattern을 바꾼 뒤 fixture 검증을 실행합니다.

./scripts/verify-public-release-fixtures.sh

이 스크립트는 fixture 디렉터리를 .tmp/public-release-fixtures 아래에 만들고 public-release-check.sh를 호출합니다. 검증 범위는 다음과 같습니다.

  • dollar 문자가 포함된 DB password assignment가 차단되는지 확인합니다.
  • comment에 placeholder를 적어도 실제처럼 보이는 secret value가 allowlist되지 않는지 확인합니다.
  • 문서화된 placeholder와 environment variable indirection은 통과하는지 확인합니다.
  • .tmp parent가 symlink이면 실행을 거부합니다. 이 기준은 공개 릴리즈 후보 builder의 cleanup guard와 맞춥니다.

fixture 검증도 GitHub 게시, 저장소 공개 설정 변경, secret rotation, commit 생성을 수행하지 않습니다.

smoke-production-integrations.sh

배포 후 Cloudflare Pages marker와 Google OAuth start redirect를 확인합니다. Secret을 요구하지 않으며, 실제 운영 결과는 공개 문서나 Git에 붙이지 않습니다.

READMATES_SMOKE_BASE_URL=https://readmates.pages.dev \
READMATES_SMOKE_AUTH_BASE_URL=https://readmates.pages.dev \
./scripts/smoke-production-integrations.sh

등록된 club host까지 확인할 때는 READMATES_SMOKE_CLUB_HOST를 추가합니다.

READMATES_SMOKE_BASE_URL=https://readmates.pages.dev \
READMATES_SMOKE_AUTH_BASE_URL=https://<primary-domain> \
READMATES_SMOKE_CLUB_HOST=https://<registered-club-host> \
./scripts/smoke-production-integrations.sh

검사 항목:

  • /.well-known/readmates-domain-check.json이 ReadMates Cloudflare Pages marker를 반환합니다.
  • /oauth2/authorization/google?returnTo=/app이 Google OAuth endpoint로 redirect됩니다.
  • Google에 전달되는 redirect_uriREADMATES_SMOKE_AUTH_BASE_URL/login/oauth2/code/google와 일치합니다.

READMATES_SMOKE_STRICT_GOOGLE=true를 추가하면 Google 응답 본문에서 redirect_uri_mismatch를 탐지하려고 시도합니다. Google 로그인 화면은 지역, 계정 상태, bot 방어에 따라 응답이 바뀔 수 있으므로 기본 검사는 ReadMates가 생성하는 provider redirect URL을 기준으로 합니다.