docs(docs): explain APT snapshot operation
Document installation, APT protocol handling, dataset selection, recursive snapshots, naming fields, package shortening, retention chains, and every runtime configuration parameter. Explain the relationship with the existing Pre-Invoke stop-gap and make the expected callback frequency and direct-dpkg limitation explicit. Include examples that show how affected package names appear in snapshot names.
This commit is contained in:
120
README.md
120
README.md
@@ -1,3 +1,121 @@
|
|||||||
# apt-pacman-hook
|
# apt-pacman-hook
|
||||||
|
|
||||||
Ubuntu apt hook for automatic snapshots
|
Ubuntu APT hook for automatic ZFS snapshots.
|
||||||
|
|
||||||
|
This is the Ubuntu/APT counterpart to [zfs-pacman-hook](https://quico.space/quico-os-setup/zfs-pacman-hook). It snapshots selected ZFS datasets before APT invokes `dpkg`, includes affected Debian package names in snapshot names, and prunes old snapshots using separate ordinary and important-package retention chains.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Install the files as follows:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo install -Dm755 apt-zfs-snapshot.sh /usr/local/sbin/apt-zfs-snapshot
|
||||||
|
sudo install -Dm644 apt-zfs-snapshot.conf /etc/apt-zfs-snapshot.conf
|
||||||
|
sudo install -Dm644 90apt-zfs-snapshot.conf /etc/apt/apt.conf.d/90apt-zfs-snapshot.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `dry_run='true'` in `/etc/apt-zfs-snapshot.conf`, run a harmless APT operation, and inspect the planned names. Set it back to `false` after verifying dataset selection and naming.
|
||||||
|
|
||||||
|
The helper runs as root through APT and requires Bash, `zfs`, and `findmnt`.
|
||||||
|
|
||||||
|
## APT integration
|
||||||
|
|
||||||
|
The hook uses `DPkg::Pre-Install-Pkgs`. APT sends its version-3 package action protocol on the file descriptor named by `APT_HOOK_INFO_FD`. Records contain the package name, old and new versions, version direction, architecture, and action. This captures dependencies selected by APT as well as explicitly requested packages.
|
||||||
|
|
||||||
|
Actions are classified as follows:
|
||||||
|
|
||||||
|
| APT record | Operation marker |
|
||||||
|
| --- | --- |
|
||||||
|
| No old version and a new version | `inst` |
|
||||||
|
| New version greater than old version | `upgr` |
|
||||||
|
| New version lower than old version | `down` |
|
||||||
|
| `**REMOVE**` action | `rmvl` |
|
||||||
|
|
||||||
|
Configuration-only records are ignored. If one callback contains multiple operation classes, the marker is `mixd`. In normal Ubuntu use, an `apt-get -y dist-upgrade` produces one useful callback and therefore one snapshot, matching the existing `DPkg::Pre-Invoke` stop-gap. APT can invoke `dpkg` more than once for complex operations; this implementation creates one snapshot per package-action callback.
|
||||||
|
|
||||||
|
## Dataset selection
|
||||||
|
|
||||||
|
By default, datasets with this exact ZFS property are selected:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
zfs set space.quico:auto-snapshot=true rpool/ROOT/ubuntu
|
||||||
|
```
|
||||||
|
|
||||||
|
With `snap_only_local_datasets='true'`, only mounted ZFS datasets are selected. This avoids snapshotting another operating system stored in the same pool. To explicitly select roots, set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
snapshot_roots='bpool/BOOT rpool/ROOT'
|
||||||
|
```
|
||||||
|
|
||||||
|
Explicit roots bypass property discovery and the mounted-dataset check. Each root is snapshotted recursively, and all roots are passed to one `zfs snapshot -r` command.
|
||||||
|
|
||||||
|
## Snapshot names
|
||||||
|
|
||||||
|
Names use this form:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dataset@apt_DATE_COUNTER_op:OP_sev:SEVERITY_pkgs:PACKAGE-LIST
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
rpool/ROOT/ubuntu@apt_2026-09-16-1430_1_op:upgr_sev:imp_pkgs:linux-image-6.17:systemd:libc6
|
||||||
|
```
|
||||||
|
|
||||||
|
The timestamp is UTC by default and is controlled by `date_format` and `timezone`. The counter starts at one and increases if a complete name already exists. This prevents collisions when identical operations happen within the same timestamp interval.
|
||||||
|
|
||||||
|
Package names are separated with `package_separator`, which defaults to `:`. Plus signs are replaced with underscores before inclusion in a ZFS name. The package list is shortened by dropping complete names from the end first, then truncating the final remaining name and adding `...` when at least four characters fit. `packages_max_length` is a best effort because the complete ZFS name is limited to 255 characters. If the fixed dataset and metadata fields cannot fit, the hook refuses to create a snapshot.
|
||||||
|
|
||||||
|
## Retention
|
||||||
|
|
||||||
|
If any affected package matches `important_names`, the snapshot belongs to the important chain. Otherwise it belongs to the trivial chain. The default important expression is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
linux-image(-.*)?|linux-headers(-.*)?|systemd|zfs-(dkms|utils)
|
||||||
|
```
|
||||||
|
|
||||||
|
The defaults retain 25 trivial snapshots and 10 important snapshots. Retention runs only after a successful snapshot and only considers snapshots with this tool's prefix and the current severity marker. Recursive snapshot trees are destroyed with `zfs destroy -r`.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`/etc/apt-zfs-snapshot.conf` is sourced as a Bash fragment. Available parameters are:
|
||||||
|
|
||||||
|
| Parameter | Default | Meaning |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `dry_run` | `false` | Print planned snapshots without changing ZFS |
|
||||||
|
| `important_names` | kernel/systemd/ZFS expression | Exact regex for important packages |
|
||||||
|
| `snapshots_trivial_keep` | `25` | Trivial snapshots retained per dataset |
|
||||||
|
| `snapshots_important_keep` | `10` | Important snapshots retained per dataset |
|
||||||
|
| `trivial_suffix` | `trv` | Ordinary severity marker |
|
||||||
|
| `important_suffix` | `imp` | Important severity marker |
|
||||||
|
| `packages_max_length` | `30` | Maximum package-list characters |
|
||||||
|
| `snap_only_local_datasets` | `true` | Restrict property-selected datasets to mounted datasets |
|
||||||
|
| `snapshot_roots` | empty | Explicit space-separated recursive snapshot roots |
|
||||||
|
| `field_separator` | `_` | Separator between snapshot fields |
|
||||||
|
| `package_separator` | `:` | Separator between package names |
|
||||||
|
| `snapshot_prefix` | `apt` | Prefix identifying these snapshots |
|
||||||
|
| `date_format` | `%F-%H%M` | Format passed to `date` |
|
||||||
|
| `timezone` | `Etc/UTC` | Timestamp timezone |
|
||||||
|
| `install_suffix` | `inst` | Install marker |
|
||||||
|
| `remove_suffix` | `rmvl` | Remove marker |
|
||||||
|
| `upgrade_suffix` | `upgr` | Upgrade marker |
|
||||||
|
| `downgrade_suffix` | `down` | Downgrade marker |
|
||||||
|
| `mixed_suffix` | `mixd` | Multiple operation classes marker |
|
||||||
|
|
||||||
|
The property name and value are fixed to `space.quico:auto-snapshot=true`, matching the original tool.
|
||||||
|
|
||||||
|
## Existing stop-gap hook
|
||||||
|
|
||||||
|
The previous `DPkg::Pre-Invoke` hook has no package-list input. This project uses `DPkg::Pre-Install-Pkgs` to retain the same practical pre-`dpkg` placement while adding package-aware names. Do not run both hooks unless two snapshots per APT operation are intentional.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- The hook covers package changes when APT invokes `dpkg`; direct `dpkg` operations are not intercepted.
|
||||||
|
- APT may invoke `dpkg` more than once in complex cases, producing more than one snapshot.
|
||||||
|
- The snapshot occurs before the corresponding `dpkg` invocation, not before APT downloads packages or resolves dependencies.
|
||||||
|
- Snapshot failures are reported but do not abort the package operation, matching the warning-oriented original behavior.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT. See [LICENSE](LICENSE).
|
||||||
|
|||||||
Reference in New Issue
Block a user