]> git.scottworley.com Git - auto-upgrade-with-pinch/blobdiff - modules/auto-upgrade.nix
Support multiple key files
[auto-upgrade-with-pinch] / modules / auto-upgrade.nix
index 1facabab99b0cfbc8263af05f61c9734b1e15f8b..e52720d8af0d1777da12f50be1148786c39d2052 100644 (file)
@@ -1,6 +1,52 @@
 { config, lib, pkgs, ... }:
 with lib;
-let cfg = config.system.autoUpgradeWithPinch;
+let
+  cfg = config.system.autoUpgradeWithPinch;
+  auto-upgrade-script = pkgs.writeShellScript "auto-upgrade" ''
+    ${pkgs.utillinux}/bin/flock /run/auto-upgrade-with-pinch ${
+      pkgs.writeShellScript "auto-upgrade-with-lock-held" ''
+        set -e
+
+        in_tmpdir() {
+          d=$(mktemp -d)
+          pushd "$d"
+          "$@"
+          popd
+          rm -r "$d"
+        }
+
+        as_user() {
+          ${
+            if cfg.userEnvironment.enable then ''
+              /run/wrappers/bin/sudo -u ${escapeShellArg cfg.userEnvironment.user} "$@"
+            '' else ''
+              :
+            ''
+          }
+        }
+
+        # Update channels
+        (
+          cd /etc/nixos
+          ${pkgs.git}/bin/git fetch
+          PATH="${pkgs.keyedgit cfg.keys}/bin:$PATH" ${pkgs.polite-merge}/bin/polite-merge --ff-only --verify-signatures
+          ${pkgs.pinch}/bin/pinch update channels
+        )
+
+        # Build
+        in_tmpdir ${config.system.build.nixos-rebuild}/bin/nixos-rebuild build
+        as_user nix-build --no-out-link '<nixpkgs>' -A ${
+          escapeShellArg cfg.userEnvironment.package
+        }
+
+        # Install
+        ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch
+        as_user nix-env -f '<nixpkgs>' -riA ${
+          escapeShellArg cfg.userEnvironment.package
+        }
+      ''
+    }
+  '';
 in {
   options = {
     system.autoUpgradeWithPinch = {
@@ -26,21 +72,81 @@ in {
         '';
       };
 
-      key = mkOption {
+      keys = mkOption {
         type = types.path;
         description = ''
-          GPG key that signs updates.  Updates are only merged if the commit
-          at the tip of the remote branch is signed with this key.
+          File containing GPG keys that sign updates.  Updates are only merged
+          if the commit at the tip of the remote branch is signed with one of
+          these keys.
         '';
       };
+
+      userEnvironment = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to update a user-environment as well.  This update is done
+            with nix-env -riA.  Note the -r!  I.e., ALL OTHER PACKAGES INSTALLED
+            WITH nix-env WILL BE DELETED!
+
+            This presumes that you have configured an "entire user environment"
+            package as shown in
+            https://nixos.wiki/wiki/FAQ#How_can_I_manage_software_with_nix-env_like_with_configuration.nix.3F
+
+            To check if you're set up for this, run "nix-env --query".  If it
+            only lists one package, you're good to go.
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          description = ''
+            The username of the user whose environment should be updated.
+          '';
+        };
+
+        package = mkOption {
+          type = types.str;
+          example = "nixos.userPackages";
+          description = ''
+            The name of the single package that is the user's entire environment.
+          '';
+        };
+
+      };
     };
   };
 
   config = lib.mkIf cfg.enable {
+
+    security.sudo.extraRules = lib.mkAfter [{
+      groups = [ "users" ];
+      commands = [{
+        command = "${auto-upgrade-script}";
+        options = [ "NOPASSWD" "NOSETENV" ];
+      }];
+    }];
+    # NOSETENV above still allows through ~17 vars, including PATH.  Block those
+    # as well:
+    security.sudo.extraConfig = ''
+      Defaults!${auto-upgrade-script} !env_check
+      Defaults!${auto-upgrade-script} !env_keep
+    '';
+
     nixpkgs.overlays = [
       (import ../overlays/keyedgit.nix)
       (import ../overlays/pinch.nix)
+      (import ../overlays/polite-merge.nix)
+      (self: super: {
+        auto-upgrade = super.writeShellScriptBin "auto-upgrade" ''
+          /run/wrappers/bin/sudo ${auto-upgrade-script}
+        '';
+      })
     ];
+
+    environment.systemPackages = [ pkgs.auto-upgrade ];
+
     systemd.services.nixos-upgrade = {
       description = "NixOS Upgrade";
       restartIfChanged = false;
@@ -58,22 +164,38 @@ in {
         gitMinimal
         gnutar
         gzip
-        pinch
         xz.bin
       ];
 
       script = ''
         set -e
-        (
-          cd /etc/nixos
-          ${pkgs.keyedgit cfg.key}/bin/git pull --ff-only --verify-signatures
-          pinch update channels
-        )
 
-        ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch --no-build-output
+        # Chill for awhile before applying updates.  If applying an update
+        # badly breaks things, we want a window in which an operator can
+        # intervene either to fix the problem or disable automatic updates.
+        sleep 2h
+
+        # Wait until outside business hours
+        now=$(date +%s)
+        day_of_week=$(date +%u)
+        business_start=$(date -d  8:00 +%s)
+        business_end=$(  date -d 17:00 +%s)
+        if (( day_of_week <= 5 && now > business_start && now < business_end ));then
+          delay=$((business_end - now))
+          echo "Waiting $delay seconds so we don't upgrade during business hours" >&2
+          sleep "$delay"
+        fi
+
+        ${auto-upgrade-script}
       '';
 
       startAt = cfg.dates;
     };
+
+    assertions = [{
+      assertion = cfg.userEnvironment.enable -> cfg.enable;
+      message =
+        "User environment upgrades cannot yet be enabled separately from system upgrades.";
+    }];
   };
 }