# 01 — Cài mới NCV DCIMS 0.2.30-r2 trên NAS riêng

**NCV DCIMS — NCV Dental Clinical Image Management System.** Tài liệu kỹ thuật cập nhật ngày 30/09/2026. `r2` là revision **bộ cài**; phiên bản ứng dụng/tag image là **`0.2.30`**.

## 1. Phạm vi, hiện trạng và điều kiện

Chỉ áp dụng cho NAS riêng chưa có NCV DCIMS, storage mới và **database trống**. Đây là hướng dẫn cho operator, không phải biên bản đã triển khai.

**NAS hiện tại đã chạy 0.2.30**, migration/role/activation/DSM sync/HTTPS smoke và đăng nhập/giao diện đã được operator xác nhận ngày 30/09. **Không cài mới hoặc chạy lại updater trên NAS đó để nhận r2.** Đối chiếu [README](../05_NGUON_TAI_LIEU/technical/README.md), [guide cập nhật r2](../05_NGUON_TAI_LIEU/technical/docs/operations/synology-update-0.2.30.md) và phần chốt cuối [evidence](../05_NGUON_TAI_LIEU/technical/docs/evidence/release-0.2.30-verification-2026-09-30.md). Một số đoạn trước đó trong nguồn là lịch sử trước khi NAS cập nhật xong.

Evidence ghi 47 migration và seed đạt trên PostgreSQL 17.10 trống bằng migration role; role denial/IDOR và dump bằng backup role đã kiểm trong môi trường giả lập. **Chưa có bằng chứng cài trắng/full rebuild Dockerfile r2 trên NAS mới**; NAS hiện hữu đã kiểm image ứng viên sửa quyền, không tương đương full rebuild. DR/offsite/reboot và UAT NAS mới còn phải nghiệm thu.

Operator chuẩn bị Container Manager/Docker Engine/Compose, CPU tương thích base images, DNS/NTP, dung lượng build/dữ liệu và truy cập registry/package registry. ZIP chứa source/Compose, không kèm image offline, credential hoặc database.

Owner NAS/chủ hệ thống chốt root storage, UID/GID, ACL/mã hóa, hostname/TLS/ingress và người nhận Admin. Policy retention/legal hold, offsite/custody khóa, quyền ngoài hệ thống hoặc restore cần owner xác nhận **khi triển khai thực**, theo [assumptions](../05_NGUON_TAI_LIEU/technical/docs/assumptions.md). Viết tài liệu không phê duyệt hoặc thực hiện các quyết định đó.

Dùng placeholder `<project-root>`, `<media-root>`, `<ledger-root>`, `<repository-root>`, `<receipts-root>`, `<runtime-uid>`, `<runtime-gid>` và tham số mạng của NAS đích. Thay trước khi chạy. Roots là đường dẫn tuyệt đối, không PII; source, media, ledger, repository, receipts không chồng lấn. Ledger tách khỏi DB/media; WORM/WriteOnce do owner storage xác nhận.

**Topology cần hiểu đúng:** [Compose certified](../05_NGUON_TAI_LIEU/technical/compose.production.certified.yml) dùng named volume PostgreSQL `ncv-dcims-postgres-data`; vị trí phụ thuộc Docker Root, không tự nằm trên Volume 2. `.env` không có biến host path DB. Nếu cần mount DB khác, owner thiết kế/kiểm override trước init; lệnh base dưới đây không đáp ứng yêu cầu đó tự động. Container/network/volume có tên cố định; đổi project name không đủ cô lập chúng.

Trước lần `up` đầu tiên, kiểm chỉ đọc:

```sh
docker ps -a --filter name=ncv-dcims --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'
docker volume ls --filter name=ncv-dcims
docker network ls --filter name=ncv-dcims
```

Nếu đã có tên trùng hoặc dữ liệu chưa rõ nguồn gốc, dừng đối chiếu; không xóa để tiếp tục. Không dùng adapter `deploy/site/nas-volume2`, `dcims.sh update`, override/DSM helper của site hiện hữu làm installer NAS bất kỳ, hoặc thay hash/UUID để vượt guard.

## 2. Nhận source r2 và tạo cấu hình riêng

