﻿# izz-immich v3.4.0 — Đặc tả Kỹ thuật

[English](TECHNICAL-SPECIFICATION.md) | [Tiếng Việt](TECHNICAL-SPECIFICATION.vi.md)  
[README English](README.md) | [README tiếng Việt](README.vi.md)

> **Phiên bản tài liệu:** 3.4.0  
> **Áp dụng cho:** izz-immich v3.4.0  
> **Cập nhật:** 2026-09-30  
> **Triển khai:** một chương trình Bash, chạy quyền root, giao diện TUI tương tác

## 1. Mục tiêu và phạm vi

`izz-immich` là bộ điều khiển vòng đời và an toàn ở cấp host cho một hệ thống Immich. Công cụ không chỉ là installer. Nó điều phối package hệ điều hành, Docker/Compose, phát hiện storage, tăng tốc phần cứng, import media, backup, restore, bảo mật mạng, repair, migration, diagnostics và các systemd job được sinh tự động.

Mục tiêu thiết kế chính là làm cho các thao tác phá hủy ở cấp host **có thể quan sát và hoàn tác khi thực tế cho phép**, đồng thời **dừng an toàn (fail-closed)** khi công cụ không thể chứng minh một giả định nguy hiểm là đúng.

Tài liệu này mô tả hành vi của v3.4.0 và chủ động ghi cả giới hạn/non-goal thay vì chỉ liệt kê tính năng.

## 2. Chính sách nền tảng hỗ trợ

### 2.1 Nền tảng được phép thay đổi hệ thống đầy đủ

Ubuntu 24.04 LTS là nền tảng duy nhất mà v3.4.0 tự động cho phép thay đổi package/repository/driver.

Trên nền tảng này, công cụ có thể:

- cài/gỡ package Debian;
- cấu hình repository Docker CE;
- cài driver/tooling GPU;
- cài UFW và unattended upgrades;
- cài tooling filesystem/storage cần cho workflow.

### 2.2 Safe Mode

Nếu `apt-get`, `dpkg` và `dpkg-query` tồn tại nhưng OS không phải Ubuntu 24.04, `PLATFORM_CAN_MUTATE=false`.

Trong Safe Mode:

- không được thay đổi package/repository/driver;
- workflow vẫn có thể chạy nếu dependency đã tồn tại;
- khi thiếu dependency cần thiết, workflow phải dừng thay vì tự sửa một host chưa được hỗ trợ đầy đủ.

Các hệ không có apt/dpkg bị từ chối.

## 3. Mô hình quyền, process và filesystem

### 3.1 Yêu cầu root

Entry point từ chối chạy nếu `EUID != 0`.

Root cần thiết vì công cụ thực hiện các thao tác như:

- quản lý package;
- thay đổi firewall;
- quản lý Docker daemon/container;
- đổi trạng thái read-only của block device;
- mount/RAID/LVM;
- tạo systemd unit;
- ghi vào `/var/lib`, `/var/log`, `/usr/local/bin` và `/etc`.

### 3.2 Umask mặc định

Process đặt:

```text
umask 077
```

Do đó state, file tạm, report/config sinh tự động và các file có thể chứa secret mặc định ở trạng thái private, trừ những file được chủ động mở quyền.

### 3.3 Các đường dẫn chính

| Đối tượng | Vị trí mặc định |
|---|---|
| Thư mục Immich | `/opt/immich-app` |
| State directory | `/var/lib/izz-immich` |
| State file | `/var/lib/izz-immich/state` |
| State lock | `/var/lib/izz-immich/state.lock` |
| API key được lưu | `/var/lib/izz-immich/api-key` |
| Log chính | `/var/log/izz-immich.log` |
| Global instance lock | `/run/izz-immich.lock` |
| Operation lock dùng chung | `/run/izz-immich-op.lock` |
| Backup mount | `/mnt/immich-backup` |
| Prefix mount Import | `/mnt/izz-src` |
| Trang chủ local | `/opt/izz-immich/home.html` |
| Backup script sinh tự động | `/usr/local/bin/izz-immich-backup.sh` |
| Firewall rollback helper | `/usr/local/bin/izz-firewall-rollback.sh` |
| Firewall subnet sync helper | `/usr/local/bin/izz-immich-firewall-sync.sh` |

