diff --git a/modules/nixos/caddy/default.nix b/modules/nixos/caddy/default.nix deleted file mode 100644 index 8600f77d..00000000 --- a/modules/nixos/caddy/default.nix +++ /dev/null @@ -1,480 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: -with lib; let - cfg = config.services.caddy; - - certs = config.security.acme.certs; - virtualHosts = attrValues cfg.virtualHosts; - acmeEnabledVhosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts; - vhostCertNames = unique (map (hostOpts: hostOpts.useACMEHost) acmeEnabledVhosts); - dependentCertNames = filter (cert: certs.${cert}.dnsProvider == null) vhostCertNames; # those that might depend on the HTTP server - independentCertNames = filter (cert: certs.${cert}.dnsProvider != null) vhostCertNames; # those that don't depend on the HTTP server - - mkVHostConf = hostOpts: let - sslCertDir = config.security.acme.certs.${hostOpts.useACMEHost}.directory; - in '' - ${hostOpts.hostName} ${concatStringsSep " " hostOpts.serverAliases} { - ${optionalString ( - hostOpts.listenAddresses != [] - ) "bind ${concatStringsSep " " hostOpts.listenAddresses}"} - ${optionalString ( - hostOpts.useACMEHost != null - ) "tls ${sslCertDir}/cert.pem ${sslCertDir}/key.pem"} - log { - ${hostOpts.logFormat} - } - - ${hostOpts.extraConfig} - } - ''; - - settingsFormat = pkgs.formats.json {}; - - configFile = - if cfg.settings != {} - then settingsFormat.generate "caddy.json" cfg.settings - else let - Caddyfile = pkgs.writeTextDir "Caddyfile" '' - { - ${cfg.globalConfig} - } - ${cfg.extraConfig} - ${concatMapStringsSep "\n" mkVHostConf virtualHosts} - ''; - - Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" {} '' - mkdir -p $out - cp --no-preserve=mode ${Caddyfile}/Caddyfile $out/Caddyfile - ${lib.getExe cfg.package} fmt --overwrite $out/Caddyfile - ''; - in "${ - if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform - then Caddyfile-formatted - else Caddyfile - }/Caddyfile"; - - etcConfigFile = "caddy/caddy_config"; - - configPath = "/etc/${etcConfigFile}"; - - mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix lib; -in { - imports = [ - (mkRemovedOptionModule [ - "services" - "caddy" - "agree" - ] "this option is no longer necessary for Caddy 2") - (mkRenamedOptionModule ["services" "caddy" "ca"] ["services" "caddy" "acmeCA"]) - (mkRenamedOptionModule ["services" "caddy" "config"] ["services" "caddy" "extraConfig"]) - ]; - - # interface - options.services.caddy = { - enable = mkEnableOption "Caddy web server"; - - user = mkOption { - default = "caddy"; - type = types.str; - description = '' - User account under which caddy runs. - - ::: {.note} - If left as the default value this user will automatically be created - on system activation, otherwise you are responsible for - ensuring the user exists before the Caddy service starts. - ::: - ''; - }; - - group = mkOption { - default = "caddy"; - type = types.str; - description = '' - Group under which caddy runs. - - ::: {.note} - If left as the default value this group will automatically be created - on system activation, otherwise you are responsible for - ensuring the group exists before the Caddy service starts. - ::: - ''; - }; - - package = mkPackageOption pkgs "caddy" {}; - - dataDir = mkOption { - type = types.path; - default = "/var/lib/caddy"; - description = '' - The data directory for caddy. - - ::: {.note} - If left as the default value this directory will automatically be created - before the Caddy server starts, otherwise you are responsible for ensuring - the directory exists with appropriate ownership and permissions. - - Caddy v2 replaced `CADDYPATH` with XDG directories. - See . - ::: - ''; - }; - - logDir = mkOption { - type = types.path; - default = "/var/log/caddy"; - description = '' - Directory for storing Caddy access logs. - - ::: {.note} - If left as the default value this directory will automatically be created - before the Caddy server starts, otherwise the sysadmin is responsible for - ensuring the directory exists with appropriate ownership and permissions. - ::: - ''; - }; - - logFormat = mkOption { - type = types.lines; - default = '' - level ERROR - ''; - example = literalExpression '' - mkForce "level INFO"; - ''; - description = '' - Configuration for the default logger. See - - for details. - ''; - }; - - configFile = mkOption { - type = types.path; - default = configFile; - defaultText = "A Caddyfile automatically generated by values from services.caddy.*"; - example = literalExpression '' - pkgs.writeText "Caddyfile" ''' - example.com - - root * /var/www/wordpress - php_fastcgi unix//run/php/php-version-fpm.sock - file_server - '''; - ''; - description = '' - Override the configuration file used by Caddy. By default, - NixOS generates one automatically. - - The configuration file is exposed at {file}`${configPath}`. - ''; - }; - - adapter = mkOption { - default = - if ((cfg.configFile != configFile) || (builtins.baseNameOf cfg.configFile) == "Caddyfile") - then "caddyfile" - else null; - defaultText = literalExpression '' - if ((cfg.configFile != configFile) || (builtins.baseNameOf cfg.configFile) == "Caddyfile") then "caddyfile" else null - ''; - example = literalExpression "nginx"; - type = with types; nullOr str; - description = '' - Name of the config adapter to use. - See - for the full list. - - If `null` is specified, the `--adapter` argument is omitted when - starting or restarting Caddy. Notably, this allows specification of a - configuration file in Caddy's native JSON format, as long as the - filename does not start with `Caddyfile` (in which case the `caddyfile` - adapter is implicitly enabled). See - for details. - - ::: {.note} - Any value other than `null` or `caddyfile` is only valid when providing - your own `configFile`. - ::: - ''; - }; - - resume = mkOption { - default = false; - type = types.bool; - description = '' - Use saved config, if any (and prefer over any specified configuration passed with `--config`). - ''; - }; - - globalConfig = mkOption { - type = types.lines; - default = ""; - example = '' - debug - servers { - protocol { - experimental_http3 - } - } - ''; - description = '' - Additional lines of configuration appended to the global config section - of the `Caddyfile`. - - Refer to - for details on supported values. - ''; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - example = '' - example.com { - encode gzip - log - root /srv/http - } - ''; - description = '' - Additional lines of configuration appended to the automatically - generated `Caddyfile`. - ''; - }; - - virtualHosts = mkOption { - type = with types; attrsOf (submodule (import ./vhost-options.nix {inherit cfg;})); - default = {}; - example = literalExpression '' - { - "hydra.example.com" = { - serverAliases = [ "www.hydra.example.com" ]; - extraConfig = ''' - encode gzip - root * /srv/http - '''; - }; - }; - ''; - description = '' - Declarative specification of virtual hosts served by Caddy. - ''; - }; - - acmeCA = mkOption { - default = null; - example = "https://acme-v02.api.letsencrypt.org/directory"; - type = with types; nullOr str; - description = '' - ::: {.note} - Sets the [`acme_ca` option](https://caddyserver.com/docs/caddyfile/options#acme-ca) - in the global options block of the resulting Caddyfile. - ::: - - The URL to the ACME CA's directory. It is strongly recommended to set - this to `https://acme-staging-v02.api.letsencrypt.org/directory` for - Let's Encrypt's [staging endpoint](https://letsencrypt.org/docs/staging-environment/) - while testing or in development. - - Value `null` should be prefered for production setups, - as it omits the `acme_ca` option to enable - [automatic issuer fallback](https://caddyserver.com/docs/automatic-https#issuer-fallback). - ''; - }; - - email = mkOption { - default = null; - type = with types; nullOr str; - description = '' - Your email address. Mainly used when creating an ACME account with your - CA, and is highly recommended in case there are problems with your - certificates. - ''; - }; - - enableReload = mkOption { - default = true; - type = types.bool; - description = '' - 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. - ''; - }; - - settings = mkOption { - type = settingsFormat.type; - default = {}; - description = '' - Structured configuration for Caddy to generate a Caddy JSON configuration file. - See for available options. - - ::: {.warning} - Using a [Caddyfile](https://caddyserver.com/docs/caddyfile) instead of a JSON config is highly recommended by upstream. - There are only very few exception to this. - - Please use a Caddyfile via {option}`services.caddy.configFile`, {option}`services.caddy.virtualHosts` or - {option}`services.caddy.extraConfig` with {option}`services.caddy.globalConfig` instead. - ::: - - ::: {.note} - Takes presence over most `services.caddy.*` options, such as {option}`services.caddy.configFile` and {option}`services.caddy.virtualHosts`, if specified. - ::: - ''; - }; - - environmentFile = mkOption { - type = with types; nullOr path; - default = null; - example = "/run/secrets/caddy.env"; - description = '' - Environment file as defined in {manpage}`systemd.exec(5)`. - - You can use environment variables to pass secrets to the service without adding - them to the world-redable nix store. - - ``` - # in configuration.nix - services.caddy.environmentFile = "/run/secrets/caddy.env"; - services.caddy.globalConfig = ''' - { - acme_ca https://acme.zerossl.com/v2/DV90 - acme_eab { - key_id {$EAB_KEY_ID} - mac_key {$EAB_MAC_KEY} - } - } - '''; - ``` - - ``` - # in /run/secrets/caddy.env - EAB_KEY_ID=secret - EAB_MAC_KEY=secret - ``` - - Find more examples - [here](https://caddyserver.com/docs/caddyfile/concepts#environment-variables) - ''; - }; - }; - - # implementation - config = mkIf cfg.enable { - assertions = - [ - { - 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`"; - } - ] - ++ map ( - name: - mkCertOwnershipAssertion { - cert = config.security.acme.certs.${name}; - groups = config.users.groups; - services = [config.systemd.services.caddy]; - } - ) - vhostCertNames; - - services.caddy.globalConfig = '' - ${optionalString (cfg.email != null) "email ${cfg.email}"} - ${optionalString (cfg.acmeCA != null) "acme_ca ${cfg.acmeCA}"} - log { - ${cfg.logFormat} - } - ''; - - # https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes - boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000; - boot.kernel.sysctl."net.core.wmem_max" = mkDefault 2500000; - - systemd.packages = [cfg.package]; - systemd.services.caddy = { - wants = map (certName: "acme-finished-${certName}.target") vhostCertNames; - after = - map (certName: "acme-selfsigned-${certName}.service") vhostCertNames - ++ map (certName: "acme-${certName}.service") independentCertNames; # avoid loading self-signed key w/ real cert, or vice-versa - before = map (certName: "acme-${certName}.service") dependentCertNames; - - wantedBy = ["multi-user.target"]; - startLimitIntervalSec = 14400; - startLimitBurst = 10; - reloadTriggers = optional cfg.enableReload cfg.configFile; - restartTriggers = optional (!cfg.enableReload) cfg.configFile; - - serviceConfig = let - runOptions = ''--config ${configPath} ${ - optionalString (cfg.adapter != null) "--adapter ${cfg.adapter}" - }''; - in { - # Override the `ExecStart` line from upstream's systemd unit file by our own: - # 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 = [ - "" - ''${lib.getExe cfg.package} 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 = - [ - "" - ] - ++ lib.optional cfg.enableReload "${lib.getExe cfg.package} reload ${runOptions} --force"; - User = cfg.user; - Group = cfg.group; - ReadWritePaths = [cfg.dataDir]; - StateDirectory = mkIf (cfg.dataDir == "/var/lib/caddy") ["caddy"]; - LogsDirectory = mkIf (cfg.logDir == "/var/log/caddy") ["caddy"]; - Restart = "on-failure"; - RestartPreventExitStatus = 1; - RestartSec = "5s"; - EnvironmentFile = optional (cfg.environmentFile != null) cfg.environmentFile; - - # TODO: attempt to upstream these options - NoNewPrivileges = true; - PrivateDevices = true; - ProtectHome = true; - }; - }; - - users.users = optionalAttrs (cfg.user == "caddy") { - caddy = { - group = cfg.group; - uid = config.ids.uids.caddy; - home = cfg.dataDir; - }; - }; - - users.groups = optionalAttrs (cfg.group == "caddy") { - caddy.gid = config.ids.gids.caddy; - }; - - security.acme.certs = let - certCfg = - map ( - certName: - nameValuePair certName { - group = mkDefault cfg.group; - reloadServices = ["caddy.service"]; - } - ) - vhostCertNames; - in - listToAttrs certCfg; - - environment.etc.${etcConfigFile}.source = cfg.configFile; - }; -} diff --git a/modules/nixos/caddy/vhost-options.nix b/modules/nixos/caddy/vhost-options.nix deleted file mode 100644 index 19fc3b05..00000000 --- a/modules/nixos/caddy/vhost-options.nix +++ /dev/null @@ -1,83 +0,0 @@ -{cfg}: { - config, - lib, - name, - ... -}: let - inherit (lib) literalExpression mkOption types; -in { - options = { - hostName = mkOption { - type = types.str; - default = name; - description = "Canonical hostname for the server."; - }; - - serverAliases = mkOption { - type = with types; listOf str; - default = []; - example = [ - "www.example.org" - "example.org" - ]; - description = '' - Additional names of virtual hosts served by this virtual host configuration. - ''; - }; - - listenAddresses = mkOption { - type = with types; listOf str; - description = '' - A list of host interfaces to bind to for this virtual host. - ''; - default = []; - example = [ - "127.0.0.1" - "::1" - ]; - }; - - useACMEHost = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - A host of an existing Let's Encrypt certificate to use. - This is mostly useful if you use DNS challenges but Caddy does not - currently support your provider. - - *Note that this option does not create any certificates, nor - does it add subdomains to existing ones – you will need to create them - manually using [](#opt-security.acme.certs).* - ''; - }; - - logFormat = mkOption { - type = types.lines; - default = '' - output file ${cfg.logDir}/access-${lib.replaceStrings ["/" " "] ["_" "_"] config.hostName}.log - ''; - defaultText = '' - output file ''${config.services.caddy.logDir}/access-''${hostName}.log - ''; - example = literalExpression '' - mkForce ''' - output discard - '''; - ''; - description = '' - Configuration for HTTP request logging (also known as access logs). See - - for details. - ''; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - description = '' - Additional lines of configuration appended to this virtual host in the - automatically generated `Caddyfile`. - ''; - }; - }; -} diff --git a/nixos/tako/services/default.nix b/nixos/tako/services/default.nix index 192a9631..e6c2afeb 100644 --- a/nixos/tako/services/default.nix +++ b/nixos/tako/services/default.nix @@ -24,6 +24,7 @@ ./searxng.nix ./tailscale.nix ./kellnr.nix + ./tuwunel.nix ]; services = { nix-serve = { diff --git a/nixos/tako/services/tuwunel.nix b/nixos/tako/services/tuwunel.nix new file mode 100644 index 00000000..fab9f0fa --- /dev/null +++ b/nixos/tako/services/tuwunel.nix @@ -0,0 +1,13 @@ +{config, ...}: { + services.matrix-tuwunel = { + enable = true; + settings.global = { + server_name = "darksailor.dev"; + unix_socket_path = "/var/run/tuwunel/tuwunel.sock"; + }; + }; + services.caddy.virtualHosts."matrix.darksailor.dev".extraConfig = '' + reverse_proxy unix//var/run/tuwunel/tuwunel.sock + ''; + users.users.caddy.extraGroups = ["tuwunel"]; +}