• LINKS 1.0.4 — VX/VX Ace 원본 재확인을 stager 계정으로·원본 권한 code 분리

    LINKS 1.0.4

    이 patch 릴리스는 이전 릴리스 1.0.3(5e262a8dde)부터 최종 소스 f9171bfa75까지를 묶었습니다. 루트 패키지는 1.0.4이고 ARCA pin은 exact 0.1.18로 그대로입니다. 1.0.3 운영 결함 하나를 고쳤습니다. VX/VX Ace 게시 직전의 원본 재확인을 stager 계정 one-shot으로 옮겼으므로, 원본을 stager 계정만 읽을 수 있는 NAS(ACL)에서도 준비가 끝까지 진행됩니다. 원본 읽기 거부와 저장소 거부는 서로 다른 code로 표시됩니다. schema는 바뀌지 않았습니다. DB 초기화와 migration이 필요 없습니다.

    업그레이드 주의: 1.0.3 → 1.0.4는 sh update.sh만 실행합니다(초기화 없음)

    1. 갱신 방법: 1.0.3에서는 배포 디렉터리에서 sh update.sh만 실행합니다. 1.0.2 이하에서 올 때는 1.0.3과 같은 1회 curl 전환을 거칩니다. 아래 절차의 tag만 1.0.4로 바꿔 새 update.sh를 한 번 받고, 그다음 sh update.sh를 실행합니다. 1.0.1 이하라면 먼저 host coordinator를 종료합니다(운영 가이드 '최초 설치와 이전 release에서의 전환'). schema는 바뀌지 않았고(infra/migrations/base.sha256이 1.0.3과 같고 migration은 0개) reset.sh를 실행하지 않습니다. 갱신 전 backup(sh backup.sh)은 선택입니다.
    2. 원본 재확인 계정: VX/VX Ace 게시 직전의 원본 재확인이 stager 계정(LINKS_RUNTIME_STAGER_UID:GID)의 one-shot runtime-package-stager(새 명령 verify-source)에서 실행됩니다. 이전에는 package worker(10001)가 했습니다. 그래서 원본을 stager 계정만 읽을 수 있는 NAS(Synology ACL)에서도 준비가 seal → stage → Builder → 원본 재확인 → 게시까지 진행됩니다. 10001 package worker(runtime-package-worker)는 원본 library를 mount하지 않습니다. 운영자 추가 작업은 없습니다. 바뀐 compose.yaml·coordinator script는 update.sh가 설치하고, .env.production·원본 ACL·저장소 소유권은 그대로 씁니다.
    3. 원본 권한 code 분리: 원본 읽기가 거부되면 새 code RUNTIME_SOURCE_PERMISSION_DENIED로 끝납니다. 이 code는 재시도할 수 없습니다. 원본 접근 권한, stager 계정, NAS ACL을 확인합니다. 저장소(sealed·artifact store) 거부는 계속 RUNTIME_STORE_PERMISSION_DENIED입니다. 두 code 모두 진단에는 경로 없는 원인 code(EACCES 등)만 남습니다.
    4. 1.0.3에서 실패한 준비: 1.0.3에서 RUNTIME_STORE_PERMISSION_DENIED(publishing 단계, cause EACCES)로 끝난 VX/VX Ace 준비는 재시도 불가로 기록돼 있습니다. 화면의 다시 시도 대신 관리자 게임 관리의 실행 빌드 관리에서 빌드 재생성으로 다시 요청합니다.
    5. NAS 조건 gate: 릴리스 전에 격리 docker:24.0.2-dind에서 이 갱신을 검증했습니다. host Compose는 공식 Docker Compose v2.20.1 binary(release의 게시 checksum으로 확인)였습니다. 계정은 stager 1026:100이고, 원본 library 최상위는 0:0 000에 1026만 여는 ACL을 두었습니다(10001은 읽지 못함). 1.0.3에서 VX Ace 준비가 운영과 같은 RUNTIME_STORE_PERMISSION_DENIED로 실패하는 것을 재현했습니다. 이어서 설치된 1.0.3 update.sh로 1.0.4 갱신, 빌드 재생성으로 build ready, 재-update까지 모두 성공했습니다(아래 검증). Synology build(v2.20.1-6047-g6817716)·Synology ACL과 공식 binary·Linux POSIX ACL은 다를 수 있으므로 실제 NAS 결과는 확인 전입니다.

    NAS 절차(1.0.3에서):

    cd /volume1/docker/links
    sh update.sh
    

    성공하면 단계 표시 6줄 뒤에 Updated to 1.0.4 (catalog fd371daaf742), runtime.package ready.가 출력됩니다. 실패하면 원인 한 줄을 출력하고 nonzero로 끝납니다.

    원본 game library는 읽기 전용으로 유지합니다. 이번 게시 작업에서 개발·운영 DB 초기화나 NAS 배포는 실행하지 않았습니다.

    VX/VX Ace 원본 재확인의 stager 계정 이동

    • coordinator의 한 attempt는 seal → stage → Builder → verify-source → publish 순서입니다. seal과 verify-source는 stager 계정의 runtime-package-stager에서 실행하고, stage와 publish는 10001 package worker에서 실행합니다. sh·PowerShell coordinator 모두 verify-source를 publish 바로 앞에 두고, 둘 사이에 다른 단계는 없습니다.
    • verify-source는 seal이 남긴 봉인 identity를 엄격히 검사합니다. 그다음 원본을 같은 함수와 인자로 다시 확인하고, 일치 결과를 경로·제목 없이 checkpoint에 남깁니다. publish는 다른 작업보다 먼저 이 결과를 handoff와 대조합니다. 결과가 없거나 맞지 않으면 게시하지 않습니다(RUNTIME_PACKAGE_INVALID_OUTPUT).
    • 봉인 뒤 원본이 바뀌면 이전과 같이 RUNTIME_SOURCE_CHANGED(재시도 가능)로 끝납니다. 원본 본문 읽기는 이전과 같이 seal 2회와 재확인 1회입니다. 게시 transaction의 source lock 보장도 바뀌지 않았습니다.
    • verify-source 실패(nonzero 또는 비어 있지 않은 출력)는 publish 없이 attempt 실패로 처리하며, coordinator는 다음 job으로 넘어갑니다.

    원본 권한 code

    • RUNTIME_SOURCE_PERMISSION_DENIED(재시도 불가): seal의 복사 전 원본 검사와 verify-source의 원본 재확인에서 원본 읽기가 거부된 경우입니다. 문구는 "원본 게임 폴더를 읽을 권한이 없습니다. 관리자가 원본 접근 권한(stager 계정·NAS ACL)을 확인해야 합니다."입니다.
    • RUNTIME_STORE_PERMISSION_DENIED의 문구에서 "원본 또는"을 뺐습니다. 이제 이 code는 저장소 거부만 뜻합니다.
    • MV/MZ 원본 거부는 기존대로 BOUNDARY_REJECTED입니다.

    문서

    • 운영 가이드(1.0.3 → 1.0.4 갱신, Stager 계정과 저장소 권한, 원본 ACL), 런타임 계약, API 계약, 보안 정책, 완료 기준선, 개발 계획(NAS gate에 verify-source 추가), tests/README를 갱신했습니다.

    알려진 제한(후속 과제)

    • 원본 재확인부터 게시까지의 간격이 늘었습니다. 늘어난 부분은 publish one-shot 시작, release 검증, 봉인 사본 검증입니다. 128 MiB 합성 원본에서 약 1.2초가 늘었고, 봉인 사본 검증은 게임 크기에 비례합니다. 그 사이 원본 변경은 이전과 같이 게시 transaction의 DB source lock과 scan 관찰에 맡깁니다.
    • 실제 NAS(Synology ACL·btrfs)에서의 VX/VX Ace package 실행과 PowerShell coordinator 전체 실행은 NOT RUN입니다.
    • 1.0.3 배포 때 Compose 2.20.1에서 관찰된 사례가 있습니다. 중단된 up이 network 없는 container를 남길 수 있는데, update.sh는 이를 감지하지 않습니다. 운영자는 docker compose --env-file .env.production --profile runtime up -d --no-deps --force-recreate <service>로 복구합니다.
    • 1.0.3과 같은 제한:
      • /artifacts 쓰기 거부는 기존 분류를 따릅니다.
      • load 뒤에 권한이 바뀌면 ARTIFACT_INVALID로 끝납니다.
      • 소유 계정이 섞인 sealed store는 지원하지 않습니다.
      • Docker 24 graphdriver에서는 Builder config digest를 pin과 직접 대조하지 않습니다(ARCA 후속).
      • 3단계 이후 실패하면 coordinator가 정지된 채 남습니다. 원인을 고친 뒤 다음 sh update.sh가 다시 기동합니다.
      • Player 세이브 관리 listener(3005)는 catalog caller token으로만 보호되며, edge network에서 TCP로 닿을 수 있습니다.
      • catalog·Player의 일반 egress는 network로 제한하지 않습니다.
      • update.sh는 opt-in profile container(translation-import 등)를 갱신하지 않습니다.

    검증과 남은 범위

    • PASS: pkg-verify 브랜치(병합 c217f0d)
      • unit: 전체 149 files / 1,190 tests, 관련 5 files / 119 tests
      • schema 32 files / 228 tests, static, runtime-workers, reset 기록기 30·update.sh 기록기 93
      • Compose config 3종, PowerShell coordinator parse·sh -n, 문서 검사, git diff --check
      • 격리 dind e2e(Compose 2.29.1): stager 1026:100과 1026 전용 원본에서 수정 전 publishing RUNTIME_STORE_PERMISSION_DENIED 재현 → 수정 뒤 build ready. 기본 10001:10001도 ready. 원본을 못 읽는 오구성은 RUNTIME_SOURCE_PERMISSION_DENIED
      • 리뷰 최종 판정: 병합 가능(P1~P3 없음)
    • PASS: 릴리스 gate(Compose v2.20.1, NAS 조건)
      • 환경: 격리 docker:24.0.2-dind(overlay2, BusyBox ash), host Compose는 공식 v2.20.1 linux-x86_64 binary(release의 게시 checksum과 일치). 합성 CA의 HTTPS release 서버가 Gitea를, 합성 TLS registry가 git.2ex.dev를 대신했습니다(dind daemon에만 적용). registry에는 실제 1.0.3 image와 이 릴리스의 1.0.4 image를 넣었고, 배포 파일은 1.0.3 tag와 이 릴리스 커밋의 Git blob입니다.
      • 운영과 같은 계정과 원본 권한: scanner·stager 1026:100, ./data bind store, 원본 library 최상위 0:0 000 + ACL u:1026:r-x. 10001의 find /library ! -readable은 /library 1줄, 1026은 0줄
      • 1.0.3 설치 → reset.sh → update.sh: exit 0(106초), Updated to 1.0.3 .... MV/MZ 준비 성공. VX Ace 준비는 seal·stage·Builder 뒤 publishing에서 RUNTIME_STORE_PERMISSION_DENIED(cause EACCES, 재시도 불가)로 실패. 운영 로그와 같습니다
      • 1.0.4 게시(registry latest = 1.0.4, asset 없음) 뒤 설치된 1.0.3 update.sh: exit 0(47초), coordinator 정지 → pull → 전체 갱신·healthy → coordinator 재기동 → Updated to 1.0.4 .... 7개 파일이 release와 같고 .tmp는 0. package worker의 렌더링 mount에 /library가 없습니다
      • 실패한 VX Ace 준비를 관리자 빌드 재생성 경로(force 요청)로 다시 요청: seal(runtime-package-stager, 1026:100) → stage → Builder → verify-source(runtime-package-stager, 1026:100) → publish → build ready. 모든 one-shot이 exit 0, attempt 1에 성공. sealed 항목은 모두 1026:100이고 Builder 계정의 읽기·탐색 실패는 0
      • 설치 상태에서 재-update: exit 0(13초), 배포 파일 변화 없음, 준비 상태 ready 유지
      • 매 배포 뒤 결과: 12개 service의 network가 선언과 일치(불일치 0), unhealthy 0, package·runtime readiness exit 0, coordinator 재시작 0
    • PASS: 최종 1.0.4 소스. 릴리스 커밋 두 개(root version, 완료 기준선의 version 문구) 뒤 다음을 다시 통과했습니다: 문서 검사(760개 파일 / Markdown 32개), static(Prettier·ESLint·typecheck·docs-static), git diff --check.
    • 게시 이미지: 같은 최종 소스에서 build했고, gate도 이 image로 실행했습니다. image 안에서 root version 1.0.4와 ARCA pin·설치본 0.1.18을 확인했습니다.
    • PARTIAL: 전체 UI/제품 acceptance. 화면·fixture·package/build 성공을 실제 browser play PASS로 기록하지 않습니다.
    • NOT RUN:
      • Synology NAS 실제 환경(Compose v2.20.1 Synology build, Synology ACL, Docker 24 btrfs)에서의 1.0.3 → 1.0.4 갱신과 VX/VX Ace package
      • PowerShell coordinator 전체 실행
      • 실제 Gitea release·registry를 대상으로 한 전체 stack 갱신(gate는 합성 release 서버·registry)
      • 실제 library/private game과 실제 scan
      • 입력·오디오·save/reload의 실제 게임 acceptance
      • live provider
      • 개발 rebuild

    배포 이미지와 파일

    • Git annotated tag는 1.0.4이고 최종 소스는 f9171bfa75입니다.
    • 단일 linux/amd64 이미지: git.2ex.dev/hanyul/links:1.0.4 = git.2ex.dev/hanyul/links:latest
    • 원격 digest: sha256:fd371daaf7427f4a725ab35a31e8214e6cd0114a3e9049c0a5b2af5be034ea09
    • release asset은 없습니다. update.sh가 tag 1.0.4의 7개 배포 파일을 HTTPS로 받습니다. 대응은 update.sh=ops/deploy-registry.sh, reset.sh=ops/reset-registry.sh, backup.sh=ops/backup-registry.sh이고, 나머지는 compose.yaml·infra/docker/Caddyfile·ops/backup-verify.compose.yaml·ops/runtime-package-coordinator.sh입니다.
    • 1.0.3 대비 바뀐 배포 파일은 두 개입니다. compose.yaml(package worker의 원본 library mount 제거)과 ops/runtime-package-coordinator.sh(publish 직전 verify-source 단계)입니다. 나머지 5개는 같습니다.
    Downloads 2