From 1f0ac736b48dbeaed1b3da975d13246b4d3a886c Mon Sep 17 00:00:00 2001 From: Sylvain Fankhauser Date: Tue, 21 Feb 2023 17:21:38 +0100 Subject: [PATCH] nixos/caddy: add support for reload --- .../manual/release-notes/rl-2311.section.md | 2 + .../services/web-servers/caddy/default.nix | 45 ++++++++++++++++--- nixos/tests/caddy.nix | 9 ++-- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md index 399bf328ffca..c4d968b80061 100644 --- a/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixos/doc/manual/release-notes/rl-2311.section.md @@ -64,6 +64,8 @@ - `spamassassin` no longer supports the `Hashcash` module. The module needs to be removed from the `loadplugin` list if it was copied over from the default `initPreConf` option. +- The Caddy module gained a new option named `services.caddy.enableReload` which is enabled by default. It allows reloading the service instead of restarting it, if only a config file has changed. This option must be disabled if you have turned off the [Caddy admin API](https://caddyserver.com/docs/caddyfile/options#admin). If you keep this option enabled, you should consider setting [`grace_period`](https://caddyserver.com/docs/caddyfile/options#grace-period) to a non-infinite value to prevent Caddy from delaying the reload indefinitely. + ## Other Notable Changes {#sec-release-23.11-notable-changes} - The Cinnamon module now enables XDG desktop integration by default. If you are experiencing collisions related to xdg-desktop-portal-gtk you can safely remove `xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];` from your NixOS configuration. diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix index 70715a237250..1056b9525ac9 100644 --- a/nixos/modules/services/web-servers/caddy/default.nix +++ b/nixos/modules/services/web-servers/caddy/default.nix @@ -41,6 +41,14 @@ let in "${if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile}/Caddyfile"; + adminDisabled = lib.fileContents (pkgs.runCommand "caddy-config-adapted" {} '' + ${cfg.package}/bin/caddy adapt --config ${configFile} ${optionalString (cfg.adapter != null) "--adapter ${cfg.adapter}"} | ${pkgs.jq}/bin/jq .admin.disabled > $out + '') == "true"; + + etcConfigFile = "caddy/caddy_config"; + + configPath = "/etc/${etcConfigFile}"; + acmeHosts = unique (catAttrs "useACMEHost" acmeVHosts); mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix; @@ -155,11 +163,16 @@ in description = lib.mdDoc '' Override the configuration file used by Caddy. By default, NixOS generates one automatically. + + The configuration file is exposed at {file}`${configPath}`. ''; }; adapter = mkOption { - default = null; + default = if (builtins.baseNameOf cfg.configFile) == "Caddyfile" then "caddyfile" else null; + defaultText = literalExpression '' + if (builtins.baseNameOf cfg.configFile) == "Caddyfile" then "caddyfile" else null + ''; example = literalExpression "nginx"; type = with types; nullOr str; description = lib.mdDoc '' @@ -275,6 +288,21 @@ in ''; }; + enableReload = mkOption { + default = true; + type = types.bool; + description = lib.mdDoc '' + Reload Caddy instead of restarting it when configuration file changes. + + Note that enabling this option requires the [admin API](https://caddyserver.com/docs/caddyfile/options#admin) + to not be turned off. + + If you enable this option, consider setting [`grace_period`](https://caddyserver.com/docs/caddyfile/options#grace-period) + to a non-infinite value in {option}`services.caddy.globalConfig` + to prevent Caddy waiting for active connections to finish, + which could delay the reload essentially indefinitely. + ''; + }; }; # implementation @@ -284,6 +312,9 @@ in { assertion = cfg.configFile == configFile -> cfg.adapter == "caddyfile" || cfg.adapter == null; message = "To specify an adapter other than 'caddyfile' please provide your own configuration via `services.caddy.configFile`"; } + { assertion = cfg.enableReload -> !adminDisabled; + message = "You need to remove `admin off` from your Caddy configuration in order to use `services.caddy.enableReload`"; + } ] ++ map (name: mkCertOwnershipAssertion { inherit (cfg) group user; cert = config.security.acme.certs.${name}; @@ -311,13 +342,16 @@ in wantedBy = [ "multi-user.target" ]; startLimitIntervalSec = 14400; startLimitBurst = 10; + reloadTriggers = optional cfg.enableReload cfg.configFile; - serviceConfig = { + serviceConfig = let + runOptions = ''--config ${configPath} ${optionalString (cfg.adapter != null) "--adapter ${cfg.adapter}"}''; + in { # https://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= # If the empty string is assigned to this option, the list of commands to start is reset, prior assignments of this option will have no effect. - ExecStart = [ "" ''${cfg.package}/bin/caddy run --config ${cfg.configFile} ${optionalString (cfg.adapter != null) "--adapter ${cfg.adapter}"} ${optionalString cfg.resume "--resume"}'' ]; - ExecReload = [ "" ''${cfg.package}/bin/caddy reload --config ${cfg.configFile} ${optionalString (cfg.adapter != null) "--adapter ${cfg.adapter}"} --force'' ]; - ExecStartPre = ''${cfg.package}/bin/caddy validate --config ${cfg.configFile} ${optionalString (cfg.adapter != null) "--adapter ${cfg.adapter}"}''; + ExecStart = [ "" ''${cfg.package}/bin/caddy run ${runOptions} ${optionalString cfg.resume "--resume"}'' ]; + # Validating the configuration before applying it ensures we’ll get a proper error that will be reported when switching to the configuration + ExecReload = [ "" ''${cfg.package}/bin/caddy reload ${runOptions} --force'' ]; User = cfg.user; Group = cfg.group; ReadWriteDirectories = cfg.dataDir; @@ -353,5 +387,6 @@ in in listToAttrs certCfg; + environment.etc.${etcConfigFile}.source = cfg.configFile; }; } diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix index c4abd33d0395..ed88f08739e8 100644 --- a/nixos/tests/caddy.nix +++ b/nixos/tests/caddy.nix @@ -20,6 +20,7 @@ import ./make-test-python.nix ({ pkgs, ... }: { } } ''; + services.caddy.enableReload = true; specialisation.etag.configuration = { services.caddy.extraConfig = lib.mkForce '' @@ -54,9 +55,9 @@ import ./make-test-python.nix ({ pkgs, ... }: { testScript = { nodes, ... }: let - etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag"; - justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload"; - multipleConfigs = "${nodes.webserver.config.system.build.toplevel}/specialisation/multiple-configs"; + etagSystem = "${nodes.webserver.system.build.toplevel}/specialisation/etag"; + justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/config-reload"; + multipleConfigs = "${nodes.webserver.system.build.toplevel}/specialisation/multiple-configs"; in '' url = "http://localhost/example.html" @@ -96,6 +97,8 @@ import ./make-test-python.nix ({ pkgs, ... }: { "${justReloadSystem}/bin/switch-to-configuration test >&2" ) webserver.wait_for_open_port(8080) + webserver.fail("journalctl -u caddy | grep -q -i stopped") + webserver.succeed("journalctl -u caddy | grep -q -i reloaded") with subtest("multiple configs are correctly merged"): webserver.succeed(