## 4. Mô hình state và atomicity

State lâu dài dùng các record dạng KEY=VALUE.

`state_write_pairs()` là primitive ghi chính:

1. lấy exclusive `flock` trên state lock;
2. dựng thay đổi trong các file tạm cùng directory;
3. kiểm tra tên key và từ chối value chứa newline;
4. giữ state cũ cho đến khi state mới hoàn chỉnh;
5. commit bằng atomic rename.

Mục tiêu là ENOSPC, EIO, gián đoạn khi ghi hoặc lỗi validation không được thay một state tốt bằng file mới bị cắt dở.

Các transition cần nhất quán nhiều key được ghi trong cùng một transaction state khi workflow cần.

Immich Update bổ sung persistent recovery state gồm `UPDATE_IN_PROGRESS`, `UPDATE_STAGE`, `UPDATE_FROM_VERSION`, `UPDATE_TARGET_VERSION` và `UPDATE_BACKUP_SET`. Khi hoàn tất thành công, active marker được xóa và metadata `LAST_UPDATE_*` được ghi lại. Các key này ngăn một lần Repair sau đó vô tình chọn version cũ khi target release có thể đã migrate database.

## 5. Mô hình concurrency

Có hai nhóm lock.

### 5.1 Global lock

`/run/izz-immich.lock` ngăn nhiều phiên TUI cùng sửa host đồng thời.

### 5.2 Operation lock

`/run/izz-immich-op.lock` được chia sẻ giữa các thao tác có thể thay đổi dữ liệu/cấu hình Immich, bao gồm cả backup job sinh tự động.

Mục tiêu là ngăn race như:

- Backup bắt đầu đúng lúc Restore đổi PostgreSQL data;
- Repair recreate container trong khi workflow khác sửa Compose;
- backup theo lịch chạy chồng lên một thao tác migration nhạy cảm.

Backup theo lịch dùng non-blocking lock. Nếu đang có thao tác được quản lý khác, lần chạy được đánh dấu busy/skip thay vì ghi đè trạng thái health trước đó thành failure giả.

Update chủ động chạy fresh pre-upgrade backup trước mutation-phase operation lock vì chính backup job cũng dùng lock này. Khi backup hoàn tất, Update mới lấy `OP_LOCK` và revalidate running version cùng recovery set trước mọi thao tác thay đổi version.

## 6. Ghi nhớ workflow qua reboot

Setup, Repair và Migration có thể cần reboot, điển hình sau khi cài/thay NVIDIA driver.

Pending workflow state lưu tên workflow và context như:

- application directory;
- port;
- mode;
- stage.

Resume logic kiểm tra context đã lưu. Nếu thiếu hoặc không hợp lệ, trạng thái được xem là không an toàn; công cụ không tự dùng default vì có thể biến reinstall thành fresh install hoặc thao tác nhầm directory.

## 7. Mô hình Docker và Compose

Công cụ kỳ vọng Docker Engine cùng Compose plugin.

Khi tìm container, công cụ ưu tiên identity theo Compose service và chỉ fallback sang tên container lịch sử khi cần. Thiết kế này tránh phụ thuộc hoàn toàn vào một container name hard-code.

Đối với Compose, thiết kế phân biệt:

- **base Compose** — cấu hình upstream/user cần giữ khi có thể;
- **izz-immich-managed override** — ví dụ `izz-hwaccel.yml`, có thể được công cụ regenerate toàn bộ.

`COMPOSE_FILE` chỉ được chỉnh phần entry thuộc quyền quản lý của izz-immich; entry riêng của người dùng được giữ.

Ở các workflow mà parse YAML bằng text là không an toàn, canonical/resolved Compose output được dùng làm authority, ví dụ xác định PostgreSQL bind source.

## 8. Initial Setup

### 8.1 Preflight

