diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 196eba87e13..87132c90840 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -995,7 +995,7 @@ ./services/web-apps/youtrack.nix ./services/web-apps/zabbix.nix ./services/web-servers/apache-httpd/default.nix - ./services/web-servers/caddy.nix + ./services/web-servers/caddy/default.nix ./services/web-servers/darkhttpd.nix ./services/web-servers/fcgiwrap.nix ./services/web-servers/hitch/default.nix diff --git a/nixos/modules/services/web-servers/caddy.nix b/nixos/modules/services/web-servers/caddy/default.nix similarity index 84% rename from nixos/modules/services/web-servers/caddy.nix rename to nixos/modules/services/web-servers/caddy/default.nix index 0a059723ccc..fd710209634 100644 --- a/nixos/modules/services/web-servers/caddy.nix +++ b/nixos/modules/services/web-servers/caddy/default.nix @@ -4,7 +4,17 @@ with lib; let cfg = config.services.caddy; - configFile = pkgs.writeText "Caddyfile" cfg.config; + vhostToConfig = vhostName: vhostAttrs: '' + ${vhostName} ${builtins.concatStringsSep " " vhostAttrs.serverAliases} { + ${vhostAttrs.extraConfig} + } + ''; + configFile = pkgs.writeText "Caddyfile" (builtins.concatStringsSep "\n" + ([ cfg.config ] ++ (mapAttrsToList vhostToConfig cfg.virtualHosts))); + + formattedConfig = pkgs.runCommand "formattedCaddyFile" { } '' + ${cfg.package}/bin/caddy fmt ${configFile} > $out + ''; tlsConfig = { apps.tls.automation.policies = [{ @@ -17,7 +27,7 @@ let adaptedConfig = pkgs.runCommand "caddy-config-adapted.json" { } '' ${cfg.package}/bin/caddy adapt \ - --config ${configFile} --adapter ${cfg.adapter} > $out + --config ${formattedConfig} --adapter ${cfg.adapter} > $out ''; tlsJSON = pkgs.writeText "tls.json" (builtins.toJSON tlsConfig); @@ -68,6 +78,27 @@ in ''; }; + virtualHosts = mkOption { + type = types.attrsOf (types.submodule (import ./vhost-options.nix { + inherit config lib; + })); + default = { }; + example = literalExample '' + { + "hydra.example.com" = { + serverAliases = [ "www.hydra.example.com" ]; + extraConfig = '''''' + encode gzip + log + root /srv/http + ''''''; + }; + }; + ''; + description = "Declarative vhost config"; + }; + + user = mkOption { default = "caddy"; type = types.str; diff --git a/nixos/modules/services/web-servers/caddy/vhost-options.nix b/nixos/modules/services/web-servers/caddy/vhost-options.nix new file mode 100644 index 00000000000..1f74295fc9a --- /dev/null +++ b/nixos/modules/services/web-servers/caddy/vhost-options.nix @@ -0,0 +1,28 @@ +# This file defines the options that can be used both for the Nginx +# main server configuration, and for the virtual hosts. (The latter +# has additional options that affect the web server as a whole, like +# the user/group to run under.) + +{ lib, ... }: + +with lib; +{ + options = { + serverAliases = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "www.example.org" "example.org" ]; + description = '' + Additional names of virtual hosts served by this virtual host configuration. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + These lines go into the vhost verbatim + ''; + }; + }; +} diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix index 063f83a2f3d..29b227c0409 100644 --- a/nixos/tests/caddy.nix +++ b/nixos/tests/caddy.nix @@ -43,49 +43,64 @@ import ./make-test-python.nix ({ pkgs, ... }: { } ''; }; + specialisation.multiple-configs.configuration = { + services.caddy.virtualHosts = { + "http://localhost:8080" = { }; + "http://localhost:8081" = { }; + }; + }; }; - }; - testScript = { nodes, ... }: let - etagSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/etag"; - justReloadSystem = "${nodes.webserver.config.system.build.toplevel}/specialisation/config-reload"; - in '' - url = "http://localhost/example.html" - webserver.wait_for_unit("caddy") - webserver.wait_for_open_port("80") + 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"; + in + '' + url = "http://localhost/example.html" + webserver.wait_for_unit("caddy") + webserver.wait_for_open_port("80") - def check_etag(url): - etag = webserver.succeed( - "curl --fail -v '{}' 2>&1 | sed -n -e \"s/^< [Ee][Tt][Aa][Gg]: *//p\"".format( - url + def check_etag(url): + etag = webserver.succeed( + "curl --fail -v '{}' 2>&1 | sed -n -e \"s/^< [Ee][Tt][Aa][Gg]: *//p\"".format( + url + ) ) - ) - etag = etag.replace("\r\n", " ") - http_code = webserver.succeed( - "curl --fail --silent --show-error -o /dev/null -w \"%{{http_code}}\" --head -H 'If-None-Match: {}' {}".format( - etag, url + etag = etag.replace("\r\n", " ") + http_code = webserver.succeed( + "curl --fail --silent --show-error -o /dev/null -w \"%{{http_code}}\" --head -H 'If-None-Match: {}' {}".format( + etag, url + ) ) - ) - assert int(http_code) == 304, "HTTP code is {}, expected 304".format(http_code) - return etag + assert int(http_code) == 304, "HTTP code is {}, expected 304".format(http_code) + return etag - with subtest("check ETag if serving Nix store paths"): - old_etag = check_etag(url) - webserver.succeed( - "${etagSystem}/bin/switch-to-configuration test >&2" - ) - webserver.sleep(1) - new_etag = check_etag(url) - assert old_etag != new_etag, "Old ETag {} is the same as {}".format( - old_etag, new_etag - ) + with subtest("check ETag if serving Nix store paths"): + old_etag = check_etag(url) + webserver.succeed( + "${etagSystem}/bin/switch-to-configuration test >&2" + ) + webserver.sleep(1) + new_etag = check_etag(url) + assert old_etag != new_etag, "Old ETag {} is the same as {}".format( + old_etag, new_etag + ) - with subtest("config is reloaded on nixos-rebuild switch"): - webserver.succeed( - "${justReloadSystem}/bin/switch-to-configuration test >&2" - ) - webserver.wait_for_open_port("8080") - ''; -}) + with subtest("config is reloaded on nixos-rebuild switch"): + webserver.succeed( + "${justReloadSystem}/bin/switch-to-configuration test >&2" + ) + webserver.wait_for_open_port("8080") + + with subtest("multiple configs are correctly merged"): + webserver.succeed( + "${multipleConfigs}/bin/switch-to-configuration test >&2" + ) + webserver.wait_for_open_port("8080") + webserver.wait_for_open_port("8081") + ''; + })