Merge pull request #243995 from nikstur/systemd-sysupdate

This commit is contained in:
Ryan Lahfa 2023-07-26 21:11:54 +02:00 committed by GitHub
commit 96f7ceb7b9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 233 additions and 21 deletions

View file

@ -34,6 +34,7 @@
- [ebusd](https://ebusd.eu), a daemon for handling communication with eBUS devices connected to a 2-wire bus system (“energy bus” used by numerous heating systems). Available as [services.ebusd](#opt-services.ebusd.enable).
- [systemd-sysupdate](https://www.freedesktop.org/software/systemd/man/systemd-sysupdate.html), atomically updates the host OS, container images, portable service images or other sources. Available as [systemd.sysupdate](opt-systemd.sysupdate).
## Backward Incompatibilities {#sec-release-23.11-incompatibilities}

View file

@ -1398,6 +1398,7 @@
./system/boot/systemd/oomd.nix
./system/boot/systemd/repart.nix
./system/boot/systemd/shutdown.nix
./system/boot/systemd/sysupdate.nix
./system/boot/systemd/tmpfiles.nix
./system/boot/systemd/user.nix
./system/boot/systemd/userdbd.nix

View file

@ -0,0 +1,142 @@
{ config, lib, pkgs, utils, ... }:
let
cfg = config.systemd.sysupdate;
format = pkgs.formats.ini { };
listOfDefinitions = lib.mapAttrsToList
(name: format.generate "${name}.conf")
(lib.filterAttrs (k: _: !(lib.hasPrefix "_" k)) cfg.transfers);
definitionsDirectory = pkgs.runCommand "sysupdate.d" { } ''
mkdir -p $out
${(lib.concatStringsSep "\n"
(map (pkg: "cp ${pkg} $out/${pkg.name}") listOfDefinitions)
)}
'';
in
{
options.systemd.sysupdate = {
enable = lib.mkEnableOption (lib.mdDoc "systemd-sysupdate") // {
description = lib.mdDoc ''
Atomically update the host OS, container images, portable service
images or other sources.
If enabled, updates are triggered in regular intervals via a
`systemd.timer` unit.
Please see
<https://www.freedesktop.org/software/systemd/man/systemd-sysupdate.html>
for more details.
'';
};
timerConfig = utils.systemdUtils.unitOptions.timerOptions.options.timerConfig // {
default = { };
description = lib.mdDoc ''
The timer configuration for performing the update.
By default, the upstream configuration is used:
<https://github.com/systemd/systemd/blob/main/units/systemd-sysupdate.timer>
'';
};
reboot = {
enable = lib.mkEnableOption (lib.mdDoc "automatically rebooting after an update") // {
description = lib.mdDoc ''
Whether to automatically reboot after an update.
If set to `true`, the system will automatically reboot via a
`systemd.timer` unit but only after a new version was installed.
This uses a unit completely separate from the one performing the
update because it is typically advisable to download updates
regularly while the system is up, but delay reboots until the
appropriate time (i.e. typically at night).
Set this to `false` if you do not want to reboot after an update. This
is useful when you update a container image or another source where
rebooting is not necessary in order to finalize the update.
'';
};
timerConfig = utils.systemdUtils.unitOptions.timerOptions.options.timerConfig // {
default = { };
description = lib.mdDoc ''
The timer configuration for rebooting after an update.
By default, the upstream configuration is used:
<https://github.com/systemd/systemd/blob/main/units/systemd-sysupdate-reboot.timer>
'';
};
};
transfers = lib.mkOption {
type = with lib.types; attrsOf format.type;
default = { };
example = {
"10-uki.conf" = {
Transfer = {
ProtectVersion = "%A";
};
Source = {
Type = "url-file";
Path = "https://download.example.com/";
MatchPattern = "nixos_@v.efi.xz";
};
Target = {
Type = "regular-file";
Path = "/EFI/Linux";
PathRelativeTo = "boot";
MatchPattern = ''
nixos_@v+@l-@d.efi"; \
nixos_@v+@l.efi \
nixos_@v.efi
'';
Mode = "0444";
TriesLeft = 3;
TriesDone = 0;
InstancesMax = 2;
};
};
};
description = lib.mdDoc ''
Specify transfers as a set of the names of the transfer files as the
key and the configuration as its value. The configuration can use all
upstream options. See
<https://www.freedesktop.org/software/systemd/man/sysupdate.d.html>
for all available options.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.additionalUpstreamSystemUnits = [
"systemd-sysupdate.service"
"systemd-sysupdate.timer"
"systemd-sysupdate-reboot.service"
"systemd-sysupdate-reboot.timer"
];
systemd.timers = {
"systemd-sysupdate" = {
wantedBy = [ "timers.target" ];
timerConfig = cfg.timerConfig;
};
"systemd-sysupdate-reboot" = lib.mkIf cfg.reboot.enable {
wantedBy = [ "timers.target" ];
timerConfig = cfg.reboot.timerConfig;
};
};
environment.etc."sysupdate.d".source = definitionsDirectory;
};
meta.maintainers = with lib.maintainers; [ nikstur ];
}

