uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
148
nixos/modules/services/web-servers/agate.nix
Normal file
148
nixos/modules/services/web-servers/agate.nix
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.agate;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.agate = {
|
||||
enable = mkEnableOption "Agate Server";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.agate;
|
||||
defaultText = literalExpression "pkgs.agate";
|
||||
description = "The package to use";
|
||||
};
|
||||
|
||||
addresses = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "0.0.0.0:1965" ];
|
||||
description = ''
|
||||
Addresses to listen on, IP:PORT, if you haven't disabled forwarding
|
||||
only set IPv4.
|
||||
'';
|
||||
};
|
||||
|
||||
contentDir = mkOption {
|
||||
default = "/var/lib/agate/content";
|
||||
type = types.path;
|
||||
description = "Root of the content directory.";
|
||||
};
|
||||
|
||||
certificatesDir = mkOption {
|
||||
default = "/var/lib/agate/certificates";
|
||||
type = types.path;
|
||||
description = "Root of the certificate directory.";
|
||||
};
|
||||
|
||||
hostnames = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Domain name of this Gemini server, enables checking hostname and port
|
||||
in requests. (multiple occurences means basic vhosts)
|
||||
'';
|
||||
};
|
||||
|
||||
language = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = "RFC 4646 Language code for text/gemini documents.";
|
||||
};
|
||||
|
||||
onlyTls_1_3 = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Only use TLSv1.3 (default also allows TLSv1.2).";
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "" ];
|
||||
example = [ "--log-ip" ];
|
||||
description = "Extra arguments to use running agate.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# available for generating certs by hand
|
||||
# it can be a bit arduous with openssl
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
systemd.services.agate = {
|
||||
description = "Agate";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "network-online.target" ];
|
||||
|
||||
script =
|
||||
let
|
||||
prefixKeyList = key: list: concatMap (v: [ key v ]) list;
|
||||
addresses = prefixKeyList "--addr" cfg.addresses;
|
||||
hostnames = prefixKeyList "--hostname" cfg.hostnames;
|
||||
in
|
||||
''
|
||||
exec ${cfg.package}/bin/agate ${
|
||||
escapeShellArgs (
|
||||
[
|
||||
"--content" "${cfg.contentDir}"
|
||||
"--certs" "${cfg.certificatesDir}"
|
||||
] ++
|
||||
addresses ++
|
||||
(optionals (cfg.hostnames != []) hostnames) ++
|
||||
(optionals (cfg.language != null) [ "--lang" cfg.language ]) ++
|
||||
(optionals cfg.onlyTls_1_3 [ "--only-tls13" ]) ++
|
||||
(optionals (cfg.extraArgs != []) cfg.extraArgs)
|
||||
)
|
||||
}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
DynamicUser = true;
|
||||
StateDirectory = "agate";
|
||||
|
||||
# Security options:
|
||||
AmbientCapabilities = "";
|
||||
CapabilityBoundingSet = "";
|
||||
|
||||
# ProtectClock= adds DeviceAllow=char-rtc r
|
||||
DeviceAllow = "";
|
||||
|
||||
LockPersonality = true;
|
||||
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = true;
|
||||
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
|
||||
RestrictNamespaces = true;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
RestrictRealtime = true;
|
||||
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@cpu-emulation"
|
||||
"~@debug"
|
||||
"~@keyring"
|
||||
"~@memlock"
|
||||
"~@obsolete"
|
||||
"~@privileged"
|
||||
"~@setuid"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
834
nixos/modules/services/web-servers/apache-httpd/default.nix
Normal file
834
nixos/modules/services/web-servers/apache-httpd/default.nix
Normal file
|
|
@ -0,0 +1,834 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.httpd;
|
||||
|
||||
certs = config.security.acme.certs;
|
||||
|
||||
runtimeDir = "/run/httpd";
|
||||
|
||||
pkg = cfg.package.out;
|
||||
|
||||
apachectl = pkgs.runCommand "apachectl" { meta.priority = -1; } ''
|
||||
mkdir -p $out/bin
|
||||
cp ${pkg}/bin/apachectl $out/bin/apachectl
|
||||
sed -i $out/bin/apachectl -e 's|$HTTPD -t|$HTTPD -t -f /etc/httpd/httpd.conf|'
|
||||
'';
|
||||
|
||||
php = cfg.phpPackage.override { apacheHttpd = pkg; };
|
||||
|
||||
phpModuleName = let
|
||||
majorVersion = lib.versions.major (lib.getVersion php);
|
||||
in (if majorVersion == "8" then "php" else "php${majorVersion}");
|
||||
|
||||
mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
|
||||
|
||||
vhosts = attrValues cfg.virtualHosts;
|
||||
|
||||
# certName is used later on to determine systemd service names.
|
||||
acmeEnabledVhosts = map (hostOpts: hostOpts // {
|
||||
certName = if hostOpts.useACMEHost != null then hostOpts.useACMEHost else hostOpts.hostName;
|
||||
}) (filter (hostOpts: hostOpts.enableACME || hostOpts.useACMEHost != null) vhosts);
|
||||
|
||||
dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts);
|
||||
|
||||
mkListenInfo = hostOpts:
|
||||
if hostOpts.listen != [] then
|
||||
hostOpts.listen
|
||||
else
|
||||
optionals (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) (map (addr: { ip = addr; port = 443; ssl = true; }) hostOpts.listenAddresses) ++
|
||||
optionals (!hostOpts.onlySSL) (map (addr: { ip = addr; port = 80; ssl = false; }) hostOpts.listenAddresses)
|
||||
;
|
||||
|
||||
listenInfo = unique (concatMap mkListenInfo vhosts);
|
||||
|
||||
enableHttp2 = any (vhost: vhost.http2) vhosts;
|
||||
enableSSL = any (listen: listen.ssl) listenInfo;
|
||||
enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
|
||||
|
||||
# NOTE: generally speaking order of modules is very important
|
||||
modules =
|
||||
[ # required apache modules our httpd service cannot run without
|
||||
"authn_core" "authz_core"
|
||||
"log_config"
|
||||
"mime" "autoindex" "negotiation" "dir"
|
||||
"alias" "rewrite"
|
||||
"unixd" "slotmem_shm" "socache_shmcb"
|
||||
"mpm_${cfg.mpm}"
|
||||
]
|
||||
++ (if cfg.mpm == "prefork" then [ "cgi" ] else [ "cgid" ])
|
||||
++ optional enableHttp2 "http2"
|
||||
++ optional enableSSL "ssl"
|
||||
++ optional enableUserDir "userdir"
|
||||
++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
|
||||
++ optional cfg.enablePHP { name = phpModuleName; path = "${php}/modules/lib${phpModuleName}.so"; }
|
||||
++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
|
||||
++ cfg.extraModules;
|
||||
|
||||
loggingConf = (if cfg.logFormat != "none" then ''
|
||||
ErrorLog ${cfg.logDir}/error.log
|
||||
|
||||
LogLevel notice
|
||||
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b" common
|
||||
LogFormat "%{Referer}i -> %U" referer
|
||||
LogFormat "%{User-agent}i" agent
|
||||
|
||||
CustomLog ${cfg.logDir}/access.log ${cfg.logFormat}
|
||||
'' else ''
|
||||
ErrorLog /dev/null
|
||||
'');
|
||||
|
||||
|
||||
browserHacks = ''
|
||||
<IfModule mod_setenvif.c>
|
||||
BrowserMatch "Mozilla/2" nokeepalive
|
||||
BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
|
||||
BrowserMatch "RealPlayer 4\.0" force-response-1.0
|
||||
BrowserMatch "Java/1\.0" force-response-1.0
|
||||
BrowserMatch "JDK/1\.0" force-response-1.0
|
||||
BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
|
||||
BrowserMatch "^WebDrive" redirect-carefully
|
||||
BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
|
||||
BrowserMatch "^gnome-vfs" redirect-carefully
|
||||
</IfModule>
|
||||
'';
|
||||
|
||||
|
||||
sslConf = ''
|
||||
<IfModule mod_ssl.c>
|
||||
SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
|
||||
|
||||
Mutex posixsem
|
||||
|
||||
SSLRandomSeed startup builtin
|
||||
SSLRandomSeed connect builtin
|
||||
|
||||
SSLProtocol ${cfg.sslProtocols}
|
||||
SSLCipherSuite ${cfg.sslCiphers}
|
||||
SSLHonorCipherOrder on
|
||||
</IfModule>
|
||||
'';
|
||||
|
||||
|
||||
mimeConf = ''
|
||||
TypesConfig ${pkg}/conf/mime.types
|
||||
|
||||
AddType application/x-x509-ca-cert .crt
|
||||
AddType application/x-pkcs7-crl .crl
|
||||
AddType application/x-httpd-php .php .phtml
|
||||
|
||||
<IfModule mod_mime_magic.c>
|
||||
MIMEMagicFile ${pkg}/conf/magic
|
||||
</IfModule>
|
||||
'';
|
||||
|
||||
luaSetPaths = let
|
||||
# support both lua and lua.withPackages derivations
|
||||
luaversion = cfg.package.lua5.lua.luaversion or cfg.package.lua5.luaversion;
|
||||
in
|
||||
''
|
||||
<IfModule mod_lua.c>
|
||||
LuaPackageCPath ${cfg.package.lua5}/lib/lua/${luaversion}/?.so
|
||||
LuaPackagePath ${cfg.package.lua5}/share/lua/${luaversion}/?.lua
|
||||
</IfModule>
|
||||
'';
|
||||
|
||||
mkVHostConf = hostOpts:
|
||||
let
|
||||
adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
|
||||
listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts);
|
||||
listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts);
|
||||
|
||||
useACME = hostOpts.enableACME || hostOpts.useACMEHost != null;
|
||||
sslCertDir =
|
||||
if hostOpts.enableACME then certs.${hostOpts.hostName}.directory
|
||||
else if hostOpts.useACMEHost != null then certs.${hostOpts.useACMEHost}.directory
|
||||
else abort "This case should never happen.";
|
||||
|
||||
sslServerCert = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerCert;
|
||||
sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey;
|
||||
sslServerChain = if useACME then "${sslCertDir}/chain.pem" else hostOpts.sslServerChain;
|
||||
|
||||
acmeChallenge = optionalString (useACME && hostOpts.acmeRoot != null) ''
|
||||
Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/"
|
||||
<Directory "${hostOpts.acmeRoot}">
|
||||
AllowOverride None
|
||||
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
|
||||
Require method GET POST OPTIONS
|
||||
Require all granted
|
||||
</Directory>
|
||||
'';
|
||||
in
|
||||
optionalString (listen != []) ''
|
||||
<VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}>
|
||||
ServerName ${hostOpts.hostName}
|
||||
${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
|
||||
ServerAdmin ${adminAddr}
|
||||
<IfModule mod_ssl.c>
|
||||
SSLEngine off
|
||||
</IfModule>
|
||||
${acmeChallenge}
|
||||
${if hostOpts.forceSSL then ''
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC]
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
|
||||
</IfModule>
|
||||
'' else mkVHostCommonConf hostOpts}
|
||||
</VirtualHost>
|
||||
'' +
|
||||
optionalString (listenSSL != []) ''
|
||||
<VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}>
|
||||
ServerName ${hostOpts.hostName}
|
||||
${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
|
||||
ServerAdmin ${adminAddr}
|
||||
SSLEngine on
|
||||
SSLCertificateFile ${sslServerCert}
|
||||
SSLCertificateKeyFile ${sslServerKey}
|
||||
${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"}
|
||||
${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"}
|
||||
${acmeChallenge}
|
||||
${mkVHostCommonConf hostOpts}
|
||||
</VirtualHost>
|
||||
''
|
||||
;
|
||||
|
||||
mkVHostCommonConf = hostOpts:
|
||||
let
|
||||
documentRoot = if hostOpts.documentRoot != null
|
||||
then hostOpts.documentRoot
|
||||
else pkgs.emptyDirectory
|
||||
;
|
||||
|
||||
mkLocations = locations: concatStringsSep "\n" (map (config: ''
|
||||
<Location ${config.location}>
|
||||
${optionalString (config.proxyPass != null) ''
|
||||
<IfModule mod_proxy.c>
|
||||
ProxyPass ${config.proxyPass}
|
||||
ProxyPassReverse ${config.proxyPass}
|
||||
</IfModule>
|
||||
''}
|
||||
${optionalString (config.index != null) ''
|
||||
<IfModule mod_dir.c>
|
||||
DirectoryIndex ${config.index}
|
||||
</IfModule>
|
||||
''}
|
||||
${optionalString (config.alias != null) ''
|
||||
<IfModule mod_alias.c>
|
||||
Alias "${config.alias}"
|
||||
</IfModule>
|
||||
''}
|
||||
${config.extraConfig}
|
||||
</Location>
|
||||
'') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
|
||||
in
|
||||
''
|
||||
${optionalString cfg.logPerVirtualHost ''
|
||||
ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log
|
||||
CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat}
|
||||
''}
|
||||
|
||||
${optionalString (hostOpts.robotsEntries != "") ''
|
||||
Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries}
|
||||
''}
|
||||
|
||||
DocumentRoot "${documentRoot}"
|
||||
|
||||
<Directory "${documentRoot}">
|
||||
Options Indexes FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
${optionalString hostOpts.enableUserDir ''
|
||||
UserDir public_html
|
||||
UserDir disabled root
|
||||
<Directory "/home/*/public_html">
|
||||
AllowOverride FileInfo AuthConfig Limit Indexes
|
||||
Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
|
||||
<Limit GET POST OPTIONS>
|
||||
Require all granted
|
||||
</Limit>
|
||||
<LimitExcept GET POST OPTIONS>
|
||||
Require all denied
|
||||
</LimitExcept>
|
||||
</Directory>
|
||||
''}
|
||||
|
||||
${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") ''
|
||||
RedirectPermanent / ${hostOpts.globalRedirect}
|
||||
''}
|
||||
|
||||
${
|
||||
let makeDirConf = elem: ''
|
||||
Alias ${elem.urlPath} ${elem.dir}/
|
||||
<Directory ${elem.dir}>
|
||||
Options +Indexes
|
||||
Require all granted
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
'';
|
||||
in concatMapStrings makeDirConf hostOpts.servedDirs
|
||||
}
|
||||
|
||||
${mkLocations hostOpts.locations}
|
||||
${hostOpts.extraConfig}
|
||||
''
|
||||
;
|
||||
|
||||
|
||||
confFile = pkgs.writeText "httpd.conf" ''
|
||||
|
||||
ServerRoot ${pkg}
|
||||
ServerName ${config.networking.hostName}
|
||||
DefaultRuntimeDir ${runtimeDir}/runtime
|
||||
|
||||
PidFile ${runtimeDir}/httpd.pid
|
||||
|
||||
${optionalString (cfg.mpm != "prefork") ''
|
||||
# mod_cgid requires this.
|
||||
ScriptSock ${runtimeDir}/cgisock
|
||||
''}
|
||||
|
||||
<IfModule prefork.c>
|
||||
MaxClients ${toString cfg.maxClients}
|
||||
MaxRequestsPerChild ${toString cfg.maxRequestsPerChild}
|
||||
</IfModule>
|
||||
|
||||
${let
|
||||
toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}";
|
||||
uniqueListen = uniqList {inputList = map toStr listenInfo;};
|
||||
in concatStringsSep "\n" uniqueListen
|
||||
}
|
||||
|
||||
User ${cfg.user}
|
||||
Group ${cfg.group}
|
||||
|
||||
${let
|
||||
mkModule = module:
|
||||
if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; }
|
||||
else if isAttrs module then { inherit (module) name path; }
|
||||
else throw "Expecting either a string or attribute set including a name and path.";
|
||||
in
|
||||
concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
|
||||
}
|
||||
|
||||
AddHandler type-map var
|
||||
|
||||
<Files ~ "^\.ht">
|
||||
Require all denied
|
||||
</Files>
|
||||
|
||||
${mimeConf}
|
||||
${loggingConf}
|
||||
${browserHacks}
|
||||
|
||||
Include ${pkg}/conf/extra/httpd-default.conf
|
||||
Include ${pkg}/conf/extra/httpd-autoindex.conf
|
||||
Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf
|
||||
Include ${pkg}/conf/extra/httpd-languages.conf
|
||||
|
||||
TraceEnable off
|
||||
|
||||
${sslConf}
|
||||
|
||||
${optionalString cfg.package.luaSupport luaSetPaths}
|
||||
|
||||
# Fascist default - deny access to everything.
|
||||
<Directory />
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all denied
|
||||
</Directory>
|
||||
|
||||
# But do allow access to files in the store so that we don't have
|
||||
# to generate <Directory> clauses for every generated file that we
|
||||
# want to serve.
|
||||
<Directory /nix/store>
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
${cfg.extraConfig}
|
||||
|
||||
${concatMapStringsSep "\n" mkVHostConf vhosts}
|
||||
'';
|
||||
|
||||
# Generate the PHP configuration file. Should probably be factored
|
||||
# out into a separate module.
|
||||
phpIni = pkgs.runCommand "php.ini"
|
||||
{ options = cfg.phpOptions;
|
||||
preferLocalBuild = true;
|
||||
}
|
||||
''
|
||||
cat ${php}/etc/php.ini > $out
|
||||
cat ${php.phpIni} > $out
|
||||
echo "$options" >> $out
|
||||
'';
|
||||
|
||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
|
||||
in
|
||||
|
||||
|
||||
{
|
||||
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
|
||||
(mkRenamedOptionModule [ "services" "httpd" "multiProcessingModule" ] [ "services" "httpd" "mpm" ])
|
||||
|
||||
# virtualHosts options
|
||||
(mkRemovedOptionModule [ "services" "httpd" "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
(mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
|
||||
];
|
||||
|
||||
# interface
|
||||
|
||||
options = {
|
||||
|
||||
services.httpd = {
|
||||
|
||||
enable = mkEnableOption "the Apache HTTP Server";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.apacheHttpd;
|
||||
defaultText = literalExpression "pkgs.apacheHttpd";
|
||||
description = ''
|
||||
Overridable attribute of the Apache HTTP Server package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
default = confFile;
|
||||
defaultText = literalExpression "confFile";
|
||||
example = literalExpression ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
|
||||
description = ''
|
||||
Override the configuration file used by Apache. By default,
|
||||
NixOS generates one automatically.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Configuration lines appended to the generated Apache
|
||||
configuration file. Note that this mechanism will not work
|
||||
when <option>configFile</option> is overridden.
|
||||
'';
|
||||
};
|
||||
|
||||
extraModules = mkOption {
|
||||
type = types.listOf types.unspecified;
|
||||
default = [];
|
||||
example = literalExpression ''
|
||||
[
|
||||
"proxy_connect"
|
||||
{ name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
Additional Apache modules to be used. These can be
|
||||
specified as a string in the case of modules distributed
|
||||
with Apache, or as an attribute set specifying the
|
||||
<varname>name</varname> and <varname>path</varname> of the
|
||||
module.
|
||||
'';
|
||||
};
|
||||
|
||||
adminAddr = mkOption {
|
||||
type = types.str;
|
||||
example = "admin@example.org";
|
||||
description = "E-mail address of the server administrator.";
|
||||
};
|
||||
|
||||
logFormat = mkOption {
|
||||
type = types.str;
|
||||
default = "common";
|
||||
example = "combined";
|
||||
description = ''
|
||||
Log format for log files. Possible values are: combined, common, referer, agent, none.
|
||||
See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
logPerVirtualHost = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
If enabled, each virtual host gets its own
|
||||
<filename>access.log</filename> and
|
||||
<filename>error.log</filename>, namely suffixed by the
|
||||
<option>hostName</option> of the virtual host.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "wwwrun";
|
||||
description = ''
|
||||
User account under which httpd children processes run.
|
||||
|
||||
If you require the main httpd process to run as
|
||||
<literal>root</literal> add the following configuration:
|
||||
<programlisting>
|
||||
systemd.services.httpd.serviceConfig.User = lib.mkForce "root";
|
||||
</programlisting>
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "wwwrun";
|
||||
description = ''
|
||||
Group under which httpd children processes run.
|
||||
'';
|
||||
};
|
||||
|
||||
logDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/log/httpd";
|
||||
description = ''
|
||||
Directory for Apache's log files. It is created automatically.
|
||||
'';
|
||||
};
|
||||
|
||||
virtualHosts = mkOption {
|
||||
type = with types; attrsOf (submodule (import ./vhost-options.nix));
|
||||
default = {
|
||||
localhost = {
|
||||
documentRoot = "${pkg}/htdocs";
|
||||
};
|
||||
};
|
||||
defaultText = literalExpression ''
|
||||
{
|
||||
localhost = {
|
||||
documentRoot = "''${package.out}/htdocs";
|
||||
};
|
||||
}
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
"foo.example.com" = {
|
||||
forceSSL = true;
|
||||
documentRoot = "/var/www/foo.example.com"
|
||||
};
|
||||
"bar.example.com" = {
|
||||
addSSL = true;
|
||||
documentRoot = "/var/www/bar.example.com";
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Specification of the virtual hosts served by Apache. Each
|
||||
element should be an attribute set specifying the
|
||||
configuration of the virtual host.
|
||||
'';
|
||||
};
|
||||
|
||||
enableMellon = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the mod_auth_mellon module.";
|
||||
};
|
||||
|
||||
enablePHP = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the PHP module.";
|
||||
};
|
||||
|
||||
phpPackage = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.php;
|
||||
defaultText = literalExpression "pkgs.php";
|
||||
description = ''
|
||||
Overridable attribute of the PHP package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
enablePerl = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the Perl module (mod_perl).";
|
||||
};
|
||||
|
||||
phpOptions = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example =
|
||||
''
|
||||
date.timezone = "CET"
|
||||
'';
|
||||
description = ''
|
||||
Options appended to the PHP configuration file <filename>php.ini</filename>.
|
||||
'';
|
||||
};
|
||||
|
||||
mpm = mkOption {
|
||||
type = types.enum [ "event" "prefork" "worker" ];
|
||||
default = "event";
|
||||
example = "worker";
|
||||
description =
|
||||
''
|
||||
Multi-processing module to be used by Apache. Available
|
||||
modules are <literal>prefork</literal> (handles each
|
||||
request in a separate child process), <literal>worker</literal>
|
||||
(hybrid approach that starts a number of child processes
|
||||
each running a number of threads) and <literal>event</literal>
|
||||
(the default; a recent variant of <literal>worker</literal>
|
||||
that handles persistent connections more efficiently).
|
||||
'';
|
||||
};
|
||||
|
||||
maxClients = mkOption {
|
||||
type = types.int;
|
||||
default = 150;
|
||||
example = 8;
|
||||
description = "Maximum number of httpd processes (prefork)";
|
||||
};
|
||||
|
||||
maxRequestsPerChild = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
example = 500;
|
||||
description = ''
|
||||
Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
|
||||
'';
|
||||
};
|
||||
|
||||
sslCiphers = mkOption {
|
||||
type = types.str;
|
||||
default = "HIGH:!aNULL:!MD5:!EXP";
|
||||
description = "Cipher Suite available for negotiation in SSL proxy handshake.";
|
||||
};
|
||||
|
||||
sslProtocols = mkOption {
|
||||
type = types.str;
|
||||
default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
|
||||
example = "All -SSLv2 -SSLv3";
|
||||
description = "Allowed SSL/TLS protocol versions.";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
# implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = all (hostOpts: !hostOpts.enableSSL) vhosts;
|
||||
message = ''
|
||||
The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it.
|
||||
Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`,
|
||||
or `services.httpd.virtualHosts.<name>.onlySSL`.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts;
|
||||
message = ''
|
||||
Options `services.httpd.virtualHosts.<name>.addSSL`,
|
||||
`services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL`
|
||||
are mutually exclusive.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts;
|
||||
message = ''
|
||||
Options `services.httpd.virtualHosts.<name>.enableACME` and
|
||||
`services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
|
||||
'';
|
||||
}
|
||||
] ++ map (name: mkCertOwnershipAssertion {
|
||||
inherit (cfg) group user;
|
||||
cert = config.security.acme.certs.${name};
|
||||
groups = config.users.groups;
|
||||
}) dependentCertNames;
|
||||
|
||||
warnings =
|
||||
mapAttrsToList (name: hostOpts: ''
|
||||
Using config.services.httpd.virtualHosts."${name}".servedFiles is deprecated and will become unsupported in a future release. Your configuration will continue to work as is but please migrate your configuration to config.services.httpd.virtualHosts."${name}".locations before the 20.09 release of NixOS.
|
||||
'') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "wwwrun") {
|
||||
wwwrun = {
|
||||
group = cfg.group;
|
||||
description = "Apache httpd user";
|
||||
uid = config.ids.uids.wwwrun;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "wwwrun") {
|
||||
wwwrun.gid = config.ids.gids.wwwrun;
|
||||
};
|
||||
|
||||
security.acme.certs = let
|
||||
acmePairs = map (hostOpts: let
|
||||
hasRoot = hostOpts.acmeRoot != null;
|
||||
in nameValuePair hostOpts.hostName {
|
||||
group = mkDefault cfg.group;
|
||||
# if acmeRoot is null inherit config.security.acme
|
||||
# Since config.security.acme.certs.<cert>.webroot's own default value
|
||||
# should take precedence set priority higher than mkOptionDefault
|
||||
webroot = mkOverride (if hasRoot then 1000 else 2000) hostOpts.acmeRoot;
|
||||
# Also nudge dnsProvider to null in case it is inherited
|
||||
dnsProvider = mkOverride (if hasRoot then 1000 else 2000) null;
|
||||
extraDomainNames = hostOpts.serverAliases;
|
||||
# Use the vhost-specific email address if provided, otherwise let
|
||||
# security.acme.email or security.acme.certs.<cert>.email be used.
|
||||
email = mkOverride 2000 (if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr);
|
||||
# Filter for enableACME-only vhosts. Don't want to create dud certs
|
||||
}) (filter (hostOpts: hostOpts.useACMEHost == null) acmeEnabledVhosts);
|
||||
in listToAttrs acmePairs;
|
||||
|
||||
# httpd requires a stable path to the configuration file for reloads
|
||||
environment.etc."httpd/httpd.conf".source = cfg.configFile;
|
||||
environment.systemPackages = [
|
||||
apachectl
|
||||
pkg
|
||||
];
|
||||
|
||||
services.logrotate = optionalAttrs (cfg.logFormat != "none") {
|
||||
enable = mkDefault true;
|
||||
settings.httpd = {
|
||||
files = "${cfg.logDir}/*.log";
|
||||
su = "${cfg.user} ${cfg.group}";
|
||||
frequency = "daily";
|
||||
rotate = 28;
|
||||
sharedscripts = true;
|
||||
compress = true;
|
||||
delaycompress = true;
|
||||
postrotate = "systemctl reload httpd.service > /dev/null 2>/dev/null || true";
|
||||
};
|
||||
};
|
||||
|
||||
services.httpd.phpOptions =
|
||||
''
|
||||
; Don't advertise PHP
|
||||
expose_php = off
|
||||
'' + optionalString (config.time.timeZone != null) ''
|
||||
|
||||
; Apparently PHP doesn't use $TZ.
|
||||
date.timezone = "${config.time.timeZone}"
|
||||
'';
|
||||
|
||||
services.httpd.extraModules = mkBefore [
|
||||
# HTTP authentication mechanisms: basic and digest.
|
||||
"auth_basic" "auth_digest"
|
||||
|
||||
# Authentication: is the user who he claims to be?
|
||||
"authn_file" "authn_dbm" "authn_anon"
|
||||
|
||||
# Authorization: is the user allowed access?
|
||||
"authz_user" "authz_groupfile" "authz_host"
|
||||
|
||||
# Other modules.
|
||||
"ext_filter" "include" "env" "mime_magic"
|
||||
"cern_meta" "expires" "headers" "usertrack" "setenvif"
|
||||
"dav" "status" "asis" "info" "dav_fs"
|
||||
"vhost_alias" "imagemap" "actions" "speling"
|
||||
"proxy" "proxy_http"
|
||||
"cache" "cache_disk"
|
||||
|
||||
# For compatibility with old configurations, the new module mod_access_compat is provided.
|
||||
"access_compat"
|
||||
];
|
||||
|
||||
systemd.tmpfiles.rules =
|
||||
let
|
||||
svc = config.systemd.services.httpd.serviceConfig;
|
||||
in
|
||||
[
|
||||
"d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
|
||||
"Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
|
||||
];
|
||||
|
||||
systemd.services.httpd = {
|
||||
description = "Apache HTTPD";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames);
|
||||
after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames;
|
||||
before = map (certName: "acme-${certName}.service") dependentCertNames;
|
||||
restartTriggers = [ cfg.configFile ];
|
||||
|
||||
path = [ pkg pkgs.coreutils pkgs.gnugrep ];
|
||||
|
||||
environment =
|
||||
optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
|
||||
// optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; };
|
||||
|
||||
preStart =
|
||||
''
|
||||
# Get rid of old semaphores. These tend to accumulate across
|
||||
# server restarts, eventually preventing it from restarting
|
||||
# successfully.
|
||||
for i in $(${pkgs.util-linux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do
|
||||
${pkgs.util-linux}/bin/ipcrm -s $i
|
||||
done
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "@${pkg}/bin/httpd httpd -f /etc/httpd/httpd.conf";
|
||||
ExecStop = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -k graceful-stop";
|
||||
ExecReload = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -k graceful";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
Type = "forking";
|
||||
PIDFile = "${runtimeDir}/httpd.pid";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
RuntimeDirectory = "httpd httpd/runtime";
|
||||
RuntimeDirectoryMode = "0750";
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
};
|
||||
};
|
||||
|
||||
# postRun hooks on cert renew can't be used to restart Apache since renewal
|
||||
# runs as the unprivileged acme user. sslTargets are added to wantedBy + before
|
||||
# which allows the acme-finished-$cert.target to signify the successful updating
|
||||
# of certs end-to-end.
|
||||
systemd.services.httpd-config-reload = let
|
||||
sslServices = map (certName: "acme-${certName}.service") dependentCertNames;
|
||||
sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames;
|
||||
in mkIf (sslServices != []) {
|
||||
wantedBy = sslServices ++ [ "multi-user.target" ];
|
||||
# Before the finished targets, after the renew services.
|
||||
# This service might be needed for HTTP-01 challenges, but we only want to confirm
|
||||
# certs are updated _after_ config has been reloaded.
|
||||
before = sslTargets;
|
||||
after = sslServices;
|
||||
restartTriggers = [ cfg.configFile ];
|
||||
# Block reloading if not all certs exist yet.
|
||||
# Happens when config changes add new vhosts/certs.
|
||||
unitConfig.ConditionPathExists = map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
TimeoutSec = 60;
|
||||
ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active httpd.service";
|
||||
ExecStartPre = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -t";
|
||||
ExecStart = "/run/current-system/systemd/bin/systemctl reload httpd.service";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{ config, lib, name, ... }:
|
||||
let
|
||||
inherit (lib) mkOption types;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
|
||||
proxyPass = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "http://www.example.org/";
|
||||
description = ''
|
||||
Sets up a simple reverse proxy as described by <link xlink:href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html#simple" />.
|
||||
'';
|
||||
};
|
||||
|
||||
index = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "index.php index.html";
|
||||
description = ''
|
||||
Adds DirectoryIndex directive. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex" />.
|
||||
'';
|
||||
};
|
||||
|
||||
alias = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
example = "/your/alias/directory";
|
||||
description = ''
|
||||
Alias directory for requests. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_alias.html#alias" />.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
These lines go to the end of the location verbatim.
|
||||
'';
|
||||
};
|
||||
|
||||
priority = mkOption {
|
||||
type = types.int;
|
||||
default = 1000;
|
||||
description = ''
|
||||
Order of this location block in relation to the others in the vhost.
|
||||
The semantics are the same as with `lib.mkOrder`. Smaller values have
|
||||
a greater priority.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
{ config, lib, name, ... }:
|
||||
let
|
||||
inherit (lib) literalExpression mkOption nameValuePair types;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
|
||||
hostName = mkOption {
|
||||
type = types.str;
|
||||
default = name;
|
||||
description = "Canonical hostname for the server.";
|
||||
};
|
||||
|
||||
serverAliases = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = ["www.example.org" "www.example.org:8080" "example.org"];
|
||||
description = ''
|
||||
Additional names of virtual hosts served by this virtual host configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
listen = mkOption {
|
||||
type = with types; listOf (submodule ({
|
||||
options = {
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
description = "Port to listen on";
|
||||
};
|
||||
ip = mkOption {
|
||||
type = types.str;
|
||||
default = "*";
|
||||
description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all.";
|
||||
};
|
||||
ssl = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable SSL (https) support.";
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = [];
|
||||
example = [
|
||||
{ ip = "195.154.1.1"; port = 443; ssl = true;}
|
||||
{ ip = "192.154.1.1"; port = 80; }
|
||||
{ ip = "*"; port = 8080; }
|
||||
];
|
||||
description = ''
|
||||
Listen addresses and ports for this virtual host.
|
||||
<note>
|
||||
<para>
|
||||
This option overrides <literal>addSSL</literal>, <literal>forceSSL</literal> and <literal>onlySSL</literal>.
|
||||
</para>
|
||||
<para>
|
||||
If you only want to set the addresses manually and not the ports, take a look at <literal>listenAddresses</literal>.
|
||||
</para>
|
||||
</note>
|
||||
'';
|
||||
};
|
||||
|
||||
listenAddresses = mkOption {
|
||||
type = with types; nonEmptyListOf str;
|
||||
|
||||
description = ''
|
||||
Listen addresses for this virtual host.
|
||||
Compared to <literal>listen</literal> this only sets the addreses
|
||||
and the ports are chosen automatically.
|
||||
'';
|
||||
default = [ "*" ];
|
||||
example = [ "127.0.0.1" ];
|
||||
};
|
||||
|
||||
enableSSL = mkOption {
|
||||
type = types.bool;
|
||||
visible = false;
|
||||
default = false;
|
||||
};
|
||||
|
||||
addSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
|
||||
<literal>listen</literal> to listen on all interfaces on the respective default
|
||||
ports (80, 443).
|
||||
'';
|
||||
};
|
||||
|
||||
onlySSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable HTTPS and reject plain HTTP connections. This will set
|
||||
defaults for <literal>listen</literal> to listen on all interfaces on port 443.
|
||||
'';
|
||||
};
|
||||
|
||||
forceSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to add a separate nginx server block that permanently redirects (301)
|
||||
all plain HTTP traffic to HTTPS. This will set defaults for
|
||||
<literal>listen</literal> to listen on all interfaces on the respective default
|
||||
ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
|
||||
'';
|
||||
};
|
||||
|
||||
enableACME = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to ask Let's Encrypt to sign a certificate for this vhost.
|
||||
Alternately, you can use an existing certificate through <option>useACMEHost</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
useACMEHost = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
A host of an existing Let's Encrypt certificate to use.
|
||||
This is useful if you have many subdomains and want to avoid hitting the
|
||||
<link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
|
||||
Alternately, you can generate a certificate through <option>enableACME</option>.
|
||||
<emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using <xref linkend="opt-security.acme.certs"/>.</emphasis>
|
||||
'';
|
||||
};
|
||||
|
||||
acmeRoot = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "/var/lib/acme/acme-challenge";
|
||||
description = ''
|
||||
Directory for the acme challenge which is PUBLIC, don't put certs or keys in here.
|
||||
Set to null to inherit from config.security.acme.
|
||||
'';
|
||||
};
|
||||
|
||||
sslServerCert = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/host.cert";
|
||||
description = "Path to server SSL certificate.";
|
||||
};
|
||||
|
||||
sslServerKey = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/host.key";
|
||||
description = "Path to server SSL certificate key.";
|
||||
};
|
||||
|
||||
sslServerChain = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/var/ca.pem";
|
||||
description = "Path to server SSL chain file.";
|
||||
};
|
||||
|
||||
http2 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable HTTP 2. HTTP/2 is supported in all multi-processing modules that come with httpd. <emphasis>However, if you use the prefork mpm, there will
|
||||
be severe restrictions.</emphasis> Refer to <link xlink:href="https://httpd.apache.org/docs/2.4/howto/http2.html#mpm-config"/> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
adminAddr = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "admin@example.org";
|
||||
description = "E-mail address of the server administrator.";
|
||||
};
|
||||
|
||||
documentRoot = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/data/webserver/docs";
|
||||
description = ''
|
||||
The path of Apache's document root directory. If left undefined,
|
||||
an empty directory in the Nix store will be used as root.
|
||||
'';
|
||||
};
|
||||
|
||||
servedDirs = mkOption {
|
||||
type = types.listOf types.attrs;
|
||||
default = [];
|
||||
example = [
|
||||
{ urlPath = "/nix";
|
||||
dir = "/home/eelco/Dev/nix-homepage";
|
||||
}
|
||||
];
|
||||
description = ''
|
||||
This option provides a simple way to serve static directories.
|
||||
'';
|
||||
};
|
||||
|
||||
servedFiles = mkOption {
|
||||
type = types.listOf types.attrs;
|
||||
default = [];
|
||||
example = [
|
||||
{ urlPath = "/foo/bar.png";
|
||||
file = "/home/eelco/some-file.png";
|
||||
}
|
||||
];
|
||||
description = ''
|
||||
This option provides a simple way to serve individual, static files.
|
||||
|
||||
<note><para>
|
||||
This option has been deprecated and will be removed in a future
|
||||
version of NixOS. You can achieve the same result by making use of
|
||||
the <literal>locations.<name>.alias</literal> option.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
<Directory /home>
|
||||
Options FollowSymlinks
|
||||
AllowOverride All
|
||||
</Directory>
|
||||
'';
|
||||
description = ''
|
||||
These lines go to httpd.conf verbatim. They will go after
|
||||
directories and directory aliases defined by default.
|
||||
'';
|
||||
};
|
||||
|
||||
enableUserDir = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable serving <filename>~/public_html</filename> as
|
||||
<literal>/~<replaceable>username</replaceable></literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
globalRedirect = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "http://newserver.example.org/";
|
||||
description = ''
|
||||
If set, all requests for this host are redirected permanently to
|
||||
the given URL.
|
||||
'';
|
||||
};
|
||||
|
||||
logFormat = mkOption {
|
||||
type = types.str;
|
||||
default = "common";
|
||||
example = "combined";
|
||||
description = ''
|
||||
Log format for Apache's log files. Possible values are: combined, common, referer, agent.
|
||||
'';
|
||||
};
|
||||
|
||||
robotsEntries = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "Disallow: /foo/";
|
||||
description = ''
|
||||
Specification of pages to be ignored by web crawlers. See <link
|
||||
xlink:href='http://www.robotstxt.org/'/> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
locations = mkOption {
|
||||
type = with types; attrsOf (submodule (import ./location-options.nix));
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
"/" = {
|
||||
proxyPass = "http://localhost:3000";
|
||||
};
|
||||
"/foo/bar.png" = {
|
||||
alias = "/home/eelco/some-file.png";
|
||||
};
|
||||
};
|
||||
'';
|
||||
description = ''
|
||||
Declarative location config. See <link
|
||||
xlink:href="https://httpd.apache.org/docs/2.4/mod/core.html#location"/> for details.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = {
|
||||
|
||||
locations = builtins.listToAttrs (map (elem: nameValuePair elem.urlPath { alias = elem.file; }) config.servedFiles);
|
||||
|
||||
};
|
||||
}
|
||||
339
nixos/modules/services/web-servers/caddy/default.nix
Normal file
339
nixos/modules/services/web-servers/caddy/default.nix
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.caddy;
|
||||
|
||||
virtualHosts = attrValues cfg.virtualHosts;
|
||||
acmeVHosts = filter (hostOpts: hostOpts.useACMEHost != null) virtualHosts;
|
||||
|
||||
mkVHostConf = hostOpts:
|
||||
let
|
||||
sslCertDir = config.security.acme.certs.${hostOpts.useACMEHost}.directory;
|
||||
in
|
||||
''
|
||||
${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" ''
|
||||
{
|
||||
${cfg.globalConfig}
|
||||
}
|
||||
${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;
|
||||
|
||||
acmeHosts = unique (catAttrs "useACMEHost" acmeVHosts);
|
||||
|
||||
mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
|
||||
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><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.
|
||||
|
||||
<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>
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.caddy;
|
||||
defaultText = literalExpression "pkgs.caddy";
|
||||
type = types.package;
|
||||
description = ''
|
||||
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>).
|
||||
'';
|
||||
};
|
||||
|
||||
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 <literal>Caddyfile</literal>.
|
||||
|
||||
Refer to <link xlink:href="https://caddyserver.com/docs/caddyfile/options#global-options"/>
|
||||
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 <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`";
|
||||
}
|
||||
] ++ map (name: mkCertOwnershipAssertion {
|
||||
inherit (cfg) group user;
|
||||
cert = config.security.acme.certs.${name};
|
||||
groups = config.users.groups;
|
||||
}) acmeHosts;
|
||||
|
||||
services.caddy.extraConfig = concatMapStringsSep "\n" mkVHostConf virtualHosts;
|
||||
services.caddy.globalConfig = ''
|
||||
${optionalString (cfg.email != null) "email ${cfg.email}"}
|
||||
${optionalString (cfg.acmeCA != null) "acme_ca ${cfg.acmeCA}"}
|
||||
log {
|
||||
${cfg.logFormat}
|
||||
}
|
||||
'';
|
||||
|
||||
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;
|
||||
|
||||
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 --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;
|
||||
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
|
||||
reloads = map (useACMEHost: nameValuePair useACMEHost { reloadServices = [ "caddy.service" ]; }) acmeHosts;
|
||||
in
|
||||
listToAttrs reloads;
|
||||
|
||||
};
|
||||
}
|
||||
79
nixos/modules/services/web-servers/caddy/vhost-options.nix
Normal file
79
nixos/modules/services/web-servers/caddy/vhost-options.nix
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
{ 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.
|
||||
|
||||
<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 = ''
|
||||
Additional lines of configuration appended to this virtual host in the
|
||||
automatically generated <literal>Caddyfile</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
77
nixos/modules/services/web-servers/darkhttpd.nix
Normal file
77
nixos/modules/services/web-servers/darkhttpd.nix
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.darkhttpd;
|
||||
|
||||
args = concatStringsSep " " ([
|
||||
cfg.rootDir
|
||||
"--port ${toString cfg.port}"
|
||||
"--addr ${cfg.address}"
|
||||
] ++ cfg.extraArgs
|
||||
++ optional cfg.hideServerId "--no-server-id"
|
||||
++ optional config.networking.enableIPv6 "--ipv6");
|
||||
|
||||
in {
|
||||
options.services.darkhttpd = with types; {
|
||||
enable = mkEnableOption "DarkHTTPd web server";
|
||||
|
||||
port = mkOption {
|
||||
default = 80;
|
||||
type = types.port;
|
||||
description = ''
|
||||
Port to listen on.
|
||||
Pass 0 to let the system choose any free port for you.
|
||||
'';
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
default = "127.0.0.1";
|
||||
type = str;
|
||||
description = ''
|
||||
Address to listen on.
|
||||
Pass `all` to listen on all interfaces.
|
||||
'';
|
||||
};
|
||||
|
||||
rootDir = mkOption {
|
||||
type = path;
|
||||
description = ''
|
||||
Path from which to serve files.
|
||||
'';
|
||||
};
|
||||
|
||||
hideServerId = mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Don't identify the server type in headers or directory listings.
|
||||
'';
|
||||
};
|
||||
|
||||
extraArgs = mkOption {
|
||||
type = listOf str;
|
||||
default = [];
|
||||
description = ''
|
||||
Additional configuration passed to the executable.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.darkhttpd = {
|
||||
description = "Dark HTTPd";
|
||||
wants = [ "network.target" ];
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
ExecStart = "${pkgs.darkhttpd}/bin/darkhttpd ${args}";
|
||||
AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
|
||||
Restart = "on-failure";
|
||||
RestartSec = "2s";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
72
nixos/modules/services/web-servers/fcgiwrap.nix
Normal file
72
nixos/modules/services/web-servers/fcgiwrap.nix
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.fcgiwrap;
|
||||
in {
|
||||
|
||||
options = {
|
||||
services.fcgiwrap = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable fcgiwrap, a server for running CGI applications over FastCGI.";
|
||||
};
|
||||
|
||||
preforkProcesses = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = "Number of processes to prefork.";
|
||||
};
|
||||
|
||||
socketType = mkOption {
|
||||
type = types.enum [ "unix" "tcp" "tcp6" ];
|
||||
default = "unix";
|
||||
description = "Socket type: 'unix', 'tcp' or 'tcp6'.";
|
||||
};
|
||||
|
||||
socketAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "/run/fcgiwrap.sock";
|
||||
example = "1.2.3.4:5678";
|
||||
description = "Socket address. In case of a UNIX socket, this should be its filesystem path.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "User permissions for the socket.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Group permissions for the socket.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.fcgiwrap = {
|
||||
after = [ "nss-user-lookup.target" ];
|
||||
wantedBy = optional (cfg.socketType != "unix") "multi-user.target";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${builtins.toString cfg.preforkProcesses} ${
|
||||
if (cfg.socketType != "unix") then "-s ${cfg.socketType}:${cfg.socketAddress}" else ""
|
||||
}";
|
||||
} // (if cfg.user != null && cfg.group != null then {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
} else { } );
|
||||
};
|
||||
|
||||
systemd.sockets = if (cfg.socketType == "unix") then {
|
||||
fcgiwrap = {
|
||||
wantedBy = [ "sockets.target" ];
|
||||
socketConfig.ListenStream = cfg.socketAddress;
|
||||
};
|
||||
} else { };
|
||||
};
|
||||
}
|
||||
111
nixos/modules/services/web-servers/hitch/default.nix
Normal file
111
nixos/modules/services/web-servers/hitch/default.nix
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
{ config, lib, pkgs, ...}:
|
||||
let
|
||||
cfg = config.services.hitch;
|
||||
ocspDir = lib.optionalString cfg.ocsp-stapling.enabled "/var/cache/hitch/ocsp";
|
||||
hitchConfig = with lib; pkgs.writeText "hitch.conf" (concatStringsSep "\n" [
|
||||
("backend = \"${cfg.backend}\"")
|
||||
(concatMapStrings (s: "frontend = \"${s}\"\n") cfg.frontend)
|
||||
(concatMapStrings (s: "pem-file = \"${s}\"\n") cfg.pem-files)
|
||||
("ciphers = \"${cfg.ciphers}\"")
|
||||
("ocsp-dir = \"${ocspDir}\"")
|
||||
"user = \"${cfg.user}\""
|
||||
"group = \"${cfg.group}\""
|
||||
cfg.extraConfig
|
||||
]);
|
||||
in
|
||||
with lib;
|
||||
{
|
||||
options = {
|
||||
services.hitch = {
|
||||
enable = mkEnableOption "Hitch Server";
|
||||
|
||||
backend = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The host and port Hitch connects to when receiving
|
||||
a connection in the form [HOST]:PORT
|
||||
'';
|
||||
};
|
||||
|
||||
ciphers = mkOption {
|
||||
type = types.str;
|
||||
default = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
|
||||
description = "The list of ciphers to use";
|
||||
};
|
||||
|
||||
frontend = mkOption {
|
||||
type = types.either types.str (types.listOf types.str);
|
||||
default = "[127.0.0.1]:443";
|
||||
description = ''
|
||||
The port and interface of the listen endpoint in the
|
||||
+ form [HOST]:PORT[+CERT].
|
||||
'';
|
||||
apply = toList;
|
||||
};
|
||||
|
||||
pem-files = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
description = "PEM files to use";
|
||||
};
|
||||
|
||||
ocsp-stapling = {
|
||||
enabled = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Whether to enable OCSP Stapling";
|
||||
};
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "hitch";
|
||||
description = "The user to run as";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "hitch";
|
||||
description = "The group to run as";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Additional configuration lines";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.hitch = {
|
||||
description = "Hitch";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
preStart = ''
|
||||
${pkgs.hitch}/sbin/hitch -t --config ${hitchConfig}
|
||||
'' + (optionalString cfg.ocsp-stapling.enabled ''
|
||||
mkdir -p ${ocspDir}
|
||||
chown -R hitch:hitch ${ocspDir}
|
||||
'');
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = "${pkgs.hitch}/sbin/hitch --daemon --config ${hitchConfig}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
LimitNOFILE = 131072;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ pkgs.hitch ];
|
||||
|
||||
users.users.hitch = {
|
||||
group = "hitch";
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.hitch = {};
|
||||
};
|
||||
}
|
||||
165
nixos/modules/services/web-servers/hydron.nix
Normal file
165
nixos/modules/services/web-servers/hydron.nix
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.hydron;
|
||||
in with lib; {
|
||||
options.services.hydron = {
|
||||
enable = mkEnableOption "hydron";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/hydron";
|
||||
example = "/home/okina/hydron";
|
||||
description = "Location where hydron runs and stores data.";
|
||||
};
|
||||
|
||||
interval = mkOption {
|
||||
type = types.str;
|
||||
default = "weekly";
|
||||
example = "06:00";
|
||||
description = ''
|
||||
How often we run hydron import and possibly fetch tags. Runs by default every week.
|
||||
|
||||
The format is described in
|
||||
<citerefentry><refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum></citerefentry>.
|
||||
'';
|
||||
};
|
||||
|
||||
password = mkOption {
|
||||
type = types.str;
|
||||
default = "hydron";
|
||||
example = "dumbpass";
|
||||
description = "Password for the hydron database.";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/keys/hydron-password-file";
|
||||
example = "/home/okina/hydron/keys/pass";
|
||||
description = "Password file for the hydron database.";
|
||||
};
|
||||
|
||||
postgresArgs = mkOption {
|
||||
type = types.str;
|
||||
description = "Postgresql connection arguments.";
|
||||
example = ''
|
||||
{
|
||||
"driver": "postgres",
|
||||
"connection": "user=hydron password=dumbpass dbname=hydron sslmode=disable"
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
postgresArgsFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/keys/hydron-postgres-args";
|
||||
example = "/home/okina/hydron/keys/postgres";
|
||||
description = "Postgresql connection arguments file.";
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "127.0.0.1:8010";
|
||||
description = "Listen on a specific IP address and port.";
|
||||
};
|
||||
|
||||
importPaths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
example = [ "/home/okina/Pictures" ];
|
||||
description = "Paths that hydron will recursively import.";
|
||||
};
|
||||
|
||||
fetchTags = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Fetch tags for imported images and webm from gelbooru.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.hydron.passwordFile = mkDefault (pkgs.writeText "hydron-password-file" cfg.password);
|
||||
services.hydron.postgresArgsFile = mkDefault (pkgs.writeText "hydron-postgres-args" cfg.postgresArgs);
|
||||
services.hydron.postgresArgs = mkDefault ''
|
||||
{
|
||||
"driver": "postgres",
|
||||
"connection": "user=hydron password=${cfg.password} host=/run/postgresql dbname=hydron sslmode=disable"
|
||||
}
|
||||
'';
|
||||
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
ensureDatabases = [ "hydron" ];
|
||||
ensureUsers = [
|
||||
{ name = "hydron";
|
||||
ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; };
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' 0750 hydron hydron - -"
|
||||
"d '${cfg.dataDir}/.hydron' - hydron hydron - -"
|
||||
"d '${cfg.dataDir}/images' - hydron hydron - -"
|
||||
"Z '${cfg.dataDir}' - hydron hydron - -"
|
||||
|
||||
"L+ '${cfg.dataDir}/.hydron/db_conf.json' - - - - ${cfg.postgresArgsFile}"
|
||||
];
|
||||
|
||||
systemd.services.hydron = {
|
||||
description = "hydron";
|
||||
after = [ "network.target" "postgresql.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
User = "hydron";
|
||||
Group = "hydron";
|
||||
ExecStart = "${pkgs.hydron}/bin/hydron serve"
|
||||
+ optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.hydron-fetch = {
|
||||
description = "Import paths into hydron and possibly fetch tags";
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "hydron";
|
||||
Group = "hydron";
|
||||
ExecStart = "${pkgs.hydron}/bin/hydron import "
|
||||
+ optionalString cfg.fetchTags "-f "
|
||||
+ (escapeShellArg cfg.dataDir) + "/images " + (escapeShellArgs cfg.importPaths);
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.hydron-fetch = {
|
||||
description = "Automatically import paths into hydron and possibly fetch tags";
|
||||
after = [ "network.target" "hydron.service" ];
|
||||
wantedBy = [ "timers.target" ];
|
||||
|
||||
timerConfig = {
|
||||
Persistent = true;
|
||||
OnCalendar = cfg.interval;
|
||||
};
|
||||
};
|
||||
|
||||
users = {
|
||||
groups.hydron.gid = config.ids.gids.hydron;
|
||||
|
||||
users.hydron = {
|
||||
description = "hydron server service user";
|
||||
home = cfg.dataDir;
|
||||
group = "hydron";
|
||||
uid = config.ids.uids.hydron;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "hydron" "baseDir" ] [ "services" "hydron" "dataDir" ])
|
||||
];
|
||||
|
||||
meta.maintainers = with maintainers; [ Madouura ];
|
||||
}
|
||||
72
nixos/modules/services/web-servers/jboss/builder.sh
Normal file
72
nixos/modules/services/web-servers/jboss/builder.sh
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
set -e
|
||||
|
||||
source $stdenv/setup
|
||||
|
||||
mkdir -p $out/bin
|
||||
|
||||
cat > $out/bin/control <<EOF
|
||||
mkdir -p $logDir
|
||||
chown -R $user $logDir
|
||||
export PATH=$PATH:$su/bin
|
||||
|
||||
start()
|
||||
{
|
||||
su $user -s /bin/sh -c "$jboss/bin/run.sh \
|
||||
-Djboss.server.base.dir=$serverDir \
|
||||
-Djboss.server.base.url=file://$serverDir \
|
||||
-Djboss.server.temp.dir=$tempDir \
|
||||
-Djboss.server.log.dir=$logDir \
|
||||
-Djboss.server.lib.url=$libUrl \
|
||||
-c default"
|
||||
}
|
||||
|
||||
stop()
|
||||
{
|
||||
su $user -s /bin/sh -c "$jboss/bin/shutdown.sh -S"
|
||||
}
|
||||
|
||||
if test "\$1" = start
|
||||
then
|
||||
trap stop 15
|
||||
|
||||
start
|
||||
elif test "\$1" = stop
|
||||
then
|
||||
stop
|
||||
elif test "\$1" = init
|
||||
then
|
||||
echo "Are you sure you want to create a new server instance (old server instance will be lost!)?"
|
||||
read answer
|
||||
|
||||
if ! test \$answer = "yes"
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf $serverDir
|
||||
mkdir -p $serverDir
|
||||
cd $serverDir
|
||||
cp -av $jboss/server/default .
|
||||
sed -i -e "s|deploy/|$deployDir|" default/conf/jboss-service.xml
|
||||
|
||||
if ! test "$useJK" = ""
|
||||
then
|
||||
sed -i -e 's|<attribute name="UseJK">false</attribute>|<attribute name="UseJK">true</attribute>|' default/deploy/jboss-web.deployer/META-INF/jboss-service.xml
|
||||
sed -i -e 's|<Engine name="jboss.web" defaultHost="localhost">|<Engine name="jboss.web" defaultHost="localhost" jvmRoute="node1">|' default/deploy/jboss-web.deployer/server.xml
|
||||
fi
|
||||
|
||||
# Make files accessible for the server user
|
||||
|
||||
chown -R $user $serverDir
|
||||
for i in \`find $serverDir -type d\`
|
||||
do
|
||||
chmod 755 \$i
|
||||
done
|
||||
for i in \`find $serverDir -type f\`
|
||||
do
|
||||
chmod 644 \$i
|
||||
done
|
||||
fi
|
||||
EOF
|
||||
|
||||
chmod +x $out/bin/*
|
||||
88
nixos/modules/services/web-servers/jboss/default.nix
Normal file
88
nixos/modules/services/web-servers/jboss/default.nix
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.jboss;
|
||||
|
||||
jbossService = pkgs.stdenv.mkDerivation {
|
||||
name = "jboss-server";
|
||||
builder = ./builder.sh;
|
||||
inherit (pkgs) jboss su;
|
||||
inherit (cfg) tempDir logDir libUrl deployDir serverDir user useJK;
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.jboss = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable JBoss. WARNING : this package is outdated and is known to have vulnerabilities.";
|
||||
};
|
||||
|
||||
tempDir = mkOption {
|
||||
default = "/tmp";
|
||||
type = types.str;
|
||||
description = "Location where JBoss stores its temp files";
|
||||
};
|
||||
|
||||
logDir = mkOption {
|
||||
default = "/var/log/jboss";
|
||||
type = types.str;
|
||||
description = "Location of the logfile directory of JBoss";
|
||||
};
|
||||
|
||||
serverDir = mkOption {
|
||||
description = "Location of the server instance files";
|
||||
default = "/var/jboss/server";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
deployDir = mkOption {
|
||||
description = "Location of the deployment files";
|
||||
default = "/nix/var/nix/profiles/default/server/default/deploy/";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
libUrl = mkOption {
|
||||
default = "file:///nix/var/nix/profiles/default/server/default/lib";
|
||||
description = "Location where the shared library JARs are stored";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
default = "nobody";
|
||||
description = "User account under which jboss runs.";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
useJK = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to use to connector to the Apache HTTP server";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.services.jboss.enable {
|
||||
systemd.services.jboss = {
|
||||
description = "JBoss server";
|
||||
script = "${jbossService}/bin/control start";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
93
nixos/modules/services/web-servers/lighttpd/cgit.nix
Normal file
93
nixos/modules/services/web-servers/lighttpd/cgit.nix
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.lighttpd.cgit;
|
||||
pathPrefix = if stringLength cfg.subdir == 0 then "" else "/" + cfg.subdir;
|
||||
configFile = pkgs.writeText "cgitrc"
|
||||
''
|
||||
# default paths to static assets
|
||||
css=${pathPrefix}/cgit.css
|
||||
logo=${pathPrefix}/cgit.png
|
||||
favicon=${pathPrefix}/favicon.ico
|
||||
|
||||
# user configuration
|
||||
${cfg.configText}
|
||||
'';
|
||||
in
|
||||
{
|
||||
|
||||
options.services.lighttpd.cgit = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If true, enable cgit (fast web interface for git repositories) as a
|
||||
sub-service in lighttpd.
|
||||
'';
|
||||
};
|
||||
|
||||
subdir = mkOption {
|
||||
default = "cgit";
|
||||
example = "";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The subdirectory in which to serve cgit. The web application will be
|
||||
accessible at http://yourserver/''${subdir}
|
||||
'';
|
||||
};
|
||||
|
||||
configText = mkOption {
|
||||
default = "";
|
||||
example = literalExpression ''
|
||||
'''
|
||||
source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
|
||||
about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
|
||||
cache-size=1000
|
||||
scan-path=/srv/git
|
||||
'''
|
||||
'';
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Verbatim contents of the cgit runtime configuration file. Documentation
|
||||
(with cgitrc example file) is available in "man cgitrc". Or online:
|
||||
http://git.zx2c4.com/cgit/tree/cgitrc.5.txt
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
# make the cgitrc manpage available
|
||||
environment.systemPackages = [ pkgs.cgit ];
|
||||
|
||||
# declare module dependencies
|
||||
services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ];
|
||||
|
||||
services.lighttpd.extraConfig = ''
|
||||
$HTTP["url"] =~ "^/${cfg.subdir}" {
|
||||
cgi.assign = (
|
||||
"cgit.cgi" => "${pkgs.cgit}/cgit/cgit.cgi"
|
||||
)
|
||||
alias.url = (
|
||||
"${pathPrefix}/cgit.css" => "${pkgs.cgit}/cgit/cgit.css",
|
||||
"${pathPrefix}/cgit.png" => "${pkgs.cgit}/cgit/cgit.png",
|
||||
"${pathPrefix}" => "${pkgs.cgit}/cgit/cgit.cgi"
|
||||
)
|
||||
setenv.add-environment = (
|
||||
"CGIT_CONFIG" => "${configFile}"
|
||||
)
|
||||
}
|
||||
'';
|
||||
|
||||
systemd.services.lighttpd.preStart = ''
|
||||
mkdir -p /var/cache/cgit
|
||||
chown lighttpd:lighttpd /var/cache/cgit
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
62
nixos/modules/services/web-servers/lighttpd/collectd.nix
Normal file
62
nixos/modules/services/web-servers/lighttpd/collectd.nix
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.lighttpd.collectd;
|
||||
opt = options.services.lighttpd.collectd;
|
||||
|
||||
collectionConf = pkgs.writeText "collection.conf" ''
|
||||
datadir: "${config.services.collectd.dataDir}"
|
||||
libdir: "${config.services.collectd.package}/lib/collectd"
|
||||
'';
|
||||
|
||||
defaultCollectionCgi = config.services.collectd.package.overrideDerivation(old: {
|
||||
name = "collection.cgi";
|
||||
dontConfigure = true;
|
||||
buildPhase = "true";
|
||||
installPhase = ''
|
||||
substituteInPlace contrib/collection.cgi --replace '"/etc/collection.conf"' '$ENV{COLLECTION_CONF}'
|
||||
cp contrib/collection.cgi $out
|
||||
'';
|
||||
});
|
||||
in
|
||||
{
|
||||
|
||||
options.services.lighttpd.collectd = {
|
||||
|
||||
enable = mkEnableOption "collectd subservice accessible at http://yourserver/collectd";
|
||||
|
||||
collectionCgi = mkOption {
|
||||
type = types.path;
|
||||
default = defaultCollectionCgi;
|
||||
defaultText = literalDocBook ''
|
||||
<literal>config.${options.services.collectd.package}</literal> configured for lighttpd
|
||||
'';
|
||||
description = ''
|
||||
Path to collection.cgi script from (collectd sources)/contrib/collection.cgi
|
||||
This option allows to use a customized version
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ];
|
||||
|
||||
services.lighttpd.extraConfig = ''
|
||||
$HTTP["url"] =~ "^/collectd" {
|
||||
cgi.assign = (
|
||||
".cgi" => "${pkgs.perl}/bin/perl"
|
||||
)
|
||||
alias.url = (
|
||||
"/collectd" => "${cfg.collectionCgi}"
|
||||
)
|
||||
setenv.add-environment = (
|
||||
"PERL5LIB" => "${with pkgs.perlPackages; makePerlPath [ CGI HTMLParser URI pkgs.rrdtool ]}",
|
||||
"COLLECTION_CONF" => "${collectionConf}"
|
||||
)
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
268
nixos/modules/services/web-servers/lighttpd/default.nix
Normal file
268
nixos/modules/services/web-servers/lighttpd/default.nix
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
# NixOS module for lighttpd web server
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.lighttpd;
|
||||
|
||||
# List of known lighttpd modules, ordered by how the lighttpd documentation
|
||||
# recommends them being imported:
|
||||
# http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
|
||||
#
|
||||
# Some modules are always imported and should not appear in the config:
|
||||
# disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ];
|
||||
#
|
||||
# For full module list, see the output of running ./configure in the lighttpd
|
||||
# source.
|
||||
allKnownModules = [
|
||||
"mod_rewrite"
|
||||
"mod_redirect"
|
||||
"mod_alias"
|
||||
"mod_access"
|
||||
"mod_auth"
|
||||
"mod_status"
|
||||
"mod_simple_vhost"
|
||||
"mod_evhost"
|
||||
"mod_userdir"
|
||||
"mod_secdownload"
|
||||
"mod_fastcgi"
|
||||
"mod_proxy"
|
||||
"mod_cgi"
|
||||
"mod_ssi"
|
||||
"mod_compress"
|
||||
"mod_usertrack"
|
||||
"mod_expire"
|
||||
"mod_rrdtool"
|
||||
"mod_accesslog"
|
||||
# Remaining list of modules, order assumed to be unimportant.
|
||||
"mod_authn_dbi"
|
||||
"mod_authn_file"
|
||||
"mod_authn_gssapi"
|
||||
"mod_authn_ldap"
|
||||
"mod_authn_mysql"
|
||||
"mod_authn_pam"
|
||||
"mod_authn_sasl"
|
||||
"mod_cml"
|
||||
"mod_deflate"
|
||||
"mod_evasive"
|
||||
"mod_extforward"
|
||||
"mod_flv_streaming"
|
||||
"mod_geoip"
|
||||
"mod_magnet"
|
||||
"mod_mysql_vhost"
|
||||
"mod_openssl" # since v1.4.46
|
||||
"mod_scgi"
|
||||
"mod_setenv"
|
||||
"mod_trigger_b4_dl"
|
||||
"mod_uploadprogress"
|
||||
"mod_vhostdb" # since v1.4.46
|
||||
"mod_webdav"
|
||||
"mod_wstunnel" # since v1.4.46
|
||||
];
|
||||
|
||||
maybeModuleString = moduleName:
|
||||
if elem moduleName cfg.enableModules then ''"${moduleName}"'' else "";
|
||||
|
||||
modulesIncludeString = concatStringsSep ",\n"
|
||||
(filter (x: x != "") (map maybeModuleString allKnownModules));
|
||||
|
||||
configFile = if cfg.configText != "" then
|
||||
pkgs.writeText "lighttpd.conf" ''
|
||||
${cfg.configText}
|
||||
''
|
||||
else
|
||||
pkgs.writeText "lighttpd.conf" ''
|
||||
server.document-root = "${cfg.document-root}"
|
||||
server.port = ${toString cfg.port}
|
||||
server.username = "lighttpd"
|
||||
server.groupname = "lighttpd"
|
||||
|
||||
# As for why all modules are loaded here, instead of having small
|
||||
# server.modules += () entries in each sub-service extraConfig snippet,
|
||||
# read this:
|
||||
#
|
||||
# http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
|
||||
# http://redmine.lighttpd.net/issues/2337
|
||||
#
|
||||
# Basically, lighttpd doesn't want to load (or even silently ignore) a
|
||||
# module for a second time, and there is no way to check if a module has
|
||||
# been loaded already. So if two services were to put the same module in
|
||||
# server.modules += (), that would break the lighttpd configuration.
|
||||
server.modules = (
|
||||
${modulesIncludeString}
|
||||
)
|
||||
|
||||
# Logging (logs end up in systemd journal)
|
||||
accesslog.use-syslog = "enable"
|
||||
server.errorlog-use-syslog = "enable"
|
||||
|
||||
${lib.optionalString cfg.enableUpstreamMimeTypes ''
|
||||
include "${pkgs.lighttpd}/share/lighttpd/doc/config/conf.d/mime.conf"
|
||||
''}
|
||||
|
||||
static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
|
||||
index-file.names = ( "index.html" )
|
||||
|
||||
${if cfg.mod_userdir then ''
|
||||
userdir.path = "public_html"
|
||||
'' else ""}
|
||||
|
||||
${if cfg.mod_status then ''
|
||||
status.status-url = "/server-status"
|
||||
status.statistics-url = "/server-statistics"
|
||||
status.config-url = "/server-config"
|
||||
'' else ""}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
options = {
|
||||
|
||||
services.lighttpd = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Enable the lighttpd web server.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.lighttpd;
|
||||
defaultText = "pkgs.lighttpd";
|
||||
type = types.package;
|
||||
description = ''
|
||||
lighttpd package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
default = 80;
|
||||
type = types.port;
|
||||
description = ''
|
||||
TCP port number for lighttpd to bind to.
|
||||
'';
|
||||
};
|
||||
|
||||
document-root = mkOption {
|
||||
default = "/srv/www";
|
||||
type = types.path;
|
||||
description = ''
|
||||
Document-root of the web server. Must be readable by the "lighttpd" user.
|
||||
'';
|
||||
};
|
||||
|
||||
mod_userdir = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If true, requests in the form /~user/page.html are rewritten to take
|
||||
the file public_html/page.html from the home directory of the user.
|
||||
'';
|
||||
};
|
||||
|
||||
enableModules = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "mod_cgi" "mod_status" ];
|
||||
description = ''
|
||||
List of lighttpd modules to enable. Sub-services take care of
|
||||
enabling modules as needed, so this option is mainly for when you
|
||||
want to add custom stuff to
|
||||
<option>services.lighttpd.extraConfig</option> that depends on a
|
||||
certain module.
|
||||
'';
|
||||
};
|
||||
|
||||
enableUpstreamMimeTypes = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to include the list of mime types bundled with lighttpd
|
||||
(upstream). If you disable this, no mime types will be added by
|
||||
NixOS and you will have to add your own mime types in
|
||||
<option>services.lighttpd.extraConfig</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
mod_status = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Show server status overview at /server-status, statistics at
|
||||
/server-statistics and list of loaded modules at /server-config.
|
||||
'';
|
||||
};
|
||||
|
||||
configText = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
example = "...verbatim config file contents...";
|
||||
description = ''
|
||||
Overridable config file contents to use for lighttpd. By default, use
|
||||
the contents automatically generated by NixOS.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
These configuration lines will be appended to the generated lighttpd
|
||||
config file. Note that this mechanism does not work when the manual
|
||||
<option>configText</option> option is used.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{ assertion = all (x: elem x allKnownModules) cfg.enableModules;
|
||||
message = ''
|
||||
One (or more) modules in services.lighttpd.enableModules are
|
||||
unrecognized.
|
||||
|
||||
Known modules: ${toString allKnownModules}
|
||||
|
||||
services.lighttpd.enableModules: ${toString cfg.enableModules}
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
services.lighttpd.enableModules = mkMerge
|
||||
[ (mkIf cfg.mod_status [ "mod_status" ])
|
||||
(mkIf cfg.mod_userdir [ "mod_userdir" ])
|
||||
# always load mod_accesslog so that we can log to the journal
|
||||
[ "mod_accesslog" ]
|
||||
];
|
||||
|
||||
systemd.services.lighttpd = {
|
||||
description = "Lighttpd Web Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig.ExecStart = "${cfg.package}/sbin/lighttpd -D -f ${configFile}";
|
||||
# SIGINT => graceful shutdown
|
||||
serviceConfig.KillSignal = "SIGINT";
|
||||
};
|
||||
|
||||
users.users.lighttpd = {
|
||||
group = "lighttpd";
|
||||
description = "lighttpd web server privilege separation user";
|
||||
uid = config.ids.uids.lighttpd;
|
||||
};
|
||||
|
||||
users.groups.lighttpd.gid = config.ids.gids.lighttpd;
|
||||
};
|
||||
}
|
||||
52
nixos/modules/services/web-servers/lighttpd/gitweb.nix
Normal file
52
nixos/modules/services/web-servers/lighttpd/gitweb.nix
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.gitweb;
|
||||
package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme {
|
||||
gitwebTheme = true;
|
||||
});
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options.services.lighttpd.gitweb = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If true, enable gitweb in lighttpd. Access it at http://yourserver/gitweb
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf config.services.lighttpd.gitweb.enable {
|
||||
|
||||
# declare module dependencies
|
||||
services.lighttpd.enableModules = [ "mod_cgi" "mod_redirect" "mod_alias" "mod_setenv" ];
|
||||
|
||||
services.lighttpd.extraConfig = ''
|
||||
$HTTP["url"] =~ "^/gitweb" {
|
||||
cgi.assign = (
|
||||
".cgi" => "${pkgs.perl}/bin/perl"
|
||||
)
|
||||
url.redirect = (
|
||||
"^/gitweb$" => "/gitweb/"
|
||||
)
|
||||
alias.url = (
|
||||
"/gitweb/static/" => "${package}/static/",
|
||||
"/gitweb/" => "${package}/gitweb.cgi"
|
||||
)
|
||||
setenv.add-environment = (
|
||||
"GITWEB_CONFIG" => "${cfg.gitwebConfigFile}",
|
||||
"HOME" => "${cfg.projectroot}"
|
||||
)
|
||||
}
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
132
nixos/modules/services/web-servers/mighttpd2.nix
Normal file
132
nixos/modules/services/web-servers/mighttpd2.nix
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.mighttpd2;
|
||||
configFile = pkgs.writeText "mighty-config" cfg.config;
|
||||
routingFile = pkgs.writeText "mighty-routing" cfg.routing;
|
||||
in {
|
||||
options.services.mighttpd2 = {
|
||||
enable = mkEnableOption "Mighttpd2 web server";
|
||||
|
||||
config = mkOption {
|
||||
default = "";
|
||||
example = ''
|
||||
# Example configuration for Mighttpd 2
|
||||
Port: 80
|
||||
# IP address or "*"
|
||||
Host: *
|
||||
Debug_Mode: Yes # Yes or No
|
||||
# If available, "nobody" is much more secure for User:.
|
||||
User: root
|
||||
# If available, "nobody" is much more secure for Group:.
|
||||
Group: root
|
||||
Pid_File: /run/mighty.pid
|
||||
Logging: Yes # Yes or No
|
||||
Log_File: /var/log/mighty # The directory must be writable by User:
|
||||
Log_File_Size: 16777216 # bytes
|
||||
Log_Backup_Number: 10
|
||||
Index_File: index.html
|
||||
Index_Cgi: index.cgi
|
||||
Status_File_Dir: /usr/local/share/mighty/status
|
||||
Connection_Timeout: 30 # seconds
|
||||
Fd_Cache_Duration: 10 # seconds
|
||||
# Server_Name: Mighttpd/3.x.y
|
||||
Tls_Port: 443
|
||||
Tls_Cert_File: cert.pem # should change this with an absolute path
|
||||
# should change this with comma-separated absolute paths
|
||||
Tls_Chain_Files: chain.pem
|
||||
# Currently, Tls_Key_File must not be encrypted.
|
||||
Tls_Key_File: privkey.pem # should change this with an absolute path
|
||||
Service: 0 # 0 is HTTP only, 1 is HTTPS only, 2 is both
|
||||
'';
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Verbatim config file to use
|
||||
(see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
|
||||
'';
|
||||
};
|
||||
|
||||
routing = mkOption {
|
||||
default = "";
|
||||
example = ''
|
||||
# Example routing for Mighttpd 2
|
||||
|
||||
# Domain lists
|
||||
[localhost www.example.com]
|
||||
|
||||
# Entries are looked up in the specified order
|
||||
# All paths must end with "/"
|
||||
|
||||
# A path to CGI scripts should be specified with "=>"
|
||||
/~alice/cgi-bin/ => /home/alice/public_html/cgi-bin/
|
||||
|
||||
# A path to static files should be specified with "->"
|
||||
/~alice/ -> /home/alice/public_html/
|
||||
/cgi-bin/ => /export/cgi-bin/
|
||||
|
||||
# Reverse proxy rules should be specified with ">>"
|
||||
# /path >> host:port/path2
|
||||
# Either "host" or ":port" can be committed, but not both.
|
||||
/app/cal/ >> example.net/calendar/
|
||||
# Yesod app in the same server
|
||||
/app/wiki/ >> 127.0.0.1:3000/
|
||||
|
||||
/ -> /export/www/
|
||||
'';
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Verbatim routing file to use
|
||||
(see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
|
||||
'';
|
||||
};
|
||||
|
||||
cores = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.int;
|
||||
description = ''
|
||||
How many cores to use.
|
||||
If null it will be determined automatically
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions =
|
||||
[ { assertion = cfg.routing != "";
|
||||
message = "You need at least one rule in mighttpd2.routing";
|
||||
}
|
||||
];
|
||||
systemd.services.mighttpd2 = {
|
||||
description = "Mighttpd2 web server";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${pkgs.haskellPackages.mighttpd2}/bin/mighty \
|
||||
${configFile} \
|
||||
${routingFile} \
|
||||
+RTS -N${optionalString (cfg.cores != null) "${cfg.cores}"}
|
||||
'';
|
||||
Type = "simple";
|
||||
User = "mighttpd2";
|
||||
Group = "mighttpd2";
|
||||
Restart = "on-failure";
|
||||
AmbientCapabilities = "cap_net_bind_service";
|
||||
CapabilityBoundingSet = "cap_net_bind_service";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.mighttpd2 = {
|
||||
group = "mighttpd2";
|
||||
uid = config.ids.uids.mighttpd2;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.mighttpd2.gid = config.ids.gids.mighttpd2;
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ fgaz ];
|
||||
}
|
||||
130
nixos/modules/services/web-servers/minio.nix
Normal file
130
nixos/modules/services/web-servers/minio.nix
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.minio;
|
||||
|
||||
legacyCredentials = cfg: pkgs.writeText "minio-legacy-credentials" ''
|
||||
MINIO_ROOT_USER=${cfg.accessKey}
|
||||
MINIO_ROOT_PASSWORD=${cfg.secretKey}
|
||||
'';
|
||||
in
|
||||
{
|
||||
meta.maintainers = [ maintainers.bachp ];
|
||||
|
||||
options.services.minio = {
|
||||
enable = mkEnableOption "Minio Object Storage";
|
||||
|
||||
listenAddress = mkOption {
|
||||
default = ":9000";
|
||||
type = types.str;
|
||||
description = "IP address and port of the server.";
|
||||
};
|
||||
|
||||
consoleAddress = mkOption {
|
||||
default = ":9001";
|
||||
type = types.str;
|
||||
description = "IP address and port of the web UI (console).";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
default = [ "/var/lib/minio/data" ];
|
||||
type = types.listOf types.path;
|
||||
description = "The list of data directories for storing the objects. Use one path for regular operation and the minimum of 4 endpoints for Erasure Code mode.";
|
||||
};
|
||||
|
||||
configDir = mkOption {
|
||||
default = "/var/lib/minio/config";
|
||||
type = types.path;
|
||||
description = "The config directory, for the access keys and other settings.";
|
||||
};
|
||||
|
||||
accessKey = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Access key of 5 to 20 characters in length that clients use to access the server.
|
||||
This overrides the access key that is generated by minio on first startup and stored inside the
|
||||
<literal>configDir</literal> directory.
|
||||
'';
|
||||
};
|
||||
|
||||
secretKey = mkOption {
|
||||
default = "";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Specify the Secret key of 8 to 40 characters in length that clients use to access the server.
|
||||
This overrides the secret key that is generated by minio on first startup and stored inside the
|
||||
<literal>configDir</literal> directory.
|
||||
'';
|
||||
};
|
||||
|
||||
rootCredentialsFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
File containing the MINIO_ROOT_USER, default is "minioadmin", and
|
||||
MINIO_ROOT_PASSWORD (length >= 8), default is "minioadmin"; in the format of
|
||||
an EnvironmentFile=, as described by systemd.exec(5).
|
||||
'';
|
||||
example = "/etc/nixos/minio-root-credentials";
|
||||
};
|
||||
|
||||
region = mkOption {
|
||||
default = "us-east-1";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The physical location of the server. By default it is set to us-east-1, which is same as AWS S3's and Minio's default region.
|
||||
'';
|
||||
};
|
||||
|
||||
browser = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = "Enable or disable access to web UI.";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.minio;
|
||||
defaultText = literalExpression "pkgs.minio";
|
||||
type = types.package;
|
||||
description = "Minio package to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
warnings = optional ((cfg.accessKey != "") || (cfg.secretKey != "")) "services.minio.`accessKey` and services.minio.`secretKey` are deprecated, please use services.minio.`rootCredentialsFile` instead.";
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.configDir}' - minio minio - -"
|
||||
] ++ (map (x: "d '" + x + "' - minio minio - - ") cfg.dataDir);
|
||||
|
||||
systemd.services.minio = {
|
||||
description = "Minio Object Storage";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --console-address ${cfg.consoleAddress} --config-dir=${cfg.configDir} ${toString cfg.dataDir}";
|
||||
Type = "simple";
|
||||
User = "minio";
|
||||
Group = "minio";
|
||||
LimitNOFILE = 65536;
|
||||
EnvironmentFile = if (cfg.rootCredentialsFile != null) then cfg.rootCredentialsFile
|
||||
else if ((cfg.accessKey != "") || (cfg.secretKey != "")) then (legacyCredentials cfg)
|
||||
else null;
|
||||
};
|
||||
environment = {
|
||||
MINIO_REGION = "${cfg.region}";
|
||||
MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.minio = {
|
||||
group = "minio";
|
||||
uid = config.ids.uids.minio;
|
||||
};
|
||||
|
||||
users.groups.minio.gid = config.ids.uids.minio;
|
||||
};
|
||||
}
|
||||
101
nixos/modules/services/web-servers/molly-brown.nix
Normal file
101
nixos/modules/services/web-servers/molly-brown.nix
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.molly-brown;
|
||||
settingsFormat = pkgs.formats.toml { };
|
||||
configFile = settingsFormat.generate "molly-brown.toml" cfg.settings;
|
||||
in {
|
||||
|
||||
options.services.molly-brown = {
|
||||
|
||||
enable = mkEnableOption "Molly-Brown Gemini server";
|
||||
|
||||
port = mkOption {
|
||||
default = 1965;
|
||||
type = types.port;
|
||||
description = ''
|
||||
TCP port for molly-brown to bind to.
|
||||
'';
|
||||
};
|
||||
|
||||
hostName = mkOption {
|
||||
type = types.str;
|
||||
default = config.networking.hostName;
|
||||
defaultText = literalExpression "config.networking.hostName";
|
||||
description = ''
|
||||
The hostname to respond to requests for. Requests for URLs with
|
||||
other hosts will result in a status 53 (PROXY REQUEST REFUSED)
|
||||
response.
|
||||
'';
|
||||
};
|
||||
|
||||
certPath = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/lib/acme/example.com/cert.pem";
|
||||
description = ''
|
||||
Path to TLS certificate. An ACME certificate and key may be
|
||||
shared with an HTTP server, but only if molly-brown has
|
||||
permissions allowing it to read such keys.
|
||||
|
||||
As an example:
|
||||
<programlisting>
|
||||
systemd.services.molly-brown.serviceConfig.SupplementaryGroups =
|
||||
[ config.security.acme.certs."example.com".group ];
|
||||
</programlisting>
|
||||
'';
|
||||
};
|
||||
|
||||
keyPath = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/lib/acme/example.com/key.pem";
|
||||
description = "Path to TLS key. See <option>CertPath</option>.";
|
||||
};
|
||||
|
||||
docBase = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/lib/molly-brown";
|
||||
description = "Base directory for Gemini content.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
inherit (settingsFormat) type;
|
||||
default = { };
|
||||
description = ''
|
||||
molly-brown configuration. Refer to
|
||||
<link xlink:href="https://tildegit.org/solderpunk/molly-brown/src/branch/master/example.conf"/>
|
||||
for details on supported values.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.molly-brown.settings = let logDir = "/var/log/molly-brown";
|
||||
in {
|
||||
Port = cfg.port;
|
||||
Hostname = cfg.hostName;
|
||||
CertPath = cfg.certPath;
|
||||
KeyPath = cfg.keyPath;
|
||||
DocBase = cfg.docBase;
|
||||
AccessLog = "${logDir}/access.log";
|
||||
ErrorLog = "${logDir}/error.log";
|
||||
};
|
||||
|
||||
systemd.services.molly-brown = {
|
||||
description = "Molly Brown gemini server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
LogsDirectory = "molly-brown";
|
||||
ExecStart = "${pkgs.molly-brown}/bin/molly-brown -c ${configFile}";
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
1009
nixos/modules/services/web-servers/nginx/default.nix
Normal file
1009
nixos/modules/services/web-servers/nginx/default.nix
Normal file
File diff suppressed because it is too large
Load diff
94
nixos/modules/services/web-servers/nginx/gitweb.nix
Normal file
94
nixos/modules/services/web-servers/nginx/gitweb.nix
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.nginx.gitweb;
|
||||
gitwebConfig = config.services.gitweb;
|
||||
package = pkgs.gitweb.override (optionalAttrs gitwebConfig.gitwebTheme {
|
||||
gitwebTheme = true;
|
||||
});
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
options.services.nginx.gitweb = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
If true, enable gitweb in nginx.
|
||||
'';
|
||||
};
|
||||
|
||||
location = mkOption {
|
||||
default = "/gitweb";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Location to serve gitweb on.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
default = "nginx";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Existing user that the CGI process will belong to. (Default almost surely will do.)
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
default = "nginx";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Group that the CGI process will belong to. (Set to <literal>config.services.gitolite.group</literal> if you are using gitolite.)
|
||||
'';
|
||||
};
|
||||
|
||||
virtualHost = mkOption {
|
||||
default = "_";
|
||||
type = types.str;
|
||||
description = ''
|
||||
VirtualHost to serve gitweb on. Default is catch-all.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.gitweb = {
|
||||
description = "GitWeb service";
|
||||
script = "${package}/gitweb.cgi --fastcgi --nproc=1";
|
||||
environment = {
|
||||
FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock";
|
||||
};
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
RuntimeDirectory = [ "gitweb" ];
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
services.nginx = {
|
||||
virtualHosts.${cfg.virtualHost} = {
|
||||
locations."${cfg.location}/static/" = {
|
||||
alias = "${package}/static/";
|
||||
};
|
||||
locations."${cfg.location}/" = {
|
||||
extraConfig = ''
|
||||
include ${config.services.nginx.package}/conf/fastcgi_params;
|
||||
fastcgi_param GITWEB_CONFIG ${gitwebConfig.gitwebConfigFile};
|
||||
fastcgi_pass unix:/run/gitweb/gitweb.sock;
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with maintainers; [ ];
|
||||
|
||||
}
|
||||
132
nixos/modules/services/web-servers/nginx/location-options.nix
Normal file
132
nixos/modules/services/web-servers/nginx/location-options.nix
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# 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 = {
|
||||
basicAuth = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
user = "password";
|
||||
};
|
||||
'';
|
||||
description = ''
|
||||
Basic Auth protection for a vhost.
|
||||
|
||||
WARNING: This is implemented to store the password in plain text in the
|
||||
Nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
basicAuthFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Basic Auth password file for a vhost.
|
||||
Can be created via: <command>htpasswd -c <filename> <username></command>.
|
||||
|
||||
WARNING: The generate file contains the users' passwords in a
|
||||
non-cryptographically-securely hashed way.
|
||||
'';
|
||||
};
|
||||
|
||||
proxyPass = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "http://www.example.org/";
|
||||
description = ''
|
||||
Adds proxy_pass directive and sets recommended proxy headers if
|
||||
recommendedProxySettings is enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
proxyWebsockets = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
example = true;
|
||||
description = ''
|
||||
Whether to support proxying websocket connections with HTTP/1.1.
|
||||
'';
|
||||
};
|
||||
|
||||
index = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "index.php index.html";
|
||||
description = ''
|
||||
Adds index directive.
|
||||
'';
|
||||
};
|
||||
|
||||
tryFiles = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "$uri =404";
|
||||
description = ''
|
||||
Adds try_files directive.
|
||||
'';
|
||||
};
|
||||
|
||||
root = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/your/root/directory";
|
||||
description = ''
|
||||
Root directory for requests.
|
||||
'';
|
||||
};
|
||||
|
||||
alias = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/your/alias/directory";
|
||||
description = ''
|
||||
Alias directory for requests.
|
||||
'';
|
||||
};
|
||||
|
||||
return = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "301 http://example.com$request_uri";
|
||||
description = ''
|
||||
Adds a return directive, for e.g. redirections.
|
||||
'';
|
||||
};
|
||||
|
||||
fastcgiParams = mkOption {
|
||||
type = types.attrsOf (types.either types.str types.path);
|
||||
default = {};
|
||||
description = ''
|
||||
FastCGI parameters to override. Unlike in the Nginx
|
||||
configuration file, overriding only some default parameters
|
||||
won't unset the default values for other parameters.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
These lines go to the end of the location verbatim.
|
||||
'';
|
||||
};
|
||||
|
||||
priority = mkOption {
|
||||
type = types.int;
|
||||
default = 1000;
|
||||
description = ''
|
||||
Order of this location block in relation to the others in the vhost.
|
||||
The semantics are the same as with `lib.mkOrder`. Smaller values have
|
||||
a greater priority.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
297
nixos/modules/services/web-servers/nginx/vhost-options.nix
Normal file
297
nixos/modules/services/web-servers/nginx/vhost-options.nix
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
# 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.)
|
||||
|
||||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
{
|
||||
options = {
|
||||
serverName = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Name of this virtual host. Defaults to attribute name in virtualHosts.
|
||||
'';
|
||||
example = "example.org";
|
||||
};
|
||||
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
listen = mkOption {
|
||||
type = with types; listOf (submodule { options = {
|
||||
addr = mkOption { type = str; description = "IP address."; };
|
||||
port = mkOption { type = int; description = "Port number."; default = 80; };
|
||||
ssl = mkOption { type = bool; description = "Enable SSL."; default = false; };
|
||||
extraParameters = mkOption { type = listOf str; description = "Extra parameters of this listen directive."; default = []; example = [ "backlog=1024" "deferred" ]; };
|
||||
}; });
|
||||
default = [];
|
||||
example = [
|
||||
{ addr = "195.154.1.1"; port = 443; ssl = true; }
|
||||
{ addr = "192.154.1.1"; port = 80; }
|
||||
];
|
||||
description = ''
|
||||
Listen addresses and ports for this virtual host.
|
||||
IPv6 addresses must be enclosed in square brackets.
|
||||
Note: this option overrides <literal>addSSL</literal>
|
||||
and <literal>onlySSL</literal>.
|
||||
|
||||
If you only want to set the addresses manually and not
|
||||
the ports, take a look at <literal>listenAddresses</literal>
|
||||
'';
|
||||
};
|
||||
|
||||
listenAddresses = mkOption {
|
||||
type = with types; listOf str;
|
||||
|
||||
description = ''
|
||||
Listen addresses for this virtual host.
|
||||
Compared to <literal>listen</literal> this only sets the addreses
|
||||
and the ports are choosen automatically.
|
||||
|
||||
Note: This option overrides <literal>enableIPv6</literal>
|
||||
'';
|
||||
default = [];
|
||||
example = [ "127.0.0.1" "::1" ];
|
||||
};
|
||||
|
||||
enableACME = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to ask Let's Encrypt to sign a certificate for this vhost.
|
||||
Alternately, you can use an existing certificate through <option>useACMEHost</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
useACMEHost = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
A host of an existing Let's Encrypt certificate to use.
|
||||
This is useful if you have many subdomains and want to avoid hitting the
|
||||
<link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
|
||||
Alternately, you can generate a certificate through <option>enableACME</option>.
|
||||
<emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using <xref linkend="opt-security.acme.certs"/>.</emphasis>
|
||||
'';
|
||||
};
|
||||
|
||||
acmeRoot = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = "/var/lib/acme/acme-challenge";
|
||||
description = ''
|
||||
Directory for the acme challenge which is PUBLIC, don't put certs or keys in here.
|
||||
Set to null to inherit from config.security.acme.
|
||||
'';
|
||||
};
|
||||
|
||||
acmeFallbackHost = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Host which to proxy requests to if acme challenge is not found. Useful
|
||||
if you want multiple hosts to be able to verify the same domain name.
|
||||
'';
|
||||
};
|
||||
|
||||
addSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
|
||||
<literal>listen</literal> to listen on all interfaces on the respective default
|
||||
ports (80, 443).
|
||||
'';
|
||||
};
|
||||
|
||||
onlySSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable HTTPS and reject plain HTTP connections. This will set
|
||||
defaults for <literal>listen</literal> to listen on all interfaces on port 443.
|
||||
'';
|
||||
};
|
||||
|
||||
enableSSL = mkOption {
|
||||
type = types.bool;
|
||||
visible = false;
|
||||
default = false;
|
||||
};
|
||||
|
||||
forceSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to add a separate nginx server block that permanently redirects (301)
|
||||
all plain HTTP traffic to HTTPS. This will set defaults for
|
||||
<literal>listen</literal> to listen on all interfaces on the respective default
|
||||
ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
|
||||
'';
|
||||
};
|
||||
|
||||
rejectSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to listen for and reject all HTTPS connections to this vhost. Useful in
|
||||
<link linkend="opt-services.nginx.virtualHosts._name_.default">default</link>
|
||||
server blocks to avoid serving the certificate for another vhost. Uses the
|
||||
<literal>ssl_reject_handshake</literal> directive available in nginx versions
|
||||
1.19.4 and above.
|
||||
'';
|
||||
};
|
||||
|
||||
kTLS = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable kTLS support.
|
||||
Implementing TLS in the kernel (kTLS) improves performance by significantly
|
||||
reducing the need for copying operations between user space and the kernel.
|
||||
Required Nginx version 1.21.4 or later.
|
||||
'';
|
||||
};
|
||||
|
||||
sslCertificate = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/host.cert";
|
||||
description = "Path to server SSL certificate.";
|
||||
};
|
||||
|
||||
sslCertificateKey = mkOption {
|
||||
type = types.path;
|
||||
example = "/var/host.key";
|
||||
description = "Path to server SSL certificate key.";
|
||||
};
|
||||
|
||||
sslTrustedCertificate = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = literalExpression ''"''${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"'';
|
||||
description = "Path to root SSL certificate for stapling and client certificates.";
|
||||
};
|
||||
|
||||
http2 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable HTTP 2.
|
||||
Note that (as of writing) due to nginx's implementation, to disable
|
||||
HTTP 2 you have to disable it on all vhosts that use a given
|
||||
IP address / port.
|
||||
If there is one server block configured to enable http2,then it is
|
||||
enabled for all server blocks on this IP.
|
||||
See https://stackoverflow.com/a/39466948/263061.
|
||||
'';
|
||||
};
|
||||
|
||||
http3 = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable HTTP 3.
|
||||
This requires using <literal>pkgs.nginxQuic</literal> package
|
||||
which can be achieved by setting <literal>services.nginx.package = pkgs.nginxQuic;</literal>.
|
||||
Note that HTTP 3 support is experimental and
|
||||
*not* yet recommended for production.
|
||||
Read more at https://quic.nginx.org/
|
||||
'';
|
||||
};
|
||||
|
||||
reuseport = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Create an individual listening socket .
|
||||
It is required to specify only once on one of the hosts.
|
||||
'';
|
||||
};
|
||||
|
||||
root = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/data/webserver/docs";
|
||||
description = ''
|
||||
The path of the web root directory.
|
||||
'';
|
||||
};
|
||||
|
||||
default = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Makes this vhost the default.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
These lines go to the end of the vhost verbatim.
|
||||
'';
|
||||
};
|
||||
|
||||
globalRedirect = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "newserver.example.org";
|
||||
description = ''
|
||||
If set, all requests for this host are redirected permanently to
|
||||
the given hostname.
|
||||
'';
|
||||
};
|
||||
|
||||
basicAuth = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
user = "password";
|
||||
};
|
||||
'';
|
||||
description = ''
|
||||
Basic Auth protection for a vhost.
|
||||
|
||||
WARNING: This is implemented to store the password in plain text in the
|
||||
Nix store.
|
||||
'';
|
||||
};
|
||||
|
||||
basicAuthFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Basic Auth password file for a vhost.
|
||||
Can be created via: <command>htpasswd -c <filename> <username></command>.
|
||||
|
||||
WARNING: The generate file contains the users' passwords in a
|
||||
non-cryptographically-securely hashed way.
|
||||
'';
|
||||
};
|
||||
|
||||
locations = mkOption {
|
||||
type = types.attrsOf (types.submodule (import ./location-options.nix {
|
||||
inherit lib;
|
||||
}));
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
"/" = {
|
||||
proxyPass = "http://localhost:3000";
|
||||
};
|
||||
};
|
||||
'';
|
||||
description = "Declarative location config";
|
||||
};
|
||||
};
|
||||
}
|
||||
282
nixos/modules/services/web-servers/phpfpm/default.nix
Normal file
282
nixos/modules/services/web-servers/phpfpm/default.nix
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.phpfpm;
|
||||
|
||||
runtimeDir = "/run/phpfpm";
|
||||
|
||||
toStr = value:
|
||||
if true == value then "yes"
|
||||
else if false == value then "no"
|
||||
else toString value;
|
||||
|
||||
fpmCfgFile = pool: poolOpts: pkgs.writeText "phpfpm-${pool}.conf" ''
|
||||
[global]
|
||||
${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)}
|
||||
${optionalString (cfg.extraConfig != null) cfg.extraConfig}
|
||||
|
||||
[${pool}]
|
||||
${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") poolOpts.settings)}
|
||||
${concatStringsSep "\n" (mapAttrsToList (n: v: "env[${n}] = ${toStr v}") poolOpts.phpEnv)}
|
||||
${optionalString (poolOpts.extraConfig != null) poolOpts.extraConfig}
|
||||
'';
|
||||
|
||||
phpIni = poolOpts: pkgs.runCommand "php.ini" {
|
||||
inherit (poolOpts) phpPackage phpOptions;
|
||||
preferLocalBuild = true;
|
||||
passAsFile = [ "phpOptions" ];
|
||||
} ''
|
||||
cat ${poolOpts.phpPackage}/etc/php.ini $phpOptionsPath > $out
|
||||
'';
|
||||
|
||||
poolOpts = { name, ... }:
|
||||
let
|
||||
poolOpts = cfg.pools.${name};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
socket = mkOption {
|
||||
type = types.str;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Path to the unix socket file on which to accept FastCGI requests.
|
||||
<note><para>This option is read-only and managed by NixOS.</para></note>
|
||||
'';
|
||||
example = "${runtimeDir}/<name>.sock";
|
||||
};
|
||||
|
||||
listen = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "/path/to/unix/socket";
|
||||
description = ''
|
||||
The address on which to accept FastCGI requests.
|
||||
'';
|
||||
};
|
||||
|
||||
phpPackage = mkOption {
|
||||
type = types.package;
|
||||
default = cfg.phpPackage;
|
||||
defaultText = literalExpression "config.services.phpfpm.phpPackage";
|
||||
description = ''
|
||||
The PHP package to use for running this PHP-FPM pool.
|
||||
'';
|
||||
};
|
||||
|
||||
phpOptions = mkOption {
|
||||
type = types.lines;
|
||||
description = ''
|
||||
"Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
|
||||
'';
|
||||
};
|
||||
|
||||
phpEnv = lib.mkOption {
|
||||
type = with types; attrsOf str;
|
||||
default = {};
|
||||
description = ''
|
||||
Environment variables used for this PHP-FPM pool.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
HOSTNAME = "$HOSTNAME";
|
||||
TMP = "/tmp";
|
||||
TMPDIR = "/tmp";
|
||||
TEMP = "/tmp";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "User account under which this pool runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
description = "Group account under which this pool runs.";
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf (oneOf [ str int bool ]);
|
||||
default = {};
|
||||
description = ''
|
||||
PHP-FPM pool directives. Refer to the "List of pool directives" section of
|
||||
<link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
|
||||
for details. Note that settings names must be enclosed in quotes (e.g.
|
||||
<literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
|
||||
'';
|
||||
example = literalExpression ''
|
||||
{
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 75;
|
||||
"pm.start_servers" = 10;
|
||||
"pm.min_spare_servers" = 5;
|
||||
"pm.max_spare_servers" = 20;
|
||||
"pm.max_requests" = 500;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = null;
|
||||
description = ''
|
||||
Extra lines that go into the pool configuration.
|
||||
See the documentation on <literal>php-fpm.conf</literal> for
|
||||
details on configuration directives.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
socket = if poolOpts.listen == "" then "${runtimeDir}/${name}.sock" else poolOpts.listen;
|
||||
group = mkDefault poolOpts.user;
|
||||
phpOptions = mkBefore cfg.phpOptions;
|
||||
|
||||
settings = mapAttrs (name: mkDefault){
|
||||
listen = poolOpts.socket;
|
||||
user = poolOpts.user;
|
||||
group = poolOpts.group;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.")
|
||||
(mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "")
|
||||
];
|
||||
|
||||
options = {
|
||||
services.phpfpm = {
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf (oneOf [ str int bool ]);
|
||||
default = {};
|
||||
description = ''
|
||||
PHP-FPM global directives. Refer to the "List of global php-fpm.conf directives" section of
|
||||
<link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
|
||||
for details. Note that settings names must be enclosed in quotes (e.g.
|
||||
<literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
|
||||
You need not specify the options <literal>error_log</literal> or
|
||||
<literal>daemonize</literal> here, since they are generated by NixOS.
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = with types; nullOr lines;
|
||||
default = null;
|
||||
description = ''
|
||||
Extra configuration that should be put in the global section of
|
||||
the PHP-FPM configuration file. Do not specify the options
|
||||
<literal>error_log</literal> or
|
||||
<literal>daemonize</literal> here, since they are generated by
|
||||
NixOS.
|
||||
'';
|
||||
};
|
||||
|
||||
phpPackage = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.php;
|
||||
defaultText = literalExpression "pkgs.php";
|
||||
description = ''
|
||||
The PHP package to use for running the PHP-FPM service.
|
||||
'';
|
||||
};
|
||||
|
||||
phpOptions = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example =
|
||||
''
|
||||
date.timezone = "CET"
|
||||
'';
|
||||
description = ''
|
||||
Options appended to the PHP configuration file <filename>php.ini</filename>.
|
||||
'';
|
||||
};
|
||||
|
||||
pools = mkOption {
|
||||
type = types.attrsOf (types.submodule poolOpts);
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
mypool = {
|
||||
user = "php";
|
||||
group = "php";
|
||||
phpPackage = pkgs.php;
|
||||
settings = {
|
||||
"pm" = "dynamic";
|
||||
"pm.max_children" = 75;
|
||||
"pm.start_servers" = 10;
|
||||
"pm.min_spare_servers" = 5;
|
||||
"pm.max_spare_servers" = 20;
|
||||
"pm.max_requests" = 500;
|
||||
};
|
||||
}
|
||||
}'';
|
||||
description = ''
|
||||
PHP-FPM pools. If no pools are defined, the PHP-FPM
|
||||
service is disabled.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.pools != {}) {
|
||||
|
||||
warnings =
|
||||
mapAttrsToList (pool: poolOpts: ''
|
||||
Using config.services.phpfpm.pools.${pool}.listen is deprecated and will become unsupported in a future release. Please reference the read-only option config.services.phpfpm.pools.${pool}.socket to access the path of your socket.
|
||||
'') (filterAttrs (pool: poolOpts: poolOpts.listen != "") cfg.pools) ++
|
||||
mapAttrsToList (pool: poolOpts: ''
|
||||
Using config.services.phpfpm.pools.${pool}.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.pools.${pool}.settings.
|
||||
'') (filterAttrs (pool: poolOpts: poolOpts.extraConfig != null) cfg.pools) ++
|
||||
optional (cfg.extraConfig != null) ''
|
||||
Using config.services.phpfpm.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.settings.
|
||||
''
|
||||
;
|
||||
|
||||
services.phpfpm.settings = {
|
||||
error_log = "syslog";
|
||||
daemonize = false;
|
||||
};
|
||||
|
||||
systemd.slices.phpfpm = {
|
||||
description = "PHP FastCGI Process manager pools slice";
|
||||
};
|
||||
|
||||
systemd.targets.phpfpm = {
|
||||
description = "PHP FastCGI Process manager pools target";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
systemd.services = mapAttrs' (pool: poolOpts:
|
||||
nameValuePair "phpfpm-${pool}" {
|
||||
description = "PHP FastCGI Process Manager service for pool ${pool}";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "phpfpm.target" ];
|
||||
partOf = [ "phpfpm.target" ];
|
||||
serviceConfig = let
|
||||
cfgFile = fpmCfgFile pool poolOpts;
|
||||
iniFile = phpIni poolOpts;
|
||||
in {
|
||||
Slice = "phpfpm.slice";
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "full";
|
||||
ProtectHome = true;
|
||||
# XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work
|
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
|
||||
Type = "notify";
|
||||
ExecStart = "${poolOpts.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
|
||||
RuntimeDirectory = "phpfpm";
|
||||
RuntimeDirectoryPreserve = true; # Relevant when multiple processes are running
|
||||
Restart = "always";
|
||||
};
|
||||
}
|
||||
) cfg.pools;
|
||||
};
|
||||
}
|
||||
135
nixos/modules/services/web-servers/pomerium.nix
Normal file
135
nixos/modules/services/web-servers/pomerium.nix
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
format = pkgs.formats.yaml {};
|
||||
in
|
||||
{
|
||||
options.services.pomerium = {
|
||||
enable = mkEnableOption "the Pomerium authenticating reverse proxy";
|
||||
|
||||
configFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = "Path to Pomerium config YAML. If set, overrides services.pomerium.settings.";
|
||||
};
|
||||
|
||||
useACMEHost = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
If set, use a NixOS-generated ACME certificate with the specified name.
|
||||
|
||||
Note that this will require you to use a non-HTTP-based challenge, or
|
||||
disable Pomerium's in-built HTTP redirect server by setting
|
||||
http_redirect_addr to null and use a different HTTP server for serving
|
||||
the challenge response.
|
||||
|
||||
If you're using an HTTP-based challenge, you should use the
|
||||
Pomerium-native autocert option instead.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
The contents of Pomerium's config.yaml, in Nix expressions.
|
||||
|
||||
Specifying configFile will override this in its entirety.
|
||||
|
||||
See <link xlink:href="https://pomerium.io/reference/">the Pomerium
|
||||
configuration reference</link> for more information about what to put
|
||||
here.
|
||||
'';
|
||||
default = {};
|
||||
type = format.type;
|
||||
};
|
||||
|
||||
secretsFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to file containing secrets for Pomerium, in systemd
|
||||
EnvironmentFile format. See the systemd.exec(5) man page.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = let
|
||||
cfg = config.services.pomerium;
|
||||
cfgFile = if cfg.configFile != null then cfg.configFile else (format.generate "pomerium.yaml" cfg.settings);
|
||||
in mkIf cfg.enable ({
|
||||
systemd.services.pomerium = {
|
||||
description = "Pomerium authenticating reverse proxy";
|
||||
wants = [ "network.target" ] ++ (optional (cfg.useACMEHost != null) "acme-finished-${cfg.useACMEHost}.target");
|
||||
after = [ "network.target" ] ++ (optional (cfg.useACMEHost != null) "acme-finished-${cfg.useACMEHost}.target");
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = optionalAttrs (cfg.useACMEHost != null) {
|
||||
CERTIFICATE_FILE = "fullchain.pem";
|
||||
CERTIFICATE_KEY_FILE = "key.pem";
|
||||
};
|
||||
startLimitIntervalSec = 60;
|
||||
script = ''
|
||||
if [[ -v CREDENTIALS_DIRECTORY ]]; then
|
||||
cd "$CREDENTIALS_DIRECTORY"
|
||||
fi
|
||||
exec "${pkgs.pomerium}/bin/pomerium" -config "${cfgFile}"
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
StateDirectory = [ "pomerium" ];
|
||||
|
||||
PrivateUsers = false; # breaks CAP_NET_BIND_SERVICE
|
||||
MemoryDenyWriteExecute = false; # breaks LuaJIT
|
||||
|
||||
NoNewPrivileges = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
DevicePolicy = "closed";
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelLogs = true;
|
||||
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
LockPersonality = true;
|
||||
SystemCallArchitectures = "native";
|
||||
|
||||
EnvironmentFile = cfg.secretsFile;
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
|
||||
|
||||
LoadCredential = optionals (cfg.useACMEHost != null) [
|
||||
"fullchain.pem:/var/lib/acme/${cfg.useACMEHost}/fullchain.pem"
|
||||
"key.pem:/var/lib/acme/${cfg.useACMEHost}/key.pem"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# postRun hooks on cert renew can't be used to restart Nginx since renewal
|
||||
# runs as the unprivileged acme user. sslTargets are added to wantedBy + before
|
||||
# which allows the acme-finished-$cert.target to signify the successful updating
|
||||
# of certs end-to-end.
|
||||
systemd.services.pomerium-config-reload = mkIf (cfg.useACMEHost != null) {
|
||||
# TODO(lukegb): figure out how to make config reloading work with credentials.
|
||||
|
||||
wantedBy = [ "acme-finished-${cfg.useACMEHost}.target" "multi-user.target" ];
|
||||
# Before the finished targets, after the renew services.
|
||||
before = [ "acme-finished-${cfg.useACMEHost}.target" ];
|
||||
after = [ "acme-${cfg.useACMEHost}.service" ];
|
||||
# Block reloading if not all certs exist yet.
|
||||
unitConfig.ConditionPathExists = [ "${config.security.acme.certs.${cfg.useACMEHost}.directory}/fullchain.pem" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
TimeoutSec = 60;
|
||||
ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active pomerium.service";
|
||||
ExecStart = "/run/current-system/systemd/bin/systemctl --no-block restart pomerium.service";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
423
nixos/modules/services/web-servers/tomcat.nix
Normal file
423
nixos/modules/services/web-servers/tomcat.nix
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.tomcat;
|
||||
tomcat = cfg.package;
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
meta = {
|
||||
maintainers = with maintainers; [ danbst ];
|
||||
};
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.tomcat = {
|
||||
enable = mkEnableOption "Apache Tomcat";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.tomcat9;
|
||||
defaultText = literalExpression "pkgs.tomcat9";
|
||||
example = lib.literalExpression "pkgs.tomcat9";
|
||||
description = ''
|
||||
Which tomcat package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
purifyOnStart = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
On startup, the `baseDir` directory is populated with various files,
|
||||
subdirectories and symlinks. If this option is enabled, these items
|
||||
(except for the `logs` and `work` subdirectories) are first removed.
|
||||
This prevents interference from remainders of an old configuration
|
||||
(libraries, webapps, etc.), so it's recommended to enable this option.
|
||||
'';
|
||||
};
|
||||
|
||||
baseDir = mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/var/tomcat";
|
||||
description = ''
|
||||
Location where Tomcat stores configuration files, web applications
|
||||
and logfiles. Note that it is partially cleared on each service startup
|
||||
if `purifyOnStart` is enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
logDirs = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.path;
|
||||
description = "Directories to create in baseDir/logs/";
|
||||
};
|
||||
|
||||
extraConfigFiles = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.path;
|
||||
description = "Extra configuration files to pull into the tomcat conf directory";
|
||||
};
|
||||
|
||||
extraEnvironment = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "ENVIRONMENT=production" ];
|
||||
description = "Environment Variables to pass to the tomcat service";
|
||||
};
|
||||
|
||||
extraGroups = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
example = [ "users" ];
|
||||
description = "Defines extra groups to which the tomcat user belongs.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "tomcat";
|
||||
description = "User account under which Apache Tomcat runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "tomcat";
|
||||
description = "Group account under which Apache Tomcat runs.";
|
||||
};
|
||||
|
||||
javaOpts = mkOption {
|
||||
type = types.either (types.listOf types.str) types.str;
|
||||
default = "";
|
||||
description = "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat";
|
||||
};
|
||||
|
||||
catalinaOpts = mkOption {
|
||||
type = types.either (types.listOf types.str) types.str;
|
||||
default = "";
|
||||
description = "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container";
|
||||
};
|
||||
|
||||
sharedLibs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications";
|
||||
};
|
||||
|
||||
serverXml = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "
|
||||
Verbatim server.xml configuration.
|
||||
This is mutually exclusive with the virtualHosts options.
|
||||
";
|
||||
};
|
||||
|
||||
commonLibs = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container";
|
||||
};
|
||||
|
||||
webapps = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [ tomcat.webapps ];
|
||||
defaultText = literalExpression "[ config.services.tomcat.package.webapps ]";
|
||||
description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
|
||||
};
|
||||
|
||||
virtualHosts = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
description = "name of the virtualhost";
|
||||
};
|
||||
aliases = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "aliases of the virtualhost";
|
||||
default = [];
|
||||
};
|
||||
webapps = mkOption {
|
||||
type = types.listOf types.path;
|
||||
description = ''
|
||||
List containing web application WAR files and/or directories containing
|
||||
web applications and configuration files for the virtual host.
|
||||
'';
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
description = "List consisting of a virtual host name and a list of web applications to deploy on each virtual host";
|
||||
};
|
||||
|
||||
logPerVirtualHost = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable logging per virtual host.";
|
||||
};
|
||||
|
||||
jdk = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.jdk;
|
||||
defaultText = literalExpression "pkgs.jdk";
|
||||
description = "Which JDK to use.";
|
||||
};
|
||||
|
||||
axis2 = {
|
||||
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Whether to enable an Apache Axis2 container";
|
||||
};
|
||||
|
||||
services = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.str;
|
||||
description = "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf config.services.tomcat.enable {
|
||||
|
||||
users.groups.tomcat.gid = config.ids.gids.tomcat;
|
||||
|
||||
users.users.tomcat =
|
||||
{ uid = config.ids.uids.tomcat;
|
||||
description = "Tomcat user";
|
||||
home = "/homeless-shelter";
|
||||
group = "tomcat";
|
||||
extraGroups = cfg.extraGroups;
|
||||
};
|
||||
|
||||
systemd.services.tomcat = {
|
||||
description = "Apache Tomcat server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
preStart = ''
|
||||
${lib.optionalString cfg.purifyOnStart ''
|
||||
# Delete most directories/symlinks we create from the existing base directory,
|
||||
# to get rid of remainders of an old configuration.
|
||||
# The list of directories to delete is taken from the "mkdir" command below,
|
||||
# excluding "logs" (because logs are valuable) and "work" (because normally
|
||||
# session files are there), and additionally including "bin".
|
||||
rm -rf ${cfg.baseDir}/{conf,virtualhosts,temp,lib,shared/lib,webapps,bin}
|
||||
''}
|
||||
|
||||
# Create the base directory
|
||||
mkdir -p \
|
||||
${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
|
||||
chown ${cfg.user}:${cfg.group} \
|
||||
${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
|
||||
|
||||
# Create a symlink to the bin directory of the tomcat component
|
||||
ln -sfn ${tomcat}/bin ${cfg.baseDir}/bin
|
||||
|
||||
# Symlink the config files in the conf/ directory (except for catalina.properties and server.xml)
|
||||
for i in $(ls ${tomcat}/conf | grep -v catalina.properties | grep -v server.xml); do
|
||||
ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
|
||||
done
|
||||
|
||||
${if cfg.extraConfigFiles != [] then ''
|
||||
for i in ${toString cfg.extraConfigFiles}; do
|
||||
ln -sfn $i ${cfg.baseDir}/conf/`basename $i`
|
||||
done
|
||||
'' else ""}
|
||||
|
||||
# Create a modified catalina.properties file
|
||||
# Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries
|
||||
sed -e 's|''${catalina.home}|''${catalina.base}|g' \
|
||||
-e 's|shared.loader=|shared.loader=''${catalina.base}/shared/lib/*.jar|' \
|
||||
${tomcat}/conf/catalina.properties > ${cfg.baseDir}/conf/catalina.properties
|
||||
|
||||
${if cfg.serverXml != "" then ''
|
||||
cp -f ${pkgs.writeTextDir "server.xml" cfg.serverXml}/* ${cfg.baseDir}/conf/
|
||||
'' else
|
||||
let
|
||||
hostElementForVirtualHost = virtualHost: ''
|
||||
<Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps"
|
||||
unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
|
||||
'' + concatStrings (innerElementsForVirtualHost virtualHost) + ''
|
||||
</Host>
|
||||
'';
|
||||
innerElementsForVirtualHost = virtualHost:
|
||||
(map (alias: ''
|
||||
<Alias>${alias}</Alias>
|
||||
'') virtualHost.aliases)
|
||||
++ (optional cfg.logPerVirtualHost ''
|
||||
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}"
|
||||
prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/>
|
||||
'');
|
||||
hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
|
||||
hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString;
|
||||
in ''
|
||||
# Create a modified server.xml which also includes all virtual hosts
|
||||
sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \
|
||||
${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
|
||||
''
|
||||
}
|
||||
${optionalString (cfg.logDirs != []) ''
|
||||
for i in ${toString cfg.logDirs}; do
|
||||
mkdir -p ${cfg.baseDir}/logs/$i
|
||||
chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/$i
|
||||
done
|
||||
''}
|
||||
${optionalString cfg.logPerVirtualHost (toString (map (h: ''
|
||||
mkdir -p ${cfg.baseDir}/logs/${h.name}
|
||||
chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name}
|
||||
'') cfg.virtualHosts))}
|
||||
|
||||
# Symlink all the given common libs files or paths into the lib/ directory
|
||||
for i in ${tomcat} ${toString cfg.commonLibs}; do
|
||||
if [ -f $i ]; then
|
||||
# If the given web application is a file, symlink it into the common/lib/ directory
|
||||
ln -sfn $i ${cfg.baseDir}/lib/`basename $i`
|
||||
elif [ -d $i ]; then
|
||||
# If the given web application is a directory, then iterate over the files
|
||||
# in the special purpose directories and symlink them into the tomcat tree
|
||||
|
||||
for j in $i/lib/*; do
|
||||
ln -sfn $j ${cfg.baseDir}/lib/`basename $j`
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
# Symlink all the given shared libs files or paths into the shared/lib/ directory
|
||||
for i in ${toString cfg.sharedLibs}; do
|
||||
if [ -f $i ]; then
|
||||
# If the given web application is a file, symlink it into the common/lib/ directory
|
||||
ln -sfn $i ${cfg.baseDir}/shared/lib/`basename $i`
|
||||
elif [ -d $i ]; then
|
||||
# If the given web application is a directory, then iterate over the files
|
||||
# in the special purpose directories and symlink them into the tomcat tree
|
||||
|
||||
for j in $i/shared/lib/*; do
|
||||
ln -sfn $j ${cfg.baseDir}/shared/lib/`basename $j`
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
# Symlink all the given web applications files or paths into the webapps/ directory
|
||||
for i in ${toString cfg.webapps}; do
|
||||
if [ -f $i ]; then
|
||||
# If the given web application is a file, symlink it into the webapps/ directory
|
||||
ln -sfn $i ${cfg.baseDir}/webapps/`basename $i`
|
||||
elif [ -d $i ]; then
|
||||
# If the given web application is a directory, then iterate over the files
|
||||
# in the special purpose directories and symlink them into the tomcat tree
|
||||
|
||||
for j in $i/webapps/*; do
|
||||
ln -sfn $j ${cfg.baseDir}/webapps/`basename $j`
|
||||
done
|
||||
|
||||
# Also symlink the configuration files if they are included
|
||||
if [ -d $i/conf/Catalina ]; then
|
||||
for j in $i/conf/Catalina/*; do
|
||||
mkdir -p ${cfg.baseDir}/conf/Catalina/localhost
|
||||
ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
|
||||
done
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
${toString (map (virtualHost: ''
|
||||
# Create webapps directory for the virtual host
|
||||
mkdir -p ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
|
||||
|
||||
# Modify ownership
|
||||
chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
|
||||
|
||||
# Symlink all the given web applications files or paths into the webapps/ directory
|
||||
# of this virtual host
|
||||
for i in "${if virtualHost ? webapps then toString virtualHost.webapps else ""}"; do
|
||||
if [ -f $i ]; then
|
||||
# If the given web application is a file, symlink it into the webapps/ directory
|
||||
ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
|
||||
elif [ -d $i ]; then
|
||||
# If the given web application is a directory, then iterate over the files
|
||||
# in the special purpose directories and symlink them into the tomcat tree
|
||||
|
||||
for j in $i/webapps/*; do
|
||||
ln -sfn $j ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $j`
|
||||
done
|
||||
|
||||
# Also symlink the configuration files if they are included
|
||||
if [ -d $i/conf/Catalina ]; then
|
||||
for j in $i/conf/Catalina/*; do
|
||||
mkdir -p ${cfg.baseDir}/conf/Catalina/${virtualHost.name}
|
||||
ln -sfn $j ${cfg.baseDir}/conf/Catalina/${virtualHost.name}/`basename $j`
|
||||
done
|
||||
fi
|
||||
fi
|
||||
done
|
||||
'') cfg.virtualHosts)}
|
||||
|
||||
${optionalString cfg.axis2.enable ''
|
||||
# Copy the Axis2 web application
|
||||
cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps
|
||||
|
||||
# Turn off addressing, which causes many errors
|
||||
sed -i -e 's%<module ref="addressing"/>%<!-- <module ref="addressing"/> -->%' ${cfg.baseDir}/webapps/axis2/WEB-INF/conf/axis2.xml
|
||||
|
||||
# Modify permissions on the Axis2 application
|
||||
chown -R ${cfg.user}:${cfg.group} ${cfg.baseDir}/webapps/axis2
|
||||
|
||||
# Symlink all the given web service files or paths into the webapps/axis2/WEB-INF/services directory
|
||||
for i in ${toString cfg.axis2.services}; do
|
||||
if [ -f $i ]; then
|
||||
# If the given web service is a file, symlink it into the webapps/axis2/WEB-INF/services
|
||||
ln -sfn $i ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $i`
|
||||
elif [ -d $i ]; then
|
||||
# If the given web application is a directory, then iterate over the files
|
||||
# in the special purpose directories and symlink them into the tomcat tree
|
||||
|
||||
for j in $i/webapps/axis2/WEB-INF/services/*; do
|
||||
ln -sfn $j ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $j`
|
||||
done
|
||||
|
||||
# Also symlink the configuration files if they are included
|
||||
if [ -d $i/conf/Catalina ]; then
|
||||
for j in $i/conf/Catalina/*; do
|
||||
ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
|
||||
done
|
||||
fi
|
||||
fi
|
||||
done
|
||||
''}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
PermissionsStartOnly = true;
|
||||
PIDFile="/run/tomcat/tomcat.pid";
|
||||
RuntimeDirectory = "tomcat";
|
||||
User = cfg.user;
|
||||
Environment=[
|
||||
"CATALINA_BASE=${cfg.baseDir}"
|
||||
"CATALINA_PID=/run/tomcat/tomcat.pid"
|
||||
"JAVA_HOME='${cfg.jdk}'"
|
||||
"JAVA_OPTS='${builtins.toString cfg.javaOpts}'"
|
||||
"CATALINA_OPTS='${builtins.toString cfg.catalinaOpts}'"
|
||||
] ++ cfg.extraEnvironment;
|
||||
ExecStart = "${tomcat}/bin/startup.sh";
|
||||
ExecStop = "${tomcat}/bin/shutdown.sh";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
170
nixos/modules/services/web-servers/traefik.nix
Normal file
170
nixos/modules/services/web-servers/traefik.nix
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.traefik;
|
||||
jsonValue = with types;
|
||||
let
|
||||
valueType = nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
(lazyAttrsOf valueType)
|
||||
(listOf valueType)
|
||||
]) // {
|
||||
description = "JSON value";
|
||||
emptyValue.value = { };
|
||||
};
|
||||
in valueType;
|
||||
dynamicConfigFile = if cfg.dynamicConfigFile == null then
|
||||
pkgs.runCommand "config.toml" {
|
||||
buildInputs = [ pkgs.remarshal ];
|
||||
preferLocalBuild = true;
|
||||
} ''
|
||||
remarshal -if json -of toml \
|
||||
< ${
|
||||
pkgs.writeText "dynamic_config.json"
|
||||
(builtins.toJSON cfg.dynamicConfigOptions)
|
||||
} \
|
||||
> $out
|
||||
''
|
||||
else
|
||||
cfg.dynamicConfigFile;
|
||||
staticConfigFile = if cfg.staticConfigFile == null then
|
||||
pkgs.runCommand "config.toml" {
|
||||
buildInputs = [ pkgs.yj ];
|
||||
preferLocalBuild = true;
|
||||
} ''
|
||||
yj -jt -i \
|
||||
< ${
|
||||
pkgs.writeText "static_config.json" (builtins.toJSON
|
||||
(recursiveUpdate cfg.staticConfigOptions {
|
||||
providers.file.filename = "${dynamicConfigFile}";
|
||||
}))
|
||||
} \
|
||||
> $out
|
||||
''
|
||||
else
|
||||
cfg.staticConfigFile;
|
||||
in {
|
||||
options.services.traefik = {
|
||||
enable = mkEnableOption "Traefik web server";
|
||||
|
||||
staticConfigFile = mkOption {
|
||||
default = null;
|
||||
example = literalExpression "/path/to/static_config.toml";
|
||||
type = types.nullOr types.path;
|
||||
description = ''
|
||||
Path to traefik's static configuration to use.
|
||||
(Using that option has precedence over <literal>staticConfigOptions</literal> and <literal>dynamicConfigOptions</literal>)
|
||||
'';
|
||||
};
|
||||
|
||||
staticConfigOptions = mkOption {
|
||||
description = ''
|
||||
Static configuration for Traefik.
|
||||
'';
|
||||
type = jsonValue;
|
||||
default = { entryPoints.http.address = ":80"; };
|
||||
example = {
|
||||
entryPoints.web.address = ":8080";
|
||||
entryPoints.http.address = ":80";
|
||||
|
||||
api = { };
|
||||
};
|
||||
};
|
||||
|
||||
dynamicConfigFile = mkOption {
|
||||
default = null;
|
||||
example = literalExpression "/path/to/dynamic_config.toml";
|
||||
type = types.nullOr types.path;
|
||||
description = ''
|
||||
Path to traefik's dynamic configuration to use.
|
||||
(Using that option has precedence over <literal>dynamicConfigOptions</literal>)
|
||||
'';
|
||||
};
|
||||
|
||||
dynamicConfigOptions = mkOption {
|
||||
description = ''
|
||||
Dynamic configuration for Traefik.
|
||||
'';
|
||||
type = jsonValue;
|
||||
default = { };
|
||||
example = {
|
||||
http.routers.router1 = {
|
||||
rule = "Host(`localhost`)";
|
||||
service = "service1";
|
||||
};
|
||||
|
||||
http.services.service1.loadBalancer.servers =
|
||||
[{ url = "http://localhost:8080"; }];
|
||||
};
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
default = "/var/lib/traefik";
|
||||
type = types.path;
|
||||
description = ''
|
||||
Location for any persistent data traefik creates, ie. acme
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
default = "traefik";
|
||||
type = types.str;
|
||||
example = "docker";
|
||||
description = ''
|
||||
Set the group that traefik runs under.
|
||||
For the docker backend this needs to be set to <literal>docker</literal> instead.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.traefik;
|
||||
defaultText = literalExpression "pkgs.traefik";
|
||||
type = types.package;
|
||||
description = "Traefik package to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0700 traefik traefik - -" ];
|
||||
|
||||
systemd.services.traefik = {
|
||||
description = "Traefik web server";
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
startLimitIntervalSec = 86400;
|
||||
startLimitBurst = 5;
|
||||
serviceConfig = {
|
||||
ExecStart =
|
||||
"${cfg.package}/bin/traefik --configfile=${staticConfigFile}";
|
||||
Type = "simple";
|
||||
User = "traefik";
|
||||
Group = cfg.group;
|
||||
Restart = "on-failure";
|
||||
AmbientCapabilities = "cap_net_bind_service";
|
||||
CapabilityBoundingSet = "cap_net_bind_service";
|
||||
NoNewPrivileges = true;
|
||||
LimitNPROC = 64;
|
||||
LimitNOFILE = 1048576;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectHome = true;
|
||||
ProtectSystem = "full";
|
||||
ReadWriteDirectories = cfg.dataDir;
|
||||
};
|
||||
};
|
||||
|
||||
users.users.traefik = {
|
||||
group = "traefik";
|
||||
home = cfg.dataDir;
|
||||
createHome = true;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.traefik = { };
|
||||
};
|
||||
}
|
||||
310
nixos/modules/services/web-servers/trafficserver/default.nix
Normal file
310
nixos/modules/services/web-servers/trafficserver/default.nix
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.trafficserver;
|
||||
user = config.users.users.trafficserver.name;
|
||||
group = config.users.groups.trafficserver.name;
|
||||
|
||||
getManualUrl = name: "https://docs.trafficserver.apache.org/en/latest/admin-guide/files/${name}.en.html";
|
||||
|
||||
yaml = pkgs.formats.yaml { };
|
||||
|
||||
mkYamlConf = name: cfg:
|
||||
if cfg != null then {
|
||||
"trafficserver/${name}.yaml".source = yaml.generate "${name}.yaml" cfg;
|
||||
} else {
|
||||
"trafficserver/${name}.yaml".text = "";
|
||||
};
|
||||
|
||||
mkRecordLines = path: value:
|
||||
if isAttrs value then
|
||||
lib.mapAttrsToList (n: v: mkRecordLines (path ++ [ n ]) v) value
|
||||
else if isInt value then
|
||||
"CONFIG ${concatStringsSep "." path} INT ${toString value}"
|
||||
else if isFloat value then
|
||||
"CONFIG ${concatStringsSep "." path} FLOAT ${toString value}"
|
||||
else
|
||||
"CONFIG ${concatStringsSep "." path} STRING ${toString value}";
|
||||
|
||||
mkRecordsConfig = cfg: concatStringsSep "\n" (flatten (mkRecordLines [ ] cfg));
|
||||
mkPluginConfig = cfg: concatStringsSep "\n" (map (p: "${p.path} ${p.arg}") cfg);
|
||||
in
|
||||
{
|
||||
options.services.trafficserver = {
|
||||
enable = mkEnableOption "Apache Traffic Server";
|
||||
|
||||
cache = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "dest_domain=example.com suffix=js action=never-cache";
|
||||
description = ''
|
||||
Caching rules that overrule the origin's caching policy.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "cache.config"}">upstream
|
||||
documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
hosting = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "domain=example.com volume=1";
|
||||
description = ''
|
||||
Partition the cache according to origin server or domain
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "hosting.config"}">
|
||||
upstream documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
ipAllow = mkOption {
|
||||
type = types.nullOr yaml.type;
|
||||
default = lib.importJSON ./ip_allow.json;
|
||||
defaultText = literalDocBook "upstream defaults";
|
||||
example = literalExpression ''
|
||||
{
|
||||
ip_allow = [{
|
||||
apply = "in";
|
||||
ip_addrs = "127.0.0.1";
|
||||
action = "allow";
|
||||
methods = "ALL";
|
||||
}];
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Control client access to Traffic Server and Traffic Server connections
|
||||
to upstream servers.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "ip_allow.yaml"}">upstream
|
||||
documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
logging = mkOption {
|
||||
type = types.nullOr yaml.type;
|
||||
default = lib.importJSON ./logging.json;
|
||||
defaultText = literalDocBook "upstream defaults";
|
||||
example = { };
|
||||
description = ''
|
||||
Configure logs.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "logging.yaml"}">upstream
|
||||
documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
parent = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
dest_domain=. method=get parent="p1.example:8080; p2.example:8080" round_robin=true
|
||||
'';
|
||||
description = ''
|
||||
Identify the parent proxies used in an cache hierarchy.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "parent.config"}">upstream
|
||||
documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
plugins = mkOption {
|
||||
default = [ ];
|
||||
|
||||
description = ''
|
||||
Controls run-time loadable plugins available to Traffic Server, as
|
||||
well as their configuration.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "plugin.config"}">upstream
|
||||
documentation</link> for more details.
|
||||
'';
|
||||
|
||||
type = with types;
|
||||
listOf (submodule {
|
||||
options.path = mkOption {
|
||||
type = str;
|
||||
example = "xdebug.so";
|
||||
description = ''
|
||||
Path to plugin. The path can either be absolute, or relative to
|
||||
the plugin directory.
|
||||
'';
|
||||
};
|
||||
options.arg = mkOption {
|
||||
type = str;
|
||||
default = "";
|
||||
example = "--header=ATS-My-Debug";
|
||||
description = "arguments to pass to the plugin";
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
records = mkOption {
|
||||
type = with types;
|
||||
let valueType = (attrsOf (oneOf [ int float str valueType ])) // {
|
||||
description = "Traffic Server records value";
|
||||
};
|
||||
in
|
||||
valueType;
|
||||
default = { };
|
||||
example = { proxy.config.proxy_name = "my_server"; };
|
||||
description = ''
|
||||
List of configurable variables used by Traffic Server.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "records.config"}">
|
||||
upstream documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
remap = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "map http://from.example http://origin.example";
|
||||
description = ''
|
||||
URL remapping rules used by Traffic Server.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "remap.config"}">
|
||||
upstream documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
splitDns = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
dest_domain=internal.corp.example named="255.255.255.255:212 255.255.255.254" def_domain=corp.example search_list="corp.example corp1.example"
|
||||
dest_domain=!internal.corp.example named=255.255.255.253
|
||||
'';
|
||||
description = ''
|
||||
Specify the DNS server that Traffic Server should use under specific
|
||||
conditions.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "splitdns.config"}">
|
||||
upstream documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
sslMulticert = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
example = "dest_ip=* ssl_cert_name=default.pem";
|
||||
description = ''
|
||||
Configure SSL server certificates to terminate the SSL sessions.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "ssl_multicert.config"}">
|
||||
upstream documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
sni = mkOption {
|
||||
type = types.nullOr yaml.type;
|
||||
default = null;
|
||||
example = literalExpression ''
|
||||
{
|
||||
sni = [{
|
||||
fqdn = "no-http2.example.com";
|
||||
https = "off";
|
||||
}];
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Configure aspects of TLS connection handling for both inbound and
|
||||
outbound connections.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "sni.yaml"}">upstream
|
||||
documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
storage = mkOption {
|
||||
type = types.lines;
|
||||
default = "/var/cache/trafficserver 256M";
|
||||
example = "/dev/disk/by-id/XXXXX volume=1";
|
||||
description = ''
|
||||
List all the storage that make up the Traffic Server cache.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "storage.config"}">
|
||||
upstream documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
strategies = mkOption {
|
||||
type = types.nullOr yaml.type;
|
||||
default = null;
|
||||
description = ''
|
||||
Specify the next hop proxies used in an cache hierarchy and the
|
||||
algorithms used to select the next proxy.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "strategies.yaml"}">
|
||||
upstream documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
|
||||
volume = mkOption {
|
||||
type = types.nullOr yaml.type;
|
||||
default = "";
|
||||
example = "volume=1 scheme=http size=20%";
|
||||
description = ''
|
||||
Manage cache space more efficiently and restrict disk usage by
|
||||
creating cache volumes of different sizes.
|
||||
|
||||
Consult the <link xlink:href="${getManualUrl "volume.config"}">
|
||||
upstream documentation</link> for more details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.etc = {
|
||||
"trafficserver/cache.config".text = cfg.cache;
|
||||
"trafficserver/hosting.config".text = cfg.hosting;
|
||||
"trafficserver/parent.config".text = cfg.parent;
|
||||
"trafficserver/plugin.config".text = mkPluginConfig cfg.plugins;
|
||||
"trafficserver/records.config".text = mkRecordsConfig cfg.records;
|
||||
"trafficserver/remap.config".text = cfg.remap;
|
||||
"trafficserver/splitdns.config".text = cfg.splitDns;
|
||||
"trafficserver/ssl_multicert.config".text = cfg.sslMulticert;
|
||||
"trafficserver/storage.config".text = cfg.storage;
|
||||
"trafficserver/volume.config".text = cfg.volume;
|
||||
} // (mkYamlConf "ip_allow" cfg.ipAllow)
|
||||
// (mkYamlConf "logging" cfg.logging)
|
||||
// (mkYamlConf "sni" cfg.sni)
|
||||
// (mkYamlConf "strategies" cfg.strategies);
|
||||
|
||||
environment.systemPackages = [ pkgs.trafficserver ];
|
||||
systemd.packages = [ pkgs.trafficserver ];
|
||||
|
||||
# Traffic Server does privilege handling independently of systemd, and
|
||||
# therefore should be started as root
|
||||
systemd.services.trafficserver = {
|
||||
enable = true;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
# These directories can't be created by systemd because:
|
||||
#
|
||||
# 1. Traffic Servers starts as root and switches to an unprivileged user
|
||||
# afterwards. The runtime directories defined below are assumed to be
|
||||
# owned by that user.
|
||||
# 2. The bin/trafficserver script assumes these directories exist.
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '/run/trafficserver' - ${user} ${group} - -"
|
||||
"d '/var/cache/trafficserver' - ${user} ${group} - -"
|
||||
"d '/var/lib/trafficserver' - ${user} ${group} - -"
|
||||
"d '/var/log/trafficserver' - ${user} ${group} - -"
|
||||
];
|
||||
|
||||
services.trafficserver = {
|
||||
records.proxy.config.admin.user_id = user;
|
||||
records.proxy.config.body_factory.template_sets_dir =
|
||||
"${pkgs.trafficserver}/etc/trafficserver/body_factory";
|
||||
};
|
||||
|
||||
users.users.trafficserver = {
|
||||
description = "Apache Traffic Server";
|
||||
isSystemUser = true;
|
||||
inherit group;
|
||||
};
|
||||
users.groups.trafficserver = { };
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"ip_allow": [
|
||||
{
|
||||
"apply": "in",
|
||||
"ip_addrs": "127.0.0.1",
|
||||
"action": "allow",
|
||||
"methods": "ALL"
|
||||
},
|
||||
{
|
||||
"apply": "in",
|
||||
"ip_addrs": "::1",
|
||||
"action": "allow",
|
||||
"methods": "ALL"
|
||||
},
|
||||
{
|
||||
"apply": "in",
|
||||
"ip_addrs": "0/0",
|
||||
"action": "deny",
|
||||
"methods": [
|
||||
"PURGE",
|
||||
"PUSH",
|
||||
"DELETE"
|
||||
]
|
||||
},
|
||||
{
|
||||
"apply": "in",
|
||||
"ip_addrs": "::/0",
|
||||
"action": "deny",
|
||||
"methods": [
|
||||
"PURGE",
|
||||
"PUSH",
|
||||
"DELETE"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"logging": {
|
||||
"formats": [
|
||||
{
|
||||
"name": "welf",
|
||||
"format": "id=firewall time=\"%<cqtd> %<cqtt>\" fw=%<phn> pri=6 proto=%<cqus> duration=%<ttmsf> sent=%<psql> rcvd=%<cqhl> src=%<chi> dst=%<shi> dstname=%<shn> user=%<caun> op=%<cqhm> arg=\"%<cqup>\" result=%<pssc> ref=\"%<{Referer}cqh>\" agent=\"%<{user-agent}cqh>\" cache=%<crc>"
|
||||
},
|
||||
{
|
||||
"name": "squid_seconds_only_timestamp",
|
||||
"format": "%<cqts> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>"
|
||||
},
|
||||
{
|
||||
"name": "squid",
|
||||
"format": "%<cqtq> %<ttms> %<chi> %<crc>/%<pssc> %<psql> %<cqhm> %<cquc> %<caun> %<phr>/%<shn> %<psct>"
|
||||
},
|
||||
{
|
||||
"name": "common",
|
||||
"format": "%<chi> - %<caun> [%<cqtn>] \"%<cqtx>\" %<pssc> %<pscl>"
|
||||
},
|
||||
{
|
||||
"name": "extended",
|
||||
"format": "%<chi> - %<caun> [%<cqtn>] \"%<cqtx>\" %<pssc> %<pscl> %<sssc> %<sscl> %<cqcl> %<pqcl> %<cqhl> %<pshl> %<pqhl> %<sshl> %<tts>"
|
||||
},
|
||||
{
|
||||
"name": "extended2",
|
||||
"format": "%<chi> - %<caun> [%<cqtn>] \"%<cqtx>\" %<pssc> %<pscl> %<sssc> %<sscl> %<cqcl> %<pqcl> %<cqhl> %<pshl> %<pqhl> %<sshl> %<tts> %<phr> %<cfsc> %<pfsc> %<crc>"
|
||||
}
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"filename": "squid",
|
||||
"format": "squid",
|
||||
"mode": "binary"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
196
nixos/modules/services/web-servers/ttyd.nix
Normal file
196
nixos/modules/services/web-servers/ttyd.nix
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.ttyd;
|
||||
|
||||
# Command line arguments for the ttyd daemon
|
||||
args = [ "--port" (toString cfg.port) ]
|
||||
++ optionals (cfg.socket != null) [ "--interface" cfg.socket ]
|
||||
++ optionals (cfg.interface != null) [ "--interface" cfg.interface ]
|
||||
++ [ "--signal" (toString cfg.signal) ]
|
||||
++ (concatLists (mapAttrsToList (_k: _v: [ "--client-option" "${_k}=${_v}" ]) cfg.clientOptions))
|
||||
++ [ "--terminal-type" cfg.terminalType ]
|
||||
++ optionals cfg.checkOrigin [ "--check-origin" ]
|
||||
++ [ "--max-clients" (toString cfg.maxClients) ]
|
||||
++ optionals (cfg.indexFile != null) [ "--index" cfg.indexFile ]
|
||||
++ optionals cfg.enableIPv6 [ "--ipv6" ]
|
||||
++ optionals cfg.enableSSL [ "--ssl-cert" cfg.certFile
|
||||
"--ssl-key" cfg.keyFile
|
||||
"--ssl-ca" cfg.caFile ]
|
||||
++ [ "--debug" (toString cfg.logLevel) ];
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.ttyd = {
|
||||
enable = mkEnableOption "ttyd daemon";
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 7681;
|
||||
description = "Port to listen on (use 0 for random port)";
|
||||
};
|
||||
|
||||
socket = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/var/run/ttyd.sock";
|
||||
description = "UNIX domain socket path to bind.";
|
||||
};
|
||||
|
||||
interface = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "eth0";
|
||||
description = "Network interface to bind.";
|
||||
};
|
||||
|
||||
username = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = "Username for basic authentication.";
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
apply = value: if value == null then null else toString value;
|
||||
description = ''
|
||||
File containing the password to use for basic authentication.
|
||||
For insecurely putting the password in the globally readable store use
|
||||
<literal>pkgs.writeText "ttydpw" "MyPassword"</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
signal = mkOption {
|
||||
type = types.ints.u8;
|
||||
default = 1;
|
||||
description = "Signal to send to the command on session close.";
|
||||
};
|
||||
|
||||
clientOptions = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
example = literalExpression ''{
|
||||
fontSize = "16";
|
||||
fontFamily = "Fira Code";
|
||||
|
||||
}'';
|
||||
description = ''
|
||||
Attribute set of client options for xtermjs.
|
||||
<link xlink:href="https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/"/>
|
||||
'';
|
||||
};
|
||||
|
||||
terminalType = mkOption {
|
||||
type = types.str;
|
||||
default = "xterm-256color";
|
||||
description = "Terminal type to report.";
|
||||
};
|
||||
|
||||
checkOrigin = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to allow a websocket connection from a different origin.";
|
||||
};
|
||||
|
||||
maxClients = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = "Maximum clients to support (0, no limit)";
|
||||
};
|
||||
|
||||
indexFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "Custom index.html path";
|
||||
};
|
||||
|
||||
enableIPv6 = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether or not to enable IPv6 support.";
|
||||
};
|
||||
|
||||
enableSSL = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether or not to enable SSL (https) support.";
|
||||
};
|
||||
|
||||
certFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "SSL certificate file path.";
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
apply = value: if value == null then null else toString value;
|
||||
description = ''
|
||||
SSL key file path.
|
||||
For insecurely putting the keyFile in the globally readable store use
|
||||
<literal>pkgs.writeText "ttydKeyFile" "SSLKEY"</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
caFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "SSL CA file path for client certificate verification.";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.int;
|
||||
default = 7;
|
||||
description = "Set log level.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions =
|
||||
[ { assertion = cfg.enableSSL
|
||||
-> cfg.certFile != null && cfg.keyFile != null && cfg.caFile != null;
|
||||
message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specefied."; }
|
||||
{ assertion = ! (cfg.interface != null && cfg.socket != null);
|
||||
message = "Cannot set both interface and socket for ttyd."; }
|
||||
{ assertion = (cfg.username != null) == (cfg.passwordFile != null);
|
||||
message = "Need to set both username and passwordFile for ttyd"; }
|
||||
];
|
||||
|
||||
systemd.services.ttyd = {
|
||||
description = "ttyd Web Server Daemon";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
# Runs login which needs to be run as root
|
||||
# login: Cannot possibly work without effective root
|
||||
User = "root";
|
||||
};
|
||||
|
||||
script = if cfg.passwordFile != null then ''
|
||||
PASSWORD=$(cat ${escapeShellArg cfg.passwordFile})
|
||||
${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
|
||||
--credential ${escapeShellArg cfg.username}:"$PASSWORD" \
|
||||
${pkgs.shadow}/bin/login
|
||||
''
|
||||
else ''
|
||||
${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
|
||||
${pkgs.shadow}/bin/login
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
155
nixos/modules/services/web-servers/unit/default.nix
Normal file
155
nixos/modules/services/web-servers/unit/default.nix
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.unit;
|
||||
|
||||
configFile = pkgs.writeText "unit.json" cfg.config;
|
||||
|
||||
in {
|
||||
options = {
|
||||
services.unit = {
|
||||
enable = mkEnableOption "Unit App Server";
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.unit;
|
||||
defaultText = literalExpression "pkgs.unit";
|
||||
description = "Unit package to use.";
|
||||
};
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "unit";
|
||||
description = "User account under which unit runs.";
|
||||
};
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "unit";
|
||||
description = "Group account under which unit runs.";
|
||||
};
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/spool/unit";
|
||||
description = "Unit data directory.";
|
||||
};
|
||||
logDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/log/unit";
|
||||
description = "Unit log directory.";
|
||||
};
|
||||
config = mkOption {
|
||||
type = types.str;
|
||||
default = ''
|
||||
{
|
||||
"listeners": {},
|
||||
"applications": {}
|
||||
}
|
||||
'';
|
||||
example = ''
|
||||
{
|
||||
"listeners": {
|
||||
"*:8300": {
|
||||
"application": "example-php-72"
|
||||
}
|
||||
},
|
||||
"applications": {
|
||||
"example-php-72": {
|
||||
"type": "php 7.2",
|
||||
"processes": 4,
|
||||
"user": "nginx",
|
||||
"group": "nginx",
|
||||
"root": "/var/www",
|
||||
"index": "index.php",
|
||||
"options": {
|
||||
"file": "/etc/php.d/default.ini",
|
||||
"admin": {
|
||||
"max_execution_time": "30",
|
||||
"max_input_time": "30",
|
||||
"display_errors": "off",
|
||||
"display_startup_errors": "off",
|
||||
"open_basedir": "/dev/urandom:/proc/cpuinfo:/proc/meminfo:/etc/ssl/certs:/var/www",
|
||||
"disable_functions": "exec,passthru,shell_exec,system"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
'';
|
||||
description = "Unit configuration in JSON format. More details here https://unit.nginx.org/configuration";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
"d '${cfg.logDir}' 0750 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
systemd.services.unit = {
|
||||
description = "Unit App Server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
preStart = ''
|
||||
[ ! -e '${cfg.stateDir}/conf.json' ] || rm -f '${cfg.stateDir}/conf.json'
|
||||
'';
|
||||
postStart = ''
|
||||
${pkgs.curl}/bin/curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
PIDFile = "/run/unit/unit.pid";
|
||||
ExecStart = ''
|
||||
${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
|
||||
--log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --tmp '/tmp' \
|
||||
--user ${cfg.user} --group ${cfg.group}
|
||||
'';
|
||||
ExecStop = ''
|
||||
${pkgs.curl}/bin/curl -X DELETE --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
|
||||
'';
|
||||
# Runtime directory and mode
|
||||
RuntimeDirectory = "unit";
|
||||
RuntimeDirectoryMode = "0750";
|
||||
# Access write directories
|
||||
ReadWritePaths = [ cfg.stateDir cfg.logDir ];
|
||||
# Security
|
||||
NoNewPrivileges = true;
|
||||
# Sandboxing
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
PrivateUsers = false;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectControlGroups = true;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
PrivateMounts = true;
|
||||
# System Call Filtering
|
||||
SystemCallArchitectures = "native";
|
||||
};
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "unit") {
|
||||
unit = {
|
||||
group = cfg.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "unit") {
|
||||
unit = { };
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
229
nixos/modules/services/web-servers/uwsgi.nix
Normal file
229
nixos/modules/services/web-servers/uwsgi.nix
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.uwsgi;
|
||||
|
||||
isEmperor = cfg.instance.type == "emperor";
|
||||
|
||||
imperialPowers =
|
||||
[
|
||||
# spawn other user processes
|
||||
"CAP_SETUID" "CAP_SETGID"
|
||||
"CAP_SYS_CHROOT"
|
||||
# transfer capabilities
|
||||
"CAP_SETPCAP"
|
||||
# create other user sockets
|
||||
"CAP_CHOWN"
|
||||
];
|
||||
|
||||
buildCfg = name: c:
|
||||
let
|
||||
plugins' =
|
||||
if any (n: !any (m: m == n) cfg.plugins) (c.plugins or [])
|
||||
then throw "`plugins` attribute in uWSGI configuration contains plugins not in config.services.uwsgi.plugins"
|
||||
else c.plugins or cfg.plugins;
|
||||
plugins = unique plugins';
|
||||
|
||||
hasPython = v: filter (n: n == "python${v}") plugins != [];
|
||||
hasPython2 = hasPython "2";
|
||||
hasPython3 = hasPython "3";
|
||||
|
||||
python =
|
||||
if hasPython2 && hasPython3 then
|
||||
throw "`plugins` attribute in uWSGI configuration shouldn't contain both python2 and python3"
|
||||
else if hasPython2 then cfg.package.python2
|
||||
else if hasPython3 then cfg.package.python3
|
||||
else null;
|
||||
|
||||
pythonEnv = python.withPackages (c.pythonPackages or (self: []));
|
||||
|
||||
uwsgiCfg = {
|
||||
uwsgi =
|
||||
if c.type == "normal"
|
||||
then {
|
||||
inherit plugins;
|
||||
} // removeAttrs c [ "type" "pythonPackages" ]
|
||||
// optionalAttrs (python != null) {
|
||||
pyhome = "${pythonEnv}";
|
||||
env =
|
||||
# Argh, uwsgi expects list of key-values there instead of a dictionary.
|
||||
let envs = partition (hasPrefix "PATH=") (c.env or []);
|
||||
oldPaths = map (x: substring (stringLength "PATH=") (stringLength x) x) envs.right;
|
||||
paths = oldPaths ++ [ "${pythonEnv}/bin" ];
|
||||
in [ "PATH=${concatStringsSep ":" paths}" ] ++ envs.wrong;
|
||||
}
|
||||
else if isEmperor
|
||||
then {
|
||||
emperor = if builtins.typeOf c.vassals != "set" then c.vassals
|
||||
else pkgs.buildEnv {
|
||||
name = "vassals";
|
||||
paths = mapAttrsToList buildCfg c.vassals;
|
||||
};
|
||||
} // removeAttrs c [ "type" "vassals" ]
|
||||
else throw "`type` attribute in uWSGI configuration should be either 'normal' or 'emperor'";
|
||||
};
|
||||
|
||||
in pkgs.writeTextDir "${name}.json" (builtins.toJSON uwsgiCfg);
|
||||
|
||||
in {
|
||||
|
||||
options = {
|
||||
services.uwsgi = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable uWSGI";
|
||||
};
|
||||
|
||||
runDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/run/uwsgi";
|
||||
description = "Where uWSGI communication sockets can live";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
instance = mkOption {
|
||||
type = with types; let
|
||||
valueType = nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
(lazyAttrsOf valueType)
|
||||
(listOf valueType)
|
||||
(mkOptionType {
|
||||
name = "function";
|
||||
description = "function";
|
||||
check = x: isFunction x;
|
||||
merge = mergeOneOption;
|
||||
})
|
||||
]) // {
|
||||
description = "Json value or lambda";
|
||||
emptyValue.value = {};
|
||||
};
|
||||
in valueType;
|
||||
default = {
|
||||
type = "normal";
|
||||
};
|
||||
example = literalExpression ''
|
||||
{
|
||||
type = "emperor";
|
||||
vassals = {
|
||||
moin = {
|
||||
type = "normal";
|
||||
pythonPackages = self: with self; [ moinmoin ];
|
||||
socket = "''${config.services.uwsgi.runDir}/uwsgi.sock";
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
uWSGI configuration. It awaits an attribute <literal>type</literal> inside which can be either
|
||||
<literal>normal</literal> or <literal>emperor</literal>.
|
||||
|
||||
For <literal>normal</literal> mode you can specify <literal>pythonPackages</literal> as a function
|
||||
from libraries set into a list of libraries. <literal>pythonpath</literal> will be set accordingly.
|
||||
|
||||
For <literal>emperor</literal> mode, you should use <literal>vassals</literal> attribute
|
||||
which should be either a set of names and configurations or a path to a directory.
|
||||
|
||||
Other attributes will be used in configuration file as-is. Notice that you can redefine
|
||||
<literal>plugins</literal> setting here.
|
||||
'';
|
||||
};
|
||||
|
||||
plugins = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = "Plugins used with uWSGI";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "uwsgi";
|
||||
description = "User account under which uWSGI runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "uwsgi";
|
||||
description = "Group account under which uWSGI runs.";
|
||||
};
|
||||
|
||||
capabilities = mkOption {
|
||||
type = types.listOf types.str;
|
||||
apply = caps: caps ++ optionals isEmperor imperialPowers;
|
||||
default = [ ];
|
||||
example = literalExpression ''
|
||||
[
|
||||
"CAP_NET_BIND_SERVICE" # bind on ports <1024
|
||||
"CAP_NET_RAW" # open raw sockets
|
||||
]
|
||||
'';
|
||||
description = ''
|
||||
Grant capabilities to the uWSGI instance. See the
|
||||
<literal>capabilities(7)</literal> for available values.
|
||||
<note>
|
||||
<para>
|
||||
uWSGI runs as an unprivileged user (even as Emperor) with the minimal
|
||||
capabilities required. This option can be used to add fine-grained
|
||||
permissions without running the service as root.
|
||||
</para>
|
||||
<para>
|
||||
When in Emperor mode, any capability to be inherited by a vassal must
|
||||
be specified again in the vassal configuration using <literal>cap</literal>.
|
||||
See the uWSGI <link
|
||||
xlink:href="https://uwsgi-docs.readthedocs.io/en/latest/Capabilities.html">docs</link>
|
||||
for more information.
|
||||
</para>
|
||||
</note>
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = optional (cfg.runDir != "/run/uwsgi") ''
|
||||
d ${cfg.runDir} 775 ${cfg.user} ${cfg.group}
|
||||
'';
|
||||
|
||||
systemd.services.uwsgi = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
Type = "notify";
|
||||
ExecStart = "${cfg.package}/bin/uwsgi --json ${buildCfg "server" cfg.instance}/server.json";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
|
||||
NotifyAccess = "main";
|
||||
KillSignal = "SIGQUIT";
|
||||
AmbientCapabilities = cfg.capabilities;
|
||||
CapabilityBoundingSet = cfg.capabilities;
|
||||
RuntimeDirectory = mkIf (cfg.runDir == "/run/uwsgi") "uwsgi";
|
||||
};
|
||||
};
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "uwsgi") {
|
||||
uwsgi = {
|
||||
group = cfg.group;
|
||||
uid = config.ids.uids.uwsgi;
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "uwsgi") {
|
||||
uwsgi.gid = config.ids.gids.uwsgi;
|
||||
};
|
||||
|
||||
services.uwsgi.package = pkgs.uwsgi.override {
|
||||
plugins = unique cfg.plugins;
|
||||
};
|
||||
};
|
||||
}
|
||||
115
nixos/modules/services/web-servers/varnish/default.nix
Normal file
115
nixos/modules/services/web-servers/varnish/default.nix
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{ config, lib, pkgs, ...}:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.varnish;
|
||||
|
||||
commandLine = "-f ${pkgs.writeText "default.vcl" cfg.config}" +
|
||||
optionalString (cfg.extraModules != []) " -p vmod_path='${makeSearchPathOutput "lib" "lib/varnish/vmods" ([cfg.package] ++ cfg.extraModules)}' -r vmod_path";
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.varnish = {
|
||||
enable = mkEnableOption "Varnish Server";
|
||||
|
||||
enableConfigCheck = mkEnableOption "checking the config during build time" // { default = true; };
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.varnish;
|
||||
defaultText = literalExpression "pkgs.varnish";
|
||||
description = ''
|
||||
The package to use
|
||||
'';
|
||||
};
|
||||
|
||||
http_address = mkOption {
|
||||
type = types.str;
|
||||
default = "*:6081";
|
||||
description = "
|
||||
HTTP listen address and port.
|
||||
";
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
description = "
|
||||
Verbatim default.vcl configuration.
|
||||
";
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/spool/varnish/${config.networking.hostName}";
|
||||
defaultText = literalExpression ''"/var/spool/varnish/''${config.networking.hostName}"'';
|
||||
description = "
|
||||
Directory holding all state for Varnish to run.
|
||||
";
|
||||
};
|
||||
|
||||
extraModules = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
example = literalExpression "[ pkgs.varnishPackages.geoip ]";
|
||||
description = "
|
||||
Varnish modules (except 'std').
|
||||
";
|
||||
};
|
||||
|
||||
extraCommandLine = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
example = "-s malloc,256M";
|
||||
description = "
|
||||
Command line switches for varnishd (run 'varnishd -?' to get list of options)
|
||||
";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
systemd.services.varnish = {
|
||||
description = "Varnish";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
preStart = ''
|
||||
mkdir -p ${cfg.stateDir}
|
||||
chown -R varnish:varnish ${cfg.stateDir}
|
||||
'';
|
||||
postStop = ''
|
||||
rm -rf ${cfg.stateDir}
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
PermissionsStartOnly = true;
|
||||
ExecStart = "${cfg.package}/sbin/varnishd -a ${cfg.http_address} -n ${cfg.stateDir} -F ${cfg.extraCommandLine} ${commandLine}";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
User = "varnish";
|
||||
Group = "varnish";
|
||||
AmbientCapabilities = "cap_net_bind_service";
|
||||
NoNewPrivileges = true;
|
||||
LimitNOFILE = 131072;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
# check .vcl syntax at compile time (e.g. before nixops deployment)
|
||||
system.extraDependencies = mkIf cfg.enableConfigCheck [
|
||||
(pkgs.runCommand "check-varnish-syntax" {} ''
|
||||
${cfg.package}/bin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)
|
||||
'')
|
||||
];
|
||||
|
||||
users.users.varnish = {
|
||||
group = "varnish";
|
||||
uid = config.ids.uids.varnish;
|
||||
};
|
||||
|
||||
users.groups.varnish.gid = config.ids.uids.varnish;
|
||||
};
|
||||
}
|
||||
262
nixos/modules/services/web-servers/zope2.nix
Normal file
262
nixos/modules/services/web-servers/zope2.nix
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.zope2;
|
||||
|
||||
zope2Opts = { name, ... }: {
|
||||
options = {
|
||||
|
||||
name = mkOption {
|
||||
default = "${name}";
|
||||
type = types.str;
|
||||
description = "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
|
||||
};
|
||||
|
||||
threads = mkOption {
|
||||
default = 2;
|
||||
type = types.int;
|
||||
description = "Specify the number of threads that Zope's ZServer web server will use to service requests. ";
|
||||
};
|
||||
|
||||
http_address = mkOption {
|
||||
default = "localhost:8080";
|
||||
type = types.str;
|
||||
description = "Give a port and address for the HTTP server.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
default = "zope2";
|
||||
type = types.str;
|
||||
description = "The name of the effective user for the Zope process.";
|
||||
};
|
||||
|
||||
clientHome = mkOption {
|
||||
default = "/var/lib/zope2/${name}";
|
||||
type = types.path;
|
||||
description = "Home directory of zope2 instance.";
|
||||
};
|
||||
extra = mkOption {
|
||||
default =
|
||||
''
|
||||
<zodb_db main>
|
||||
mount-point /
|
||||
cache-size 30000
|
||||
<blobstorage>
|
||||
blob-dir /var/lib/zope2/${name}/blobstorage
|
||||
<filestorage>
|
||||
path /var/lib/zope2/${name}/filestorage/Data.fs
|
||||
</filestorage>
|
||||
</blobstorage>
|
||||
</zodb_db>
|
||||
'';
|
||||
type = types.lines;
|
||||
description = "Extra zope.conf";
|
||||
};
|
||||
|
||||
packages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
description = "The list of packages you want to make available to the zope2 instance.";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.zope2.instances = mkOption {
|
||||
default = {};
|
||||
type = with types; attrsOf (submodule zope2Opts);
|
||||
example = literalExpression ''
|
||||
{
|
||||
plone01 = {
|
||||
http_address = "127.0.0.1:8080";
|
||||
extra =
|
||||
'''
|
||||
<zodb_db main>
|
||||
mount-point /
|
||||
cache-size 30000
|
||||
<blobstorage>
|
||||
blob-dir /var/lib/zope2/plone01/blobstorage
|
||||
<filestorage>
|
||||
path /var/lib/zope2/plone01/filestorage/Data.fs
|
||||
</filestorage>
|
||||
</blobstorage>
|
||||
</zodb_db>
|
||||
''';
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = "zope2 instances to be created automaticaly by the system.";
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf (cfg.instances != {}) {
|
||||
|
||||
users.users.zope2 = {
|
||||
isSystemUser = true;
|
||||
group = "zope2";
|
||||
};
|
||||
users.groups.zope2 = {};
|
||||
|
||||
systemd.services =
|
||||
let
|
||||
|
||||
createZope2Instance = opts: name:
|
||||
let
|
||||
interpreter = pkgs.writeScript "interpreter"
|
||||
''
|
||||
import sys
|
||||
|
||||
_interactive = True
|
||||
if len(sys.argv) > 1:
|
||||
_options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
|
||||
_interactive = False
|
||||
for (_opt, _val) in _options:
|
||||
if _opt == '-i':
|
||||
_interactive = True
|
||||
elif _opt == '-c':
|
||||
exec _val
|
||||
elif _opt == '-m':
|
||||
sys.argv[1:] = _args
|
||||
_args = []
|
||||
__import__("runpy").run_module(
|
||||
_val, {}, "__main__", alter_sys=True)
|
||||
|
||||
if _args:
|
||||
sys.argv[:] = _args
|
||||
__file__ = _args[0]
|
||||
del _options, _args
|
||||
execfile(__file__)
|
||||
|
||||
if _interactive:
|
||||
del _interactive
|
||||
__import__("code").interact(banner="", local=globals())
|
||||
'';
|
||||
env = pkgs.buildEnv {
|
||||
name = "zope2-${name}-env";
|
||||
paths = [
|
||||
pkgs.python27
|
||||
pkgs.python27Packages.recursivePthLoader
|
||||
pkgs.python27Packages."plone.recipe.zope2instance"
|
||||
] ++ attrValues pkgs.python27.modules
|
||||
++ opts.packages;
|
||||
postBuild =
|
||||
''
|
||||
echo "#!$out/bin/python" > $out/bin/interpreter
|
||||
cat ${interpreter} >> $out/bin/interpreter
|
||||
'';
|
||||
};
|
||||
conf = pkgs.writeText "zope2-${name}-conf"
|
||||
''
|
||||
%define INSTANCEHOME ${env}
|
||||
instancehome $INSTANCEHOME
|
||||
%define CLIENTHOME ${opts.clientHome}/${opts.name}
|
||||
clienthome $CLIENTHOME
|
||||
|
||||
debug-mode off
|
||||
security-policy-implementation C
|
||||
verbose-security off
|
||||
default-zpublisher-encoding utf-8
|
||||
zserver-threads ${toString opts.threads}
|
||||
effective-user ${opts.user}
|
||||
|
||||
pid-filename ${opts.clientHome}/${opts.name}/pid
|
||||
lock-filename ${opts.clientHome}/${opts.name}/lock
|
||||
python-check-interval 1000
|
||||
enable-product-installation off
|
||||
|
||||
<environment>
|
||||
zope_i18n_compile_mo_files false
|
||||
</environment>
|
||||
|
||||
<eventlog>
|
||||
level INFO
|
||||
<logfile>
|
||||
path /var/log/zope2/${name}.log
|
||||
level INFO
|
||||
</logfile>
|
||||
</eventlog>
|
||||
|
||||
<logger access>
|
||||
level WARN
|
||||
<logfile>
|
||||
path /var/log/zope2/${name}-Z2.log
|
||||
format %(message)s
|
||||
</logfile>
|
||||
</logger>
|
||||
|
||||
<http-server>
|
||||
address ${opts.http_address}
|
||||
</http-server>
|
||||
|
||||
<zodb_db temporary>
|
||||
<temporarystorage>
|
||||
name temporary storage for sessioning
|
||||
</temporarystorage>
|
||||
mount-point /temp_folder
|
||||
container-class Products.TemporaryFolder.TemporaryContainer
|
||||
</zodb_db>
|
||||
|
||||
${opts.extra}
|
||||
'';
|
||||
ctlScript = pkgs.writeScript "zope2-${name}-ctl-script"
|
||||
''
|
||||
#!${env}/bin/python
|
||||
|
||||
import sys
|
||||
import plone.recipe.zope2instance.ctl
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(plone.recipe.zope2instance.ctl.main(
|
||||
["-C", "${conf}"]
|
||||
+ sys.argv[1:]))
|
||||
'';
|
||||
|
||||
ctl = pkgs.writeScript "zope2-${name}-ctl"
|
||||
''
|
||||
#!${pkgs.bash}/bin/bash -e
|
||||
export PYTHONHOME=${env}
|
||||
exec ${ctlScript} "$@"
|
||||
'';
|
||||
in {
|
||||
#description = "${name} instance";
|
||||
after = [ "network.target" ]; # with RelStorage also add "postgresql.service"
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = opts.packages;
|
||||
preStart =
|
||||
''
|
||||
mkdir -p /var/log/zope2/
|
||||
touch /var/log/zope2/${name}.log
|
||||
touch /var/log/zope2/${name}-Z2.log
|
||||
chown ${opts.user} /var/log/zope2/${name}.log
|
||||
chown ${opts.user} /var/log/zope2/${name}-Z2.log
|
||||
|
||||
mkdir -p ${opts.clientHome}/filestorage ${opts.clientHome}/blobstorage
|
||||
mkdir -p ${opts.clientHome}/${opts.name}
|
||||
chown ${opts.user} ${opts.clientHome} -R
|
||||
|
||||
${ctl} adduser admin admin
|
||||
'';
|
||||
|
||||
serviceConfig.Type = "forking";
|
||||
serviceConfig.ExecStart = "${ctl} start";
|
||||
serviceConfig.ExecStop = "${ctl} stop";
|
||||
serviceConfig.ExecReload = "${ctl} restart";
|
||||
};
|
||||
|
||||
in listToAttrs (map (name: { name = "zope2-${name}"; value = createZope2Instance (builtins.getAttr name cfg.instances) name; }) (builtins.attrNames cfg.instances));
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue