• LINKS 1.0 — forward migration 사슬·운영 명령 단순화·문서 정리

    LINKS 1.0

    이전 릴리스 0.22.1(5cf5e1f2c3)부터 최종 소스 edac251454까지를 묶은 1.0 릴리스입니다. 루트 패키지는 1.0.0, ARCA는 exact 0.1.17(변경 없음)입니다. 1.0부터 schema 변경은 DB 초기화 대신 forward migration으로 배포합니다. 기준 schema에 migration ledger(links_schema_migration_history)가 추가되어 checksum이 바뀌었고, 그 checksum이 migration 사슬의 시작점입니다. 운영 명령 update.sh·reset.sh는 인자 없는 단일 동작으로 단순화했습니다.

    업그레이드 주의 — 0.x → 1.0은 마지막으로 한 번 DB 초기화(sh reset.sh)가 필요합니다

    1.0 기준 schema checksum 14481d17c5947342be029bf6f850d2f862372b988dcbc1809360cdd7b333c918(infra/migrations/base.sha256)이 forward migration 사슬의 시작점입니다(현재 migration 0개). 0.22.1 이하 schema(743448f6…)로 만든 DB는 사슬 밖이므로 1.0 image는 0.x DB를 열지 않습니다. update.sh는 container를 바꾸지 않고 Run: sh reset.sh로 멈춥니다. 1.0 이후 release부터는 초기화 없이 update.sh가 migration으로 갱신합니다.

    1. 전환 절차(한 번):

      1. 필요한 데이터가 있으면 아직 교체하지 않은 0.22.1 배포 파일로 sh backup.sh를 실행하고, 그 backup을 data/backups 밖으로 옮깁니다(reset.sh가 지웁니다). backup은 같은 schema 버전 image에서만 복구할 수 있으므로 이 backup은 1.0으로 가져올 수 없고 0.22.1 image로 되돌릴 때만 씁니다.
      2. package coordinator를 정지합니다.
      3. 1.0 태그의 배포 파일 7개를 받아 아래 SHA-256과 모두 같을 때만 교체합니다(운영 가이드 운영 배포의 받기·대조 블록, release_version='1.0').
      4. sh reset.sh → sh update.sh.
      5. coordinator를 시작하고 update.sh가 마지막 줄에 출력한 --package readiness 명령을 실행합니다. 종료 코드 0이어야 배포 완료입니다.
      6. 최초 관리자 등록(다른 사용자는 초대 링크로 다시 가입), 라이브러리 스캔과 실행 준비를 다시 진행합니다.
      cd /volume1/docker/links
      sh reset.sh
      sh update.sh
      # coordinator 시작 뒤
      docker compose --env-file '.env.production' exec -T catalog node apps/worker/dist/runtime-readiness-command.js --package
      
    2. update.sh·reset.sh는 인자를 받지 않습니다: --defer-package-readiness·--execute·--delete-rtp와 환경 파일 인자를 삭제했고, 어떤 인자든 Unknown argument로 Docker 호출 전에 거부합니다. 둘 다 .env.production만 읽습니다. reset.sh는 확인 없는 하드리셋이며 RTP 캐시·backups·translation-imports를 포함한 data 저장소 전체를 지웁니다(환경 파일·원본 게임·Compose·배포 script는 남습니다).

    3. 운영 디렉터리에 state/가 없습니다: translation import 기본 입력 경로가 ./state/translation-imports에서 **./data/translation-imports**로 바뀌었습니다. LINKS_TRANSLATION_IMPORT_SOURCE를 지정하지 않았다면 입력 파일을 새 위치에 둡니다.

    4. DB 비밀번호는 모두 서로 달라야 합니다: 적용기가 provision 전에 모든 login role 비밀번호를 bootstrap superuser 비밀번호(LINKS_DATABASE_PASSWORD·POSTGRES_PASSWORD)와, 그리고 서로와 대조합니다. .env.production에 같은 값이 있으면 migration container가 role 이름만 담은 오류로 실패하고 update.sh가 멈춥니다(role은 바뀌지 않음). 배포 전에 모든 LINKS_*_DATABASE_PASSWORD를 서로 다르게 둡니다.

    5. 알려진 제한(후속 과제): Player 세이브 관리 listener(3005)는 catalog caller token으로만 보호되며 edge network에서 TCP로 닿을 수 있습니다. catalog·Player의 일반 egress를 network가 제한하지 않습니다. update.sh는 opt-in profile container(translation-import 등)를 갱신하지 않으므로 갱신 뒤 직접 다시 기동합니다.

    원본 game library는 읽기 전용으로 유지하며, reset.sh는 library와 겹치거나 symlink인 저장소를 지우기 전에 거부합니다. 이번 게시 작업에서 개발·운영 DB 초기화나 NAS 배포는 실행하지 않았습니다.

    Schema forward migration

    • 기준 schema(infra/schema/NNN_<domain>.sql)는 계속 빈 DB에 적용하는 현재 schema 전체입니다. schema를 바꾸는 변경은 도메인 파일 수정과 같은 내용을 적용하는 infra/migrations/NNNN_<name>.sql(머리 -- from:·-- target: 기준 schema checksum)을 함께 추가합니다. 둘 중 하나만 바꾸면 schema test가 실패합니다.
    • 적용기(migration service)는 advisory lock 아래에서 DB 상태를 판정합니다.
      • empty: 기준 schema를 적용하고 버전을 기록합니다.
      • current: login role만 다시 provision합니다.
      • migratable: pending migration 전체, 단계별 ledger 기록, 버전 갱신을 한 transaction으로 적용합니다. 한 단계라도 실패하면 아무것도 반영하지 않습니다.
      • chain-incomplete: image에 migration이 빠진 결함입니다. 초기화 대상이 아니며 DB를 바꾸지 않습니다.
      • mismatch·legacy-migrations·unversioned: 사슬 밖이며 초기화가 필요합니다.
      • schema-status.js는 같은 판정을 pendingCount·pending과 함께 출력합니다.
    • ledger links_schema_migration_history(이름·파일 SHA-256·from/target checksum·적용 시각)를 추가했고 catalog scope의 쓰기 권한은 회수했습니다.
    • 검증은 빈 DB fresh 적용과 migration 적용 결과의 구조 등가입니다. pg_catalog 기반 구조 비교 helper가 relation·column·type·constraint·index·view·function·trigger·policy·grant·comment 등을 정규화해 비교하고, 실제 사슬이 base.sha256에서 현재 기준 schema까지 이어지는지 확인합니다.
    • backup 복구 gate는 같은 schema 버전 또는 사슬 안 이전 버전 backup만 받습니다. 사슬 안 backup은 pg_restore 뒤 migration을 적용하고, ledger가 그 경로의 migration을 같은 파일 SHA-256으로 기록했는지 확인합니다. 사슬 밖·구 migration 시대 backup은 거부합니다.
    • 작업 규칙(AGENTS.md)과 schema 정책 문서를 DB 초기화 전제에서 1.0 forward migration 사슬 기준으로 바꿨습니다.

    운영 명령 단순화

    • reset.sh(ops/reset-registry.sh) 하드리셋:
      • 인자·확인 없이 모든 profile의 container·network·named volume을 내리고, Volume 표의 모든 저장소 내용을 지운 뒤 소유자·권한(postgres 0700, 나머지 0755)으로 다시 만듭니다.
      • 경로는 env 파일을 직접 읽지 않고 Compose 렌더링의 bind source(출력하지 않음)를 씁니다. 그래서 shell 변수 우선·따옴표·export·interpolation·~·.. 해석이 실제 mount와 같습니다.
      • down 전에 다음 저장소를 거부하고 아무것도 바꾸지 않습니다: 원본 library와 같거나 그 상위·하위인 저장소, 경로나 중간 디렉터리가 symlink인 저장소, filesystem root로 해석되는 library·저장소.
      • 성공하면 Done. Run: sh update.sh 한 줄을 출력합니다.
    • update.sh(ops/deploy-registry.sh) latest 갱신:
      • LINKS_REGISTRY_TAG(비어 있으면 latest)로 갱신합니다.
      • [3/5]에서 PostgreSQL이 정지돼 있으면 postgres만 먼저 기동하고, 새 image의 schema-status.js로 상태를 항상 확인합니다.
      • migratable이면 Pending migrations: <n>을 출력하고 up 때 migration이 적용합니다. chain-incomplete는 image 결함 메시지로, 사슬 밖은 Run: sh reset.sh로 container를 바꾸지 않고 멈춥니다.
      • container 변경 뒤 실패에는 Rollback: 한 줄을 출력합니다. migration이 이미 적용된 뒤의 실패에는 tag rollback 대신 pre-update backup을 이전 image로 복구하라고 안내합니다.
      • 성공 출력은 단계 표시와 마지막 두 줄(coordinator 시작과 --package 명령)입니다.
    • 운영 배포 디렉터리는 state/를 쓰지 않습니다.
    • 기록기: 배포 wrapper 40개 사례, reset 24개 사례입니다. Compose 의미의 dotenv 렌더링 mock을 공용 모듈로 분리했습니다.

    보안

    • 적용기가 9개 login role(runtime stager·builder·GC, launch broker, translation worker, runtime packager, catalog, scanner, metadata worker)의 비밀번호를 bootstrap superuser와, 그리고 서로와 한 번에 대조합니다. 충돌은 role 이름만으로 모아 보고합니다.
    • 검사는 기준 schema 적용·provision 전에 하므로 실패하면 어떤 role도 바뀌지 않습니다. 일부 조합만 보던 기존의 분산 비교를 대체합니다.

    문서

    • 개발 계획과 완료 기준선을 1.0 기준으로 정리했습니다(완료 항목 삭제, 현재 제공 범위·최신 검증만 유지). README·운영 가이드·테스트 전략은 운영 세 명령과 forward migration 기준으로 다시 썼습니다.
    • 계약·규칙 문서의 어긋난 서술과 죽은 anchor를 현재 코드에 맞췄습니다(Player 3005 노출, catalog egress, wrapper 거부 단언, 비밀번호 대조 범위 등). 문서 link 검사 결과는 484개 / 깨짐 0입니다.

    검사

    • engine-filter browser check를 현재 UI의 상세 필터 dialog 흐름(엔진 select·조건 적용·조건 chip·URL 복원·전체 보기)과 Firefox 실행에 맞췄습니다.

    검증과 남은 범위

    • PASS — migrations 브랜치(병합 af86da4): unit 25, schema 214, backup-restore, static, 문서 검사.
    • PASS — opssimple 브랜치(병합 ccd43b1): 배포 wrapper 기록기 40개·reset 기록기 24개 사례, compose config, BusyBox sh 실행, 실제 Compose 렌더링 probe, 격리 foundation, static, 문서 검사.
    • PASS — docs-1.0 브랜치(병합 660f3d6): run-docs, run-static, link 검사 484개 / 깨짐 0.
    • PASS — pre10-fixes 브랜치(병합 cadb746): packages/db unit 76, schema 228, static, engine-filter browser check Chromium·Firefox, 문서 검사.
    • PASS — 최종 1.0 소스: 릴리스 커밋 두 개(root version, 완료 기준선 version 문구) 뒤 문서 검사(747개 파일 / Markdown 30개), static, git diff --check를 다시 통과했습니다. 게시 이미지는 같은 최종 소스에서 build했고, image 안의 root version 1.0.0·ARCA 0.1.17·base.sha256을 확인했습니다.
    • PARTIAL — 전체 UI/제품 acceptance: 화면·fixture·package/build 성공을 실제 browser play PASS로 기록하지 않습니다.
    • NOT RUN:
      • 실제 0.x → 1.0 NAS 초기화·배포
      • NAS 설치 Compose의 실제 .env.production으로 migration 적용
      • 실제 registry pull·container 교체 중 --package readiness
      • 실제 1.0 이후 migration 적용(아직 migration 0개)
      • 실제 library/private game과 실제 scan
      • 입력·오디오·save/reload의 실제 게임 acceptance
      • 실제 모바일 기기·보조 기술
      • live provider
      • 개발 rebuild

    배포 이미지와 파일

    • Git annotated tag: 1.0, 최종 소스 edac251454.
    • 단일 linux/amd64 이미지: git.2ex.dev/hanyul/links:1.0 = git.2ex.dev/hanyul/links:latest.
    • 원격 digest: sha256:9cb0a3455902bc899f4d0746410ae9422f492cb8426735a21974324423b5cd5a.
    • 아래 SHA-256은 checkout의 CRLF 변환본이 아닌 1.0 태그의 Git blob 원본 byte 기준입니다.
    • NAS 배포 목록은 7개입니다(update.sh=ops/deploy-registry.sh, reset.sh=ops/reset-registry.sh, backup.sh=ops/backup-registry.sh).
    • 0.22.1 대비 바뀐 파일은 compose.yaml, ops/deploy-registry.sh, ops/reset-registry.sh 세 개입니다. 0.x → 1.0 전환은 새 reset.sh로 실행해야 하므로 7개를 모두 1.0 파일로 맞춥니다.
    SHA-256 파일 0.22.1 대비
    ce841dde3a12e46a05677ca78fa229cce690899d2b51eb99da131e5b64a112a1 compose.yaml 변경 (translation import 기본 경로 ./data/translation-imports)
    6e1fd3f2f73faee52abca28c8552cde417f496d86fc3d78c7001dec480e171a3 infra/docker/Caddyfile 동일
    39a4c55050e76043c8ca70f11f59dbdf08fff7ef38c1f1ec878f96194988d9d8 ops/backup-verify.compose.yaml 동일
    e516bea3688ad135556105772b5b8d603795bb04c7d7ce6ca760cbcf15714d52 ops/deploy-registry.sh 변경 (인자 없음, migration 상태 분기, 정지 postgres 선기동)
    88d7b26d510ee67a25760dae8a9611be25592ea651a8d9870afaf9e714f37c54 ops/reset-registry.sh 변경 (인자 없는 하드리셋, Compose 렌더링 경로 판정)
    89375a986b3cb64cf74145541701b32c6a0e983a72ca46be21a8bcfad7beaa3d ops/backup-registry.sh 동일
    79c5fbd082ada3117e0915af76c714ed498ea018f151a998cd23f53d92fc3530 ops/runtime-package-coordinator.sh 동일
    ce841dde3a12e46a05677ca78fa229cce690899d2b51eb99da131e5b64a112a1  compose.yaml
    6e1fd3f2f73faee52abca28c8552cde417f496d86fc3d78c7001dec480e171a3  infra/docker/Caddyfile
    39a4c55050e76043c8ca70f11f59dbdf08fff7ef38c1f1ec878f96194988d9d8  ops/backup-verify.compose.yaml
    e516bea3688ad135556105772b5b8d603795bb04c7d7ce6ca760cbcf15714d52  ops/deploy-registry.sh
    88d7b26d510ee67a25760dae8a9611be25592ea651a8d9870afaf9e714f37c54  ops/reset-registry.sh
    89375a986b3cb64cf74145541701b32c6a0e983a72ca46be21a8bcfad7beaa3d  ops/backup-registry.sh
    79c5fbd082ada3117e0915af76c714ed498ea018f151a998cd23f53d92fc3530  ops/runtime-package-coordinator.sh
    
    Downloads 2