forked from mirrors/nixpkgs
Merge pull request #147973 from aanderse/nixos/caddy
nixos/caddy: introduce several new options
This commit is contained in:
commit
baa0e61569
|
@ -4,159 +4,81 @@ with lib;
|
|||
|
||||
let
|
||||
cfg = config.services.caddy;
|
||||
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
|
||||
'';
|
||||
virtualHosts = attrValues cfg.virtualHosts;
|
||||
acmeVHosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts;
|
||||
|
||||
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
|
||||
mkVHostConf = hostOpts:
|
||||
let
|
||||
sslCertDir = config.security.acme.certs.${hostOpts.useACMEHost}.directory;
|
||||
in
|
||||
''
|
||||
else
|
||||
adaptedConfig;
|
||||
${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;
|
||||
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";
|
||||
|
||||
config = mkOption {
|
||||
default = "";
|
||||
example = ''
|
||||
example.com {
|
||||
encode gzip
|
||||
log
|
||||
root /srv/http
|
||||
}
|
||||
'';
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Verbatim Caddyfile to use.
|
||||
Caddy v2 supports multiple config formats via adapters (see <option>services.caddy.adapter</option>).
|
||||
'';
|
||||
};
|
||||
|
||||
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
|
||||
'''''';
|
||||
};
|
||||
};
|
||||
'';
|
||||
description = "Declarative vhost config";
|
||||
};
|
||||
|
||||
|
||||
user = mkOption {
|
||||
default = "caddy";
|
||||
type = types.str;
|
||||
description = "User account under which caddy runs.";
|
||||
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>
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
default = "caddy";
|
||||
type = types.str;
|
||||
description = "Group account under which caddy runs.";
|
||||
};
|
||||
|
||||
adapter = mkOption {
|
||||
default = "caddyfile";
|
||||
example = "nginx";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Name of the config adapter to use.
|
||||
See https://caddyserver.com/docs/config-adapters for the full list.
|
||||
'';
|
||||
};
|
||||
Group account under which caddy runs.
|
||||
|
||||
resume = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Use saved config, if any (and prefer over configuration passed with <option>services.caddy.config</option>).
|
||||
'';
|
||||
};
|
||||
|
||||
ca = mkOption {
|
||||
default = "https://acme-v02.api.letsencrypt.org/directory";
|
||||
example = "https://acme-staging-v02.api.letsencrypt.org/directory";
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
Certificate authority ACME server. The default (Let's Encrypt
|
||||
production server) should be fine for most people. Set it to null if
|
||||
you don't want to include any authority (or if you want to write a more
|
||||
fine-graned configuration manually)
|
||||
'';
|
||||
};
|
||||
|
||||
email = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = "Email address (for Let's Encrypt certificate)";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
default = "/var/lib/caddy";
|
||||
type = types.path;
|
||||
description = ''
|
||||
The data directory, for storing certificates. Before 17.09, this
|
||||
would create a .caddy directory. With 17.09 the contents of the
|
||||
.caddy directory are in the specified data directory instead.
|
||||
|
||||
Caddy v2 replaced CADDYPATH with XDG directories.
|
||||
See https://caddyserver.com/docs/conventions#file-locations.
|
||||
<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>
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -168,11 +90,176 @@ in
|
|||
Caddy package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/caddy";
|
||||
description = ''
|
||||
The data directory for caddy.
|
||||
|
||||
<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>
|
||||
'';
|
||||
};
|
||||
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
adapter = mkOption {
|
||||
default = "caddyfile";
|
||||
example = "nginx";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Name of the config adapter to use.
|
||||
See <link xlink:href="https://caddyserver.com/docs/config-adapters"/>
|
||||
for the full list.
|
||||
|
||||
<note><para>
|
||||
Any value other than <literal>caddyfile</literal> is only valid when
|
||||
providing your own <option>configFile</option>.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
resume = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Use saved config, if any (and prefer over any specified configuration passed with <literal>--config</literal>).
|
||||
'';
|
||||
};
|
||||
|
||||
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 <literal>Caddyfile</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
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 = "https://acme-v02.api.letsencrypt.org/directory";
|
||||
example = "https://acme-staging-v02.api.letsencrypt.org/directory";
|
||||
type = with types; nullOr str;
|
||||
description = ''
|
||||
The URL to the ACME CA's directory. It is strongly recommended to set
|
||||
this to Let's Encrypt's staging endpoint for testing or development.
|
||||
|
||||
Set it to <literal>null</literal> if you want to write a more
|
||||
fine-grained configuration manually.
|
||||
'';
|
||||
};
|
||||
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
# implementation
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{ assertion = cfg.adapter != "caddyfile" -> cfg.configFile != configFile;
|
||||
message = "Any value other than 'caddyfile' is only valid when providing your own `services.caddy.configFile`";
|
||||
}
|
||||
];
|
||||
|
||||
services.caddy.extraConfig = concatMapStringsSep "\n" mkVHostConf virtualHosts;
|
||||
|
||||
systemd.packages = [ cfg.package ];
|
||||
systemd.services.caddy = {
|
||||
wants = map (hostOpts: "acme-finished-${hostOpts.useACMEHost}.target") acmeVHosts;
|
||||
after = map (hostOpts: "acme-selfsigned-${hostOpts.useACMEHost}.service") acmeVHosts;
|
||||
before = map (hostOpts: "acme-${hostOpts.useACMEHost}.service") acmeVHosts;
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
startLimitIntervalSec = 14400;
|
||||
startLimitBurst = 10;
|
||||
|
@ -180,13 +267,17 @@ in
|
|||
serviceConfig = {
|
||||
# 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 ${optionalString cfg.resume "--resume"} --config ${configJSON}" ];
|
||||
ExecReload = [ "" "${cfg.package}/bin/caddy reload --config ${configJSON}" ];
|
||||
ExecStart = [ "" "${cfg.package}/bin/caddy run --config ${cfg.configFile} --adapter ${cfg.adapter} ${optionalString cfg.resume "--resume"}" ];
|
||||
ExecReload = [ "" "${cfg.package}/bin/caddy reload --config ${cfg.configFile} --adapter ${cfg.adapter}" ];
|
||||
|
||||
ExecStartPre = "${cfg.package}/bin/caddy validate --config ${cfg.configFile} --adapter ${cfg.adapter}";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
ReadWriteDirectories = cfg.dataDir;
|
||||
StateDirectory = mkIf (cfg.dataDir == "/var/lib/caddy") [ "caddy" ];
|
||||
LogsDirectory = mkIf (cfg.logDir == "/var/log/caddy") [ "caddy" ];
|
||||
Restart = "on-abnormal";
|
||||
SupplementaryGroups = mkIf (length acmeVHosts != 0) [ "acme" ];
|
||||
|
||||
# TODO: attempt to upstream these options
|
||||
NoNewPrivileges = true;
|
||||
|
@ -200,7 +291,6 @@ in
|
|||
group = cfg.group;
|
||||
uid = config.ids.uids.caddy;
|
||||
home = cfg.dataDir;
|
||||
createHome = true;
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -208,5 +298,12 @@ in
|
|||
caddy.gid = config.ids.gids.caddy;
|
||||
};
|
||||
|
||||
security.acme.certs =
|
||||
let
|
||||
eachACMEHost = unique (catAttrs "useACMEHost" acmeVHosts);
|
||||
reloads = map (useACMEHost: nameValuePair useACMEHost { reloadServices = [ "caddy.service" ]; }) eachACMEHost;
|
||||
in
|
||||
listToAttrs reloads;
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
# 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;
|
||||
{ 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 = types.listOf types.str;
|
||||
type = with types; listOf str;
|
||||
default = [ ];
|
||||
example = [ "www.example.org" "example.org" ];
|
||||
description = ''
|
||||
|
@ -17,12 +21,59 @@ with lib;
|
|||
'';
|
||||
};
|
||||
|
||||
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.
|
||||
|
||||
<emphasis>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 <xref linkend="opt-security.acme.certs"/>. Additionally,
|
||||
you should probably add the <literal>caddy</literal> user to the
|
||||
<literal>acme</literal> group to grant access to the certificates.</emphasis>
|
||||
'';
|
||||
};
|
||||
|
||||
logFormat = mkOption {
|
||||
type = types.lines;
|
||||
default = ''
|
||||
output file ${cfg.logDir}/access-${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
|
||||
<link xlink:href="https://caddyserver.com/docs/caddyfile/directives/log#log"/>
|
||||
for details.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
These lines go into the vhost verbatim
|
||||
Additional lines of configuration appended to this virtual host in the
|
||||
automatically generated <literal>Caddyfile</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue