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.
122 lines
6.2 KiB
Markdown
122 lines
6.2 KiB
Markdown
# apt-pacman-hook
|
|
|
|
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).
|