diff --git a/nixos/modules/misc/documentation/modular-services.nix b/nixos/modules/misc/documentation/modular-services.nix index 7fab6c1b456b..639e11ab6292 100644 --- a/nixos/modules/misc/documentation/modular-services.nix +++ b/nixos/modules/misc/documentation/modular-services.nix @@ -24,6 +24,7 @@ let "" = fakeSubmodule pkgs.autopush-rs.services.autoendpoint; "" = fakeSubmodule pkgs.ghostunnel.services.default; + "" = fakeSubmodule pkgs.git-pages.services.default; "" = fakeSubmodule pkgs.ktls-utils.services.default; "" = fakeSubmodule pkgs.php.services.default; "" = fakeSubmodule pkgs.snid.services.default; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 4839cb6ec95c..8277f7331a1b 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -697,6 +697,7 @@ in geth = runTest ./geth.nix; ghostunnel = runTest ./ghostunnel.nix; ghostunnel-modular = runTest ./ghostunnel-modular.nix; + git-pages-modular = runTest ./git-pages.nix; gitdaemon = runTest ./gitdaemon.nix; gitea = import ./gitea.nix { inherit pkgs runTest; diff --git a/nixos/tests/git-pages.nix b/nixos/tests/git-pages.nix new file mode 100644 index 000000000000..c7e19b7cf846 --- /dev/null +++ b/nixos/tests/git-pages.nix @@ -0,0 +1,65 @@ +{ pkgs, ... }: +{ + name = "git-pages-modular-service"; + + nodes.machine = { pkgs, ... }: { + environment.systemPackages = [ pkgs.curl ]; + + system.services.git-pages = { + imports = [ pkgs.git-pages.services.default ]; + git-pages = { + settings.server = { + pages = "tcp/:3000"; + caddy = "tcp/:3001"; + metrics = "tcp/:3002"; + }; + }; + systemd.service.environment.PAGES_INSECURE = "1"; + }; + + services.caddy = { + enable = true; + configFile = pkgs.writeText "Caddyfile" '' + { + admin off + persist_config off + auto_https disable_redirects + on_demand_tls { + permission http http://localhost:3001 + } + } + https://, http:// { + tls { + on_demand + } + reverse_proxy http://localhost:3000 + } + ''; + }; + + networking.firewall.allowedTCPPorts = [ 80 ]; + }; + + testScript = + let + testSite = pkgs.runCommand "git-pages-testsite.tar" { } '' + echo It works! > index.html + tar cvf $out index.html + ''; + in + '' + start_all() + + machine.wait_for_unit("caddy.service") + machine.wait_for_open_port(80) + machine.wait_for_unit("git-pages.service") + machine.wait_for_open_port(3001) + machine.wait_for_open_port(3002) + machine.fail("curl -f http://localhost/.git-pages/health") + machine.succeed("curl -f http://localhost/ -X PUT --data-binary @${testSite} --header 'Content-Type: application/x-tar'") + machine.wait_until_succeeds("test -f /var/lib/git-pages/data/site/localhost/.index") + machine.succeed("curl -f http://localhost/.git-pages/health") + machine.succeed("curl -f http://localhost/ | grep -F 'It works!'") + machine.succeed("curl -f http://localhost:3002/metrics") + ''; +} diff --git a/pkgs/by-name/gi/git-pages/package.nix b/pkgs/by-name/gi/git-pages/package.nix index 311f34df1061..d51fd4513585 100644 --- a/pkgs/by-name/gi/git-pages/package.nix +++ b/pkgs/by-name/gi/git-pages/package.nix @@ -2,8 +2,12 @@ lib, buildGoModule, fetchFromCodeberg, + fetchpatch, nix-update-script, versionCheckHook, + formats, + coreutils, + nixosTests, }: buildGoModule (finalAttrs: { @@ -18,6 +22,16 @@ buildGoModule (finalAttrs: { hash = "sha256-4yQ3RRJbOfMaqjJJ6CRRN7TuaYY8ScLXxMZPd4tWPwk="; }; + patches = [ + # bugfix to avoid creating parent directory on start + # remove when https://codeberg.org/git-pages/git-pages/pulls/258 is available in the release + (fetchpatch { + name = "mkdirall-parent-dir-create.patch"; + url = "https://codeberg.org/git-pages/git-pages/commit/507e57edbcfc0ec933a877bf26b1756ca0a61870.patch"; + hash = "sha256-1CjU4yGmDOmYsxo3U44Cg2xLJkrmUOX5ZXTycdLs6OE="; + }) + ]; + subPackages = [ "." ]; vendorHash = "sha256-NNIkzgRki2rtCVUnnhT44rEBcMZYiJPmsXySpxiHYR0="; @@ -31,7 +45,16 @@ buildGoModule (finalAttrs: { nativeInstallCheckInputs = [ versionCheckHook ]; versionCheckProgramArg = "-version"; - passthru.updateScript = nix-update-script { }; + passthru = { + tests = { inherit (nixosTests) git-pages-modular; }; + updateScript = nix-update-script { }; + services.default = { + imports = [ + (lib.modules.importApply ./service.nix { inherit formats coreutils; }) + ]; + git-pages.package = finalAttrs.finalPackage; + }; + }; meta = { description = "Scalable static site server for Git forges (like GitHub Pages or Netlify"; diff --git a/pkgs/by-name/gi/git-pages/service.nix b/pkgs/by-name/gi/git-pages/service.nix new file mode 100644 index 000000000000..61c8b645d0ad --- /dev/null +++ b/pkgs/by-name/gi/git-pages/service.nix @@ -0,0 +1,116 @@ +# Non-module dependencies (`importApply`) +{ formats, coreutils }: + +{ + config, + lib, + options, + name, + ... +}: +let + cfg = config.git-pages; + settingsFormat = formats.toml { }; + configFile = "git-pages.toml"; + configOutPath = config.configData.${configFile}.path; +in +{ + _class = "service"; + + meta.maintainers = with lib.maintainers; [ + dtomvan + phanirithvij + ]; + + options.git-pages = { + package = lib.mkOption { + description = "Package to use for git-pages"; + defaultText = "The git-pages package that provided this module."; + type = lib.types.package; + }; + + secretFile = lib.mkOption { + description = '' + File that contains secrets for the git-pages config. + If values in this file are set, any options specified take priority over the options set in + {option}`git-pages.settings`. + + ::: {.note} + See the [git-pages documentation](https://git-pages.org/running-a-server/#configuration) on + secrets and environment variables. + ::: + ''; + default = null; + type = lib.types.nullOr lib.types.str; + }; + settings = lib.mkOption { + type = settingsFormat.type; + description = '' + Settings to set in config.toml. + + ::: {.note} + See the [git-pages documentation](https://git-pages.org/running-a-server/#configuration) on configuring the server. + ::: + ''; + default = { }; + }; + }; + + config = { + git-pages.settings.storage.fs.root = lib.mkDefault "/var/lib/${name}/data"; + + process.argv = [ + (lib.getExe cfg.package) + "-config" + configOutPath + ]; + + configData."${configFile}".source = settingsFormat.generate configFile cfg.settings; + } + // lib.optionalAttrs (options ? systemd) { + systemd.service = { + description = "Forge-agnostic static site server"; + documentation = [ "https://git-pages.org/running-a-server/" ]; + + after = [ "network.target" ]; + wants = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + restartTriggers = [ config.configData."${configFile}".source ]; + + serviceConfig = { + Restart = "always"; + + StateDirectory = name; + WorkingDirectory = "%S/${name}"; + BindReadOnlyPaths = [ configOutPath ]; + + LoadCredential = lib.optional (cfg.secretFile != null) "secrets.toml:${cfg.secretFile}"; + + User = name; + DynamicUser = true; + + # systemd service hardening + ProtectHome = true; + MemoryDenyWriteExecute = true; + PrivateDevices = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectControlGroups = true; + RestrictSUIDSGID = true; + RestrictRealtime = true; + RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX"; + RestrictNamespaces = true; + LockPersonality = true; + ProtectKernelLogs = true; + ProtectKernelTunables = true; + ProtectHostname = true; + ProtectKernelModules = true; + PrivateUsers = true; + ProtectClock = true; + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = "@system-service"; + }; + }; + }; +}