Cài mới kiểm tra các command cơ bản, dung lượng trống tối thiểu của root filesystem và khả năng truy cập Internet cần cho setup.

### 8.2 An toàn khi cài mới

Fresh install chỉ được phép vào path dành riêng đã validate.

Nếu directory không rỗng:

- `.env` + `docker-compose.yml` đầy đủ có thể được xem là lần cài dở/existing install và được đề nghị adopt;
- nội dung bất kỳ hoặc cấu hình không đầy đủ bị từ chối.

Fresh setup không cố ý ghi đè một directory bất kỳ đã có dữ liệu.

### 8.3 Cấu hình được sinh

Fresh setup:

- tải Compose và example environment của Immich;
- đặt `UPLOAD_LOCATION` và `DB_DATA_LOCATION` dạng absolute path;
- sinh random database password;
- tùy chọn đổi published port;
- lưu `.env` và Compose với quyền hạn chế;
- áp hardware acceleration nếu phù hợp;
- pull image và start Compose;
- verify service state cùng HTTP/API của Immich trước khi ghi installation state.

### 8.4 Reinstall semantics

Nếu đã có installation, Repair được khuyến nghị.

Forced reinstall:

- giữ `.env` hiện tại;
- giữ base Compose graph hiện tại;
- giữ data path và port;
- cố gắng giữ chính xác version Immich đang chạy;
- snapshot config trước recreate;
- dùng environment override tạm thời cho version khi cần;
- chỉ commit persistent exact version pin sau khi deployment recreate verify thành công.

Reinstall vì vậy được tách rõ khỏi Upgrade.

## 9. Hardware acceleration

Hardware scan nhận diện NVIDIA, Intel, AMD hoặc không có GPU được hỗ trợ.

### 9.1 NVIDIA

Workflow có thể cài NVIDIA driver và container toolkit trên nền tảng được hỗ trợ đầy đủ. Boundary reboot được lưu state và có thể resume.

Compose override yêu cầu NVIDIA GPU resource và dùng CUDA machine-learning image.

### 9.2 Intel

Override truyền `/dev/dri`, bật Quick Sync cho server transcoding và chọn OpenVINO machine-learning image.

### 9.3 AMD

Override truyền `/dev/dri` cho workload server dùng VAAPI.

### 9.4 Verification

Sau khi generate/thay override, công cụ resolve Compose configuration và kiểm tra service block đã resolve. Nếu device/image mong đợi không xuất hiện, thay đổi bị rollback.

Base Compose không được xem là file có thể vứt bỏ chỉ vì đang cấu hình hardware acceleration.

## 10. Kiến trúc Media Import

### 10.1 Quyền sở hữu resource trong session

Một Import session mount source một lần và có thể chạy nhiều lần import. Cleanup chỉ release resource đã được chính session ghi nhận là do nó tạo.

MD array, LVM volume group hoặc mount đã tồn tại trước session không bị cố ý dừng.

### 10.2 Source discovery

Workflow có thể xử lý:

- APFS;
- NTFS/NTFS3;
- exFAT;
- ext4;
- btrfs;
- RAID/LVM stack tương thích thường gặp trên ổ Synology.

Auto-mount được giới hạn theo hướng external/removable source. System/data disk được bảo vệ khỏi hành vi auto-mount nguy hiểm.

### 10.3 Read-only protection

Bảo vệ theo nhiều lớp:

1. filesystem mount option yêu cầu read-only;
2. khi có thể, block device bên dưới được đặt read-only bằng `blockdev --setro`;
3. cleanup chỉ trả block device về read-write nếu chính session đã thay đổi nó và việc trả lại là an toàn.

Điều này bảo vệ trước filesystem có thể muốn replay journal/log dù workflow mong muốn chỉ đọc.

### 10.4 API key

API key có thể được lưu tùy chọn trong private state directory với mode 600.

Key được truyền vào container `immich-cli` bằng environment thay vì ghi trong command file lâu dài.

### 10.5 Kiểm tra Import

