Merge pull request #34 from nix-community/configuration-limit
Lanzatool: respect configuration limit
This commit is contained in:
commit
f431622732
17
flake.nix
17
flake.nix
|
@ -50,14 +50,14 @@
|
||||||
{ src
|
{ src
|
||||||
, target ? null
|
, target ? null
|
||||||
, doCheck ? true
|
, doCheck ? true
|
||||||
|
, extraArgs ? { }
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
cleanedSrc = craneLib.cleanCargoSource src;
|
|
||||||
commonArgs = {
|
commonArgs = {
|
||||||
src = cleanedSrc;
|
inherit src;
|
||||||
CARGO_BUILD_TARGET = target;
|
CARGO_BUILD_TARGET = target;
|
||||||
inherit doCheck;
|
inherit doCheck;
|
||||||
};
|
} // extraArgs;
|
||||||
|
|
||||||
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
|
||||||
in
|
in
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
lanzabooteCrane = buildRustApp {
|
lanzabooteCrane = buildRustApp {
|
||||||
src = ./rust/lanzaboote;
|
src = craneLib.cleanCargoSource ./rust/lanzaboote;
|
||||||
target = "x86_64-unknown-uefi";
|
target = "x86_64-unknown-uefi";
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
};
|
};
|
||||||
|
@ -82,6 +82,13 @@
|
||||||
|
|
||||||
lanzatoolCrane = buildRustApp {
|
lanzatoolCrane = buildRustApp {
|
||||||
src = ./rust/lanzatool;
|
src = ./rust/lanzatool;
|
||||||
|
extraArgs = {
|
||||||
|
TEST_SYSTEMD = pkgs.systemd;
|
||||||
|
checkInputs = with pkgs; [
|
||||||
|
binutils-unwrapped
|
||||||
|
sbsigntool
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
lanzatool-unwrapped = lanzatoolCrane.package;
|
lanzatool-unwrapped = lanzatoolCrane.package;
|
||||||
|
@ -134,6 +141,8 @@
|
||||||
lanzaboote
|
lanzaboote
|
||||||
lanzatool
|
lanzatool
|
||||||
];
|
];
|
||||||
|
|
||||||
|
TEST_SYSTEMD = pkgs.systemd;
|
||||||
};
|
};
|
||||||
|
|
||||||
checks.x86_64-linux = {
|
checks.x86_64-linux = {
|
||||||
|
|
|
@ -5,11 +5,24 @@ let
|
||||||
sbctlWithPki = pkgs.sbctl.override {
|
sbctlWithPki = pkgs.sbctl.override {
|
||||||
databasePath = "/tmp/pki";
|
databasePath = "/tmp/pki";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.boot.lanzaboote = {
|
options.boot.lanzaboote = {
|
||||||
enable = mkEnableOption "Enable the LANZABOOTE";
|
enable = mkEnableOption "Enable the LANZABOOTE";
|
||||||
enrollKeys = mkEnableOption "Automatic enrollment of the keys using sbctl";
|
enrollKeys = mkEnableOption "Automatic enrollment of the keys using sbctl";
|
||||||
|
configurationLimit = mkOption {
|
||||||
|
default = null;
|
||||||
|
example = 120;
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Maximum number of latest generations in the boot menu.
|
||||||
|
Useful to prevent boot partition running out of disk space.
|
||||||
|
`null` means no limit i.e. all generations
|
||||||
|
that were not garbage collected yet.
|
||||||
|
'';
|
||||||
|
};
|
||||||
pkiBundle = mkOption {
|
pkiBundle = mkOption {
|
||||||
type = types.nullOr types.path;
|
type = types.nullOr types.path;
|
||||||
description = "PKI bundle containg db, PK, KEK";
|
description = "PKI bundle containg db, PK, KEK";
|
||||||
|
@ -49,6 +62,7 @@ in
|
||||||
${cfg.package}/bin/lanzatool install \
|
${cfg.package}/bin/lanzatool install \
|
||||||
--public-key ${cfg.publicKeyFile} \
|
--public-key ${cfg.publicKeyFile} \
|
||||||
--private-key ${cfg.privateKeyFile} \
|
--private-key ${cfg.privateKeyFile} \
|
||||||
|
--configuration-limit ${toString configurationLimit} \
|
||||||
${config.boot.loader.efi.efiSysMountPoint} \
|
${config.boot.loader.efi.efiSysMountPoint} \
|
||||||
/nix/var/nix/profiles/system-*-link
|
/nix/var/nix/profiles/system-*-link
|
||||||
'';
|
'';
|
||||||
|
|
|
@ -20,6 +20,20 @@ version = "0.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "assert_cmd"
|
||||||
|
version = "2.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3"
|
||||||
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
|
"doc-comment",
|
||||||
|
"predicates",
|
||||||
|
"predicates-core",
|
||||||
|
"predicates-tree",
|
||||||
|
"wait-timeout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atty"
|
name = "atty"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
|
@ -75,6 +89,18 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"once_cell",
|
||||||
|
"regex-automata",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.77"
|
version = "1.0.77"
|
||||||
|
@ -140,6 +166,12 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "difflib"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
@ -151,6 +183,18 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "doc-comment"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
|
@ -170,6 +214,17 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "goblin"
|
name = "goblin"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -205,6 +260,15 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -216,14 +280,17 @@ name = "lanzatool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"assert_cmd",
|
||||||
"blake3",
|
"blake3",
|
||||||
"bootspec",
|
"bootspec",
|
||||||
"clap",
|
"clap",
|
||||||
"goblin",
|
"goblin",
|
||||||
"nix",
|
"nix",
|
||||||
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -241,6 +308,12 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
|
@ -271,6 +344,39 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates"
|
||||||
|
version = "2.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd"
|
||||||
|
dependencies = [
|
||||||
|
"difflib",
|
||||||
|
"itertools",
|
||||||
|
"predicates-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-core"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "predicates-tree"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d"
|
||||||
|
dependencies = [
|
||||||
|
"predicates-core",
|
||||||
|
"termtree",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -313,6 +419,36 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
@ -322,6 +458,12 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
@ -337,6 +479,15 @@ version = "1.0.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scroll"
|
name = "scroll"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -434,6 +585,12 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termtree"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.15.0"
|
version = "1.15.0"
|
||||||
|
@ -452,6 +609,32 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wait-timeout"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
|
@ -16,3 +16,8 @@ tempfile = "3.3.0"
|
||||||
blake3 = "1.3.3"
|
blake3 = "1.3.3"
|
||||||
# TODO: wait for a upstream release and pin it.
|
# TODO: wait for a upstream release and pin it.
|
||||||
bootspec = { git = "https://github.com/DeterminateSystems/bootspec" }
|
bootspec = { git = "https://github.com/DeterminateSystems/bootspec" }
|
||||||
|
walkdir = "2.3.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_cmd = "2.0.7"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
|
@ -27,10 +27,14 @@ struct InstallCommand {
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
private_key: PathBuf,
|
private_key: PathBuf,
|
||||||
|
|
||||||
|
/// Configuration limit
|
||||||
|
#[arg(long, default_value_t = 1)]
|
||||||
|
configuration_limit: usize,
|
||||||
|
|
||||||
/// EFI system partition mountpoint (e.g. efiSysMountPoint)
|
/// EFI system partition mountpoint (e.g. efiSysMountPoint)
|
||||||
esp: PathBuf,
|
esp: PathBuf,
|
||||||
|
|
||||||
/// List of generations (e.g. /nix/var/nix/profiles/system-*-link)
|
/// List of generation links (e.g. /nix/var/nix/profiles/system-*-link)
|
||||||
generations: Vec<PathBuf>,
|
generations: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +61,7 @@ fn install(args: InstallCommand) -> Result<()> {
|
||||||
install::Installer::new(
|
install::Installer::new(
|
||||||
PathBuf::from(lanzaboote_stub),
|
PathBuf::from(lanzaboote_stub),
|
||||||
key_pair,
|
key_pair,
|
||||||
|
args.configuration_limit,
|
||||||
args.esp,
|
args.esp,
|
||||||
args.generations,
|
args.generations,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
use anyhow::{Context, Result};
|
use std::array::IntoIter;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
|
||||||
use crate::generation::Generation;
|
use crate::generation::Generation;
|
||||||
|
|
||||||
pub struct EspPaths {
|
pub struct EspPaths {
|
||||||
pub esp: PathBuf,
|
pub esp: PathBuf,
|
||||||
|
pub efi: PathBuf,
|
||||||
pub nixos: PathBuf,
|
pub nixos: PathBuf,
|
||||||
pub kernel: PathBuf,
|
pub kernel: PathBuf,
|
||||||
pub initrd: PathBuf,
|
pub initrd: PathBuf,
|
||||||
|
@ -19,32 +22,52 @@ pub struct EspPaths {
|
||||||
impl EspPaths {
|
impl EspPaths {
|
||||||
pub fn new(esp: impl AsRef<Path>, generation: &Generation) -> Result<Self> {
|
pub fn new(esp: impl AsRef<Path>, generation: &Generation) -> Result<Self> {
|
||||||
let esp = esp.as_ref();
|
let esp = esp.as_ref();
|
||||||
let esp_nixos = esp.join("EFI/nixos");
|
let efi = esp.join("EFI");
|
||||||
let esp_linux = esp.join("EFI/Linux");
|
let efi_nixos = efi.join("nixos");
|
||||||
let esp_systemd = esp.join("EFI/systemd");
|
let efi_linux = efi.join("Linux");
|
||||||
let esp_efi_fallback_dir = esp.join("EFI/BOOT");
|
let efi_systemd = efi.join("systemd");
|
||||||
|
let efi_efi_fallback_dir = efi.join("BOOT");
|
||||||
|
|
||||||
let bootspec = &generation.spec.bootspec;
|
let bootspec = &generation.spec.bootspec;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
esp: esp.to_path_buf(),
|
esp: esp.to_path_buf(),
|
||||||
nixos: esp_nixos.clone(),
|
efi,
|
||||||
kernel: esp_nixos.join(nixos_path(&bootspec.kernel, "bzImage")?),
|
nixos: efi_nixos.clone(),
|
||||||
initrd: esp_nixos.join(nixos_path(
|
kernel: efi_nixos.join(nixos_path(&bootspec.kernel, "bzImage")?),
|
||||||
|
initrd: efi_nixos.join(nixos_path(
|
||||||
bootspec
|
bootspec
|
||||||
.initrd
|
.initrd
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.context("Lanzaboote does not support missing initrd yet")?,
|
.context("Lanzaboote does not support missing initrd yet")?,
|
||||||
"initrd",
|
"initrd",
|
||||||
)?),
|
)?),
|
||||||
linux: esp_linux.clone(),
|
linux: efi_linux.clone(),
|
||||||
lanzaboote_image: esp_linux.join(generation_path(generation)),
|
lanzaboote_image: efi_linux.join(generation_path(generation)),
|
||||||
efi_fallback_dir: esp_efi_fallback_dir.clone(),
|
efi_fallback_dir: efi_efi_fallback_dir.clone(),
|
||||||
efi_fallback: esp_efi_fallback_dir.join("BOOTX64.EFI"),
|
efi_fallback: efi_efi_fallback_dir.join("BOOTX64.EFI"),
|
||||||
systemd: esp_systemd.clone(),
|
systemd: efi_systemd.clone(),
|
||||||
systemd_boot: esp_systemd.join("systemd-bootx64.efi"),
|
systemd_boot: efi_systemd.join("systemd-bootx64.efi"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the used file paths to store as garbage collection roots.
|
||||||
|
pub fn to_iter(&self) -> IntoIter<&PathBuf, 11> {
|
||||||
|
[
|
||||||
|
&self.esp,
|
||||||
|
&self.efi,
|
||||||
|
&self.nixos,
|
||||||
|
&self.kernel,
|
||||||
|
&self.initrd,
|
||||||
|
&self.linux,
|
||||||
|
&self.lanzaboote_image,
|
||||||
|
&self.efi_fallback_dir,
|
||||||
|
&self.efi_fallback,
|
||||||
|
&self.systemd,
|
||||||
|
&self.systemd_boot,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nixos_path(path: impl AsRef<Path>, name: &str) -> Result<PathBuf> {
|
fn nixos_path(path: impl AsRef<Path>, name: &str) -> Result<PathBuf> {
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
|
/// Keeps track of the garbage collection roots.
|
||||||
|
///
|
||||||
|
/// The internal HashSet contains all the paths still in use. These paths
|
||||||
|
/// are used to find all **unused** paths and delete them.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Roots(HashSet<PathBuf>);
|
||||||
|
|
||||||
|
impl Roots {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(HashSet::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extend the garbage collection roots.
|
||||||
|
///
|
||||||
|
/// Not only the file paths of roots themselves, but also all parent directories that should
|
||||||
|
/// not be garbage collected need to be **explicitly** added to the roots. For example, if you
|
||||||
|
/// have a path: `rootdir/example/file.txt`, the three paths: `rootdir`, `rootdir/example`, and
|
||||||
|
/// `rootdir/example/file.txt` need to be added for the right files to be garbage collected.
|
||||||
|
pub fn extend<'a>(&mut self, other: impl IntoIterator<Item = &'a PathBuf>) {
|
||||||
|
self.0.extend(other.into_iter().cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn in_use(&self, entry: Option<&DirEntry>) -> bool {
|
||||||
|
match entry {
|
||||||
|
Some(e) => self.0.contains(e.path()),
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_garbage(&self, directory: impl AsRef<Path>) -> Result<()> {
|
||||||
|
// Find all the paths not used anymore.
|
||||||
|
let entries_not_in_use = WalkDir::new(directory.as_ref())
|
||||||
|
.into_iter()
|
||||||
|
.filter(|e| !self.in_use(e.as_ref().ok()));
|
||||||
|
|
||||||
|
// Remove all entries not in use.
|
||||||
|
for e in entries_not_in_use {
|
||||||
|
let entry = e?;
|
||||||
|
let path = entry.path();
|
||||||
|
println!("'{}' not in use anymore. Removing...", path.display());
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
// If a directory is marked as unused all its children can be deleted too.
|
||||||
|
fs::remove_dir_all(path)
|
||||||
|
.with_context(|| format!("Failed to remove directory: {:?}", path))?;
|
||||||
|
} else {
|
||||||
|
// Ignore failing to remove path because the parent directory might have been removed before.
|
||||||
|
fs::remove_file(path).ok();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keep_used_file() -> Result<()> {
|
||||||
|
let tmpdir = tempfile::tempdir()?;
|
||||||
|
let rootdir = create_dir(tmpdir.path().join("root"))?;
|
||||||
|
|
||||||
|
let used_file = create_file(rootdir.join("root_file"))?;
|
||||||
|
|
||||||
|
let mut roots = Roots::new();
|
||||||
|
roots.extend(vec![&rootdir, &used_file]);
|
||||||
|
roots.collect_garbage(&rootdir)?;
|
||||||
|
|
||||||
|
assert!(used_file.exists());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_unused_file() -> Result<()> {
|
||||||
|
let tmpdir = tempfile::tempdir()?;
|
||||||
|
let rootdir = create_dir(tmpdir.path().join("root"))?;
|
||||||
|
|
||||||
|
let unused_file = create_file(rootdir.join("unused_file"))?;
|
||||||
|
|
||||||
|
let mut roots = Roots::new();
|
||||||
|
roots.extend(vec![&rootdir]);
|
||||||
|
roots.collect_garbage(&rootdir)?;
|
||||||
|
|
||||||
|
assert!(!unused_file.exists());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_empty_unused_directory() -> Result<()> {
|
||||||
|
let tmpdir = tempfile::tempdir()?;
|
||||||
|
let rootdir = create_dir(tmpdir.path().join("root"))?;
|
||||||
|
|
||||||
|
let unused_directory = create_dir(rootdir.join("unused_directory"))?;
|
||||||
|
|
||||||
|
let mut roots = Roots::new();
|
||||||
|
roots.extend(vec![&rootdir]);
|
||||||
|
roots.collect_garbage(&rootdir)?;
|
||||||
|
|
||||||
|
assert!(!unused_directory.exists());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn delete_unused_directory_with_unused_file_inside() -> Result<()> {
|
||||||
|
let tmpdir = tempfile::tempdir()?;
|
||||||
|
let rootdir = create_dir(tmpdir.path().join("root"))?;
|
||||||
|
|
||||||
|
let unused_directory = create_dir(rootdir.join("unused_directory"))?;
|
||||||
|
let unused_file_in_directory =
|
||||||
|
create_file(unused_directory.join("unused_file_in_directory"))?;
|
||||||
|
|
||||||
|
let mut roots = Roots::new();
|
||||||
|
roots.extend(vec![&rootdir]);
|
||||||
|
roots.collect_garbage(&rootdir)?;
|
||||||
|
|
||||||
|
assert!(!unused_directory.exists());
|
||||||
|
assert!(!unused_file_in_directory.exists());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keep_used_dirctory_with_used_and_unused_file() -> Result<()> {
|
||||||
|
let tmpdir = tempfile::tempdir()?;
|
||||||
|
let rootdir = create_dir(tmpdir.path().join("root"))?;
|
||||||
|
|
||||||
|
let used_directory = create_dir(rootdir.join("used_directory"))?;
|
||||||
|
let used_file_in_directory = create_file(used_directory.join("used_file_in_directory"))?;
|
||||||
|
let unused_file_in_directory =
|
||||||
|
create_file(used_directory.join("unused_file_in_directory"))?;
|
||||||
|
|
||||||
|
let mut roots = Roots::new();
|
||||||
|
roots.extend(vec![&rootdir, &used_directory, &used_file_in_directory]);
|
||||||
|
roots.collect_garbage(&rootdir)?;
|
||||||
|
|
||||||
|
assert!(used_directory.exists());
|
||||||
|
assert!(used_file_in_directory.exists());
|
||||||
|
assert!(!unused_file_in_directory.exists());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_file(path: PathBuf) -> Result<PathBuf> {
|
||||||
|
fs::File::create(&path)?;
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir(path: PathBuf) -> Result<PathBuf> {
|
||||||
|
fs::create_dir(&path)?;
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,3 @@
|
||||||
use serde::de::IntoDeserializer;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -9,6 +6,8 @@ use anyhow::{anyhow, Context, Result};
|
||||||
use bootspec::generation::Generation as BootspecGeneration;
|
use bootspec::generation::Generation as BootspecGeneration;
|
||||||
use bootspec::BootJson;
|
use bootspec::BootJson;
|
||||||
use bootspec::SpecialisationName;
|
use bootspec::SpecialisationName;
|
||||||
|
use serde::de::IntoDeserializer;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct SecureBootExtension {
|
pub struct SecureBootExtension {
|
||||||
|
@ -22,6 +21,14 @@ pub struct ExtendedBootJson {
|
||||||
pub extensions: SecureBootExtension,
|
pub extensions: SecureBootExtension,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A system configuration.
|
||||||
|
///
|
||||||
|
/// Can be built from a GenerationLink.
|
||||||
|
///
|
||||||
|
/// NixOS represents a generation as a symlink to a toplevel derivation. This toplevel derivation
|
||||||
|
/// contains most of the information necessary to install the generation onto the EFI System
|
||||||
|
/// Partition. The only information missing is the version number which is encoded in the file name
|
||||||
|
/// of the generation link.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Generation {
|
pub struct Generation {
|
||||||
/// Profile symlink index
|
/// Profile symlink index
|
||||||
|
@ -33,17 +40,8 @@ pub struct Generation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Generation {
|
impl Generation {
|
||||||
fn extract_extensions(bootspec: &BootJson) -> Result<SecureBootExtension> {
|
pub fn from_link(link: &GenerationLink) -> Result<Self> {
|
||||||
Ok(Deserialize::deserialize(
|
let bootspec_path = link.path.join("boot.json");
|
||||||
bootspec.extensions.get("lanzaboote")
|
|
||||||
.context("Failed to extract Lanzaboote-specific extension from Bootspec, missing lanzaboote field in `extensions`")?
|
|
||||||
.clone()
|
|
||||||
.into_deserializer()
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_toplevel(toplevel: impl AsRef<Path>) -> Result<Self> {
|
|
||||||
let bootspec_path = toplevel.as_ref().join("boot.json");
|
|
||||||
let generation: BootspecGeneration = serde_json::from_slice(
|
let generation: BootspecGeneration = serde_json::from_slice(
|
||||||
&fs::read(bootspec_path).context("Failed to read bootspec file")?,
|
&fs::read(bootspec_path).context("Failed to read bootspec file")?,
|
||||||
)
|
)
|
||||||
|
@ -56,7 +54,7 @@ impl Generation {
|
||||||
let extensions = Self::extract_extensions(&bootspec)?;
|
let extensions = Self::extract_extensions(&bootspec)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
version: parse_version(toplevel)?,
|
version: link.version,
|
||||||
specialisation_name: None,
|
specialisation_name: None,
|
||||||
spec: ExtendedBootJson {
|
spec: ExtendedBootJson {
|
||||||
bootspec,
|
bootspec,
|
||||||
|
@ -65,6 +63,15 @@ impl Generation {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_extensions(bootspec: &BootJson) -> Result<SecureBootExtension> {
|
||||||
|
Ok(Deserialize::deserialize(
|
||||||
|
bootspec.extensions.get("lanzaboote")
|
||||||
|
.context("Failed to extract Lanzaboote-specific extension from Bootspec, missing lanzaboote field in `extensions`")?
|
||||||
|
.clone()
|
||||||
|
.into_deserializer()
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn specialise(&self, name: &SpecialisationName, bootspec: &BootJson) -> Result<Self> {
|
pub fn specialise(&self, name: &SpecialisationName, bootspec: &BootJson) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
version: self.version,
|
version: self.version,
|
||||||
|
@ -87,6 +94,25 @@ impl fmt::Display for Generation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A link pointing to a generation.
|
||||||
|
///
|
||||||
|
/// Can be built from a symlink in /nix/var/nix/profiles/ alone because the name of the
|
||||||
|
/// symlink enocdes the version number.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct GenerationLink {
|
||||||
|
pub version: u64,
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenerationLink {
|
||||||
|
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
version: parse_version(&path).context("Failed to parse version")?,
|
||||||
|
path: PathBuf::from(path.as_ref()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse version number from a path.
|
/// Parse version number from a path.
|
||||||
///
|
///
|
||||||
/// Expects a path in the format of "system-{version}-link".
|
/// Expects a path in the format of "system-{version}-link".
|
||||||
|
|
|
@ -8,37 +8,71 @@ use nix::unistd::sync;
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
|
|
||||||
use crate::esp::EspPaths;
|
use crate::esp::EspPaths;
|
||||||
use crate::generation::Generation;
|
use crate::gc::Roots;
|
||||||
|
use crate::generation::{Generation, GenerationLink};
|
||||||
use crate::pe;
|
use crate::pe;
|
||||||
use crate::signature::KeyPair;
|
use crate::signature::KeyPair;
|
||||||
|
|
||||||
pub struct Installer {
|
pub struct Installer {
|
||||||
|
gc_roots: Roots,
|
||||||
lanzaboote_stub: PathBuf,
|
lanzaboote_stub: PathBuf,
|
||||||
key_pair: KeyPair,
|
key_pair: KeyPair,
|
||||||
|
configuration_limit: usize,
|
||||||
esp: PathBuf,
|
esp: PathBuf,
|
||||||
generations: Vec<PathBuf>,
|
generation_links: Vec<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Installer {
|
impl Installer {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
lanzaboote_stub: PathBuf,
|
lanzaboote_stub: PathBuf,
|
||||||
key_pair: KeyPair,
|
key_pair: KeyPair,
|
||||||
|
configuration_limit: usize,
|
||||||
esp: PathBuf,
|
esp: PathBuf,
|
||||||
generations: Vec<PathBuf>,
|
generation_links: Vec<PathBuf>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
gc_roots: Roots::new(),
|
||||||
lanzaboote_stub,
|
lanzaboote_stub,
|
||||||
key_pair,
|
key_pair,
|
||||||
|
configuration_limit,
|
||||||
esp,
|
esp,
|
||||||
generations,
|
generation_links,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn install(&self) -> Result<()> {
|
pub fn install(&mut self) -> Result<()> {
|
||||||
for toplevel in &self.generations {
|
let mut links = self
|
||||||
let generation_result = Generation::from_toplevel(toplevel)
|
.generation_links
|
||||||
.with_context(|| format!("Failed to build generation from toplevel: {toplevel:?}"));
|
.iter()
|
||||||
|
.map(GenerationLink::from_path)
|
||||||
|
.collect::<Result<Vec<GenerationLink>>>()?;
|
||||||
|
|
||||||
|
// A configuration limit of 0 means there is no limit.
|
||||||
|
if self.configuration_limit > 0 {
|
||||||
|
// Sort the links by version.
|
||||||
|
links.sort_by_key(|l| l.version);
|
||||||
|
|
||||||
|
// Only install the number of generations configured.
|
||||||
|
links = links
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.take(self.configuration_limit)
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
self.install_links(links)?;
|
||||||
|
|
||||||
|
self.gc_roots.collect_garbage(&self.esp)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn install_links(&mut self, links: Vec<GenerationLink>) -> Result<()> {
|
||||||
|
for link in links {
|
||||||
|
let generation_result = Generation::from_link(&link)
|
||||||
|
.with_context(|| format!("Failed to build generation from link: {link:?}"));
|
||||||
|
|
||||||
|
// Ignore failing to read a generation so that old malformed generations do not stop
|
||||||
|
// lanzatool from working.
|
||||||
let generation = match generation_result {
|
let generation = match generation_result {
|
||||||
Ok(generation) => generation,
|
Ok(generation) => generation,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -61,15 +95,15 @@ impl Installer {
|
||||||
.context("Failed to install specialisation")?;
|
.context("Failed to install specialisation")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_generation(&self, generation: &Generation) -> Result<()> {
|
fn install_generation(&mut self, generation: &Generation) -> Result<()> {
|
||||||
let bootspec = &generation.spec.bootspec;
|
let bootspec = &generation.spec.bootspec;
|
||||||
let secureboot_extensions = &generation.spec.extensions;
|
let secureboot_extensions = &generation.spec.extensions;
|
||||||
|
|
||||||
let esp_paths = EspPaths::new(&self.esp, generation)?;
|
let esp_paths = EspPaths::new(&self.esp, generation)?;
|
||||||
|
self.gc_roots.extend(esp_paths.to_iter());
|
||||||
|
|
||||||
let kernel_cmdline =
|
let kernel_cmdline =
|
||||||
assemble_kernel_cmdline(&bootspec.init, bootspec.kernel_params.clone());
|
assemble_kernel_cmdline(&bootspec.init, bootspec.kernel_params.clone());
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
mod cli;
|
mod cli;
|
||||||
mod esp;
|
mod esp;
|
||||||
|
mod gc;
|
||||||
mod generation;
|
mod generation;
|
||||||
mod install;
|
mod install;
|
||||||
mod pe;
|
mod pe;
|
||||||
|
|
|
@ -0,0 +1,155 @@
|
||||||
|
use std::ffi::OsStr;
|
||||||
|
use std::fs;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::fs::symlink;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::process::Output;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use assert_cmd::Command;
|
||||||
|
use rand::distributions::Alphanumeric;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
/// Create a mock generation link.
|
||||||
|
///
|
||||||
|
/// Creates the generation link using the specified version inside a mock profiles directory
|
||||||
|
/// (mimicking /nix/var/nix/profiles). Returns the path to the generation link.
|
||||||
|
pub fn setup_generation_link(
|
||||||
|
tmpdir: &Path,
|
||||||
|
profiles_directory: &Path,
|
||||||
|
version: u64,
|
||||||
|
) -> Result<PathBuf> {
|
||||||
|
let toplevel = setup_toplevel(tmpdir).context("Failed to setup toplevel")?;
|
||||||
|
|
||||||
|
let bootspec = json!({
|
||||||
|
"v1": {
|
||||||
|
"init": format!("init-v{}", version),
|
||||||
|
"initrd": toplevel.join("initrd"),
|
||||||
|
"kernel": toplevel.join("kernel"),
|
||||||
|
"kernelParams": [
|
||||||
|
"amd_iommu=on",
|
||||||
|
"amd_iommu=pt",
|
||||||
|
"iommu=pt",
|
||||||
|
"kvm.ignore_msrs=1",
|
||||||
|
"kvm.report_ignored_msrs=0",
|
||||||
|
"udev.log_priority=3",
|
||||||
|
"systemd.unified_cgroup_hierarchy=1",
|
||||||
|
"loglevel=4"
|
||||||
|
],
|
||||||
|
"label": "LanzaOS",
|
||||||
|
"toplevel": toplevel,
|
||||||
|
"system": "x86_64-linux",
|
||||||
|
"specialisation": {},
|
||||||
|
"extensions": {
|
||||||
|
"lanzaboote": { "osRelease": toplevel.join("os-release") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let generation_link_path = profiles_directory.join(format!("system-{}-link", version));
|
||||||
|
fs::create_dir(&generation_link_path)?;
|
||||||
|
|
||||||
|
let bootspec_path = generation_link_path.join("boot.json");
|
||||||
|
let mut file = fs::File::create(bootspec_path)?;
|
||||||
|
file.write_all(&serde_json::to_vec(&bootspec)?)?;
|
||||||
|
|
||||||
|
Ok(generation_link_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Setup a mock toplevel inside a temporary directory.
|
||||||
|
///
|
||||||
|
/// Accepts the temporary directory as a parameter so that the invoking function retains control of
|
||||||
|
/// it (and when it goes out of scope).
|
||||||
|
fn setup_toplevel(tmpdir: &Path) -> Result<PathBuf> {
|
||||||
|
// Generate a random toplevel name so that multiple toplevel paths can live alongside each
|
||||||
|
// other in the same directory.
|
||||||
|
let toplevel = tmpdir.join(format!("toplevel-{}", random_string(8)));
|
||||||
|
fs::create_dir(&toplevel)?;
|
||||||
|
|
||||||
|
let test_systemd = systemd_location_from_env()?;
|
||||||
|
let test_systemd_stub = format!("{test_systemd}/lib/systemd/boot/efi/linuxx64.efi.stub");
|
||||||
|
|
||||||
|
let initrd_path = toplevel.join("initrd");
|
||||||
|
let kernel_path = toplevel.join("kernel");
|
||||||
|
let systemd_path = toplevel.join("systemd");
|
||||||
|
let os_release_path = toplevel.join("os-release");
|
||||||
|
|
||||||
|
// To simplify the test setup, we use the systemd stub for all PE binaries used by lanzatool.
|
||||||
|
// Lanzatool doesn't care whether its actually a kernel or initrd but only whether it can
|
||||||
|
// manipulate the PE binary with objcopy and/or sign it with sbsigntool. For testing lanzatool
|
||||||
|
// in isolation this should suffice.
|
||||||
|
fs::copy(&test_systemd_stub, initrd_path)?;
|
||||||
|
fs::copy(&test_systemd_stub, kernel_path)?;
|
||||||
|
symlink(&test_systemd, systemd_path)?;
|
||||||
|
setup_os_release(&os_release_path).context("Failed to setup os-release")?;
|
||||||
|
|
||||||
|
Ok(toplevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_string(length: usize) -> String {
|
||||||
|
thread_rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(length)
|
||||||
|
.map(char::from)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_os_release(path: &Path) -> Result<()> {
|
||||||
|
let content = r#"
|
||||||
|
ID=lanzaos
|
||||||
|
NAME=LanzaOS
|
||||||
|
PRETTY_NAME="LanzaOS 23.05 (Goat)"
|
||||||
|
VERSION="23.05 (Goat)"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut file = fs::File::create(path)?;
|
||||||
|
file.write_all(content.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the `lanzaboote install` command.
|
||||||
|
pub fn lanzaboote_install(
|
||||||
|
config_limit: u64,
|
||||||
|
esp_mountpoint: &Path,
|
||||||
|
generation_links: impl IntoIterator<Item = impl AsRef<OsStr>>,
|
||||||
|
) -> Result<Output> {
|
||||||
|
// To simplify the test setup, we use the systemd stub here instead of the lanzaboote stub. See
|
||||||
|
// the comment in setup_toplevel for details.
|
||||||
|
let test_systemd = systemd_location_from_env()?;
|
||||||
|
let test_systemd_stub = format!("{test_systemd}/lib/systemd/boot/efi/linuxx64.efi.stub");
|
||||||
|
|
||||||
|
let mut cmd = Command::cargo_bin("lanzatool")?;
|
||||||
|
let output = cmd
|
||||||
|
.env("LANZABOOTE_STUB", test_systemd_stub)
|
||||||
|
.arg("install")
|
||||||
|
.arg("--public-key")
|
||||||
|
.arg("tests/fixtures/uefi-keys/db.pem")
|
||||||
|
.arg("--private-key")
|
||||||
|
.arg("tests/fixtures/uefi-keys/db.key")
|
||||||
|
.arg("--configuration-limit")
|
||||||
|
.arg(config_limit.to_string())
|
||||||
|
.arg(esp_mountpoint)
|
||||||
|
.args(generation_links)
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
// Print debugging output.
|
||||||
|
// This is a weird hack to make cargo test capture the output.
|
||||||
|
// See https://github.com/rust-lang/rust/issues/12309
|
||||||
|
print!("{}", String::from_utf8(output.stdout.clone())?);
|
||||||
|
print!("{}", String::from_utf8(output.stderr.clone())?);
|
||||||
|
|
||||||
|
// Also walk the entire ESP mountpoint and print each path for debugging
|
||||||
|
for entry in walkdir::WalkDir::new(esp_mountpoint) {
|
||||||
|
println!("{}", entry?.path().display());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read location of systemd installation from an environment variable.
|
||||||
|
fn systemd_location_from_env() -> Result<String> {
|
||||||
|
let error_msg = "TEST_SYSTEMD environment variable is not set. TEST_SYSTEMD has to point to a systemd installation.
|
||||||
|
On a system with Nix installed, you can set it with: export TEST_SYSTEMD=$(nix-build '<nixpkgs>' -A systemd)";
|
||||||
|
std::env::var("TEST_SYSTEMD").context(error_msg)
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
3cEKjh9NywA7eLWDxRxEf/vq3vdWeFY=
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,29 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE+TCCAuGgAwIBAgIRAPmrvtqrYB4tl9V5HFxxLYowDQYJKoZIhvcNAQELBQAw
|
||||||
|
LjEVMBMGA1UEBhMMRGF0YWJhc2UgS2V5MRUwEwYDVQQDEwxEYXRhYmFzZSBLZXkw
|
||||||
|
HhcNMjIxMTIzMTI1NjU1WhcNMjcxMTIzMTI1NjU1WjAuMRUwEwYDVQQGEwxEYXRh
|
||||||
|
YmFzZSBLZXkxFTATBgNVBAMTDERhdGFiYXNlIEtleTCCAiIwDQYJKoZIhvcNAQEB
|
||||||
|
BQADggIPADCCAgoCggIBAK4tln0LCpqCkQeKqN1ddrxffWH+hNNwA3re40TRWTUp
|
||||||
|
3lowTMTOGukkfVpyqPpx8YyxEuxWL5r0xTxxfMpmGOz2tS5/KkzAKVIFnkf1LXWY
|
||||||
|
kMXS87ZYFP7qlt5T+Af9clzjgIVtjkhJAnF/gmJDzqkqsY+MWrRKympwSX8K957F
|
||||||
|
vkuNIreohHbWb6P4C/7sgtYBEWGALOO15nIoeGZT4cGnp40Mcz/6xaAnAfBMOG0q
|
||||||
|
A/bIxVllrkhIcc3BhShRhmVL8XZHXxcS11c4hUmzAtdNKrhl2l3A3AsdJbuVsN9q
|
||||||
|
AMMZ1+9BkQT/0q6z9coOib+8aA790WKFF+bbVDRgr6gaMK12MqGa3UkKFf1pCylH
|
||||||
|
xLNaPtxPFU11g1IK0z47OTxTJ93jH0x3g1uylc8zRs8Kbmry2PSwEQ3CEnoKDD4y
|
||||||
|
ito3ffhh3pxySUL7eHNFCPgVy6BUavHm9DKMfyB00iAGf5u9qSSGzfy4fcTXMrt2
|
||||||
|
sbW/3YfOXLKtf0OyUTx5TZZavcqGU10BLGK1pG3uvdn12tf8KGUXoxx8C/0NWtQS
|
||||||
|
KVkdItqjvKNPx1UgpLHZ2NttfGC0TP4vpphpj309m+A7/ItS8Uou5dnsnfcE6Kie
|
||||||
|
y2jCd+mZHEvpC13F5O6eIrOTKiXu4h6etyh6qYX+wqFG8ew8yvJ2oVhw4PkZw01z
|
||||||
|
AgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAbh22
|
||||||
|
GBG/orDJrOClvt8qABL60ojnilLh4J5BwGDwBcXE/wETvXb+o3GsjTqqHdXYCQx0
|
||||||
|
e3SpR/xQCfKzuaE4ysaszKFcNbhaezLmo/pzD2v0UA6Jr56re/jkYMtLQL4+LAUP
|
||||||
|
kCH66vc6qETQwtAzV3CYQ5TirwLLD6z3luPhm2FzyXTU9eydbsRp7FuSKq8mVXKP
|
||||||
|
cskrVJSjWD7fOsy/sZq3S+xmA3w1C8XRAgz+xlfIQkuu0mAa3jJ3JGjDyu1lAZ6N
|
||||||
|
oUc897npQ2tpYyGRQ/zOmKSE9oUlcEqXLPk2nDgenhG0oAdM/jJ54rnuR2x3zhBx
|
||||||
|
gyPEvWVzjzX8E//9Kc6VocisnI/tckibUPHlMJFHFBXiU8m6vO28cCRl4tRsH/K9
|
||||||
|
jOnm+ztraiXI7mqBfTchR7Ga0EmnWFf38y3/YkrbVYbjK6o/+/T72Sje0MfCAvTl
|
||||||
|
KLOtvaQWQm36KP/2PH3DZ/k/v8+U76oeUBoI/DDH8E4rR1cUORvUYH8MZyPIKM4e
|
||||||
|
GN3+/zjr5gw70iLC/p8C+8rCzxwoernIMcFlL+SaeybnzXvH3VqO/XAFA/lxMu4V
|
||||||
|
K7Gzrtr5n+FiSTXytdIu18o6Av65XN5hzwQvVeWIx/E5Jry/JlSQgho8CnqLK8g5
|
||||||
|
USdRgvRJN7xJM3szP93f7aNgIYSys2cCxtZxunU=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,38 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keep_only_configured_number_of_generations() -> Result<()> {
|
||||||
|
let esp_mountpoint = tempdir()?;
|
||||||
|
let tmpdir = tempdir()?;
|
||||||
|
let profiles = tempdir()?;
|
||||||
|
let generation_links: Vec<PathBuf> = [1, 2, 3]
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| {
|
||||||
|
common::setup_generation_link(tmpdir.path(), profiles.path(), v)
|
||||||
|
.expect("Failed to setup generation link")
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let stub_count = || count_files(&esp_mountpoint.path().join("EFI/Linux")).unwrap();
|
||||||
|
|
||||||
|
// Install all 3 generations.
|
||||||
|
let output0 = common::lanzaboote_install(0, esp_mountpoint.path(), generation_links.clone())?;
|
||||||
|
assert!(output0.status.success());
|
||||||
|
assert_eq!(stub_count(), 3);
|
||||||
|
|
||||||
|
// Call `lanzatool install` again with a config limit of 2 and assert that one is deleted.
|
||||||
|
let output1 = common::lanzaboote_install(2, esp_mountpoint.path(), generation_links)?;
|
||||||
|
assert!(output1.status.success());
|
||||||
|
assert_eq!(stub_count(), 2);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_files(path: &Path) -> Result<usize> {
|
||||||
|
Ok(fs::read_dir(path)?.count())
|
||||||
|
}
|
Loading…
Reference in New Issue