Compare commits
31 Commits
f7f6d71250
...
70145d5897
Author | SHA1 | Date | |
---|---|---|---|
70145d5897 | |||
05a9f22b01 | |||
f5956063a9 | |||
12207a1376 | |||
2a256272d8 | |||
ba2c7040a8 | |||
6f92693c0f | |||
aa60a863d8 | |||
0222cc9649 | |||
e7c913c4db | |||
7747e9bdc3 | |||
f8592c215a | |||
f5bb694768 | |||
8e8be69f12 | |||
60b3a92abb | |||
673fb15b46 | |||
36cd7ea16d | |||
8fbf16bda0 | |||
7427d8477f | |||
8dca85c98f | |||
b1249d6a40 | |||
188e3481af | |||
b128b5f2ae | |||
740f4bd36a | |||
6a666c5c8e | |||
1e56f28dc8 | |||
0e9290a727 | |||
ae42f40f46 | |||
52bc1ba132 | |||
136ec7875e | |||
1113a32888 |
23
README.md
23
README.md
@@ -6,7 +6,7 @@ Arch Linux pacman hook for automatic ZFS snapshots
|
|||||||
|
|
||||||
Get started like so:
|
Get started like so:
|
||||||
|
|
||||||
1. Install dependency `zfs-auto-snapshot`
|
1. Install dependency `jq`
|
||||||
1. Clone repo into arbitrary path `<repo>`
|
1. Clone repo into arbitrary path `<repo>`
|
||||||
1. Make `pacman-zfs-snapshot.sh` executable
|
1. Make `pacman-zfs-snapshot.sh` executable
|
||||||
```
|
```
|
||||||
@@ -14,20 +14,27 @@ Get started like so:
|
|||||||
```
|
```
|
||||||
1. Symlink to files, for example
|
1. Symlink to files, for example
|
||||||
```
|
```
|
||||||
ln -s <repo>/pacman-zfs-snapshot.sh /usr/local/bin/pacman-zfs-snapshot
|
sudo ln -s <repo>/pacman-zfs-snapshot.sh /usr/local/bin/pacman-zfs-snapshot
|
||||||
ln -s <repo>/pacman-zfs-snapshot.hook /usr/share/libalpm/hooks/pacman-zfs-snapshot.hook
|
sudo ln -s <repo>/pacman-zfs-snapshot-install.hook /usr/share/libalpm/hooks/pacman-zfs-snapshot-install.hook
|
||||||
ln -s <repo>/pacman-zfs-snapshot.conf /etc/pacman-zfs-snapshot.conf
|
sudo ln -s <repo>/pacman-zfs-snapshot-remove.hook /usr/share/libalpm/hooks/pacman-zfs-snapshot-remove.hook
|
||||||
|
sudo ln -s <repo>/pacman-zfs-snapshot-upgrade.hook /usr/share/libalpm/hooks/pacman-zfs-snapshot-upgrade.hook
|
||||||
|
sudo ln -s <repo>/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.
|
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
|
1. For datasets you want auto-snapshotted add property `space.quico:auto-snapshot=true`
|
||||||
|
```
|
||||||
|
zfs set space.quico:auto-snapshot=true zpool/root/archlinux
|
||||||
|
```
|
||||||
|
With any other property and any other value datasets will not be auto-snapshotted.
|
||||||
|
1. Adjust `pacman-zfs-snapshot.conf` to your liking. You may want to set `do_dry_run='true'` for a start and just reinstall a benign package to get a feel for what this hook would do.
|
||||||
|
|
||||||
# What's it do?
|
# 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.
|
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 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.
|
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.
|
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.
|
||||||
|
|
||||||
@@ -50,7 +57,7 @@ Commit _types_ besides `fix` and `feat` are:
|
|||||||
|
|
||||||
The following _scopes_ are known for this project. A Conventional Commits commit message may optionally use one of the following scopes or none:
|
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
|
- `conf`: How we deal with script config
|
||||||
- `script`: Any other script work that doesn't specifically fall into the above scopes
|
- `script`: Any other script work that doesn't specifically fall into the above scopes
|
||||||
|
- `hook`: Configuring the hook(s)
|
||||||
- `meta`: Affects the project's repo layout, readme content, file names etc.
|
- `meta`: Affects the project's repo layout, readme content, file names etc.
|
||||||
|
@@ -1,14 +1,12 @@
|
|||||||
[Trigger]
|
[Trigger]
|
||||||
Operation = Install
|
Operation = Install
|
||||||
Operation = Upgrade
|
|
||||||
Operation = Remove
|
|
||||||
Type = Package
|
Type = Package
|
||||||
Target = *
|
Target = *
|
||||||
|
|
||||||
[Action]
|
[Action]
|
||||||
Description = Create ZFS snapshot on active system dataset
|
Description = Create ZFS snapshot on active system dataset
|
||||||
When = PreTransaction
|
When = PreTransaction
|
||||||
Exec = /bin/sh -c 'while read -r f; do echo "$f"; done | /usr/local/bin/pacman-zfs-snapshot'
|
Exec = /bin/sh -c 'while read -r f; do echo "$f"; done | /usr/local/bin/pacman-zfs-snapshot install'
|
||||||
Depends = zfs-auto-snapshot
|
Depends = jq
|
||||||
AbortOnFail
|
AbortOnFail
|
||||||
NeedsTargets
|
NeedsTargets
|
12
pacman-zfs-snapshot-remove.hook
Normal file
12
pacman-zfs-snapshot-remove.hook
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[Trigger]
|
||||||
|
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 remove'
|
||||||
|
Depends = jq
|
||||||
|
AbortOnFail
|
||||||
|
NeedsTargets
|
12
pacman-zfs-snapshot-upgrade.hook
Normal file
12
pacman-zfs-snapshot-upgrade.hook
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[Trigger]
|
||||||
|
Operation = Upgrade
|
||||||
|
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 upgrade'
|
||||||
|
Depends = jq
|
||||||
|
AbortOnFail
|
||||||
|
NeedsTargets
|
@@ -1,12 +1,35 @@
|
|||||||
|
do_dry_run='true'
|
||||||
|
|
||||||
# Pipe-separated list of kernel names. Will be matched against regular
|
# Pipe-separated list of kernel names. Will be matched against regular
|
||||||
# expression ^this_var_here$. Snapshots taken before a pacman transaction on
|
# expression ^this_var_here$. Snapshots taken before a pacman transaction on
|
||||||
# an important package have a separate retention from snapshots for trivial
|
# an important package have a separate retention from snapshots for trivial
|
||||||
# packages. Lends itself to keeping kernel updates separate from everything
|
# packages. Lends itself to keeping kernel updates separate from everything
|
||||||
# else.
|
# else.
|
||||||
important_names='tmux'
|
important_names='tmux'
|
||||||
|
# zfs pkgs, systemd?
|
||||||
|
|
||||||
snaps_trivial_keep='15'
|
snaps_trivial_keep='15'
|
||||||
snaps_important_keep='5'
|
snaps_important_keep='5'
|
||||||
|
|
||||||
snaps_trivial_suffix='trv'
|
snaps_trivial_suffix='trv'
|
||||||
snaps_important_suffix='imp'
|
snaps_important_suffix='imp'
|
||||||
|
|
||||||
|
pkgs_list_max_length='24'
|
||||||
|
|
||||||
|
# Hook will by default snapshot all datasets that have the property
|
||||||
|
# 'space.quico:auto-snapshot=true' set, even the ones that are not currently
|
||||||
|
# mounted and may belong to unrelated operating systems. Set
|
||||||
|
# snap_only_local_datasets='true' to limit snapshots to only those datasets
|
||||||
|
# that have aforementioned property and at the same time are currently
|
||||||
|
# mounted in your running OS. Currently mounted is defined as:
|
||||||
|
# findmnt --json --list --output 'fstype,source,target' | \
|
||||||
|
# jq --raw-output '.[][] | select(.fstype=="zfs") | .source'
|
||||||
|
snap_only_local_datasets='true'
|
||||||
|
|
||||||
|
snap_field_separator='_'
|
||||||
|
snap_name_prefix='pac'
|
||||||
|
snap_date_format='+F-%H%M'
|
||||||
|
|
||||||
|
snap_op_installation_suffix='inst'
|
||||||
|
snap_op_remove_suffix='rmvl'
|
||||||
|
snap_op_upgrade_suffix='upgr'
|
||||||
|
@@ -5,6 +5,9 @@ while read pkg; do
|
|||||||
pkgs+=("${pkg}")
|
pkgs+=("${pkg}")
|
||||||
done
|
done
|
||||||
|
|
||||||
|
declare operation
|
||||||
|
operation="${1}"
|
||||||
|
|
||||||
declare conf_file
|
declare conf_file
|
||||||
conf_file='/etc/pacman-zfs-snapshot.conf'
|
conf_file='/etc/pacman-zfs-snapshot.conf'
|
||||||
|
|
||||||
@@ -13,11 +16,46 @@ if [[ -r "${conf_file}" ]]; then
|
|||||||
source "${conf_file}"
|
source "${conf_file}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [[ ! "${do_dry_run}" ]]; then do_dry_run='true'; fi
|
||||||
if [[ ! "${important_names}" ]]; then important_names='linux'; fi
|
if [[ ! "${important_names}" ]]; then important_names='linux'; fi
|
||||||
if [[ ! "${snaps_trivial_keep}" ]]; then snaps_trivial_keep='5'; fi
|
if [[ ! "${snaps_trivial_keep}" ]]; then snaps_trivial_keep='15'; fi
|
||||||
if [[ ! "${snaps_important_keep}" ]]; then snaps_important_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_trivial_suffix}" ]]; then snaps_trivial_suffix='trv'; fi
|
||||||
if [[ ! "${snaps_important_suffix}" ]]; then snaps_important_suffix='imp'; fi
|
if [[ ! "${snaps_important_suffix}" ]]; then snaps_important_suffix='imp'; fi
|
||||||
|
if [[ ! "${pkgs_list_max_length}" ]]; then pkgs_list_max_length='24'; fi
|
||||||
|
if [[ ! "${snap_only_local_datasets}" ]]; then snap_only_local_datasets='true'; fi
|
||||||
|
if [[ ! "${snap_field_separator}" ]]; then snap_field_separator='_'; fi
|
||||||
|
if [[ ! "${snap_name_prefix}" ]]; then snap_name_prefix='pac'; fi
|
||||||
|
if [[ ! "${snap_date_format}" ]]; then snap_date_format='+F-%H%M'; fi
|
||||||
|
if [[ ! "${snap_op_installation_suffix}" ]]; then snap_op_installation_suffix='inst'; fi
|
||||||
|
if [[ ! "${snap_op_remove_suffix}" ]]; then snap_op_remove_suffix='rmvl'; fi
|
||||||
|
if [[ ! "${snap_op_upgrade_suffix}" ]]; then snap_op_upgrade_suffix='upgr'; fi
|
||||||
|
|
||||||
|
function pprint () {
|
||||||
|
local style msg exit_code
|
||||||
|
style="${1:?}"
|
||||||
|
msg="${2:?}"
|
||||||
|
exit_code="${3}"
|
||||||
|
|
||||||
|
local color_reset color_lyellow
|
||||||
|
color_reset='\e[0m'
|
||||||
|
color_lyellow='\e[93m'
|
||||||
|
color_red='\e[31m'
|
||||||
|
|
||||||
|
case "${style}" in
|
||||||
|
warn)
|
||||||
|
printf -- "${color_lyellow}"'[WARN]'"${color_reset}"' %s\n' "${msg}"
|
||||||
|
;;
|
||||||
|
err)
|
||||||
|
printf -- "${color_red}"'[ERR]'"${color_reset}"' %s\n' "${msg}"
|
||||||
|
;;
|
||||||
|
info)
|
||||||
|
printf -- '[INFO] %s\n' "${msg}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[[ "${exit_code}" ]] && exit "${exit_code}"
|
||||||
|
}
|
||||||
|
|
||||||
function split_pkgs_by_importance () {
|
function split_pkgs_by_importance () {
|
||||||
local pkgs_in_transaction
|
local pkgs_in_transaction
|
||||||
@@ -26,22 +64,214 @@ function split_pkgs_by_importance () {
|
|||||||
if grep -Piq -- '^'"${important_names}"'$' <<<"${pkg}"; then
|
if grep -Piq -- '^'"${important_names}"'$' <<<"${pkg}"; then
|
||||||
important_pkgs_in_transaction+=("${pkg}")
|
important_pkgs_in_transaction+=("${pkg}")
|
||||||
else
|
else
|
||||||
unimportant_pkgs_in_transaction+=("${pkg}")
|
trivial_pkgs_in_transaction+=("${pkg}")
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set_severity () {
|
||||||
|
if [[ "${#important_pkgs_in_transaction[@]}" -ge '1' ]]; then
|
||||||
|
severity="${snaps_important_suffix}"
|
||||||
|
else
|
||||||
|
severity="${snaps_trivial_suffix}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_globally_snappable_datasets () {
|
||||||
|
local datasets_list
|
||||||
|
# For all datasets show their 'space.quico:auto-snapshot' property; only
|
||||||
|
# print dataset name in column 1 and property value in column 2. In awk
|
||||||
|
# limit this list to datasets where tab-delimited column 2 has exact
|
||||||
|
# string '^true$' then further limit output by eliminating snapshots
|
||||||
|
# from list, i.e. dataset names that contain an '@' character.
|
||||||
|
datasets_list="$(zfs get -H -o 'name,value' 'space.quico:auto-snapshot' | \
|
||||||
|
awk -F'\t' '{if($2 ~ /^true$/ && $1 !~ /@/) print $1}')"
|
||||||
|
while IFS= read -r dataset; do
|
||||||
|
globally_snappable_datasets+=("${dataset}")
|
||||||
|
done <<<"${datasets_list}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_local_snappable_datasets () {
|
||||||
|
local datasets_list
|
||||||
|
datasets_list="$(findmnt --json --list --output 'fstype,source,target' | \
|
||||||
|
jq --raw-output '.[][] | select(.fstype=="zfs") | .source')"
|
||||||
|
while IFS= read -r dataset; do
|
||||||
|
local_snappable_datasets+=("${dataset}")
|
||||||
|
done <<<"${datasets_list}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function trim_globally_snappable_datasets () {
|
||||||
|
for global_dataset in "${globally_snappable_datasets[@]}"; do
|
||||||
|
for local_dataset in "${local_snappable_datasets[@]}"; do
|
||||||
|
if grep -Piq -- '^'"${local_dataset}"'$' <<<"${global_dataset}"; then
|
||||||
|
snappable_datasets+=("${global_dataset}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function write_pkg_list_oneline () {
|
||||||
|
local unabridged_pkg_list_oneline
|
||||||
|
if [[ "${severity}" == 'imp' ]]; then
|
||||||
|
for pkg in "${important_pkgs_in_transaction[@]}"; do
|
||||||
|
if [[ "${unabridged_pkg_list_oneline}" ]]; then
|
||||||
|
unabridged_pkg_list_oneline="${unabridged_pkg_list_oneline}"','"${pkg}"
|
||||||
|
else
|
||||||
|
unabridged_pkg_list_oneline="${pkg}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [[ "${#trivial_pkgs_in_transaction[@]}" -ge '1' ]]; then
|
||||||
|
for pkg in "${trivial_pkgs_in_transaction[@]}"; do
|
||||||
|
if [[ "${unabridged_pkg_list_oneline}" ]]; then
|
||||||
|
unabridged_pkg_list_oneline="${unabridged_pkg_list_oneline}"','"${pkg}"
|
||||||
|
else
|
||||||
|
unabridged_pkg_list_oneline="${pkg}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function find_max_dataset_name_length () {
|
||||||
|
local longest_op_suffix op_suffix_string
|
||||||
|
longest_op_suffix='0'
|
||||||
|
for op_suffix in "${snap_op_installation_suffix}" "${snap_op_remove_suffix}" "${snap_op_upgrade_suffix}"; do
|
||||||
|
if [[ "${#op_suffix}" -gt "${longest_op_suffix}" ]]; then
|
||||||
|
longest_op_suffix="${#op_suffix}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
op_suffix_string="$(head -c "${longest_op_suffix}" '/dev/zero' | tr '\0' '_')"
|
||||||
|
|
||||||
|
local longest_sev_suffix sev_suffix_string
|
||||||
|
longest_sev_suffix='0'
|
||||||
|
for sev_suffix in "${snaps_trivial_suffix}" "${snaps_important_suffix}"; do
|
||||||
|
if [[ "${#sev_suffix}" -gt "${longest_sev_suffix}" ]]; then
|
||||||
|
longest_sev_suffix="${#sev_suffix}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
sev_suffix_string="$(head -c "${longest_sev_suffix}" '/dev/zero' | tr '\0' '_')"
|
||||||
|
|
||||||
|
local max_dataset_name_length dataset_name_no_pkgs
|
||||||
|
max_dataset_name_length='0'
|
||||||
|
for dataset in "${snappable_datasets[@]}"; do
|
||||||
|
dataset_name_no_pkgs="${dataset}"'@'"${snap_name_prefix}${snap_field_separator}${date_string}${snap_field_separator}"'op:'"${op_suffix_string}${snap_field_separator}"'sev:'"${sev_suffix_string}${snap_field_separator}"'pkgs:'
|
||||||
|
if [[ "${#dataset_name_no_pkgs}" -gt "${max_dataset_name_length}" ]]; then
|
||||||
|
max_dataset_name_length="${#dataset_name_no_pkgs}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "${max_dataset_name_length}" -gt "${max_zfs_snapshot_name_length}" ]]; then
|
||||||
|
pprint 'warn' 'Snapshot name would exceed ZFS '"${max_zfs_snapshot_name_length}"' chars limit. Skipping snapshots ...' '0'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function trim_pkg_list_oneline () {
|
||||||
|
local available_pkg_list_length
|
||||||
|
available_pkg_list_length="$((${max_zfs_snapshot_name_length} - ${max_dataset_name_length}))"
|
||||||
|
if [[ "${available_pkg_list_length}" -lt "{pkgs_list_max_length}" ]]; then
|
||||||
|
# If we have fewer characters available than the user wants limit
|
||||||
|
# package list length
|
||||||
|
pkgs_list_max_length="${available_pkg_list_length}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local shorter_pkg_list
|
||||||
|
shorter_pkg_list="${pkg_list_oneline}"
|
||||||
|
while [[ "${#shorter_pkg_list}" -gt "${pkgs_list_max_length}" ]]; do
|
||||||
|
shorter_pkg_list="${shorter_pkg_list%,*}"
|
||||||
|
if ! grep -Piq ',' <<<"${shorter_pkg_list}"; then
|
||||||
|
# Only one package remains in package list, no need to continue
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "${#shorter_pkg_list}" -gt "${pkgs_list_max_length}" ]]; do
|
||||||
|
# If this is still too long we empty the package list
|
||||||
|
shorter_pkg_list=''
|
||||||
|
done
|
||||||
|
trimmed_pkg_list_oneline="${shorter_pkg_list}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_snaps () {
|
||||||
|
local snap_name
|
||||||
|
for snappable_dataset in "${snappable_datasets[@]}"; do
|
||||||
|
snap_name="${snappable_dataset}"'@'"${snap_name_prefix}${snap_field_separator}${date_string}${snap_field_separator}"'op:'"${operation}${snap_field_separator}"'sev:'"${severity}${snap_field_separator}"'pkgs:'"${trimmed_pkg_list_oneline}"
|
||||||
|
if [[ "${do_dry_run}" == 'true' ]]; then
|
||||||
|
pprint 'info' 'Dry-run, pretending to run zfs snapshot '"${snap_name}"
|
||||||
|
else
|
||||||
|
zfs snapshot "${snap_name}" && {
|
||||||
|
successfully_snapped_datasets+=("${snappable_dataset}")
|
||||||
|
pprint 'info' 'Snapshot done: '"${snap_name}"
|
||||||
|
} || {
|
||||||
|
pprint 'warn' 'Snapshot failed: '"${snap_name}"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_snaps_in_cur_sev () {
|
||||||
|
local dataset_to_query
|
||||||
|
dataset_to_query="${1:?}"
|
||||||
|
snap_list="$(zfs list -H -o 'name' -t snapshot "${dataset_to_query}")"
|
||||||
|
snaps_done_by_us="$(grep -Pi -- '@'"${snap_name_prefix}${snap_field_separator}" <<<"${snap_list}")"
|
||||||
|
snaps_in_cur_sev="$(grep -Pi -- "${snap_field_separator}"'sev:'"${severity}${snap_field_separator}" <<<"${snaps_done_by_us}")"
|
||||||
|
printf -- '%s' "${snaps_in_cur_sev}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_retention () {
|
||||||
|
local snap_list snaps_done_by_us snaps_in_cur_sev snaps_limit oldest_snap
|
||||||
|
if [[ "${do_dry_run}" == 'true' ]]; then
|
||||||
|
pprint 'info' 'Dry-run, skipping potential zfs destroy operations ...'
|
||||||
|
else
|
||||||
|
for successfully_snapped_dataset in "${successfully_snapped_datasets[@]}"; do
|
||||||
|
snaps_in_cur_sev="$(get_snaps_in_cur_sev "${successfully_snapped_dataset}")"
|
||||||
|
if [[ "${severity}" == "${snaps_important_suffix}" ]]; then
|
||||||
|
snaps_limit="${snaps_important_keep}"
|
||||||
|
else
|
||||||
|
snaps_limit="${snaps_trivial_keep}"
|
||||||
|
fi
|
||||||
|
while [[ "$(get_snaps_in_cur_sev "${successfully_snapped_dataset}" | wc -l)" -gt "${snaps_limit}" ]]; do
|
||||||
|
oldest_snap="$(get_snaps_in_cur_sev "${successfully_snapped_dataset}" | head -n1)"
|
||||||
|
zfs destroy "${oldest_snap}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
function main () {
|
function main () {
|
||||||
local pkgs_in_transaction
|
local pkgs_in_transaction
|
||||||
pkgs_in_transaction=("${@}")
|
pkgs_in_transaction=("${@}")
|
||||||
|
|
||||||
declare -a important_pkgs_in_transaction unimportant_pkgs_in_transaction
|
local -a important_pkgs_in_transaction trivial_pkgs_in_transaction
|
||||||
split_pkgs_by_importance "${pkgs_in_transaction[@]}"
|
split_pkgs_by_importance "${pkgs_in_transaction[@]}"
|
||||||
|
|
||||||
|
local severity
|
||||||
#for pkg in "${!important_pkgs_in_transaction[@]}"; do
|
set_severity
|
||||||
# printf -- 'Array item '"'"'%s'"'"' equals '"'"'%s'"'"'\n' "${pkg}" "${important_pkgs_in_transaction[${pkg}]}"
|
|
||||||
#done
|
local -a globally_snappable_datasets
|
||||||
|
get_globally_snappable_datasets
|
||||||
|
|
||||||
|
local -a snappable_datasets
|
||||||
|
if [[ "${snap_only_local_datasets}" == 'true' ]]; then
|
||||||
|
local local_snappable_datasets
|
||||||
|
get_local_snappable_datasets
|
||||||
|
trim_globally_snappable_datasets
|
||||||
|
else
|
||||||
|
snappable_datasets=("${globally_snappable_datasets}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local pkg_list_oneline
|
||||||
|
write_pkg_list_oneline
|
||||||
|
|
||||||
|
local date_string max_zfs_snapshot_name_length max_dataset_name_length
|
||||||
|
date_string="$(date +"${snap_date_format}")"
|
||||||
|
max_zfs_snapshot_name_length='255'
|
||||||
|
find_max_dataset_name_length
|
||||||
|
|
||||||
|
local trimmed_pkg_list_oneline
|
||||||
|
trim_pkg_list_oneline
|
||||||
|
|
||||||
|
local -a successfully_snapped_datasets
|
||||||
|
do_snaps
|
||||||
|
do_retention
|
||||||
}
|
}
|
||||||
|
|
||||||
main "${pkgs[@]}"
|
main "${pkgs[@]}"
|
||||||
|
Reference in New Issue
Block a user