Upload workflow phân tích kết quả CLI và có thể chạy lượt upload thứ hai làm heuristic toàn vẹn: nếu lượt hai có zero new upload thì không có source file bổ sung nào được CLI nhận ở lần kiểm tra đó.

Đây là heuristic ở mức ứng dụng, không phải bằng chứng archive cryptographic.

## 11. Identity và supply-chain của immich-cli

CLI image được hard-pin bằng OCI digest.

Tái sử dụng local image theo nguyên tắc identity-first:

1. dùng exact `repo@digest` nếu inspect được local;
2. nếu không, quét local image ID và chỉ chấp nhận image có `RepoDigests` chứa pinned digest;
3. nếu vẫn không có, pull exact pinned digest;
4. nếu không chứng minh được identity và cũng không pull được, fail thay vì chạy một local image không xác minh.

Không giả định classic `docker save`/`docker load` giữ `RepoDigests`. Với air-gap hoàn toàn, nên dùng internal registry/mirror hoặc image store giữ được manifest identity.

Diagnostic của hàm trả về image reference được ghi ra stderr để command substitution không nuốt thông báo cần thiết.

## 12. Kiến trúc Backup

### 12.1 Layout và lịch

Mount mặc định: `/mnt/immich-backup`.

Generated systemd timer chạy lúc:

```text
03:17
```

Mốc này được tách khỏi khung backup database nội bộ thường gặp của Immich quanh 02:00.

### 12.2 Nội dung backup set

Mỗi set theo timestamp chứa:

- `immich-db.sql.gz`;
- snapshot configuration;
- manifest.

Library được duy trì trong cây backup dùng chung thay vì nhân bản vào mọi timestamp set.

### 12.3 Database backup

Generated job resolve PostgreSQL service/container và chạy:

- `pg_dumpall --clean --if-exists`;
- nén qua gzip;
- kiểm tra exit status các stage liên quan;
- `gzip -t`;
- kiểm tra header PostgreSQL cluster dump.

### 12.4 Library backup

`rsync -a` đồng bộ upload library mà không dùng `--delete`.

Thư mục database backup nội bộ `UPLOAD_LOCATION/backups/` của Immich được loại trừ để external backup không lưu lặp các internal dump.

### 12.5 Configuration backup

Cùng set lưu:

- `.env`;
- `docker-compose.yml`;
- các file đang tồn tại được tham chiếu qua `COMPOSE_FILE`.

Copy được verify bằng `cmp`.

### 12.6 Mô hình status

Kết quả có thể gồm:

- `ok`;
- `partial` — database thành công nhưng library/config chưa đầy đủ;
- `failed`;
- busy/skip khi operation lock đang bị giữ.

Last-status file được ghi atomic và dashboard đọc trực tiếp.

### 12.7 Retention

Timestamp set cũ có thể bị xóa theo tuổi, nhưng số lượng tối thiểu các set valid mới nhất luôn được giữ bất kể tuổi. Một chuỗi backup fail dài không được phép xóa các set tốt cuối cùng.

Shared library chủ động không bị prune bởi retention này.

### 12.8 Giới hạn consistency

Database được dump trước, sau đó mới sync media library.

Vì vậy backup **không phải transactional point-in-time snapshot**. Upload/xóa đồng thời có thể tạo khác biệt nhỏ giữa DB và file đã copy. Restore UI nêu rõ giới hạn này.

## 13. Restore và Disaster Recovery

### 13.1 Triết lý preflight

Restore cố gắng chứng minh các giả định quan trọng trước khi bắt đầu phá hủy.

Kiểm tra gồm:

- dump đọc được và đúng format;
- có matching config;
- validate current/target data path;
- expected mount có mặt;
- PostgreSQL bind source từ Docker/Compose;
- dung lượng đích;
- filesystem identity để kiểm tra tổng capacity DB + library.

### 13.2 Mount safety

`fstab_mount_ancestor()` dùng thông tin target được parse bởi `findmnt --fstab`/libmount thay vì tự parse `/etc/fstab`.

