From f7f6d712500e9d0caa26d54b143e3191190a88bc Mon Sep 17 00:00:00 2001 From: hygienic-books Date: Sun, 5 Mar 2023 08:15:16 +0100 Subject: [PATCH] feat(meta): Initial commit (#1) --- README.md | 55 +++++++++++++++++++++++++++++++++++++++- pacman-zfs-snapshot.conf | 12 +++++++++ pacman-zfs-snapshot.hook | 14 ++++++++++ pacman-zfs-snapshot.sh | 47 ++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 pacman-zfs-snapshot.conf create mode 100644 pacman-zfs-snapshot.hook create mode 100755 pacman-zfs-snapshot.sh diff --git a/README.md b/README.md index 2085a29..51484fe 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,56 @@ # zfs-pacman-hook -Arch Linux pacman hook for automatic snapshots \ No newline at end of file +Arch Linux pacman hook for automatic ZFS snapshots + +# Setup + +Get started like so: + +1. Install dependency `zfs-auto-snapshot` +1. Clone repo into arbitrary path `` +1. Make `pacman-zfs-snapshot.sh` executable + ``` + chmod +x /pacman-zfs-snapshot.sh + ``` +1. Symlink to files, for example + ``` + ln -s /pacman-zfs-snapshot.sh /usr/local/bin/pacman-zfs-snapshot + ln -s /pacman-zfs-snapshot.hook /usr/share/libalpm/hooks/pacman-zfs-snapshot.hook + ln -s /pacman-zfs-snapshot.conf /etc/pacman-zfs-snapshot.conf + ``` + Note that while you may choose arbitrary locations for symlinks the `pacman-zfs-snapshot.hook` file references `/usr/local/bin/pacman-zfs-snapshot`. Change that accordingly if you need to. +1. Adjust `pacman-zfs-snapshot.conf` to your liking + +# What's it do? + +In `pacman` on every `PreTransaction`, meaning right before any actual operation on a package begins, we trigger a ZFS snapshot. By default we identify the active system dataset by doing `findmnt / --noheadings --output source`. If exactly one source returns that is the exact name of a ZFS dataset in an imported zpool we create a snapshot on it. If no source returns we silently exit. If more than one source returns we raise an error and halt the `pacman` transaction. + +We retain two different snapshot chains, one for `pacman` transactions that only affect what we are calling _trivial_ packages and a separate chain for _important_ packages. By default only the exact regular expression package name match `^linux$` is considered important. Whenever an important package is affected by a transaction a snapshot goes into the corresponding chain. In all other cases - when an important package is not affected - snapshots go into the trivial chain + +We use `zfs-auto-snapshot` both for snapshot creation and for destroying old snapshots. The _trivial_ snapshot chain by default keeps 15 snapshots, the _important_ chain keeps 5. The thought process here is that you will likely not futz around with a kernel every day whereas you may very well install arbitrary packages multiple times a day. Snapshots should keep you safe for a couple of days hence the defaults of 5 and 15 snapshots, respectively. + +You may optionally include more ZFS datasets in this snapshot mechanism. Have a look at `pacman-zfs-snapshot.conf`, its comments should be clear enough to get you going. + +# Development + +## Conventional commits + +This project uses [Conventional Commits](https://www.conventionalcommits.org/) for its commit messages. + +### Commit types + +Commit _types_ besides `fix` and `feat` are: + +- `build`: Project structure, directory layout, build instructions for roll-out +- `refactor`: Keeping functionality while streamlining or otherwise improving function flow +- `test`: Working on test coverage +- `docs`: Documentation for project or components + +### Commit scopes + +The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none: + +- `auto`: Change how `zfs-auto-snapshot` is run +- `conf`: How we deal with script config +- `script`: Any other script work that doesn't specifically fall into the above scopes +- `meta`: Affects the project's repo layout, readme content, file names etc. diff --git a/pacman-zfs-snapshot.conf b/pacman-zfs-snapshot.conf new file mode 100644 index 0000000..b4712a7 --- /dev/null +++ b/pacman-zfs-snapshot.conf @@ -0,0 +1,12 @@ +# Pipe-separated list of kernel names. Will be matched against regular +# expression ^this_var_here$. Snapshots taken before a pacman transaction on +# an important package have a separate retention from snapshots for trivial +# packages. Lends itself to keeping kernel updates separate from everything +# else. +important_names='tmux' + +snaps_trivial_keep='15' +snaps_important_keep='5' + +snaps_trivial_suffix='trv' +snaps_important_suffix='imp' diff --git a/pacman-zfs-snapshot.hook b/pacman-zfs-snapshot.hook new file mode 100644 index 0000000..668e9cf --- /dev/null +++ b/pacman-zfs-snapshot.hook @@ -0,0 +1,14 @@ +[Trigger] +Operation = Install +Operation = Upgrade +Operation = Remove +Type = Package +Target = * + +[Action] +Description = Create ZFS snapshot on active system dataset +When = PreTransaction +Exec = /bin/sh -c 'while read -r f; do echo "$f"; done | /usr/local/bin/pacman-zfs-snapshot' +Depends = zfs-auto-snapshot +AbortOnFail +NeedsTargets diff --git a/pacman-zfs-snapshot.sh b/pacman-zfs-snapshot.sh new file mode 100755 index 0000000..68eec95 --- /dev/null +++ b/pacman-zfs-snapshot.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +declare -a pkgs +while read pkg; do + pkgs+=("${pkg}") +done + +declare conf_file +conf_file='/etc/pacman-zfs-snapshot.conf' + +declare important_names snaps_trivial_keep snaps_important_keep snaps_trivial_suffix snaps_important_suffix +if [[ -r "${conf_file}" ]]; then + source "${conf_file}" +fi + +if [[ ! "${important_names}" ]]; then important_names='linux'; fi +if [[ ! "${snaps_trivial_keep}" ]]; then snaps_trivial_keep='5'; fi +if [[ ! "${snaps_important_keep}" ]]; then snaps_important_keep='5'; fi +if [[ ! "${snaps_trivial_suffix}" ]]; then snaps_trivial_suffix='trv'; fi +if [[ ! "${snaps_important_suffix}" ]]; then snaps_important_suffix='imp'; fi + +function split_pkgs_by_importance () { + local pkgs_in_transaction + pkgs_in_transaction=("${@}") + for pkg in "${pkgs_in_transaction[@]}"; do + if grep -Piq -- '^'"${important_names}"'$' <<<"${pkg}"; then + important_pkgs_in_transaction+=("${pkg}") + else + unimportant_pkgs_in_transaction+=("${pkg}") + fi + done +} + +function main () { + local pkgs_in_transaction + pkgs_in_transaction=("${@}") + + declare -a important_pkgs_in_transaction unimportant_pkgs_in_transaction + split_pkgs_by_importance "${pkgs_in_transaction[@]}" + + + #for pkg in "${!important_pkgs_in_transaction[@]}"; do + # printf -- 'Array item '"'"'%s'"'"' equals '"'"'%s'"'"'\n' "${pkg}" "${important_pkgs_in_transaction[${pkg}]}" + #done +} + +main "${pkgs[@]}"