Change:
let cfg = config.services.caddy; <del> 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 = [{ issuers = [{ inherit (cfg) ca email; module = "acme"; }]; }]; }; adaptedConfig = pkgs.runCommand "caddy-config-adapted.json" { } '' ${cfg.package}/bin/caddy adapt  --config ${formattedConfig} --adapter ${cfg.adapter} > $out ''; tlsJSON = pkgs.writeText "tls.json" (builtins.toJSON tlsConfig); # merge the TLS config options we expose with the ones originating in the Caddyfile configJSON = if cfg.ca != null then let tlsConfigMerge = '' {"apps": {"tls": {"automation": {"policies": (if .[0].apps.tls.automation.policies == .[1]?.apps.tls.automation.policies then .[0].apps.tls.automation.policies else (.[0].apps.tls.automation.policies + .[1]?.apps.tls.automation.policies) end) } } } }''; in pkgs.runCommand "caddy-config.json" { } '' ${pkgs.jq}/bin/jq -s '.[0] * ${tlsConfigMerge}' ${adaptedConfig} ${tlsJSON} > $out </del> <ins> virtualHosts = attrValues cfg.virtualHosts; acmeVHosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts; mkVHostConf = hostOpts: let sslCertDir = config.security.acme.certs.${hostOpts.useACMEHost}.directory; in </ins> '' <del> else adaptedConfig; </del> <ins> ${hostOpts.hostName} ${concatStringsSep " " hostOpts.serverAliases} { bind ${concatStringsSep " " hostOpts.listenAddresses} ${optionalString (hostOpts.useACMEHost != null) "tls ${sslCertDir}/cert.pem ${sslCertDir}/key.pem"} log { ${hostOpts.logFormat} } ${hostOpts.extraConfig} } ''; configFile = let Caddyfile = pkgs.writeText "Caddyfile" '' { ${optionalString (cfg.email != null) "email ${cfg.email}"} ${optionalString (cfg.acmeCA != null) "acme_ca ${cfg.acmeCA}"} log { ${cfg.logFormat} } } ${cfg.extraConfig} ''; Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } '' ${cfg.package}/bin/caddy fmt ${Caddyfile} > $out ''; in if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform then Caddyfile-formatted else Caddyfile; </ins> in { imports = [ (mkRemovedOptionModule [ "services" "caddy" "agree" ] "this option is no longer necessary for Caddy 2") <ins> (mkRenamedOptionModule [ "services" "caddy" "ca" ] [ "services" "caddy" "acmeCA" ]) (mkRenamedOptionModule [ "services" "caddy" "config" ] [ "services" "caddy" "extraConfig" ]) </ins> ]; <ins> # interface </ins> options.services.caddy = { enable = mkEnableOption "Caddy web server"; <del> config = mkOption { default = ""; example = '' example.com { encode gzip log root /srv/http } </del> <ins> user = mkOption { default = "caddy"; type = types.str; description = '' User account under which caddy runs. <note><para> 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. </para></note> </ins> ''; <del> type = types.lines; </del> <ins> }; group = mkOption { default = "caddy"; type = types.str; </ins> description = '' <del> Verbatim Caddyfile to use. Caddy v2 supports multiple config formats via adapters (see <option>services.caddy.adapter</option>). </del> <ins> Group account under which caddy runs. <note><para> 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. </para></note> </ins> ''; }; <del> virtualHosts = mkOption { type = types.attrsOf (types.submodule (import ./vhost-options.nix { inherit config lib; })); default = { }; example = literalExpression '' { "hydra.example.com" = { serverAliases = [ "www.hydra.example.com" ]; extraConfig = '''''' encode gzip log root /srv/http ''''''; }; }; </del> <ins> package = mkOption { default = pkgs.caddy; defaultText = literalExpression "pkgs.caddy"; type = types.package; description = '' Caddy package to use. </ins> ''; <del> description = "Declarative vhost config"; </del> }; <ins> dataDir = mkOption { type = types.path; default = "/var/lib/caddy"; description = '' The data directory for caddy. </ins> <del> user = mkOption { default = "caddy"; type = types.str; description = "User account under which caddy runs."; </del> <ins> <note> <para> 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. </para> <para> Caddy v2 replaced <literal>CADDYPATH</literal> with XDG directories. See <link xlink:href="https://caddyserver.com/docs/conventions#file-locations"/>. </para> </note> ''; </ins> }; <del> group = mkOption { default = "caddy"; type = types.str; description = "Group account under which caddy runs."; </del> <ins> logDir = mkOption { type = types.path; default = "/var/log/caddy"; description = '' Directory for storing Caddy access logs. <note><para> 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. </para></note> ''; }; logFormat = mkOption { type = types.lines; default = '' level ERROR ''; example = literalExpression '' mkForce "level INFO"; ''; description = '' Configuration for the default logger. See <link xlink:href="https://caddyserver.com/docs/caddyfile/options#log"/> 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. ''; </ins> }; adapter = mkOption {

Discussion:
Describe the bug When i try to import a config file, i get an error: When i copy the exact same text in , it does work. The file does exist and i can read it, even with the caddy user! So this seem to be a bug. To Reproduce Steps to reproduce the behavior: ... ... ... Expected behavior A clear and concise description of what you expected to happen. Additional context Add any other context about the problem here. Notify maintainers Metadata system: host os: multi-user?: sandbox: version: channels(root): nixpkgs: Maintainer information:
Without knowing anything about my first guess would be this: Is it possible this command is evaluating your configuration? If so, the issue is that the command is being run sandboxed so has no access outside the sandbox to your file. Build time commands can be tough.
That would make sense. So NixOS brakes the import feature.
Out of curiosity can you please paste your relevant config?
what exactly do you want to see? For a workaround, i just copied everything into the NixOS option: But in some cases, it's nice to have the webserver config. together with the website sourcecode:
Thanks for posting. The context helps. I think this is what you want:
It could make sense to add an off-by-default option to opt-out build-time configuration check.
Given my above solution, I'm not sure what value it would offer in this scenario. What scenario did you have in mind Can this config file hold secrets maybe?
Then you don't have to use such a workaround on NixOS and can just use it as described in it's documentation. There can be secrets in it, but i don't see how that's relevant here:
Hi, module maintainer here. I was the person who made the original PR for adapting s for Caddy 2, and I had no idea about the directive at the time. The motivation was to generate TLS settings from the module options and merge them with JSON-ified . Please use as suggested for now, and I'll see how I can make this easier in the future.
I marked this as stale due to inactivity. &rarr;