Với destination nằm dưới một configured mountpoint, deepest applicable fstab ancestor được chọn và `findmnt -M` được dùng để chứng minh mountpoint đó thực sự đang mounted.

Cơ chế này bảo vệ failure mode điển hình: `/data` lẽ ra là ổ riêng nhưng đang unmounted, khiến restore ghi xuyên vào root filesystem.

Mount expectation được kiểm tra lại ở thời điểm gần thao tác phá hủy nhất có thể.

### 13.3 Capacity check

Yêu cầu dung lượng DB và library được kiểm tra riêng.

Filesystem identity được resolve bằng `findmnt -T` với `MAJ:MIN`. Nếu hai destination nằm cùng filesystem, công cụ kiểm tra **tổng** requirement thay vì để hai kiểm tra riêng đều pass rồi cùng làm đầy một ổ.

Nếu không resolve được filesystem identity cần cho quyết định an toàn này, workflow fail-closed thay vì im lặng bỏ combined check.

### 13.4 PostgreSQL bind verification

`DB_DATA_LOCATION` mong muốn phải khớp PostgreSQL bind source lấy từ running container hoặc canonical `docker compose config --format json`.

Sau khi config Compose từ backup được restore, bind được resolve lại trước khi cho PostgreSQL hoạt động. Điều này phát hiện backup có Compose graph có thể đổi database sang path khác.

### 13.5 Restore khi database cũ tồn tại

Khi có current database:

1. stop Compose;
2. chứng minh PostgreSQL không còn chạy;
3. move database directory cũ sang rollback location theo timestamp;
4. tạo replacement directory theo owner/mode tham chiếu từ directory cũ;
5. restore config/database;
6. start và verify;
7. giữ rollback database đến khi người dùng chọn xóa.

### 13.6 Disaster Recovery

Khi target mới/trống, công cụ có thể tạo PostgreSQL destination mới và restore vào đó.

Trước khi cho phép, runtime check từ chối ownership model mà izz-immich không thể suy luận an toàn, gồm explicit non-root PostgreSQL service user không hỗ trợ và Docker rootless/user-namespace mapping.

Implementation chủ động **không** hard-code host UID như `999:999`.

### 13.7 Rollback

Restore rollback có thứ tự. Service không được cố ý start lại nếu database gốc chưa thể quay về validated location.

Nếu rollback không thể hoàn thành an toàn, workflow dừng và in manual-recovery instruction thay vì start PostgreSQL trên empty/wrong directory.

## 14. Network security transaction

### 14.1 Chế độ Tailscale-only

Mode này được xem như một transaction:

1. phát hiện điều kiện khiến automatic rollback không an toàn;
2. snapshot UFW/Compose/Tailscale state trước thay đổi;
3. sinh rollback helper;
4. arm dead-man timer và chứng minh timer active;
5. áp UFW policy;
6. cài `ufw-docker` đã pin/xác minh;
7. bind Immich về loopback;
8. recreate Compose;
9. verify listening socket thực tế;
10. cấu hình/verify Tailscale Serve;
11. verify Immich qua HTTPS;
12. yêu cầu người dùng xác nhận từ thiết bị khác trong tailnet;
13. chỉ sau đó mới disarm dead-man và persist managed mode.

Bất kỳ bước lỗi nào đều kích hoạt immediate rollback.

### 14.2 Foreign firewall stack

Vì transaction có thể reset UFW và rollback snapshot chỉ hiểu managed stack, foreign firewall/network chain được xử lý bảo thủ.

Không có đường “force anyway” đơn giản khi công cụ không thể phục hồi semantic state của môi trường.

### 14.3 Đồng bộ Docker subnet

`ufw-docker` được cài với thông tin subnet Docker thực tế.

Generated sync service/timer fingerprint tập Docker subnet. Các workflow Compose do izz-immich biết cũng trigger refresh ngay; timer là safety net cho thay đổi bên ngoài workflow đó.

### 14.4 Giới hạn policy trong tailnet

Tailscale tự nhận traffic trên `tailscale0` theo Tailscale policy. UFW trên host không được mô tả như authorization engine theo từng thiết bị tailnet.

