zfs: allow snapshot before activation
This commit is contained in:
parent
5fd0b1dc83
commit
588c8f4e67
5 changed files with 148 additions and 0 deletions
|
|
@ -29,6 +29,11 @@ Template:
|
|||
to include the ldap UID instead of the email but I kept the email address.
|
||||
This means there is no migration to do.
|
||||
|
||||
## New Features
|
||||
|
||||
- Add `shb.zfs.snapshotBeforeActivation` option to take a ZFS snapshot of given datasets before activation.
|
||||
See [the manual](https://shb.skarabox.com/blocks-zfs.html) for more details.
|
||||
|
||||
# v0.8.0
|
||||
|
||||
## Breaking Changes
|
||||
|
|
|
|||
|
|
@ -1928,6 +1928,18 @@
|
|||
"blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.path": [
|
||||
"blocks-zfs.html#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.path"
|
||||
],
|
||||
"blocks-zfs-options-shb.zfs.snapshotBeforeActivation.datasets": [
|
||||
"blocks-zfs.html#blocks-zfs-options-shb.zfs.snapshotBeforeActivation.datasets"
|
||||
],
|
||||
"blocks-zfs-options-shb.zfs.snapshotBeforeActivation.enable": [
|
||||
"blocks-zfs.html#blocks-zfs-options-shb.zfs.snapshotBeforeActivation.enable"
|
||||
],
|
||||
"blocks-zfs-options-shb.zfs.snapshotBeforeActivation.recursive": [
|
||||
"blocks-zfs.html#blocks-zfs-options-shb.zfs.snapshotBeforeActivation.recursive"
|
||||
],
|
||||
"blocks-zfs-options-shb.zfs.snapshotBeforeActivation.template": [
|
||||
"blocks-zfs.html#blocks-zfs-options-shb.zfs.snapshotBeforeActivation.template"
|
||||
],
|
||||
"blocks-zfs-usage": [
|
||||
"blocks-zfs.html#blocks-zfs-usage"
|
||||
],
|
||||
|
|
@ -1937,6 +1949,9 @@
|
|||
"blocks-zfs-usage-backup-files": [
|
||||
"blocks-zfs.html#blocks-zfs-usage-backup-files"
|
||||
],
|
||||
"blocks-zfs-usage-snapshot-before-activation": [
|
||||
"blocks-zfs.html#blocks-zfs-usage-snapshot-before-activation"
|
||||
],
|
||||
"build-time-validation": [
|
||||
"service-implementation-guide.html#build-time-validation"
|
||||
],
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
|
||||
let
|
||||
cfg = config.shb.zfs;
|
||||
|
||||
datasets =
|
||||
if cfg.snapshotBeforeActivation.datasets != null then
|
||||
cfg.snapshotBeforeActivation.datasets
|
||||
else
|
||||
config.boot.zfs.extraPools;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
|
@ -145,6 +151,37 @@ in
|
|||
|
||||
);
|
||||
};
|
||||
|
||||
snapshotBeforeActivation = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Take a snapshot of all datasets before activation";
|
||||
default = false;
|
||||
};
|
||||
|
||||
template = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "Bash command to generate the snapshot name. $1 is the path to the new generation.";
|
||||
default = ''pre-$(date --utc '+%y%m%dT%H%M%S')-$(basename "$1")'';
|
||||
};
|
||||
|
||||
datasets = lib.mkOption {
|
||||
type = lib.types.nullOr (lib.types.listOf lib.types.str);
|
||||
description = ''
|
||||
Defines all datasets to take a snapshot of.
|
||||
|
||||
If set to `null`, the default, take the list of datasets from `config.boot.zfs.extraPools`.
|
||||
|
||||
The snapshots are not recursive unless specificed in the `recursive` option.
|
||||
'';
|
||||
};
|
||||
|
||||
recursive = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "If true, take snapshots recurisevly on the given datasets.";
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
# The implementation is greatly inspired by https://discourse.nixos.org/t/configure-zfs-filesystems-after-install/48633/3
|
||||
|
|
@ -203,5 +240,17 @@ in
|
|||
mergeAttrs = lib.foldl lib.mergeAttrs { };
|
||||
in
|
||||
mergeAttrs (lib.mapAttrsToList mkPool cfg.pools);
|
||||
|
||||
system.preSwitchChecks."shb-zfs-snapshot" = lib.mkIf cfg.snapshotBeforeActivation.enable (
|
||||
''
|
||||
name="${cfg.snapshotBeforeActivation.template}"
|
||||
''
|
||||
+ (
|
||||
let
|
||||
recursiveFlag = lib.optionalString cfg.snapshotBeforeActivation.recursive "-r";
|
||||
in
|
||||
lib.concatMapStringsSep "\n" (ds: "zfs snapshot ${recursiveFlag} ${ds}@\"$name\"") datasets
|
||||
)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ Defined in [`/modules/blocks/zfs.nix`](@REPO@/modules/blocks/zfs.nix):
|
|||
```
|
||||
|
||||
This block creates ZFS datasets, optionally mounts them and sets permissions on the mount point.
|
||||
It also enables taking snapshots on activation.
|
||||
|
||||
## Features {#blocks-zfs-features}
|
||||
|
||||
|
|
@ -22,9 +23,11 @@ This block creates ZFS datasets, optionally mounts them and sets permissions on
|
|||
- Sets permissions, [owner](#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.owner), [group](#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.group) and [ACL](#blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.defaultACLs) on the mount point.
|
||||
- Backup of the files in the dataset [`shb.zfs.<name>.backup`][backup] through the [backup contract](./contracts-backup.html).
|
||||
- Backup of the dataset itself [`shb.zfs.<name>.datasetbackup`][datasetbackup] through the [dataset backup contract](./contracts-datasetbackup.html).
|
||||
- Take a snapshot [before activating or deploying][activationsnapshot] allowing a safe rollback.
|
||||
|
||||
[backup]: #blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.backup
|
||||
[datasetbackup]: #blocks-zfs-options-shb.zfs.pools._name_.datasets._name_.datasetbackup
|
||||
[activationsnapshot]: #blocks-zfs-options-shb.zfs.snapshotBeforeActivation.enable
|
||||
|
||||
## Usage {#blocks-zfs-usage}
|
||||
|
||||
|
|
@ -93,6 +96,39 @@ For example, with the restic module as the file backup contract provider:
|
|||
|
||||
See the [Restic block](blocks-restic.html) for more examples.
|
||||
|
||||
### Snapshot before activation {#blocks-zfs-usage-snapshot-before-activation}
|
||||
|
||||
To take a snapshot of all datasets before activating a new configuration:
|
||||
|
||||
```nix
|
||||
{
|
||||
shb.zfs.snapshotBeforeActivation.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
This does not take a recursive snapshot by default, to do it recursively, do:
|
||||
|
||||
```nix
|
||||
{
|
||||
shb.zfs.snapshotBeforeActivation = {
|
||||
enable = true;
|
||||
recursive = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
We did not specify datasets manually, which means the datasets list comes from `boot.zfs.extraPools`.
|
||||
To specify the datasets manually, you can use:
|
||||
|
||||
```nix
|
||||
{
|
||||
shb.zfs.snapshotBeforeActivation = {
|
||||
enable = true;
|
||||
datasets = [ "root" "root/var" ];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Options Reference {#blocks-zfs-options}
|
||||
|
||||
```{=include=} options
|
||||
|
|
|
|||
|
|
@ -63,10 +63,27 @@
|
|||
group = "syncthing";
|
||||
defaultACLs = "g:syncthing:rwX";
|
||||
};
|
||||
|
||||
shb.zfs.snapshotBeforeActivation = {
|
||||
enable = true;
|
||||
recursive = true;
|
||||
};
|
||||
|
||||
specialisation = {
|
||||
test.configuration = { };
|
||||
test2.configuration = {
|
||||
# A plus sign is not allowed as the snapshot name so it will make it fail.
|
||||
shb.zfs.snapshotBeforeActivation.template = "+";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
|
||||
switch = name: "${specialisations}/${name}/bin/switch-to-configuration test";
|
||||
in
|
||||
''
|
||||
import difflib
|
||||
|
||||
|
|
@ -98,12 +115,38 @@
|
|||
diff = difflib.context_diff(expect, out)
|
||||
raise Exception(f"Unexpected mounts:\n{"\n".join(diff)}")
|
||||
|
||||
def assert_count_snapshots(name, count):
|
||||
out = machine.succeed("zfs list -Ht snapshot root/one")
|
||||
print(out)
|
||||
if count != len(out.splitlines()):
|
||||
raise Exception(f"Expected {count} dataset")
|
||||
|
||||
machine.start(allow_reboot=True)
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
assert_facl()
|
||||
assert_mounts()
|
||||
|
||||
with subtest("no snapshot yet"):
|
||||
print(machine.succeed("zpool list"))
|
||||
print(machine.succeed("zfs list"))
|
||||
assert_count_snapshots("root/one", 0)
|
||||
assert_count_snapshots("root/two", 0)
|
||||
assert_count_snapshots("root/none", 0)
|
||||
assert_count_snapshots("data/two", 0)
|
||||
|
||||
machine.succeed("${switch "test"}")
|
||||
|
||||
with subtest("snapshot created"):
|
||||
print(machine.succeed("zpool list"))
|
||||
print(machine.succeed("zfs list"))
|
||||
assert_count_snapshots("root/one", 1)
|
||||
assert_count_snapshots("root/two", 1)
|
||||
assert_count_snapshots("root/none", 1)
|
||||
assert_count_snapshots("data/two", 1)
|
||||
|
||||
machine.fail("${switch "test2"}")
|
||||
|
||||
# TODO: make this work. zpool import does not work after reboot.
|
||||
# machine.crash()
|
||||
# machine.wait_for_unit("multi-user.target")
|
||||
|
|
|
|||
Loading…
Reference in a new issue