Trên Windows, đối chiếu hash ZIP với sidecar:

```powershell
Get-FileHash -Algorithm SHA256 .\ncv-dcims-production-0.2.30-complete-synology-r2.zip
Get-Content .\ncv-dcims-production-0.2.30-complete-synology-r2.zip.sha256.txt
```

Upload staging riêng tư; giải nén bằng File Station nếu NAS không có `unzip`. Đặt source vào `<project-root>` mới, không đè project hiện hữu. Root phải có trực tiếp `package.json`, `apps`, `deploy`, `scripts`, Compose và mẫu env. Manifest root/API/Web cùng ghi `0.2.30`. Dockerfile r2 sửa quyền đọc/traverse **program trong image**, không sửa quyền host data.

Sao chép [mẫu production root](../05_NGUON_TAI_LIEU/technical/.env.production.example) thành `.env` tại project mới. Mẫu còn có path Volume 2 và host/IP minh họa: phải thay theo NAS đích. Không dùng env thật của NAS hiện tại hoặc guide credential một URL của 0.2.29. Những khóa chính cần đối chiếu:

```dotenv
NCV_DCIMS_APP_VERSION=0.2.30
NCV_DCIMS_PUBLIC_HOST=<public-host>
NCV_DCIMS_ALLOWED_HOSTS=<danh-sach-host-chinh-xac-co-public-host>
NCV_DCIMS_CLOUDFLARED_TRUSTED_IP=<mot-ipv4-peer-ingress-duoc-tin>
NCV_DCIMS_CORS_ORIGINS=<danh-sach-origin-https-chinh-xac>
NCV_DCIMS_GATEWAY_BIND_IP=<bind-ip-da-duyet>
NCV_DCIMS_HTTP_PORT=<http-port>
NCV_DCIMS_HTTPS_PORT=<https-port>
NCV_DCIMS_POSTGRES_DB=ncv_dcims
NCV_DCIMS_POSTGRES_USER=ncv_dcims_app
NCV_DCIMS_RUNTIME_UID=<runtime-uid>
NCV_DCIMS_RUNTIME_GID=<runtime-gid>
NCV_DCIMS_DB_ADMIN_URL_FILE=./secrets/database-url.txt
NCV_DCIMS_DATABASE_URL_FILE=./secrets/runtime-database-url.txt
NCV_DCIMS_MIGRATION_DATABASE_URL_FILE=./secrets/migration-database-url.txt
NCV_DCIMS_BACKUP_DATABASE_URL_FILE=./secrets/backup-database-url.txt
NCV_DCIMS_NAS_MEDIA_ROOT=<media-root>
NCV_DCIMS_NAS_LEDGER_ROOT=<ledger-root>
NCV_DCIMS_NAS_BACKUP_REPOSITORY_ROOT=<repository-root>
NCV_DCIMS_NAS_BACKUP_RECEIPTS_ROOT=<receipts-root>
NCV_DCIMS_BACKUP_ENCRYPTION_KEY_ID=<id-khoa-khong-phai-secret>
```

Giữ các khóa đường dẫn password/TLS/share secret/receipt key từ mẫu; chuẩn bị file tương ứng ở mục 3. Mỗi khóa xuất hiện một lần. Giữ **false** cho ba cờ `BACKUP_OFFSITE_CONFIGURED`, `BACKUP_POLICY_APPROVED`, `BACKUP_RECOVERY_KEY_TESTED`, ba cờ `MEDIA_PURGE_SCHEDULER_ENABLED`, `MEDIA_PURGE_EXECUTION_ENABLED`, `MEDIA_PURGE_LEGAL_HOLD_CLEAR_CONFIRMED` và `EXTERNAL_API_ENABLED` cho tới khi owner thực sự hoàn thành/xác nhận điều kiện tương ứng (tên đầy đủ có prefix `NCV_DCIMS_`).

Init user `ncv_dcims_app` trong mẫu là **quản trị PostgreSQL của DB mới**, không phải role runtime dù tên có chữ `app`. Có thể chọn tên khác trước init nhưng phải khớp admin URL/password; sửa env sau khi volume có dữ liệu không tự đổi user/password DB.

