Unverified Commit 4ce7d0ad authored by Aleksana's avatar Aleksana Committed by GitHub
Browse files

angrr: 0.1.5 -> 0.2.0 (#471312)

parents 07be023e 62ea5b9a
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -55,6 +55,8 @@ of pulling the upstream container image from Docker Hub. If you want the old beh

- The Bash implementation of the `nixos-rebuild` program is removed. All switchable systems now use the Python rewrite. Any prior usage of `system.rebuild.enableNg` must now be removed. If you have any outstanding issues with the new implementation, please open an issue on GitHub.

- `services.angrr` now uses TOML for configuration. Define policies with `services.angrr.settings` (generate TOML file) or point to a file using `services.angrr.configFile`. The legacy options `services.angrr.period`, `services.angrr.ownedOnly`, and `services.angrr.removeRoot` have been removed. See `man 5 angrr` and the description of `services.angrr.settings` options for examples and details.

- `services.pingvin-share` has been removed as the `pingvin-share.backend` package was broken and the project was archived upstream.

## Other Notable Changes {#sec-release-26.05-notable-changes}
+247 −22
Original line number Diff line number Diff line
@@ -8,36 +8,242 @@
let
  cfg = config.services.angrr;
  direnvCfg = config.programs.direnv.angrr;
in
{
  meta.maintainers = pkgs.angrr.meta.maintainers;
  toml = pkgs.formats.toml { };
  exampleSettings = {
    temporary-root-policies = {
      direnv = {
        path-regex = "/\\.direnv/";
        period = "14d";
      };
      result = {
        path-regex = "/result[^/]*$";
        period = "3d";
      };
    };
    profile-policies = {
      system = {
        profile-paths = [ "/nix/var/nix/profiles/system" ];
        keep-since = "14d";
        keep-latest-n = 5;
        keep-booted-system = true;
        keep-current-system = true;
      };
      user = {
        enable = false;
        profile-paths = [
          "~/.local/state/nix/profiles/profile"
          "/nix/var/nix/profiles/per-user/root/profile"
        ];
        keep-since = "1d";
        keep-latest-n = 1;
        keep-booted-system = false;
        keep-current-system = false;
      };
    };
  };
  settingsOptions = {
    freeformType = toml.type;
    options = {
    services.angrr = {
      enable = lib.mkEnableOption "angrr";
      package = lib.mkPackageOption pkgs "angrr" { };
      period = lib.mkOption {
      owned-only = lib.mkOption {
        type =
          with lib.types;
          enum [
            "auto"
            "true"
            "false"
          ];
        default = "auto";
        description = ''
          Only monitors owned symbolic link target of GC roots.

          - "auto": behaves like true for normal users, false for root.
          - "true": only monitor GC roots owned by the current user.
          - "false": monitor all GC roots.
        '';
      };
      temporary-root-policies = lib.mkOption {
        type = with lib.types; attrsOf (submodule temporaryRootPolicyOptions);
        default = { };
        description = ''
          Policies for temporary GC roots(e.g. result and direnv).
        '';
      };
      profile-policies = lib.mkOption {
        type = with lib.types; attrsOf (submodule profilePolicyOptions);
        default = { };
        description = ''
          Profile GC root policies.
        '';
      };
      touch = {
        project-globs = lib.mkOption {
          type = with lib.types; listOf str;
          default = [
            "!.git"
          ];
          description = ''
            List of glob patterns to include or exclude files when touching GC roots.

            Only applied when `angrr touch` is invoked with the `--project` flag.
            Patterns use an inverted gitignore-style semantics.
            See <https://docs.rs/ignore/latest/ignore/overrides/struct.OverrideBuilder.html#method.add>.
          '';
        };
      };
    };
  };
  commonPolicyOptions = {
    options = {
      enable = lib.mkEnableOption "this angrr policy" // {
        default = true;
        example = false;
      };
    };
  };
  temporaryRootPolicyOptions = {
    freeformType = toml.type;
    imports = [ commonPolicyOptions ];
    options = {
      path-regex = lib.mkOption {
        type = lib.types.str;
        default = "7d";
        example = "2weeks";
        description = ''
          The retention period of auto GC roots.
          Regex pattern to match the GC root path.
        '';
      };
      period = lib.mkOption {
        type = with lib.types; nullOr str;
        default = null;
        description = ''
          Retention period for the GC roots matched by this policy.
        '';
      };
      priority = lib.mkOption {
        type = lib.types.int;
        default = 100;
        description = ''
          Priority of this policy.

          Lower number means higher priority, if multiple policies monitor the
          same path, the one with higher priority will be applied.
        '';
      };
      filter = lib.mkOption {
        type = with lib.types; nullOr (submodule filterOptions);
        default = null;
        description = ''
          External filter program to further filter GC roots matched by this policy.
        '';
      };
      ignore-prefixes = lib.mkOption {
        type = with lib.types; nullOr (listOf str);
        default = null;
        description = ''
          List of path prefixes to ignore.

          If null is specified, angrr builtin settings will be used.
        '';
      };
      ignore-prefixes-in-home = lib.mkOption {
        type = with lib.types; nullOr (listOf str);
        default = null;
        description = ''
          Path prefixes to ignore under home directory.

          If null is specified, angrr builtin settings will be used.
        '';
      };
      removeRoot = lib.mkOption {
    };
  };
  profilePolicyOptions = {
    freeformType = toml.type;
    imports = [ commonPolicyOptions ];
    options = {
      profile-paths = lib.mkOption {
        type = with lib.types; listOf str;
        description = ''
          Paths to the Nix profile.

          When angrr runs in owned-only mode, and the option begins with `~`,
          it will be expanded to the home directory of the current user.

          When angrr does not run in owned-only mode, and the option begins with `~`,
          it will be expanded to the home of all users discovered respectively.
        '';
      };
      keep-since = lib.mkOption {
        type = with lib.types; nullOr str;
        default = null;
        description = ''
          Retention period for the GC roots in this profile.
        '';
      };
      keep-latest-n = lib.mkOption {
        type = with lib.types; nullOr int;
        default = null;
        description = ''
          Keep the latest N GC roots in this profile.
        '';
      };
      keep-current-system = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Whether to pass the `--remove-root` option to angrr.
          Whether to keep the current system generation. Only useful for system profiles.
        '';
      };
      ownedOnly = lib.mkOption {
      keep-booted-system = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = ''
          Control the `--remove-root=<true|false>` option of angrr.
          Whether to keep the last booted system generation. Only useful for system profiles.
        '';
      };
    };
  };
  filterOptions = {
    freeformType = toml.type;
    options = {
      program = lib.mkOption {
        type = lib.types.str;
        description = ''
          Path to the external filter program.
        '';
        apply = b: if b then "true" else "false";
      };
      arguments = lib.mkOption {
        type = with lib.types; listOf str;
        default = [ ];
        description = ''
          Extra command-line arguments pass to the external filter program.
        '';
      };
    };
  };

  # toml.generate does not support null values, we need to filter them out first
  filteredSettings = lib.filterAttrsRecursive (name: value: value != null) cfg.settings;
  originalConfigFile = toml.generate "angrr.toml" filteredSettings;
  validatedConfigFile = pkgs.runCommand "angrr-config.toml" { } ''
    ${lib.getExe cfg.package} validate --config "${originalConfigFile}" > $out
  '';

  configFileMigrationMsg = ''
    This option has been removed since angrr 0.2.0.
    Please use `services.angrr.settings` to configure retention policies through configuration file.

    See <https://github.com/linyinfeng/angrr/tree/main?tab=readme-ov-file#nixos-module-usage> for a configuration example.
  '';
in
{
  meta.maintainers = pkgs.angrr.meta.maintainers;
  imports = [
    (lib.mkRemovedOptionModule [ "services" "angrr" "period" ] configFileMigrationMsg)
    (lib.mkRemovedOptionModule [ "services" "angrr" "removeRoot" ] configFileMigrationMsg)
    (lib.mkRemovedOptionModule [ "services" "angrr" "ownedOnly" ] configFileMigrationMsg)
  ];
  options = {
    services.angrr = {
      enable = lib.mkEnableOption "angrr";
      package = lib.mkPackageOption pkgs "angrr" { };
      logLevel = lib.mkOption {
        type =
          with lib.types;
@@ -61,10 +267,28 @@ in
          Extra command-line arguments pass to angrr.
        '';
      };
      settings = lib.mkOption {
        type = lib.types.submodule settingsOptions;
        example = exampleSettings;
        description = ''
          Global configuration for angrr in TOML format.
        '';
      };
      configFile = lib.mkOption {
        type = with lib.types; nullOr path;
        default = validatedConfigFile;
        defaultText = "TOML file generated from {option}`services.angrr.settings`";
        description = ''
          Path to the angrr configuration file in TOML format.

          If not set, the configuration generated from {option}`services.angrr.settings` will be used.
          If specified, {option}`services.angrr.settings` will be ignored.
        '';
      };
      enableNixGcIntegration = lib.mkOption {
        type = lib.types.bool;
        description = ''
          Whether to enable nix-gc.service integration
          Whether to enable nix-gc.service integration.
        '';
      };
      timer = {
@@ -107,16 +331,17 @@ in
      }

      {
        environment.etc."angrr/config.toml".source = cfg.configFile;

        systemd.services.angrr = {
          description = "Auto Nix GC Roots Retention";
          script = ''
            ${lib.getExe cfg.package} run \
              --log-level "${cfg.logLevel}" \
              --period "${cfg.period}" \
              ${lib.optionalString cfg.removeRoot "--remove-root"} \
              --owned-only="${cfg.ownedOnly}" \
              --no-prompt ${lib.escapeShellArgs cfg.extraArgs}
              --no-prompt \
              ${lib.escapeShellArgs cfg.extraArgs}
          '';
          environment.ANGRR_LOG_STYLE = "systemd";
          serviceConfig = {
            Type = "oneshot";
          };
@@ -144,7 +369,7 @@ in
      (lib.mkIf (config.programs.direnv.enable && direnvCfg.enable) {
        environment.etc."direnv/lib/angrr.sh".source = "${cfg.package}/share/direnv/lib/angrr.sh";
        programs.direnv.direnvrcExtra = lib.mkIf direnvCfg.autoUse ''
          use angrr
          _angrr_auto_use "$@"
        '';
      })
    ]
+162 −10
Original line number Diff line number Diff line
{ ... }:
{ pkgs, ... }:
let
  drvForTest =
    name:
    pkgs.runCommand "angrr-test-${name}" { } ''
      mkdir --parents "$out"
      echo "${name}" >"$out/${name}"
    '';
in
{
  name = "angrr";
  nodes = {
    machine = {
      services.angrr = {
        enable = true;
        settings = {
          temporary-root-policies = {
            result = {
              path-regex = "/result[^/]*$";
              period = "7d";
            };
            direnv = {
              path-regex = "/\\.direnv/";
              period = "14d";
            };
          };
          profile-policies = {
            system = {
              profile-paths = [ "/nix/var/nix/profiles/system" ];
              keep-since = "7d"; # do not keep based on time
              keep-latest-n = 2; # keep latest
              keep-current-system = true;
              keep-booted-system = true;
            };
            user = {
              profile-paths = [
                "~/.local/state/nix/profiles/profile"
                "/nix/var/nix/profiles/per-user/root/profile"
              ];
              # keep-since = "0d"; # do not keep based on time
              keep-latest-n = 2;
            };
          };
          touch = {
            project-globs = [
              "!result-glob-ignored"
            ];
          };
        };
      };
      # `angrr.service` integrates to `nix-gc.service` by default
      nix.gc.automatic = true;

@@ -19,7 +60,23 @@
      # Test direnv integration
      programs.direnv.enable = true;
      # Verbose logging for angrr in direnv
      environment.variables.ANGRR_DIRENV_LOG = "angrr=debug";
      environment.variables.ANGRR_DIRENV_LOG = "debug";

      # Add some store paths to machine for test
      environment.etc."drvs-for-test".text = ''
        ${drvForTest "drv1"}
        ${drvForTest "drv2"}
        ${drvForTest "drv3"}
        ${drvForTest "drv4"}
        ${drvForTest "drv5"}
        ${drvForTest "drv6"}
        ${drvForTest "drv7"}
        ${drvForTest "drv8"}
        ${drvForTest "fake-booted-system"}
      '';

      # Unit start limit workaround
      systemd.services.angrr.unitConfig.StartLimitBurst = 10;
    };
  };

@@ -51,7 +108,7 @@
    machine.succeed("touch /tmp/result-root-auto-gc-root-2 --no-dereference")
    machine.succeed("touch /tmp/result-user-auto-gc-root-2 --no-dereference")

    machine.systemctl("start nix-gc.service")
    machine.systemctl("start angrr.service")
    # Only GC roots `-1` are removed
    machine.succeed("test ! -e /tmp/result-root-auto-gc-root-1")
    machine.succeed("readlink  /tmp/result-root-auto-gc-root-2")
@@ -60,7 +117,7 @@

    # Change time again
    machine.succeed("date -s '8 days'")
    machine.systemctl("start nix-gc.service")
    machine.systemctl("start angrr.service")
    # All auto GC roots are removed
    machine.succeed("test ! -e /tmp/result-root-auto-gc-root-2")
    machine.succeed("test ! -e /tmp/result-user-auto-gc-root-2")
@@ -69,20 +126,115 @@
    machine.succeed("mkdir /tmp/test-direnv")
    machine.succeed("echo >/tmp/test-direnv/.envrc") # Simply create an empty .envrc
    machine.succeed("nix build /run/current-system --out-link /tmp/test-direnv/.direnv/gc-root")
    machine.succeed("nix build /run/current-system --out-link /tmp/test-direnv/result")
    machine.succeed("cd /tmp/test-direnv; direnv allow; direnv exec . true")

    # The root will be removed if we does not use the direnv recently
    machine.succeed("date -s '8 days'")
    machine.systemctl("start nix-gc.service")
    machine.succeed("date -s '15 days'")
    machine.systemctl("start angrr.service")
    machine.succeed("test ! -e /tmp/test-direnv/.direnv/gc-root")
    machine.succeed("test ! -e /tmp/test-direnv/result")

    # Recreate the root
    machine.succeed("nix build /run/current-system --out-link /tmp/test-direnv/.direnv/gc-root")
    machine.succeed("nix build /run/current-system --out-link /tmp/test-direnv/result")
    machine.succeed("nix build /run/current-system --out-link /tmp/test-direnv/result-glob-ignored")
    machine.succeed("nix build /run/current-system --out-link /tmp/test-outside-direnv/result")

    # The root will not be remove if we use the direnv recently
    machine.succeed("date -s '8 days'")
    machine.succeed("cd /tmp/test-direnv; direnv exec . true")
    machine.systemctl("start nix-gc.service")
    machine.succeed("date -s '15 days'")
    # test the case that $PWD is different from project root
    machine.succeed("cd /tmp; direnv exec /tmp/test-direnv true")
    machine.systemctl("start angrr.service")
    machine.succeed("readlink  /tmp/test-direnv/.direnv/gc-root")
    machine.succeed("readlink  /tmp/test-direnv/result")
    machine.succeed("test ! -e /tmp/test-direnv/result-glob-ignored")
    machine.succeed("test ! -e /tmp/test-outside-direnv/result")

    # System profile policy test
    # Create a profile for test
    machine.succeed("mkdir /tmp/profile-test")
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set ${drvForTest "drv1"}") # generation 1
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set ${drvForTest "drv2"}") # generation 2
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set ${drvForTest "drv3"}") # generation 3
    machine.succeed("ln --symbolic --force --no-dereference ${drvForTest "fake-booted-system"} /run/booted-system")
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set /run/booted-system")   # generation 4
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set /run/current-system")  # generation 5
    machine.succeed("date -s '8 days'")
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set ${drvForTest "drv4"}") # generation 6
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set ${drvForTest "drv5"}") # generation 7
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set ${drvForTest "drv6"}") # generation 8
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set ${drvForTest "drv7"}") # generation 9
    machine.succeed("nix-env --profile /nix/var/nix/profiles/system --set ${drvForTest "drv8"}") # generation 10
    # Rollback to generation 2 to simulate current system
    for _ in range(0, 10 - 2):
      machine.succeed("nix-env --rollback --profile /nix/var/nix/profiles/system")

    # Run policy
    machine.systemctl("start angrr.service")

    # Test
    machine.succeed("sh -c 'test $(readlink /nix/var/nix/profiles/system) = system-2-link'")
    machine.succeed("test ! -e /nix/var/nix/profiles/system-1-link")
    machine.succeed("readlink  /nix/var/nix/profiles/system-2-link")  # Keep since it is current generation
    machine.succeed("test ! -e /nix/var/nix/profiles/system-3-link")
    machine.succeed("readlink  /nix/var/nix/profiles/system-4-link")  # Keep by keep-booted-system
    machine.succeed("readlink  /nix/var/nix/profiles/system-5-link")  # Keep by keep-current-system
    machine.succeed("readlink  /nix/var/nix/profiles/system-6-link")  # Keep by keep-since
    machine.succeed("readlink  /nix/var/nix/profiles/system-7-link")  # Keep by keep-since
    machine.succeed("readlink  /nix/var/nix/profiles/system-8-link")  # Keep by keep-since
    machine.succeed("readlink  /nix/var/nix/profiles/system-9-link")  # Keep by keep-latest-n
    machine.succeed("readlink  /nix/var/nix/profiles/system-10-link") # Keep by keep-latest-n

    # User profile policy test 1
    # Normal user
    machine.succeed("su normal --command 'nix profile add ${drvForTest "drv1"}'")
    machine.succeed("su normal --command 'nix profile add ${drvForTest "drv2"}'")
    machine.succeed("su normal --command 'nix profile add ${drvForTest "drv3"}'")
    # Root user
    machine.succeed("nix profile add ${drvForTest "drv1"}")
    machine.succeed("nix profile add ${drvForTest "drv2"}")
    machine.succeed("nix profile add ${drvForTest "drv3"}")

    # Run policy
    machine.systemctl("start angrr.service")

    # Test
    machine.succeed("sh -c 'test $(readlink ~normal/.local/state/nix/profiles/profile) = profile-3-link'")
    machine.succeed("test ! -e ~normal/.local/state/nix/profiles/profile-1-link")
    machine.succeed("readlink  ~normal/.local/state/nix/profiles/profile-2-link") # Keep by keep-latest-n
    machine.succeed("readlink  ~normal/.local/state/nix/profiles/profile-3-link") # Keep since it is current generation
    machine.succeed("sh -c 'test $(readlink /nix/var/nix/profiles/per-user/root/profile) = profile-3-link'")
    machine.succeed("test ! -e /nix/var/nix/profiles/per-user/root/profile-1-link")
    machine.succeed("readlink  /nix/var/nix/profiles/per-user/root/profile-2-link") # Keep by keep-latest-n
    machine.succeed("readlink  /nix/var/nix/profiles/per-user/root/profile-3-link") # Keep since it is current generation

    # User profile policy test 2
    # Create GC roots again
    machine.succeed("su normal --command 'nix profile add ${drvForTest "drv1"}'")
    machine.succeed("su normal --command 'nix profile add ${drvForTest "drv2"}'")
    machine.succeed("su normal --command 'nix profile add ${drvForTest "drv3"}'")
    machine.succeed("nix profile add ${drvForTest "drv1"}")
    machine.succeed("nix profile add ${drvForTest "drv2"}")
    machine.succeed("nix profile add ${drvForTest "drv3"}")

    # Run policy in owned-only mode as normal user
    machine.succeed("su normal --command 'angrr run --no-prompt'")

    # Test
    machine.succeed("sh -c 'test $(readlink ~normal/.local/state/nix/profiles/profile) = profile-6-link'")
    machine.succeed("test ! -e ~normal/.local/state/nix/profiles/profile-1-link")
    machine.succeed("test ! -e ~normal/.local/state/nix/profiles/profile-2-link")
    machine.succeed("test ! -e ~normal/.local/state/nix/profiles/profile-3-link")
    machine.succeed("test ! -e ~normal/.local/state/nix/profiles/profile-4-link")
    machine.succeed("readlink  ~normal/.local/state/nix/profiles/profile-5-link") # Keep by keep-latest-n
    machine.succeed("readlink  ~normal/.local/state/nix/profiles/profile-6-link") # Keep since it is current generation
    machine.succeed("sh -c 'test $(readlink /nix/var/nix/profiles/per-user/root/profile) = profile-6-link'")
    machine.succeed("test ! -e /nix/var/nix/profiles/per-user/root/profile-1-link")
    machine.succeed("readlink  /nix/var/nix/profiles/per-user/root/profile-2-link")
    machine.succeed("readlink  /nix/var/nix/profiles/per-user/root/profile-3-link")
    machine.succeed("readlink  /nix/var/nix/profiles/per-user/root/profile-4-link") # Not monitored
    machine.succeed("readlink  /nix/var/nix/profiles/per-user/root/profile-5-link") # Not monitored
    machine.succeed("readlink  /nix/var/nix/profiles/per-user/root/profile-6-link") # Not monitored
  '';
}
+9 −6
Original line number Diff line number Diff line
{
  lib,
  stdenv,
  rustPlatform,
  fetchFromGitHub,
  installShellFiles,
  nixosTests,
  testers,
  nix-update-script,
  go-md2man,
}:

rustPlatform.buildRustPackage (finalAttrs: {
  pname = "angrr";
  version = "0.1.5";
  version = "0.2.0";

  src = fetchFromGitHub {
    owner = "linyinfeng";
    repo = "angrr";
    tag = "v${finalAttrs.version}";
    hash = "sha256-PT3oCNPRvEroyVNiICeO0hSHDzKUC6KcP9HnIw1kMQE=";
    hash = "sha256-Z+B0MO5ZoPJveO571mlzNVedBEac7P4RE7Cq8e/9bJk=";
  };

  cargoHash = "sha256-lDOH4Ceap69fX6VWbgQoQfmYWZI+jPE0LJiXmqrTRn8=";
  cargoHash = "sha256-j36vyfIP63Qmd55vaVb9buqrCItXwFalelzU8BlKm9s=";

  buildAndTestSubdir = "angrr";

  nativeBuildInputs = [ installShellFiles ];
  nativeBuildInputs = [
    go-md2man
    installShellFiles
  ];
  postBuild = ''
    mkdir --parents build/{man-pages,shell-completions}
    cargo xtask man-pages --out build/man-pages
@@ -50,7 +53,7 @@ rustPlatform.buildRustPackage (finalAttrs: {
  };

  meta = {
    description = "Temporary GC Roots Cleaner";
    description = "Auto Nix GC Root Retention";
    homepage = "https://github.com/linyinfeng/angrr";
    license = [ lib.licenses.mit ];
    maintainers = with lib.maintainers; [ yinfeng ];