Dùng Tailscale ACL/grants nếu cần restriction bên trong tailnet.

## 15. Chính sách dependency và supply-chain

Các dependency ngoài chạy quyền cao dùng immutable reference do maintainer lựa chọn.

v3.4.0 pin:

- `apfs-fuse` bằng Git commit;
- `immich-cli` bằng OCI digest;
- NVIDIA verification image bằng digest;
- `ufw-docker` bằng Git commit.

`ufw-docker` tải về được kiểm tra `bash -n`, cài quyền hạn chế, ghi checksum và chỉ reuse offline khi commit/checksum đã lưu khớp.

Đổi dependency version được xem là thay đổi source/release rõ ràng, không phải trust-on-first-use ở máy người dùng.

## 16. Cập nhật Immich

v3.4.0 bổ sung workflow Update riêng. Update được chủ động tách khỏi Repair và Reinstall.

Nguyên tắc chính:

- **Repair/Reinstall** phải giữ installed version khi có thể xác định chính xác version đó;
- **Update** là workflow duy nhất chủ động chuyển từ một exact Immich release sang một exact stable release mới hơn.

### 16.1 Điều kiện và version discovery

Update yêu cầu:

- `.env` hiện hữu;
- Docker daemon hoạt động;
- xác định được exact Immich version từ server container hiện có;
- Immich đang healthy và trả lời local HTTP health check;
- không có recovery marker `UPDATE_IN_PROGRESS` từ lần Update trước.

Latest stable được xác định bằng redirect `/releases/latest` của GitHub. Target stable bị giới hạn ở exact tag dạng `vMAJOR.MINOR.PATCH`; moving tag như `release`/`latest`, prerelease và downgrade đều bị từ chối bởi validation logic.

### 16.2 Recovery point bắt buộc trước nâng cấp

Update từ chối tiếp tục nếu chưa có generated backup job của izz-immich hoặc backup disk không mounted đúng UUID mong đợi.

Ngay trước upgrade, workflow chạy một backup mới và bắt buộc:

- timestamp phải mới hơn thời điểm Update bắt đầu;
- kết quả backup là `ok`;
- UUID khớp;
- `db=ok`;
- `library=ok`;
- `config=ok`;
- database gzip dump đọc được;
- set có `.env` và `docker-compose.yml`;
- Immich version trong manifest khớp exact current version.

Set này trở thành pre-upgrade recovery point được ghi trong Update state.

### 16.3 Xác nhận của operator

Trước mutation, UI hiển thị:

- current version;
- target version;
- recovery-point path;
- upstream release-notes URL;
- upstream upgrade-guide URL.

Nếu đổi major version sẽ có cảnh báo bổ sung. Người dùng phải xác nhận đã đọc release notes/breaking changes và gõ lại chính xác target version. Countdown ngắn tạo thêm cửa sổ cuối để Ctrl+C nếu muốn hủy.

### 16.4 Mutation transaction

Sau khi fresh backup hoàn tất, mutation phase chạy dưới `OP_LOCK` và revalidate current version cùng backup set.

Workflow sau đó:

1. resolve Compose graph hiện tại với `IMMICH_VERSION=TARGET`;
2. pull exact target images;
3. ghi persistent Update recovery state;
4. ghi `IMMICH_VERSION=TARGET` vào `.env`;
5. start target Compose stack;
6. refresh managed Docker/UFW subnet khi phù hợp;
7. verify Immich;
8. verify actual running image version đúng target.

Target được persist **trước lần target start đầu tiên** vì lần start này có thể chạy one-way database migration. Điều đó ngăn Compose/Repair sau đó vô tình start version cũ trên database đã migrate.

### 16.5 Success và failure semantics

Khi verify thành công, active Update recovery key được xóa và ghi `LAST_UPDATE_FROM`, `LAST_UPDATE_TO`, `LAST_UPDATE_BACKUP_SET`, `LAST_UPDATE_TIME`.