Host không có scheme/port; CORS dùng origin đầy đủ, có port nếu cần, không path/slash cuối. Renderer Gateway bắt buộc đúng một trusted IPv4 ngay cả khi không dùng Cloudflare; owner mạng đối chiếu [renderer](../05_NGUON_TAI_LIEU/technical/deploy/gateway/render-nginx-config.sh), không điền IP giả để qua validation. Compose không tạo tunnel hoặc reverse proxy DSM.

## 3. Ba credential DB, secret và quyền storage

Tạo `secrets/` riêng tư tại project. Không ghi giá trị secret vào env, command line, log, screenshot, source hoặc PDF. Các file theo mẫu root:

| File trong `secrets/` | Consumer / yêu cầu |
| --- | --- |
| `postgres-password.txt` | Password init user, nguyên văn |
| `database-url.txt` | Admin URL, khớp init user/password/database |
| `runtime-database-url.txt` | Role **`ncv_dcims_runtime`**: DML, API |
| `migration-database-url.txt` | Role **`ncv_dcims_migration`**: owner schema/objects, migrate/seed/bootstrap |
| `backup-database-url.txt` | Role **`ncv_dcims_backup`**: đọc DB cho dump |
| `media-share-token-secret.txt` | Secret độc lập, ngẫu nhiên mạnh, tối thiểu 32 byte |
| `tls-certificate.pem`, `tls-private-key.pem` | Cặp TLS hợp lệ cho hostname đích |
| `backup-receipt-private-key.pem`, `backup-receipt-public-key.pem` | Ed25519 ký/xác minh receipt, không mã hóa dữ liệu |
| `bootstrap-admin-password.txt` | Password Admin tạm, chỉ cho bootstrap một lần |

Cấu trúc admin URL, chỉ là placeholder:

```text
postgresql://<init-user>:<password-percent-encode>@ncv-dcims-postgres:5432/ncv_dcims?schema=ncv_dcims
```

Ba role URL cùng host/port/database/schema, username đúng bảng trên; password khác nhau và khác admin. Password trong URL percent-encode; file PostgreSQL password vẫn nguyên văn. Generator mục 4 tạo ba URL từ admin URL, chưa tạo role trong DB.

Receipt keys: trên máy tin cậy có Node, tại root source, chọn thư mục tuyệt đối mới/rỗng:

```powershell
$env:NCV_DCIMS_BACKUP_KEY_OUTPUT_ROOT='<thu-muc-tuyet-doi-moi-hoac-rong>'
node scripts/generate-backup-receipt-keys.mjs --generate
```

[Công cụ](../05_NGUON_TAI_LIEU/technical/scripts/generate-backup-receipt-keys.mjs) tạo `receipt-private.pem`/`receipt-public.pem`; chuyển an toàn và đặt đúng tên trong bảng. Private signing key chỉ mount vào backup, API chỉ nhận public key. Mã hóa backup/offsite/recovery key vẫn phải cấu hình riêng. Password Admin: 12–128 ký tự, không chỉ khoảng trắng hoặc ký tự điều khiển.

Owner storage xác nhận share thật đang mount/mở khóa và quyền UID/GID. API ghi media; backup đọc media/ledger, ghi repository/receipts. Gateway chạy `101:101`, cần đọc TLS. Secret-init chỉ chỉnh bảy file được chỉ định trong Compose, không xử lý TLS, PostgreSQL password hoặc bootstrap password. Operator chuẩn bị quyền đọc tối thiểu cho các consumer này; bootstrap password không có group/other write. Không chmod 777 hoặc chown/chmod đệ quy ledger WORM.

## 4. Build, generator và preflight

SSH vào **NAS đích**, dùng shell có quyền Docker (`sudo sh` nếu cần). Khai báo trong cùng shell; mất SSH thì khai báo lại và kiểm bước đã đạt trước tiếp tục:

```sh
set -eu
umask 077
DCIMS_PROJECT='<project-root>'
cd "$DCIMS_PROJECT"
dcims_compose() {
  docker compose --project-name ncv-dcims-production \
    --project-directory "$DCIMS_PROJECT" --env-file "$DCIMS_PROJECT/.env" \
    -f "$DCIMS_PROJECT/compose.production.certified.yml" "$@"
}
dcims_compose --profile maintenance build ncv-dcims-api ncv-dcims-web ncv-dcims-migrate
dcims_compose --profile backup build ncv-dcims-backup
```

