-{ lib, config, ... }: with lib;
+{ lib, config, pkgs, ... }: with lib;
let
cfg = config.services.websites;
in
description = "Name of the httpd instance to assign this type to";
};
ips = mkOption {
- type = listOf string;
+ type = listOf str;
default = [];
description = "ips to listen to";
};
options = {
enable = mkEnableOption "Add default no-ssl vhost for this instance";
host = mkOption {
- type = string;
+ type = str;
description = "The hostname to use for this vhost";
};
root = mkOption {
description = "The root folder to serve";
};
indexFile = mkOption {
- type = string;
+ type = str;
default = "index.html";
description = "The index file to show.";
};
description = "The fallback vhost that will be defined as first vhost in Apache";
type = submodule {
options = {
- certName = mkOption { type = string; };
- hosts = mkOption { type = listOf string; };
+ certName = mkOption { type = str; };
+ hosts = mkOption { type = listOf str; };
root = mkOption { type = nullOr path; };
+ forceSSL = mkOption {
+ type = bool;
+ default = true;
+ description = ''
+ Automatically create a corresponding non-ssl vhost
+ that will only redirect to the ssl version
+ '';
+ };
extraConfig = mkOption { type = listOf lines; default = []; };
};
};
description = "List of no ssl vhosts to define for Apache";
type = attrsOf (submodule {
options = {
- hosts = mkOption { type = listOf string; };
+ hosts = mkOption { type = listOf str; };
root = mkOption { type = nullOr path; };
extraConfig = mkOption { type = listOf lines; default = []; };
};
description = "List of vhosts to define for Apache";
type = attrsOf (submodule {
options = {
- certName = mkOption { type = string; };
+ certName = mkOption { type = str; };
addToCerts = mkOption {
type = bool;
default = false;
description = "Use these to certificates. Is ignored (considered true) if certMainHost is not null";
};
certMainHost = mkOption {
- type = nullOr string;
+ type = nullOr str;
description = "Use that host as 'main host' for acme certs";
default = null;
};
- hosts = mkOption { type = listOf string; };
+ hosts = mkOption { type = listOf str; };
root = mkOption { type = nullOr path; };
+ forceSSL = mkOption {
+ type = bool;
+ default = true;
+ description = ''
+ Automatically create a corresponding non-ssl vhost
+ that will only redirect to the ssl version
+ '';
+ };
extraConfig = mkOption { type = listOf lines; default = []; };
};
});
};
watchPaths = mkOption {
- type = listOf string;
+ type = listOf str;
default = [];
description = ''
Paths to watch that should trigger a reload of httpd
};
config.services.httpd = let
- redirectVhost = ips: { # Should go last, catchall http -> https redirect
- listen = map (ip: { inherit ip; port = 80; }) ips;
- hostName = "redirectSSL";
- serverAliases = [ "*" ];
- enableSSL = false;
- logFormat = "combinedVhost";
- documentRoot = "/var/lib/acme/acme-challenge";
- extraConfig = ''
- RewriteEngine on
- RewriteCond "%{REQUEST_URI}" "!^/\.well-known"
- RewriteRule ^(.+) https://%{HTTP_HOST}$1 [R=301]
- # To redirect in specific "VirtualHost *:80", do
- # RedirectMatch 301 ^/((?!\.well-known.*$).*)$ https://host/$1
- # rather than rewrite
- '';
- };
nosslVhost = ips: cfg: {
listen = map (ip: { inherit ip; port = 80; }) ips;
hostName = cfg.host;
- enableSSL = false;
logFormat = "combinedVhost";
documentRoot = cfg.root;
extraConfig = ''
'';
};
toVhost = ips: vhostConf: {
- enableSSL = true;
- sslServerCert = "${config.security.acme2.certs."${vhostConf.certName}".directory}/cert.pem";
- sslServerKey = "${config.security.acme2.certs."${vhostConf.certName}".directory}/key.pem";
- sslServerChain = "${config.security.acme2.certs."${vhostConf.certName}".directory}/chain.pem";
+ forceSSL = vhostConf.forceSSL or true;
+ useACMEHost = vhostConf.certName;
logFormat = "combinedVhost";
- listen = map (ip: { inherit ip; port = 443; }) ips;
+ listen = if vhostConf.forceSSL
+ then lists.flatten (map (ip: [{ inherit ip; port = 443; ssl = true; } { inherit ip; port = 80; }]) ips)
+ else map (ip: { inherit ip; port = 443; ssl = true; }) ips;
hostName = builtins.head vhostConf.hosts;
serverAliases = builtins.tail vhostConf.hosts or [];
documentRoot = vhostConf.root;
extraConfig = builtins.concatStringsSep "\n" vhostConf.extraConfig;
};
toVhostNoSSL = ips: vhostConf: {
- enableSSL = false;
logFormat = "combinedVhost";
listen = map (ip: { inherit ip; port = 80; }) ips;
hostName = builtins.head vhostConf.hosts;
in attrsets.mapAttrs' (name: icfg: attrsets.nameValuePair
icfg.httpdName (mkIf icfg.enable {
enable = true;
- listen = map (ip: { inherit ip; port = 443; }) icfg.ips;
- stateDir = "/run/httpd_${name}";
logPerVirtualHost = true;
multiProcessingModule = "worker";
# https://ssl-config.mozilla.org/#server=apache&version=2.4.41&config=intermediate&openssl=1.0.2t&guideline=5.4
+ # test with https://www.ssllabs.com/ssltest/analyze.html?d=www.immae.eu&s=176.9.151.154&latest
sslProtocols = "all -SSLv3 -TLSv1 -TLSv1.1";
sslCiphers = builtins.concatStringsSep ":" [
"ECDHE-ECDSA-AES128-GCM-SHA256" "ECDHE-RSA-AES128-GCM-SHA256"
logFormat = "combinedVhost";
extraModules = lists.unique icfg.modules;
extraConfig = builtins.concatStringsSep "\n" icfg.extraConfig;
- virtualHosts = [ (toVhost icfg.ips icfg.fallbackVhost) ]
- ++ optionals (icfg.nosslVhost.enable) [ (nosslVhost icfg.ips icfg.nosslVhost) ]
- ++ (attrsets.mapAttrsToList (n: v: toVhostNoSSL icfg.ips v) icfg.vhostNoSSLConfs)
- ++ (attrsets.mapAttrsToList (n: v: toVhost icfg.ips v) icfg.vhostConfs)
- ++ [ (redirectVhost icfg.ips) ];
+
+ virtualHosts = with attrsets; {
+ ___fallbackVhost = toVhost icfg.ips icfg.fallbackVhost;
+ } // (optionalAttrs icfg.nosslVhost.enable {
+ nosslVhost = nosslVhost icfg.ips icfg.nosslVhost;
+ }) // (mapAttrs' (n: v: nameValuePair ("nossl_" + n) (toVhostNoSSL icfg.ips v)) icfg.vhostNoSSLConfs)
+ // (mapAttrs' (n: v: nameValuePair ("ssl_" + n) (toVhost icfg.ips v)) icfg.vhostConfs);
})
) cfg.env;
}
) cfg.env;
- config.security.acme2.certs = let
+ config.security.acme.certs = let
typesToManage = attrsets.filterAttrs (k: v: v.enable) cfg.env;
flatVhosts = lists.flatten (attrsets.mapAttrsToList (k: v:
attrValues v.vhostConfs
(name: path: "ln -s ${path} $out/${cfg.webappDirsName}/${name}") cfg.webappDirs)
}
'';
+
+ config.systemd.services = let
+ package = httpdName: config.services.httpd.${httpdName}.package.out;
+ cfgFile = httpdName: config.services.httpd.${httpdName}.configFile;
+ serviceChange = attrsets.mapAttrs' (name: icfg:
+ attrsets.nameValuePair
+ "httpd${icfg.httpdName}" {
+ stopIfChanged = false;
+ serviceConfig.ExecStart =
+ lib.mkForce "@${package icfg.httpdName}/bin/httpd httpd -f /etc/httpd/httpd_${icfg.httpdName}.conf";
+ serviceConfig.ExecStop =
+ lib.mkForce "${package icfg.httpdName}/bin/httpd -f /etc/httpd/httpd_${icfg.httpdName}.conf -k graceful-stop";
+ serviceConfig.ExecReload =
+ lib.mkForce "${package icfg.httpdName}/bin/httpd -f /etc/httpd/httpd_${icfg.httpdName}.conf -k graceful";
+ }
+ ) cfg.env;
+ serviceReload = attrsets.mapAttrs' (name: icfg:
+ attrsets.nameValuePair
+ "httpd${icfg.httpdName}-config-reload" {
+ wants = [ "httpd${icfg.httpdName}.service" ];
+ wantedBy = [ "multi-user.target" ];
+ restartTriggers = [ (cfgFile icfg.httpdName) ];
+ # commented, because can cause extra delays during activate for this config:
+ # services.nginx.virtualHosts."_".locations."/".proxyPass = "http://blabla:3000";
+ # stopIfChanged = false;
+ serviceConfig.Type = "oneshot";
+ serviceConfig.TimeoutSec = 60;
+ script = ''
+ if ${pkgs.systemd}/bin/systemctl -q is-active httpd${icfg.httpdName}.service ; then
+ ${package icfg.httpdName}/bin/httpd -f /etc/httpd/httpd_${icfg.httpdName}.conf -t && \
+ ${pkgs.systemd}/bin/systemctl reload httpd${icfg.httpdName}.service
+ fi
+ '';
+ serviceConfig.RemainAfterExit = true;
+ }
+ ) cfg.env;
+ in
+ serviceChange // serviceReload;
}