Nếu target start/verification thất bại sau khi target pin đã được ghi:

- izz-immich **không** tự restore old version;
- `UPDATE_IN_PROGRESS` tiếp tục tồn tại;
- target vẫn được pin;
- path của pre-upgrade recovery point vẫn được giữ;
- không cho bắt đầu Update mới.

Operator được hướng tới Repair target đang pin hoặc dùng Backup & Restore với pre-upgrade recovery point nếu cần phục hồi database.

Đây là chủ đích: rollback image/config đơn thuần không được xem là database downgrade an toàn.

### 16.6 Known issue của v3.4.0

Nhánh `module_update()` dùng để nhập exact target version thủ công hiện có mismatch biến đầu vào: kết quả dialog được gán vào `in` nhưng target lại đọc từ `input_ver`.

Vì vậy **đường nhập exact version thủ công không hoạt động trong v3.4.0**. Lựa chọn **Latest stable** không bị ảnh hưởng. Tài liệu ghi rõ defect này thay vì mô tả intended manual path như một chức năng đang hoạt động.

## 17. Repair

Repair là hướng được khuyến nghị khi installation hiện hữu bị lỗi.

Trong v3.4.0, Repair được thiết kế để giữ version:

- nếu resolve được exact version từ server container hiện hữu, Repair normalize `.env` về đúng version đó trước khi pull;
- nếu `.env` đã chứa exact version, version đó được dùng lại;
- Repair chỉ pull/recreate đúng exact version;
- nếu cả container lẫn `.env` không chứng minh được exact version, Repair dừng thay vì pull moving tag có thể vô tình upgrade database.

Khi `UPDATE_IN_PROGRESS=1`, Repair chỉ dùng `UPDATE_TARGET_VERSION`; không quay lại pre-update version. Nếu Repair verify target thành công, nó có thể finalize Update state.

Thiết kế vẫn tách “clean upstream reset” của config thành action riêng khỏi routine container recreation; custom Compose không bị xem là disposable.

## 18. Migration

### 18.1 Chuẩn bị

Profile máy nguồn ghi các thông tin như:

- CPU/GPU class;
- boot mode;
- application directory;
- Immich port;
- root filesystem UUID;
- ESP UUID;
- Immich version.

Migration có thể snapshot base Compose và lưu chính xác restart policy của từng container.

### 18.2 Tạm chặn auto-start

Khi người dùng chọn, restart policy được đổi tạm để container không tự start trên máy đích trước khi operator verify storage, boot và hardware state.

Policy gốc được lưu riêng và khôi phục theo từng container.

### 18.3 Khôi phục trên máy đích

Workflow có thể:

- so sánh GPU và boot mode cũ/mới;
- inspect/repair liên kết `/boot/efi`/fstab ở UEFI mode;
- cài/resume NVIDIA driver khi cần;
- restore và verify restart policy gốc;
- regenerate hardware acceleration nếu hardware thay đổi;
- start Compose và verify Immich.

Automatic startup bị từ chối nếu policy gốc không thể được restore và verify đầy đủ.

## 19. Dashboard và Diagnostics

`health_snapshot()` cung cấp health view dùng chung cho dashboard và các quyết định liên quan.

Nó đánh giá:

- Docker daemon;
- Immich service state;
- free space trên các filesystem chứa dữ liệu Immich;
- backup timer/status/age;
- Tailscale;
- managed firewall state;
- recovery marker của một Immich Update chưa hoàn tất/chưa verify.

Dashboard chuyển các trạng thái này thành action item như low disk, backup missing/stale/partial, Immich chạy chưa đầy đủ hoặc Update cần Repair/Restore.

Diagnostics có thể:

- chạy full system check;
- xem tool log;
- export support report.

Report export áp redaction rule cho pattern password/token/API key phổ biến và Tailscale IP, nhưng người dùng vẫn được yêu cầu tự đọc lại trước khi chia sẻ.

## 20. Generated script và systemd unit

Công cụ sinh một số privileged helper, đặc biệt backup và firewall sync/rollback.