Đây là lệnh theo build target/service của source; chưa chứng nhận full build NAS mới. Giữ Dockerfile r2/pnpm/lockfile. Nếu build lỗi, dừng, không build song song hoặc đổi API sang root để sửa EACCES.

Khi admin URL đã có và cả ba role file **chưa tồn tại**, dùng generator từ guide r2 đã tham số hóa đường dẫn:

```sh
DCIMS_ADMIN_FILE="$DCIMS_PROJECT/secrets/database-url.txt"
docker run --rm --network none --user 0:0 --entrypoint node \
  --mount "type=bind,src=$DCIMS_ADMIN_FILE,dst=/old-admin-url,readonly" \
  --mount "type=bind,src=$DCIMS_PROJECT/secrets,dst=/role-secrets" \
  ncv-dcims-api-migration:0.2.30 dist/scripts/generate-database-role-secrets.js \
  --admin-url-file /old-admin-url --output-directory /role-secrets </dev/null
```

Đạt `DB_ROLE_SECRET_FILES_CREATED`; [generator](../05_NGUON_TAI_LIEU/technical/apps/api/scripts/generate-database-role-secrets.ts) không kết nối DB, không in giá trị, từ chối ghi đè. Lỗi/partial files: giữ trạng thái, không xóa/sinh lại theo phỏng đoán.

```sh
dcims_compose config --quiet
dcims_compose run --rm -T --no-deps ncv-dcims-secret-init </dev/null
```

Cả hai phải exit 0. `config` không xác minh nội dung secret/TLS/quyền mount. Nếu media/ledger đều mới/rỗng, chưa khóa WORM và owner chấp nhận cơ chế quyền của job, có thể chạy:

```sh
dcims_compose run --rm -T --no-deps ncv-dcims-storage-init </dev/null
```

Job đổi owner/quyền thư mục media/ledger; không chạy trên ledger WORM hiện hữu. Nếu site dùng ACL riêng, owner chuẩn bị/kiểm quyền thay cho job. Repository/receipts phải chuẩn bị riêng, storage-init không xử lý hai root đó.

## 5. DB trống → role → migrate

Theo [deployment hiện hành](../05_NGUON_TAI_LIEU/technical/docs/production-deployment.md), không bật API/Web/Gateway trước khi hoàn tất bootstrap và provision lần hai. Sau khi xác minh volume mới và không xung đột tên:

```sh
dcims_compose up -d ncv-dcims-postgres
docker inspect --format '{{.State.Running}}|{{.State.Health.Status}}' ncv-dcims-postgres
```

Chờ `true|healthy`; health không chứng minh credential role đúng. Chạy từng lệnh, chỉ tiếp tục khi lệnh trước exit 0:

```sh
dcims_compose run --rm -T --no-deps ncv-dcims-db-roles </dev/null
dcims_compose run --rm -T --no-deps ncv-dcims-migrate </dev/null
```

Role job phải có `DB_ROLES_PROVISIONED`; migration phải `migrate deploy` thành công. [Provisioning](../05_NGUON_TAI_LIEU/technical/apps/api/scripts/provision-database-roles.ts) dùng admin URL trong **job maintenance riêng**, tạo ba role hạn quyền và schema `ncv_dcims`. API không dùng admin; migrate chỉ dùng migration URL; backup chỉ dùng backup URL. Secret-init có bind admin file để đổi quyền, không có network và không quản trị DB.

Source có 47 migration, gồm bootstrap nguồn checklist còn thiếu trên DB trống; không bỏ/sửa migration lịch sử. Lỗi thì giữ trạng thái và dừng; không reset/resolve thủ công hoặc seed để che lỗi. Sau migrate vẫn chưa có Admin ứng dụng. Admin ứng dụng khác admin PostgreSQL.

## 6. Seed/bootstrap Admin một lần, rồi provision lại

