Loading nixos/modules/services/networking/harmonia.nix +255 −86 Original line number Diff line number Diff line Loading @@ -6,23 +6,51 @@ }: let cfg = config.services.harmonia; cacheCfg = cfg.cache; daemonCfg = cfg.daemon; format = pkgs.formats.toml { }; signKeyPaths = cfg.signKeyPaths ++ lib.optional (cfg.signKeyPath != null) cfg.signKeyPath; signKeyPaths = cacheCfg.signKeyPaths ++ (if cacheCfg.signKeyPath != null then [ cacheCfg.signKeyPath ] else [ ]); credentials = lib.imap0 (i: signKeyPath: { id = "sign-key-${toString i}"; path = signKeyPath; }) signKeyPaths; in { imports = [ # Renamed options for flat harmonia -> harmonia.cache (lib.mkRenamedOptionModule [ "services" "harmonia" "enable" ] [ "services" "harmonia" "cache" "enable" ] ) (lib.mkRenamedOptionModule [ "services" "harmonia" "signKeyPath" ] [ "services" "harmonia" "cache" "signKeyPath" ] ) (lib.mkRenamedOptionModule [ "services" "harmonia" "signKeyPaths" ] [ "services" "harmonia" "cache" "signKeyPaths" ] ) (lib.mkRenamedOptionModule [ "services" "harmonia" "settings" ] [ "services" "harmonia" "cache" "settings" ] ) # Note: package stays at the top level ]; options = { services.harmonia = { package = lib.mkPackageOption pkgs "harmonia" { }; cache = { enable = lib.mkEnableOption "Harmonia: Nix binary cache written in Rust"; signKeyPath = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "DEPRECATED: Use `services.harmonia.signKeyPaths` instead. Path to the signing key to use for signing the cache"; description = "DEPRECATED: Use `services.harmonia-dev.cache.signKeyPaths` instead. Path to the signing key to use for signing the cache"; }; signKeyPaths = lib.mkOption { Loading @@ -31,8 +59,6 @@ in description = "Paths to the signing keys to use for signing the cache"; }; package = lib.mkPackageOption pkgs "harmonia" { }; settings = lib.mkOption { inherit (format) type; default = { }; Loading @@ -42,12 +68,47 @@ in ''; }; }; daemon = { enable = lib.mkEnableOption "Harmonia daemon: Nix daemon protocol implementation"; socketPath = lib.mkOption { type = lib.types.str; default = "/run/harmonia-daemon/socket"; description = "Path where the daemon socket will be created"; }; storeDir = lib.mkOption { type = lib.types.str; default = "/nix/store"; description = "Path to the Nix store directory"; }; config = lib.mkIf cfg.enable { warnings = lib.optional ( cfg.signKeyPath != null ) "`services.harmonia.signKeyPath` is deprecated, use `services.harmonia.signKeyPaths` instead"; dbPath = lib.mkOption { type = lib.types.str; default = "/nix/var/nix/db/db.sqlite"; description = "Path to the Nix database"; }; logLevel = lib.mkOption { type = lib.types.str; default = "info"; description = "Log level for the daemon"; }; }; }; }; config = lib.mkMerge [ (lib.mkIf cacheCfg.enable { warnings = if cacheCfg.signKeyPath != null then [ "`services.harmonia.cache.signKeyPath` is deprecated, use `services.harmonia.cache.signKeyPaths` instead" ] else [ ]; nix.settings.extra-allowed-users = [ "harmonia" ]; users.users.harmonia = { isSystemUser = true; Loading @@ -55,15 +116,27 @@ in }; users.groups.harmonia = { }; services.harmonia.cache.settings = builtins.mapAttrs (_: v: lib.mkDefault v) ( { bind = "[::]:5000"; workers = 4; max_connection_rate = 256; priority = 50; } // lib.optionalAttrs daemonCfg.enable { daemon_socket = daemonCfg.socketPath; } ); systemd.services.harmonia = { description = "harmonia binary cache service"; requires = [ "nix-daemon.socket" ]; after = [ "network.target" ]; requires = if daemonCfg.enable then [ "harmonia-daemon.service" ] else [ "nix-daemon.socket" ]; after = [ "network.target" ] ++ lib.optional daemonCfg.enable "harmonia-daemon.service"; wantedBy = [ "multi-user.target" ]; environment = { CONFIG_FILE = format.generate "harmonia.toml" cfg.settings; CONFIG_FILE = format.generate "harmonia.toml" cacheCfg.settings; SIGN_KEY_PATHS = lib.strings.concatMapStringsSep " " ( credential: "%d/${credential.id}" ) credentials; Loading Loading @@ -113,5 +186,101 @@ in LimitNOFILE = 65536; }; }; }) (lib.mkIf daemonCfg.enable { systemd.services.harmonia-daemon = let daemonConfig = { socket_path = daemonCfg.socketPath; store_dir = daemonCfg.storeDir; db_path = daemonCfg.dbPath; log_level = daemonCfg.logLevel; }; daemonConfigFile = format.generate "harmonia-daemon.toml" daemonConfig; in { description = "Harmonia Nix daemon protocol server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; environment = { RUST_LOG = daemonCfg.logLevel; RUST_BACKTRACE = "1"; HARMONIA_DAEMON_CONFIG = daemonConfigFile; }; serviceConfig = { Type = "simple"; ExecStart = lib.getExe' cfg.package "harmonia-daemon"; Restart = "on-failure"; RestartSec = 5; # Socket will be created at runtime RuntimeDirectory = "harmonia-daemon"; # Run as root to access the Nix database # Note: The Nix database is owned by root and requires root access NoNewPrivileges = true; PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; # SQLite needs write access for WAL mode ReadWritePaths = [ (builtins.dirOf daemonCfg.dbPath) # Need write access for WAL and SHM files ]; ReadOnlyPaths = [ daemonCfg.storeDir ]; # System call filtering SystemCallFilter = [ "@system-service" "~@privileged" "@chown" # for sockets "~@resources" ]; SystemCallArchitectures = "native"; # Capabilities CapabilityBoundingSet = ""; # Device access DeviceAllow = [ "" ]; PrivateDevices = true; # Kernel protection ProtectKernelModules = true; ProtectKernelTunables = true; ProtectControlGroups = true; ProtectKernelLogs = true; ProtectHostname = true; ProtectClock = true; # Memory protection MemoryDenyWriteExecute = true; LockPersonality = true; # Process visibility ProcSubset = "pid"; ProtectProc = "invisible"; # Namespace restrictions RestrictNamespaces = true; PrivateMounts = true; # Network restrictions RestrictAddressFamilies = "AF_UNIX"; PrivateNetwork = false; # Resource limits LimitNOFILE = 65536; RestrictRealtime = true; # Misc restrictions UMask = "0077"; }; }; }) ]; } nixos/tests/harmonia.nix +2 −2 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ nodes = { harmonia = { services.harmonia = { services.harmonia.cache = { enable = true; signKeyPaths = [ (pkgs.writeText "cache-key" "cache.example.com-1:9FhO0w+7HjZrhvmzT1VlAZw4OSAlFGTgC24Seg3tmPl4gZBdwZClzTTHr9cVzJpwsRSYLTu7hEAQe3ljy92CWg==") Loading Loading @@ -37,7 +37,7 @@ harmonia.wait_for_unit("harmonia.service") client01.wait_until_succeeds("curl -f http://harmonia:5000/nix-cache-info | grep '${toString nodes.harmonia.services.harmonia.settings.priority}' >&2") client01.wait_until_succeeds("curl -f http://harmonia:5000/nix-cache-info | grep '${toString nodes.harmonia.services.harmonia.cache.settings.priority}' >&2") client01.succeed("curl -f http://harmonia:5000/version | grep '${nodes.harmonia.services.harmonia.package.version}' >&2") client01.succeed("cat /etc/nix/nix.conf >&2") Loading pkgs/by-name/ha/harmonia/package.nix +5 −4 Original line number Diff line number Diff line Loading @@ -10,16 +10,16 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "harmonia"; version = "2.1.0"; version = "3.0.0"; src = fetchFromGitHub { owner = "nix-community"; repo = "harmonia"; tag = "harmonia-v${finalAttrs.version}"; hash = "sha256-Ch7CBPwSKZxCmZwFunNCA8E74TcOWp9MLbhe3/glQ6w="; hash = "sha256-BovRI3p2KXwQ6RF49NqLc0uKP/Jk+yA8E0eqScaIP68="; }; cargoHash = "sha256-7HZoXNL7nf6NUNnh6gzXsZ2o4eeEQL7/KDdIcbh7/jM="; cargoHash = "sha256-X3A+gV32itmt0SqepioT64IGzHfrCdLsQjF6EDwCTbo="; doCheck = false; Loading @@ -41,8 +41,9 @@ rustPlatform.buildRustPackage (finalAttrs: { meta = { description = "Nix binary cache"; homepage = "https://github.com/nix-community/harmonia"; changelog = "https://github.com/nix-community/harmonia/releases/tag/${finalAttrs.src.tag}"; license = lib.licenses.mit; maintainers = with lib.maintainers; [ mic92 ]; mainProgram = "harmonia"; mainProgram = "harmonia-cache"; }; }) Loading
nixos/modules/services/networking/harmonia.nix +255 −86 Original line number Diff line number Diff line Loading @@ -6,23 +6,51 @@ }: let cfg = config.services.harmonia; cacheCfg = cfg.cache; daemonCfg = cfg.daemon; format = pkgs.formats.toml { }; signKeyPaths = cfg.signKeyPaths ++ lib.optional (cfg.signKeyPath != null) cfg.signKeyPath; signKeyPaths = cacheCfg.signKeyPaths ++ (if cacheCfg.signKeyPath != null then [ cacheCfg.signKeyPath ] else [ ]); credentials = lib.imap0 (i: signKeyPath: { id = "sign-key-${toString i}"; path = signKeyPath; }) signKeyPaths; in { imports = [ # Renamed options for flat harmonia -> harmonia.cache (lib.mkRenamedOptionModule [ "services" "harmonia" "enable" ] [ "services" "harmonia" "cache" "enable" ] ) (lib.mkRenamedOptionModule [ "services" "harmonia" "signKeyPath" ] [ "services" "harmonia" "cache" "signKeyPath" ] ) (lib.mkRenamedOptionModule [ "services" "harmonia" "signKeyPaths" ] [ "services" "harmonia" "cache" "signKeyPaths" ] ) (lib.mkRenamedOptionModule [ "services" "harmonia" "settings" ] [ "services" "harmonia" "cache" "settings" ] ) # Note: package stays at the top level ]; options = { services.harmonia = { package = lib.mkPackageOption pkgs "harmonia" { }; cache = { enable = lib.mkEnableOption "Harmonia: Nix binary cache written in Rust"; signKeyPath = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; description = "DEPRECATED: Use `services.harmonia.signKeyPaths` instead. Path to the signing key to use for signing the cache"; description = "DEPRECATED: Use `services.harmonia-dev.cache.signKeyPaths` instead. Path to the signing key to use for signing the cache"; }; signKeyPaths = lib.mkOption { Loading @@ -31,8 +59,6 @@ in description = "Paths to the signing keys to use for signing the cache"; }; package = lib.mkPackageOption pkgs "harmonia" { }; settings = lib.mkOption { inherit (format) type; default = { }; Loading @@ -42,12 +68,47 @@ in ''; }; }; daemon = { enable = lib.mkEnableOption "Harmonia daemon: Nix daemon protocol implementation"; socketPath = lib.mkOption { type = lib.types.str; default = "/run/harmonia-daemon/socket"; description = "Path where the daemon socket will be created"; }; storeDir = lib.mkOption { type = lib.types.str; default = "/nix/store"; description = "Path to the Nix store directory"; }; config = lib.mkIf cfg.enable { warnings = lib.optional ( cfg.signKeyPath != null ) "`services.harmonia.signKeyPath` is deprecated, use `services.harmonia.signKeyPaths` instead"; dbPath = lib.mkOption { type = lib.types.str; default = "/nix/var/nix/db/db.sqlite"; description = "Path to the Nix database"; }; logLevel = lib.mkOption { type = lib.types.str; default = "info"; description = "Log level for the daemon"; }; }; }; }; config = lib.mkMerge [ (lib.mkIf cacheCfg.enable { warnings = if cacheCfg.signKeyPath != null then [ "`services.harmonia.cache.signKeyPath` is deprecated, use `services.harmonia.cache.signKeyPaths` instead" ] else [ ]; nix.settings.extra-allowed-users = [ "harmonia" ]; users.users.harmonia = { isSystemUser = true; Loading @@ -55,15 +116,27 @@ in }; users.groups.harmonia = { }; services.harmonia.cache.settings = builtins.mapAttrs (_: v: lib.mkDefault v) ( { bind = "[::]:5000"; workers = 4; max_connection_rate = 256; priority = 50; } // lib.optionalAttrs daemonCfg.enable { daemon_socket = daemonCfg.socketPath; } ); systemd.services.harmonia = { description = "harmonia binary cache service"; requires = [ "nix-daemon.socket" ]; after = [ "network.target" ]; requires = if daemonCfg.enable then [ "harmonia-daemon.service" ] else [ "nix-daemon.socket" ]; after = [ "network.target" ] ++ lib.optional daemonCfg.enable "harmonia-daemon.service"; wantedBy = [ "multi-user.target" ]; environment = { CONFIG_FILE = format.generate "harmonia.toml" cfg.settings; CONFIG_FILE = format.generate "harmonia.toml" cacheCfg.settings; SIGN_KEY_PATHS = lib.strings.concatMapStringsSep " " ( credential: "%d/${credential.id}" ) credentials; Loading Loading @@ -113,5 +186,101 @@ in LimitNOFILE = 65536; }; }; }) (lib.mkIf daemonCfg.enable { systemd.services.harmonia-daemon = let daemonConfig = { socket_path = daemonCfg.socketPath; store_dir = daemonCfg.storeDir; db_path = daemonCfg.dbPath; log_level = daemonCfg.logLevel; }; daemonConfigFile = format.generate "harmonia-daemon.toml" daemonConfig; in { description = "Harmonia Nix daemon protocol server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; environment = { RUST_LOG = daemonCfg.logLevel; RUST_BACKTRACE = "1"; HARMONIA_DAEMON_CONFIG = daemonConfigFile; }; serviceConfig = { Type = "simple"; ExecStart = lib.getExe' cfg.package "harmonia-daemon"; Restart = "on-failure"; RestartSec = 5; # Socket will be created at runtime RuntimeDirectory = "harmonia-daemon"; # Run as root to access the Nix database # Note: The Nix database is owned by root and requires root access NoNewPrivileges = true; PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; # SQLite needs write access for WAL mode ReadWritePaths = [ (builtins.dirOf daemonCfg.dbPath) # Need write access for WAL and SHM files ]; ReadOnlyPaths = [ daemonCfg.storeDir ]; # System call filtering SystemCallFilter = [ "@system-service" "~@privileged" "@chown" # for sockets "~@resources" ]; SystemCallArchitectures = "native"; # Capabilities CapabilityBoundingSet = ""; # Device access DeviceAllow = [ "" ]; PrivateDevices = true; # Kernel protection ProtectKernelModules = true; ProtectKernelTunables = true; ProtectControlGroups = true; ProtectKernelLogs = true; ProtectHostname = true; ProtectClock = true; # Memory protection MemoryDenyWriteExecute = true; LockPersonality = true; # Process visibility ProcSubset = "pid"; ProtectProc = "invisible"; # Namespace restrictions RestrictNamespaces = true; PrivateMounts = true; # Network restrictions RestrictAddressFamilies = "AF_UNIX"; PrivateNetwork = false; # Resource limits LimitNOFILE = 65536; RestrictRealtime = true; # Misc restrictions UMask = "0077"; }; }; }) ]; }
nixos/tests/harmonia.nix +2 −2 Original line number Diff line number Diff line Loading @@ -5,7 +5,7 @@ nodes = { harmonia = { services.harmonia = { services.harmonia.cache = { enable = true; signKeyPaths = [ (pkgs.writeText "cache-key" "cache.example.com-1:9FhO0w+7HjZrhvmzT1VlAZw4OSAlFGTgC24Seg3tmPl4gZBdwZClzTTHr9cVzJpwsRSYLTu7hEAQe3ljy92CWg==") Loading Loading @@ -37,7 +37,7 @@ harmonia.wait_for_unit("harmonia.service") client01.wait_until_succeeds("curl -f http://harmonia:5000/nix-cache-info | grep '${toString nodes.harmonia.services.harmonia.settings.priority}' >&2") client01.wait_until_succeeds("curl -f http://harmonia:5000/nix-cache-info | grep '${toString nodes.harmonia.services.harmonia.cache.settings.priority}' >&2") client01.succeed("curl -f http://harmonia:5000/version | grep '${nodes.harmonia.services.harmonia.package.version}' >&2") client01.succeed("cat /etc/nix/nix.conf >&2") Loading
pkgs/by-name/ha/harmonia/package.nix +5 −4 Original line number Diff line number Diff line Loading @@ -10,16 +10,16 @@ rustPlatform.buildRustPackage (finalAttrs: { pname = "harmonia"; version = "2.1.0"; version = "3.0.0"; src = fetchFromGitHub { owner = "nix-community"; repo = "harmonia"; tag = "harmonia-v${finalAttrs.version}"; hash = "sha256-Ch7CBPwSKZxCmZwFunNCA8E74TcOWp9MLbhe3/glQ6w="; hash = "sha256-BovRI3p2KXwQ6RF49NqLc0uKP/Jk+yA8E0eqScaIP68="; }; cargoHash = "sha256-7HZoXNL7nf6NUNnh6gzXsZ2o4eeEQL7/KDdIcbh7/jM="; cargoHash = "sha256-X3A+gV32itmt0SqepioT64IGzHfrCdLsQjF6EDwCTbo="; doCheck = false; Loading @@ -41,8 +41,9 @@ rustPlatform.buildRustPackage (finalAttrs: { meta = { description = "Nix binary cache"; homepage = "https://github.com/nix-community/harmonia"; changelog = "https://github.com/nix-community/harmonia/releases/tag/${finalAttrs.src.tag}"; license = lib.licenses.mit; maintainers = with lib.maintainers; [ mic92 ]; mainProgram = "harmonia"; mainProgram = "harmonia-cache"; }; })