View file

@ -772,6 +772,7 @@ in {
systemd-portabled = handleTest ./systemd-portabled.nix {};
systemd-repart = handleTest ./systemd-repart.nix {};
systemd-shutdown = handleTest ./systemd-shutdown.nix {};
systemd-sysupdate = runTest ./systemd-sysupdate.nix;
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {};
systemd-misc = handleTest ./systemd-misc.nix {};

View file

@ -0,0 +1,21 @@
{ pkgs, ... }:
pkgs.runCommand "gpg-keyring" { nativeBuildInputs = [ pkgs.gnupg ]; } ''
mkdir -p $out
export GNUPGHOME=$out
cat > foo <<EOF
%echo Generating a basic OpenPGP key
%no-protection
Key-Type: EdDSA
Key-Curve: ed25519
Name-Real: Bob Foobar
Name-Email: bob@foo.bar
Expire-Date: 0
# Do a commit here, so that we can later print "done"
%commit
%echo done
EOF
gpg --batch --generate-key foo
rm $out/S.gpg-agent $out/S.gpg-agent.*
gpg --export bob@foo.bar -a > $out/pubkey.gpg
''

View file

@ -1,26 +1,6 @@
import ./make-test-python.nix ({pkgs, lib, ...}:
let
gpgKeyring = (pkgs.runCommand "gpg-keyring" { buildInputs = [ pkgs.gnupg ]; } ''
mkdir -p $out
export GNUPGHOME=$out
cat > foo <<EOF
%echo Generating a basic OpenPGP key
%no-protection
Key-Type: DSA
Key-Length: 1024
Subkey-Type: ELG-E
Subkey-Length: 1024
Name-Real: Bob Foobar
Name-Email: bob@foo.bar
Expire-Date: 0
# Do a commit here, so that we can later print "done"
%commit
%echo done
EOF
gpg --batch --generate-key foo
rm $out/S.gpg-agent $out/S.gpg-agent.*
gpg --export bob@foo.bar -a > $out/pubkey.gpg
'');
gpgKeyring = import ./common/gpg-keyring.nix { inherit pkgs; };
nspawnImages = (pkgs.runCommand "localhost" { buildInputs = [ pkgs.coreutils pkgs.gnupg ]; } ''
mkdir -p $out

View file

@ -0,0 +1,66 @@
# Tests downloading a signed update aritfact from a server to a target machine.
# This test does not rely on the `systemd.timer` units provided by the
# `systemd-sysupdate` module but triggers the `systemd-sysupdate` service
# manually to make the test more robust.
{ lib, pkgs, ... }:
let
gpgKeyring = import ./common/gpg-keyring.nix { inherit pkgs; };
in
{
name = "systemd-sysupdate";
meta.maintainers = with lib.maintainers; [ nikstur ];
nodes = {
server = { pkgs, ... }: {
networking.firewall.enable = false;
services.nginx = {
enable = true;
virtualHosts."server" = {
root = pkgs.runCommand "sysupdate-artifacts" { buildInputs = [ pkgs.gnupg ]; } ''
mkdir -p $out
cd $out
echo "nixos" > nixos_1.efi
sha256sum nixos_1.efi > SHA256SUMS
export GNUPGHOME="$(mktemp -d)"
cp -R ${gpgKeyring}/* $GNUPGHOME
gpg --batch --sign --detach-sign --output SHA256SUMS.gpg SHA256SUMS
'';
};
};
};
target = {
systemd.sysupdate = {
enable = true;
transfers = {
"uki" = {
Source = {
Type = "url-file";
Path = "http://server/";
MatchPattern = "nixos_@v.efi";
};
Target = {
Path = "/boot/EFI/Linux";
MatchPattern = "nixos_@v.efi";
};
};
};
};
environment.etc."systemd/import-pubring.gpg".source = "${gpgKeyring}/pubkey.gpg";
};
};
testScript = ''
server.wait_for_unit("nginx.service")
target.succeed("systemctl start systemd-sysupdate")
assert "nixos" in target.wait_until_succeeds("cat /boot/EFI/Linux/nixos_1.efi", timeout=5)
'';
}