**Vướng mắc source:** [Compose bootstrap](../05_NGUON_TAI_LIEU/technical/compose.bootstrap-admin.synology.yml) đặt production nhưng thiếu share secret/CORS/quarantine; [bootstrap-admin.ts](../05_NGUON_TAI_LIEU/technical/apps/api/prisma/bootstrap-admin.ts) gọi [config](../05_NGUON_TAI_LIEU/technical/apps/api/src/config.ts) yêu cầu cả ba. Job cũng chưa đặt UID/GID site. **Compose bootstrap gốc chưa được chứng nhận chạy đạt trên NAS mới.** Không chạy nguyên trạng hoặc đổi sang development để bỏ validation.

Tại NAS đích, tạo `.env.bootstrap-admin` riêng tư; không thay `.env` production. [Mẫu root](../05_NGUON_TAI_LIEU/technical/.env.bootstrap-admin.example) có path Volume 2 cần thay. Nội dung đối chiếu:

```dotenv
NCV_DCIMS_APP_VERSION=0.2.30
NCV_DCIMS_RUNTIME_UID=<runtime-uid>
NCV_DCIMS_RUNTIME_GID=<runtime-gid>
NCV_DCIMS_BOOTSTRAP_ADMIN_USERNAME=admin
NCV_DCIMS_BOOTSTRAP_ADMIN_FULL_NAME=<ten-admin-owner-chon>
NCV_DCIMS_DATABASE_URL_FILE=./secrets/migration-database-url.txt
NCV_DCIMS_BOOTSTRAP_ADMIN_PASSWORD_FILE=./secrets/bootstrap-admin-password.txt
NCV_DCIMS_MEDIA_SHARE_TOKEN_SECRET_FILE=./secrets/media-share-token-secret.txt
NCV_DCIMS_CORS_ORIGINS=<cung-origin-https-trong-env-production>
```

**Trong env bootstrap, `DATABASE_URL_FILE` trỏ migration; trong env production, khóa này trỏ runtime.** Không dùng admin URL cho bootstrap. Chuẩn bị bootstrap password file đọc được bởi UID/GID đích; secret-init không sửa nó.

Tạo `compose.bootstrap-admin.local.yml` tại NAS, cạnh Compose gốc. Mẫu bổ sung suy ra từ source, đã kiểm cấu trúc merge, **chưa chạy bootstrap trên NAS mới**:

```yaml
services:
  ncv-dcims-bootstrap-admin:
    user: "${NCV_DCIMS_RUNTIME_UID:?Set UID}:${NCV_DCIMS_RUNTIME_GID:?Set GID}"
    environment:
      NCV_DCIMS_CORS_ORIGINS: ${NCV_DCIMS_CORS_ORIGINS:?Set CORS}
      NCV_DCIMS_MEDIA_SHARE_TOKEN_SECRET_FILE: /run/secrets/ncv_dcims_media_share_token_secret
      NCV_DCIMS_MEDIA_QUARANTINE_ROOT: /var/lib/ncv-dcims/media/quarantine
    secrets:
      - ncv_dcims_media_share_token_secret
secrets:
  ncv_dcims_media_share_token_secret:
    file: ${NCV_DCIMS_MEDIA_SHARE_TOKEN_SECRET_FILE:?Set share secret file}
```

Quarantine ở đây là tham số validation, bootstrap không upload/mount media. [Entrypoint](../05_NGUON_TAI_LIEU/technical/deploy/docker/load-database-secret.sh) đọc DB/share secret vào process. Trong cùng shell NAS:

```sh
dcims_bootstrap() {
  docker compose --project-name ncv-dcims-bootstrap-admin \
    --project-directory "$DCIMS_PROJECT" \
    --env-file "$DCIMS_PROJECT/.env.bootstrap-admin" \
    -f "$DCIMS_PROJECT/compose.bootstrap-admin.synology.yml" \
    -f "$DCIMS_PROJECT/compose.bootstrap-admin.local.yml" "$@"
}
dcims_bootstrap config --quiet
dcims_bootstrap run --rm -T --no-deps ncv-dcims-bootstrap-admin </dev/null
```

Job chạy `pnpm db:seed` rồi `pnpm db:bootstrap-admin`, bằng migration credential. [Seed](../05_NGUON_TAI_LIEU/technical/apps/api/prisma/seed.ts) tạo permission/ba system role/checklist, mặc định không tạo user/bệnh nhân demo. Bootstrap chỉ tạo Admin khi chưa có user, ghi audit và bắt đổi password lần đầu. Đạt: exit 0 và thông báo tạo Admin.

