기여
저장소에서 개발하고 변경을 검증하는 문서는 docs/contributing/에 있다.
- 개발 환경과 검증
- 구성요소와 경계
- CI에서 실행되는 것
- 콘솔 데모 모드: 서버 없이 화면 보기
- 로컬 모델(Ollama·LiteLLM)로 확인하기
- 콘솔·Slack 용어집
- 설계 결정 기록
릴리스 게시와 공급망
이 절은 저장소에 게시 권한이 있는 릴리스 관리자의 절차다. 설치 운영자는 배포 패키지 계약에서 이미 게시한 digest를 읽는다.
릴리스 준비 PR로 게시
- 변경 기록의 미출시 내용을 새 버전 절로 옮기는 PR을 만든다. 보안 변경과 운영자가 해야 할 일을 함께 적는다. 버전은 정확한 semver를 따른다.
- PR을 머지하면 Images가 새 버전 제목을 감지한다. 제목 중복·이름 변경·형식 오류·기존 태그나 Release·재작성된 history는 실패한다.
- 같은 commit의 main 이미지·chart 게시와 CI, 모든 source 보관 검사를 기다린다.
- release-approve가 요청하는 release 환경 승인을 한 번 받는다. 승인 전에는 릴리스 태그·이미지·chart·Release가 없다.
- 이미 검사한 main digest에 릴리스 태그를 붙여 다시 읽고 chart를 게시한다. 그 뒤 승인 commit에 annotated tag를 만들고 manifest가 붙은 GitHub Release를 공개한다.
이미지에 OCI version label을 더하려고 재빌드하지 않는다. 승격 이미지는 main 빌드와 같은 digest이며 버전은 태그·Release·manifest에서 읽는다. provenance·SBOM attestation을 생성하지 않는 pipeline에서는 존재하지 않는 보증을 가정하지 않는다.
승인 거절 또는 30일 만료는 실행을 실패시킨다. 같은 실행의 실패 job을 재실행해도 새 release 환경 승인을 받아야 한다. staging artifact가 남아 있어야 같은 digest로 이어갈 수 있다. 이미 다른 digest를 가리키는 태그나 다른 실행이 만든 같은 버전은 덮어쓰지 않는다. 전체 실행을 다시 빌드하여 우회하지 않는다. images-promote 동시 실행 잠금은 같은 릴리스 게시를 직렬화하고, 기존 태그·Release 검사는 다른 실행이 같은 버전을 중복 게시하지 못하게 한다.
사람이 태그를 만드는 경로
main의 승인된 commit에 정확한 vX.Y.Z 또는 semver prerelease 태그를 push한다. 태그 대상은 origin/main의 ancestor여야 한다. 축약 태그 v1이나 build metadata가 붙은 v1.2.3+build는 사용하지 않는다. 이런 태그는 이미지만 나가고 chart job에서 실패한다. 이 경로는 이미지를 다시 빌드한다. publish 앞 release 승인 한 번 뒤 sources·index·promote·chart·release가 이어진다. 소스 보관·라이선스·취약점 검사와 두 아키텍처 smoke가 모두 성공해야 게시된다. main-<12>와 digest가 같다고 가정하지 않는다.
Release는 draft로 만들고 manifest를 업로드·재조회한 뒤 공개한다. 같은 태그의 내용이 다르면 실패하고 덮어쓰지 않는다. 게시 뒤 Chart.yaml을 다음 개발 버전으로 올린다. release pipeline의 태그 생성 권한은 저장소 ruleset에도 허용돼 있어야 한다.
main 게시가 실패하거나 게시되지 않은 commit이 있어도 다음 main 머지를 막지는 않는다. 연속 push가 합쳐지면 일부 commit에는 게시 실행이나 main-<12> 태그가 없을 수 있다. 한번 게시한 main 태그는 불변이며 같은 이름을 새 digest로 덮지 않는다. chart의 main 버전은 <Chart.yaml version>-main-<12>다. -main.<12>를 쓰지 않는 이유는 숫자로만 이뤄지고 0으로 시작하는 SHA 조각이 semver의 숫자 prerelease 식별자 규칙을 위반할 수 있기 때문이다.
공급망 검사 정책
수정판이 있는 high·critical 취약점만 막는다. 수정판이 없는 발견은 보고만 하고 실패시키지 않는다.
- 무엇을 보나. 세 이미지(Grype, Debian 패키지와
/app의 npm 패키지)와bun.lock전체(bun audit, dev 의존성 포함)다. "수정판이 있다"는 이미지에서는 Grype의fix.state가fixed인 것이고, npm에서는 GitHub advisory database에서 잠긴 버전을 포함하는 범위에first_patched_version이 있는 것이다. Debian의not-fixed·wont-fix와 수정판 없는 npm advisory는 막지 않는다. - 어디서 막나. images.yml의
supply-chainjob이 판정하고, branch protection의 required check다. PR, main push, 매일 03:17 UTC 실행, 수동 실행,v*tag에서 모두 돈다. tag의publish와 main의publish-main은supply-chain을 기다리고, 승격할 digest를 다시 검사해 같은 기준으로 막는다. - 검사가 답을 못 받으면. Grype DB나 advisory 서비스가 응답하지 않은
error는 PR과 main push에서 경고만 남긴다. 외부 서비스 장애로 모든 머지가 멈추면 안 되기 때문이다. 매일 실행과 tag에서는 실패한다. main push도 게시는 하지 않는다.publish-main이supply-chain결과를 같은 파일로 다시 판정해error면 실패한다. 이미지가 빌드되지 않아 결과가 아예 없으면 어디서든 실패한다. - 보고. 수정판 없는 high·critical도 각 검사의 step summary(접힌 목록)와
supply-chain-resultsartifact(검사마다 JSON 하나, 항목마다fix가 null)에 남는다. 매일 실행이나 수동 실행이 실패하면ci-supply-chain라벨 이슈가 열린다. - 밤사이 새 advisory. 수정판이 있는 advisory가 새로 나오면 main을 포함해 모든 PR의
supply-chain이 한꺼번에 빨개진다. 의도한 동작이다. 의존성이나 base를 올리는 PR 하나로 푼다. Debian 패키지는 다음 날의APT_UPGRADE_KEY로 새 빌드가 받는다. 당일에 받으려면 수동 실행(gh workflow run Images)이 그날 key로 빌드한다. - 예외. 올릴 수 없는 수정(예:
upgrade가 보류한 Debian 패키지, 코드 경로가 닿지 않는 advisory)은.github/vulnerability-exceptions.json에{"id", "package", "reason", "expires"}로 적는다.id는 검사 결과의id(CVE-… 또는 GHSA-…),package는 패키지 이름이다.reason과expires(YYYY-MM-DD, 그날까지 적용)가 없으면 npm 검사가error가 된다. 만료되면 다시 막는다. 예외는 PR 리뷰를 거친다.
bun .github/scripts/npm-audit.ts /tmp/sc && cat /tmp/sc/npm.txt # the npm verdict, locally
.github/scripts/supply-chain-verdict.sh /tmp/sc warn # verdict over the four result files (control-host, worker, egress-proxy, npm)제3자 고지(THIRD_PARTY_NOTICES.md와 이미지마다 /app/DEBIAN_SOURCES.md)를 만드는 방법은 ci.md에 있다.