diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index 418a048b1b62..3c84331ef775 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -4526,6 +4526,12 @@ githubId = 8228888; name = "Charlie Hanley"; }; + channinghe = { + email = "channinghey@gmail.com"; + github = "ChanningHe"; + githubId = 52875777; + name = "Channing He"; + }; chaoflow = { email = "flo@chaoflow.net"; github = "chaoflow"; diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 0011534ca895..bc717edc6f8f 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -20,6 +20,8 @@ - [udp-over-tcp](https://github.com/mullvad/udp-over-tcp), a tunnel for proxying UDP traffic over a TCP stream. Available as `services.udp-over-tcp`. +- [Komodo Periphery](https://github.com/moghtech/komodo), a multi-server Docker and Git deployment agent by Komodo. Available as [services.komodo-periphery](#opt-services.komodo-periphery.enable). + ## Backward Incompatibilities {#sec-release-26.05-incompatibilities} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 2df1045248b5..778e20eda557 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -411,6 +411,7 @@ ./services/accessibility/orca.nix ./services/accessibility/speechd.nix ./services/admin/docuum.nix + ./services/admin/komodo-periphery.nix ./services/admin/meshcentral.nix ./services/admin/oxidized.nix ./services/admin/pgadmin.nix diff --git a/nixos/modules/services/admin/komodo-periphery.nix b/nixos/modules/services/admin/komodo-periphery.nix new file mode 100644 index 000000000000..5b7643baeea6 --- /dev/null +++ b/nixos/modules/services/admin/komodo-periphery.nix @@ -0,0 +1,338 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.komodo-periphery; + settingsFormat = pkgs.formats.toml { }; + + genFinalSettings = + let + baseSettings = { + port = cfg.port; + bind_ip = cfg.bindIp; + root_directory = cfg.rootDirectory; + repo_dir = "${cfg.rootDirectory}/repos"; + stack_dir = "${cfg.rootDirectory}/stacks"; + ssl_enabled = cfg.ssl.enable; + } + // lib.optionalAttrs cfg.ssl.enable { + ssl_key_file = cfg.ssl.keyFile; + ssl_cert_file = cfg.ssl.certFile; + } + // { + logging = { + level = cfg.logging.level; + stdio = cfg.logging.stdio; + } + // lib.optionalAttrs (cfg.logging.otlpEndpoint != "") { + otlp_endpoint = cfg.logging.otlpEndpoint; + }; + allowed_ips = cfg.allowedIps; + passkeys = cfg.passkeys; + disable_terminals = cfg.disableTerminals; + disable_container_exec = cfg.disableContainerExec; + stats_polling_rate = cfg.statsPollingRate; + container_stats_polling_rate = cfg.containerStatsPollingRate; + legacy_compose_cli = cfg.legacyComposeCli; + include_disk_mounts = cfg.includeDiskMounts; + exclude_disk_mounts = cfg.excludeDiskMounts; + } + // cfg.extraSettings; + in + lib.filterAttrsRecursive (_: v: v != null && v != { } && v != [ ]) baseSettings; + + configFile = + if cfg.configFile == null then + settingsFormat.generate "komodo-periphery.toml" genFinalSettings + else + cfg.configFile; +in +{ + options.services.komodo-periphery = { + enable = lib.mkEnableOption "Periphery, a multi-server Docker and Git deployment agent by Komodo"; + + package = lib.mkPackageOption pkgs "komodo" { }; + + configFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Path to the periphery configuration file. If null, a configuration file will be generated from the module options."; + example = lib.literalExpression '' + pkgs.writeText "periphery.toml" ''' + port = 8120 + bind_ip = "[::]" + ssl_enabled = true + [logging] + level = "info" + ''' + ''; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 8120; + description = "Port for the Periphery agent to listen on."; + }; + + bindIp = lib.mkOption { + type = lib.types.str; + default = "[::]"; + description = "IP address to bind to."; + }; + + rootDirectory = lib.mkOption { + type = lib.types.path; + default = "/var/lib/komodo-periphery"; + description = "Root directory for Komodo Periphery data."; + }; + + ssl = { + enable = lib.mkEnableOption "SSL/TLS support" // { + default = true; + }; + + keyFile = lib.mkOption { + type = lib.types.path; + default = "${cfg.rootDirectory}/ssl/key.pem"; + defaultText = lib.literalExpression ''"''${config.services.komodo-periphery.rootDirectory}/ssl/key.pem"''; + description = "Path to SSL key file."; + }; + + certFile = lib.mkOption { + type = lib.types.path; + default = "${cfg.rootDirectory}/ssl/cert.pem"; + defaultText = lib.literalExpression ''"''${config.services.komodo-periphery.rootDirectory}/ssl/cert.pem"''; + description = "Path to SSL certificate file."; + }; + }; + + logging = { + level = lib.mkOption { + type = lib.types.enum [ + "off" + "error" + "warn" + "info" + "debug" + "trace" + ]; + default = "info"; + description = "Logging verbosity level."; + }; + + stdio = lib.mkOption { + type = lib.types.enum [ + "standard" + "json" + "none" + ]; + default = "standard"; + description = "Logging format for stdout/stderr."; + }; + + otlpEndpoint = lib.mkOption { + type = lib.types.str; + default = ""; + description = "OpenTelemetry OTLP endpoint for traces."; + example = "http://localhost:4317"; + }; + }; + + allowedIps = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "IP addresses or subnets allowed to call the periphery API. Empty list allows all."; + example = [ + "::ffff:12.34.56.78" + "10.0.10.0/24" + ]; + }; + + passkeys = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = '' + Passkeys required to access the periphery API. + WARNING: These will be stored in the Nix store in plain text! + ''; + example = [ "your-secure-passkey" ]; + }; + + extraSettings = lib.mkOption { + type = settingsFormat.type; + default = { }; + description = "Extra settings to add to the generated TOML config."; + example = { + secrets.GITHUB_TOKEN = "ghp_xxxx"; + }; + }; + + disableTerminals = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Disable remote shell access through Periphery."; + }; + + disableContainerExec = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Disable remote container shell access through Periphery."; + }; + + statsPollingRate = lib.mkOption { + type = lib.types.str; + default = "5-sec"; + description = "System stats polling interval."; + example = "10-sec"; + }; + + containerStatsPollingRate = lib.mkOption { + type = lib.types.str; + default = "30-sec"; + description = "Container stats polling interval."; + example = "1-min"; + }; + + legacyComposeCli = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Use `docker-compose` instead of `docker compose`."; + }; + + includeDiskMounts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Only include these mount paths in disk reporting."; + example = [ + "/mnt/data" + "/mnt/backup" + ]; + }; + + excludeDiskMounts = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Exclude these mount paths from disk reporting."; + example = [ + "/tmp" + "/boot" + ]; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "komodo-periphery"; + description = "User under which the Periphery agent runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "komodo-periphery"; + description = "Group under which the Periphery agent runs."; + }; + + environmentFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Environment file for additional configuration via environment variables."; + example = "/run/secrets/komodo-periphery.env"; + }; + + environment = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + description = "Environment variables to set for the service."; + example = { + RUST_LOG = "komodo=debug"; + DOCKER_HOST = "unix:///var/run/docker.sock"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + virtualisation.docker.enable = true; + + users.users.${cfg.user} = lib.mkIf (cfg.user == "komodo-periphery") { + isSystemUser = true; + group = cfg.group; + description = "Komodo Periphery service user"; + home = cfg.rootDirectory; + extraGroups = [ "docker" ]; + }; + + users.groups.${cfg.group} = lib.mkIf (cfg.group == "komodo-periphery") { }; + + systemd.tmpfiles.settings."10-komodo-periphery" = { + "${cfg.rootDirectory}".d = { + mode = "0755"; + user = cfg.user; + group = cfg.group; + }; + "${cfg.rootDirectory}/repos".d = { + mode = "0755"; + user = cfg.user; + group = cfg.group; + }; + "${cfg.rootDirectory}/stacks".d = { + mode = "0755"; + user = cfg.user; + group = cfg.group; + }; + "${cfg.rootDirectory}/ssl".d = { + mode = "0700"; + user = cfg.user; + group = cfg.group; + }; + }; + + systemd.services.komodo-periphery = { + description = "Komodo Periphery - Multi-server Docker and Git deployment agent"; + after = [ + "network-online.target" + "docker.service" + ]; + wants = [ + "network-online.target" + "docker.service" + ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + SupplementaryGroups = [ "docker" ]; + Restart = "on-failure"; + RestartSec = "10s"; + WorkingDirectory = cfg.rootDirectory; + + ExecStart = lib.escapeShellArgs [ + "${lib.getExe' cfg.package "periphery"}" + "--config-path" + (if cfg.configFile != null then cfg.configFile else configFile) + ]; + + Environment = lib.mapAttrsToList (name: value: "${name}=${value}") ( + cfg.environment + // lib.optionalAttrs (!cfg.disableTerminals) { + PATH = "/run/current-system/sw/bin:/run/wrappers/bin"; + } + ); + EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile; + + StateDirectory = "komodo-periphery"; + StateDirectoryMode = "0755"; + + NoNewPrivileges = true; + PrivateTmp = true; + ProtectSystem = "full"; + ProtectHome = true; + }; + }; + }; + + meta.maintainers = with lib.maintainers; [ channinghe ]; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index fc153e0b4e40..2f9c770a135a 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -828,6 +828,7 @@ in kmonad = runTest ./kmonad.nix; knot = runTest ./knot.nix; komga = runTest ./komga.nix; + komodo-periphery = runTest ./komodo-periphery.nix; krb5 = discoverTests (import ./krb5); ksm = runTest ./ksm.nix; kthxbye = runTest ./kthxbye.nix; diff --git a/nixos/tests/komodo-periphery.nix b/nixos/tests/komodo-periphery.nix new file mode 100644 index 000000000000..64bcbfa7b6e3 --- /dev/null +++ b/nixos/tests/komodo-periphery.nix @@ -0,0 +1,26 @@ +{ lib, ... }: +{ + name = "komodo-periphery"; + meta = { + maintainers = with lib.maintainers; [ channinghe ]; + }; + + nodes.machine = + { config, pkgs, ... }: + { + virtualisation.docker.enable = true; + services.komodo-periphery = { + enable = true; + bindIp = "127.0.0.1"; + port = 8120; + ssl.enable = false; + passkeys = [ "test-passkey" ]; + }; + }; + + testScript = '' + machine.wait_for_unit("komodo-periphery.service") + machine.wait_for_open_port(8120) + machine.succeed("systemctl status komodo-periphery.service") + ''; +} diff --git a/pkgs/by-name/ko/komodo/package.nix b/pkgs/by-name/ko/komodo/package.nix index 756d3595f4c5..e51a2d926c0b 100644 --- a/pkgs/by-name/ko/komodo/package.nix +++ b/pkgs/by-name/ko/komodo/package.nix @@ -3,6 +3,7 @@ rustPlatform, fetchFromGitHub, nix-update-script, + nixosTests, }: rustPlatform.buildRustPackage rec { @@ -22,7 +23,12 @@ rustPlatform.buildRustPackage rec { # > error: doctest failed, to rerun pass `-p komodo_client --doc` doCheck = false; - passthru.updateScript = nix-update-script { }; + passthru = { + updateScript = nix-update-script { }; + tests = { + inherit (nixosTests) komodo-periphery; + }; + }; meta = { description = "Tool to build and deploy software on many servers";