Nếu seed đạt/bootstrap lỗi, kiểm config/password và trạng thái user trước chọn thao tác tiếp; không lặp theo phỏng đoán. Không chạy seed/bootstrap hoặc bootstrap-test-accounts trên DB vận hành.

**Bắt buộc provision lần hai sau seed/bootstrap, trước API:**

```sh
dcims_compose run --rm -T --no-deps ncv-dcims-db-roles </dev/null
```

Phải có `DB_ROLES_PROVISIONED`. Khi các bảng đã có, bước này chặn runtime đọc `_prisma_migrations` và UPDATE/DELETE `audit_logs`; runtime giữ DML nghiệp vụ, migration sở hữu objects, backup chỉ đọc.

## 7. Activate, smoke và nghiệm thu owner

Sau PostgreSQL healthy, migration/bootstrap/provision lần hai đạt, mount/quyền đúng:

```sh
dcims_compose up -d --no-build --pull never --no-deps ncv-dcims-api ncv-dcims-web ncv-dcims-gateway
dcims_compose ps
sh scripts/synology/ncv-dcims-post-update-smoke-test.sh 'https://<host>[:<port-neu-can>]' 0.2.30 </dev/null
```

Thay URL placeholder toàn bộ. Bốn service PostgreSQL/API/Web/Gateway phải healthy, API/Web đúng tag `:0.2.30`. Owner cấu hình DNS/TLS/ingress; chỉ Gateway public. Internal healthcheck bỏ kiểm certificate, không thay smoke HTTPS có kiểm TLS. [Smoke](../05_NGUON_TAI_LIEU/technical/scripts/synology/ncv-dcims-post-update-smoke-test.sh) phải có `NCV_DCIMS_POST_UPDATE_SMOKE_OK`; kiểm health/header/title/auth boundary/Swagger-version bị chặn, không so phiên bản public hoặc chứng minh image đúng.

Đăng nhập Admin và đổi password. Owner UAT nghiệm thu bằng dữ liệu giả: ba vai trò, scope/IDOR chéo user/branch/patient/case, upload đúng/sai định dạng, private ảnh/PDF, confirm/reopen, chia sẻ/thu hồi, phân trang/tìm/lọc/Back–Forward và responsive 390/768/1440. Không dùng bệnh nhân thật hoặc xóa ca thật để thử. Runtime không có antivirus scanner; vẫn có quarantine/MIME/signature/decode/PDF validation.

**Backup:** theo [backup/restore](../05_NGUON_TAI_LIEU/technical/docs/backup-restore.md), lịch hiện có 02:00 `Asia/Ho_Chi_Minh`, retention 60 ngày; không tự đổi policy. Compose backup là job một lượt, không tự tạo lịch. Owner phải cấu hình lịch/mã hóa/offsite/custody và kiểm recovery, rồi mới xác nhận các cờ backup tương ứng. Lệnh dưới chỉ dùng khi các điều kiện đã được owner xác nhận:

```sh
dcims_compose --profile backup run --rm -T --no-deps ncv-dcims-backup </dev/null
```

Lệnh thực sự tạo backup/receipt và có thể rotation; không phải preflight chỉ đọc. Dump parse/checksum không thay restore DB **cùng media** cô lập. Purge/External API giữ tắt trong quy trình này; bật là quyết định riêng.

Owner storage kiểm ACL/WORM, mount share mã hóa, startup guard và reboot có kiểm soát trước dùng dữ liệu thật. Không coi helper site cũ đã cài trên NAS mới; `restart: unless-stopped` có thể chạy trước khi share mount. [Startup guard](../05_NGUON_TAI_LIEU/technical/docs/operations/synology-storage-startup-guard.md) có path/số container lịch sử cần đối chiếu, không chép mù hoặc tạo marker trên fallback folder để vượt lỗi.