Ở các phần đã triển khai theo mẫu an toàn, generated script được:

1. render vào private temporary path;
2. kiểm tra `bash -n`;
3. install vào temporary destination;
4. atomic rename sang operational location.

Generated job cũ có thể được regenerate khi main izz-immich version thay đổi.

## 21. Error handling và cleanup

Signal handling bao gồm `INT`, `TERM`, `HUP`.

Global cleanup chịu trách nhiệm cho private temporary data, tracked Import resource, pull process và active transaction cleanup.

Import có behavior riêng: interrupt trong lúc upload hủy upload đó nhưng không nhất thiết teardown toàn Import session, cho phép dùng lại mounted source để retry.

Network transaction được xử lý đặc biệt: nếu rollback của security transaction đang áp dở thất bại, lỗi được báo nổi bật và snapshot được giữ để manual recovery.

Update có recovery model riêng: sau khi target được pin và có thể đã chạy database migration, interrupt/failure không trigger automatic version downgrade. Persistent Update state được giữ cho Repair hoặc Restore.

## 22. Giao diện và ngôn ngữ

TUI dùng `whiptail` sau bước chọn ngôn ngữ/consent ban đầu.

Hỗ trợ tiếng Việt và tiếng Anh. Menu chia thành:

- Simple mode — công việc thường ngày;
- Advanced mode — administration, backup/restore, controlled Immich Update, security, setup/repair, migration.

Advanced mode cố ý tách các thao tác có blast radius lớn khỏi daily task.

## 23. Giới hạn và non-goal

### 23.1 Backup không phải filesystem snapshot

Database và media được capture nối tiếp, không phải một filesystem/database-wide transaction.

### 23.2 Không tự map ownership rootless/userns trong Disaster Recovery

Công cụ từ chối case không hỗ trợ thay vì tự giả định host UID.

### 23.3 Không tự merge foreign firewall/network stack

Host chạy K3s, Calico, custom iptables/nftables orchestration hoặc network stack phức tạp khác có thể bị managed Tailscale-only transaction từ chối.

### 23.4 Tailscale-only không phải ACL engine

Mode này bảo vệ exposure từ LAN/Internet và publish Immich qua Tailscale, nhưng không thay Tailscale ACL/grants.

### 23.5 Air-gapped CLI vẫn phải chứng minh identity

Local image chỉ có expected tag không đủ trusted. Không giả định classic `docker save/load` giữ RepoDigests.

### 23.6 Full package mutation chỉ dành cho Ubuntu 24.04

Các host apt/dpkg khác được xử lý bảo thủ cho đến khi project validate rõ ràng.

### 23.7 Update không tự động downgrade

Sau khi target release có thể đã migrate database, izz-immich không coi việc start image cũ là rollback an toàn. Recovery dựa trên pre-upgrade backup set thay vì automatic image downgrade.

### 23.8 Nhập exact target version thủ công bị lỗi trong v3.4.0

Manual target-version dialog có defect về biến input trong release này. Hãy dùng Latest stable cho tới khi implementation được sửa.

## 24. Giả định an toàn vận hành

Thiết kế giả định:

- administrator chủ động cấp root;
- kernel, Docker daemon và package manager thuộc trusted computing base;
- local root có thể đọc state của izz-immich và API key đã lưu;
- người dùng kiểm tra backup độc lập trước khi coi đó là bản duy nhất;
- storage quan trọng được OS báo trạng thái trung thực;
- upstream có thể offline, vì vậy local reuse được ưu tiên khi chứng minh được immutable identity;
- upstream Immich release metadata/release page mà Update dùng để discovery có thể truy cập và được tin cậy đủ cho version discovery, trong khi actual container identity vẫn được kiểm soát bằng exact release tag.

## 25. Versioning và kỷ luật tài liệu

Đặc tả này gắn với **v3.4.0**.

Thay đổi chức năng/an toàn trong tương lai nên cập nhật đồng thời:

- version trong script;
- hai bản README;
- hai bản Technical Specification;
- dependency pin nếu có thay đổi chủ đích.