| Việc cần owner đóng | Owner / ưu tiên / bằng chứng |
| --- | --- |
| NAS/storage/volume/UID/GID/build/guard | Owner NAS + storage / P0: full build r2, mount/quyền, vị trí DB và reboot trên NAS đích |
| TLS/ingress/private endpoints | Owner mạng / P0: HTTPS smoke, trusted peer và firewall đúng topology |
| Backup/retention/hold/offsite/recovery/restore | Chủ hệ thống + owner backup / P0 trước dữ liệu thật: policy xác nhận và restore DB–media cô lập |
| UAT vai trò/scope/responsive | Owner UAT / P0: biên bản dữ liệu giả, 390/768/1440 và nghiệp vụ |

Bất kỳ bước lỗi: dừng phụ thuộc, giữ bước cuối/output đã loại secret. Không `down -v`, Delete/Clean, prune, reset DB hoặc chmod/chown hàng loạt. Rollback/restore sau thay role/schema cần chủ hệ thống duyệt phương án khi triển khai thực.

Kiểm lượt viết guide: Compose certified và merge bootstrap `config --quiet` đạt với tham số giả; nguồn có 47 migration. **Chưa chạy** lint/type-check/unit/integration/build/E2E hay cài NAS mới: chỉ sửa tài liệu. Evidence release là kết quả lượt kiểm trước, không chứng nhận cài mới. Tài liệu bàn giao không thay biên bản nghiệm thu triển khai.

## 8. Nguồn kỹ thuật đi kèm

Guide có ở 02_HUONG_DAN_CAP_NHAT và 05_NGUON_TAI_LIEU. Các nguồn trực tiếp đã được kèm trong `05_NGUON_TAI_LIEU/technical/`, giữ đường dẫn từ root. Bảng dưới chỉ ra các nguồn chính; `04_KIEM_CHUNG/NGUON_THAM_CHIEU.json` ghi đầy đủ file, SHA-256 và nguồn lấy từ installer r2 hoặc snapshot tài liệu kỹ thuật ngày 30/09/2026.

| Source từ root repository | Mục đích |
| --- | --- |
| `README.md` | Hiện trạng NAS |
| `docs/assumptions.md` | Policy/owner/giới hạn |
| `docs/production-deployment.md` | Thứ tự cài mới hiện hành; không có `docs/operations/deployment-production.md` trong nguồn đã đọc |
| `docs/operations/synology-update-0.2.30.md` | Guide r2/generator/site boundary |
| `docs/evidence/release-0.2.30-verification-2026-09-30.md` | DB trống/role/NAS/giới hạn |
| `docs/backup-restore.md` | Backup/retention/restore |
| `docs/operations/synology-storage-startup-guard.md` | Guard cần đối chiếu site |
| `.env.production.example` | Mẫu root, không secret thật |
| `.env.bootstrap-admin.example` | Bootstrap root, thay path |
| `compose.production.certified.yml` | Service/job/secret/volume |
| `compose.bootstrap-admin.synology.yml` | Seed/bootstrap gốc |
| `deploy/docker/api.Dockerfile` | Image API/migration r2 |
| `deploy/docker/load-database-secret.sh` | Entrypoint DB/share secret |
| `deploy/gateway/render-nginx-config.sh` | Host/trusted IPv4 |
| `apps/api/scripts/generate-database-role-secrets.ts` | Sinh ba credential |
| `apps/api/scripts/provision-database-roles.ts` | Role/grants/revokes |
| `apps/api/prisma/seed.ts` | Seed hệ thống |
| `apps/api/prisma/bootstrap-admin.ts` | Admin một lần |
| `apps/api/src/config.ts` | Production validation |
| `scripts/generate-backup-receipt-keys.mjs` | Ed25519 receipt keys |
| `scripts/synology/ncv-dcims-post-update-smoke-test.sh` | HTTPS smoke |
| `apps/api/src/auth/password-policy.ts` | Password policy |
| `scripts/run-backup-scheduler.mjs` | Scheduler riêng |
| `deploy/docker/backup.Dockerfile` | Backup một lượt |

Đã kèm các nguồn tham chiếu theo cấu trúc trên. Source tham chiếu không thay installer đầy đủ để build/chạy; liên kết phụ bên trong source có thể cần checkout đầy đủ. Giữ nguyên các tài liệu 0.2.29/lịch sử.
