parametrize services on the domain name
This commit is contained in:
parent
2b332886c4
commit
2f2c2161a3
6 changed files with 377 additions and 285 deletions
|
@ -5,96 +5,21 @@
|
||||||
}:
|
}:
|
||||||
{ configDir ? "/etc/haproxy"
|
{ configDir ? "/etc/haproxy"
|
||||||
, configFile ? "haproxy.cfg"
|
, configFile ? "haproxy.cfg"
|
||||||
, frontends ? []
|
, config
|
||||||
, backends ? []
|
|
||||||
, certPath
|
|
||||||
, user ? "haproxy"
|
|
||||||
, group ? "haproxy"
|
|
||||||
|
|
||||||
, statsEnable ? false
|
|
||||||
, statsPort ? 8404
|
|
||||||
, statsUri ? "/stats"
|
|
||||||
, statsRefresh ? "10s"
|
|
||||||
, prometheusStatsUri ? null
|
|
||||||
}:
|
}:
|
||||||
|
|
||||||
with builtins;
|
with builtins;
|
||||||
|
with lib.attrsets;
|
||||||
|
with lib.lists;
|
||||||
with lib.strings;
|
with lib.strings;
|
||||||
let
|
let
|
||||||
|
|
||||||
stats = if statsEnable then "" else ''
|
configcreator = pkgs.callPackage ./configcreator.nix {};
|
||||||
frontend stats
|
|
||||||
bind localhost:${toString statsPort}
|
|
||||||
mode http
|
|
||||||
stats enable
|
|
||||||
# stats hide-version
|
|
||||||
stats uri ${statsUri}
|
|
||||||
stats refresh ${statsRefresh}
|
|
||||||
'' + (if prometheusStatsUri == null then "" else ''
|
|
||||||
http-request use-service prometheus-exporter if { path ${prometheusStatsUri} }
|
|
||||||
'');
|
|
||||||
|
|
||||||
indent = spaces: content:
|
|
||||||
concatMapStrings
|
|
||||||
(x: spaces + x + "\n")
|
|
||||||
(splitString "\n" content);
|
|
||||||
|
|
||||||
frontends_str = concatMapStrings (acl: indent " " acl) frontends;
|
|
||||||
backends_str = concatStringsSep "\n" backends;
|
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
utils.mkConfigFile {
|
utils.mkConfigFile {
|
||||||
name = configFile;
|
name = configFile;
|
||||||
dir = configDir;
|
dir = configDir;
|
||||||
content = ''
|
content = configcreator.render (configcreator.default config);
|
||||||
global
|
}
|
||||||
# Load the plugin handling Let's Encrypt request
|
|
||||||
# lua-load /etc/haproxy/plugins/haproxy-acme-validation-plugin-0.1.1/acme-http01-webroot.lua
|
|
||||||
|
|
||||||
# Silence a warning issued by haproxy. Using 2048
|
|
||||||
# instead of the default 1024 makes the connection stronger.
|
|
||||||
tune.ssl.default-dh-param 2048
|
|
||||||
|
|
||||||
maxconn 20000
|
|
||||||
|
|
||||||
user ${user}
|
|
||||||
group ${group}
|
|
||||||
|
|
||||||
log /dev/log local0 info
|
|
||||||
|
|
||||||
# Include ssl cipher in log output.
|
|
||||||
# tune.ssl.capture-cipherlist-size 800
|
|
||||||
|
|
||||||
defaults
|
|
||||||
log global
|
|
||||||
option httplog
|
|
||||||
|
|
||||||
timeout connect 10s
|
|
||||||
timeout client 15s
|
|
||||||
timeout server 30s
|
|
||||||
timeout queue 100s
|
|
||||||
|
|
||||||
frontend http-to-https
|
|
||||||
mode http
|
|
||||||
bind *:80
|
|
||||||
redirect scheme https code 301 if !{ ssl_fc }
|
|
||||||
|
|
||||||
${stats}
|
|
||||||
|
|
||||||
frontend https
|
|
||||||
mode http
|
|
||||||
|
|
||||||
# log-format "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %sslv %sslc %[ssl_fc_cipherlist_str]"
|
|
||||||
|
|
||||||
bind *:443 ssl crt ${certPath}
|
|
||||||
http-request set-header X-Forwarded-Port %[dst_port]
|
|
||||||
http-request set-header X-Forwarded-For %[src]
|
|
||||||
http-request add-header X-Forwarded-Proto https
|
|
||||||
http-response set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"
|
|
||||||
|
|
||||||
${frontends_str}
|
|
||||||
|
|
||||||
${backends_str}
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,18 +2,250 @@
|
||||||
}:
|
}:
|
||||||
|
|
||||||
with builtins;
|
with builtins;
|
||||||
|
with lib;
|
||||||
with lib.attrsets;
|
with lib.attrsets;
|
||||||
with lib.lists;
|
with lib.lists;
|
||||||
with lib.strings;
|
with lib.strings;
|
||||||
rec {
|
let
|
||||||
|
augmentedContent = fieldName: rules: parent: set:
|
||||||
|
let
|
||||||
|
print = {rule = k: parent: v:
|
||||||
|
assert assertMsg (isString v || isInt v) "cannot print key '${fieldName}.${k}' of type '${typeOf v}', should be string or int instead";
|
||||||
|
"${k} ${toString v}";};
|
||||||
|
|
||||||
|
matchingRule = k: v: findFirst (rule: rule.match k parent v) print rules;
|
||||||
|
|
||||||
|
augment = parent: k: v:
|
||||||
|
let
|
||||||
|
match = matchingRule k v;
|
||||||
|
rule = if hasAttr "rule" match then match.rule else null;
|
||||||
|
rules = if hasAttr "rules" match then match.rules else null;
|
||||||
|
indent = map (x: if hasAttr "indent" match then match.indent + x else x);
|
||||||
|
headerFn = if hasAttr "header" match then match.header else null;
|
||||||
|
header = optional (headerFn != null) (headerFn k);
|
||||||
|
trailer = optional (headerFn != null) "";
|
||||||
|
in
|
||||||
|
if rule != null
|
||||||
|
then rule k parent v
|
||||||
|
else
|
||||||
|
assert assertMsg (isAttrs v) "attempt to apply rules on key '${toString k}' which is a '${typeOf v}' but should be a set:\n${toString v}";
|
||||||
|
header ++ indent (augmentedContent "${fieldName}.${k}" rules (parent ++ [k]) v) ++ trailer;
|
||||||
|
|
||||||
|
augmented = mapAttrsToList (augment parent) (
|
||||||
|
assert assertMsg (isAttrs set) "attempt to apply rules on field ${fieldName} having type '${typeOf set}':\n${toString set}";
|
||||||
|
set
|
||||||
|
);
|
||||||
|
in
|
||||||
|
flatten augmented;
|
||||||
|
|
||||||
|
updateByPath = path: fn: set:
|
||||||
|
if hasAttrByPath path set then
|
||||||
|
recursiveUpdate set (setAttrByPath path (fn (getAttrFromPath path set)))
|
||||||
|
else
|
||||||
|
set;
|
||||||
|
|
||||||
|
schema =
|
||||||
|
let
|
||||||
|
mkRule =
|
||||||
|
{ redirect ? false
|
||||||
|
, scheme ? "https"
|
||||||
|
, code ? null
|
||||||
|
, condition ? null
|
||||||
|
}:
|
||||||
|
concatStringsRecursive " " [
|
||||||
|
(optional redirect "redirect")
|
||||||
|
"scheme" scheme
|
||||||
|
(optional (code != null) "code ${toString code}")
|
||||||
|
(optional (condition != null) "if ${condition}")
|
||||||
|
];
|
||||||
|
|
||||||
|
mkBind =
|
||||||
|
{ addr
|
||||||
|
, ssl ? false
|
||||||
|
, crt ? null
|
||||||
|
}:
|
||||||
|
concatStringsRecursive " " [
|
||||||
|
"bind"
|
||||||
|
addr
|
||||||
|
(optional ssl "ssl")
|
||||||
|
(optional (crt != null) "crt ${crt}")
|
||||||
|
];
|
||||||
|
|
||||||
|
mkServer =
|
||||||
|
{ name
|
||||||
|
, address
|
||||||
|
, balance ? null
|
||||||
|
, check ? null
|
||||||
|
, httpcheck ? null
|
||||||
|
, forwardfor ? true
|
||||||
|
}:
|
||||||
|
[
|
||||||
|
"mode http"
|
||||||
|
(optional forwardfor "option forwardfor")
|
||||||
|
(optional (httpcheck != null) "option httpchk ${httpcheck}")
|
||||||
|
(optional (balance != null) "balance ${balance}")
|
||||||
|
(concatStringsRecursive " " [
|
||||||
|
"server"
|
||||||
|
name
|
||||||
|
address
|
||||||
|
(optionals (check != null) (mapAttrsToList (k: v: "${k} ${v}") check))
|
||||||
|
])
|
||||||
|
];
|
||||||
|
in [
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "defaults";
|
||||||
|
indent = " ";
|
||||||
|
header = k: k;
|
||||||
|
rules = [
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "timeout";
|
||||||
|
rule = k: parent: v: mapAttrsToList (k1: v1: "${k} ${k1} ${v1}") v;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "global";
|
||||||
|
indent = " ";
|
||||||
|
header = k: k;
|
||||||
|
rules = [];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "frontend";
|
||||||
|
rules = [
|
||||||
|
{
|
||||||
|
match = k: parent: v: true;
|
||||||
|
header = k: "frontend " + k;
|
||||||
|
indent = " ";
|
||||||
|
rules = [
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "rules";
|
||||||
|
rule = k: parent: v: map mkRule v;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "bind" && isAttrs v;
|
||||||
|
rule = k: parent: v: mkBind v;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "use_backend";
|
||||||
|
rule = k: parent: v:
|
||||||
|
let
|
||||||
|
use = name: value: "use_backend ${name} ${toString value}";
|
||||||
|
in
|
||||||
|
if isList v then
|
||||||
|
map (v: use v.name v.value) v
|
||||||
|
else
|
||||||
|
use v.name v.value;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
match = k: parent: v: true ;
|
||||||
|
rule = k: parent: v:
|
||||||
|
let
|
||||||
|
l = prefix: v:
|
||||||
|
if isAttrs v then
|
||||||
|
mapAttrsToList (k: v: l "${prefix} ${k}" v) v
|
||||||
|
else if isList v then
|
||||||
|
map (l prefix) v
|
||||||
|
else if isBool v then
|
||||||
|
optional v prefix
|
||||||
|
else
|
||||||
|
assert assertMsg (isString v) "value for field ${k} should be a string, bool, attr or list, got: ${typeOf v}";
|
||||||
|
"${prefix} ${v}";
|
||||||
|
in
|
||||||
|
l k v;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "backend";
|
||||||
|
rules = [
|
||||||
|
{
|
||||||
|
match = k: parent: v: true;
|
||||||
|
header = k: "backend " + k;
|
||||||
|
indent = " ";
|
||||||
|
rules = [
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "options";
|
||||||
|
rule = k: parent: v: v;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
match = k: parent: v: k == "servers";
|
||||||
|
rule = k: parent: v: map mkServer v;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
# {
|
||||||
|
# match = k: v: k == "plugins";
|
||||||
|
# rule = k: v: mkPlugins v;
|
||||||
|
# }
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
concatStringsRecursive = sep: strings:
|
||||||
|
concatStringsSep sep (flatten strings);
|
||||||
|
|
||||||
|
recursiveMerge = attrList:
|
||||||
|
let f = attrPath:
|
||||||
|
zipAttrsWith (n: values:
|
||||||
|
if tail values == [] then
|
||||||
|
head values
|
||||||
|
else if all isList values then
|
||||||
|
concatLists values
|
||||||
|
else if all isAttrs values then
|
||||||
|
f (attrPath ++ [n]) values
|
||||||
|
else
|
||||||
|
last values
|
||||||
|
);
|
||||||
|
in f [] attrList;
|
||||||
|
|
||||||
|
assertHasAttr = name: attrPath: v:
|
||||||
|
assertMsg
|
||||||
|
(hasAttrByPath attrPath v)
|
||||||
|
"no ${last attrPath} defined in config for site ${name}.${concatStringsSep "." (init attrPath)}, found attr names: ${toString (attrNames (getAttrFromPath (init attrPath) v))}";
|
||||||
|
|
||||||
|
# Takes a function producing a [nameValuePair], applies it to
|
||||||
|
# all name-value pair in the given set and merges the resulting
|
||||||
|
# [[nameValuePair]].
|
||||||
|
mapAttrsFlatten = f: set: listToAttrs (concatLists (mapAttrsToList f set));
|
||||||
|
|
||||||
|
mapIfIsAttrs = f: value:
|
||||||
|
if isAttrs value
|
||||||
|
then f value
|
||||||
|
else value;
|
||||||
|
|
||||||
|
flattenAttrs = sep: cond: set:
|
||||||
|
let
|
||||||
|
recurse = mapIfIsAttrs (mapAttrsFlatten (
|
||||||
|
n: v: let
|
||||||
|
result = recurse v;
|
||||||
|
in
|
||||||
|
if isAttrs result && cond n v
|
||||||
|
then mapAttrsToList (n2: v2: nameValuePair "${n}${sep}${n2}" v2) result
|
||||||
|
else [(nameValuePair n result)]
|
||||||
|
));
|
||||||
|
in recurse set;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit updateByPath recursiveMerge;
|
||||||
|
|
||||||
default =
|
default =
|
||||||
{ user
|
{ user
|
||||||
, group
|
, group
|
||||||
, certPath
|
, certPath
|
||||||
|
, plugins ? []
|
||||||
, stats ? null
|
, stats ? null
|
||||||
, debug ? false
|
, debug ? false
|
||||||
|
, sites ? {}
|
||||||
}: {
|
}: {
|
||||||
|
# inherit plugins;
|
||||||
|
|
||||||
global = {
|
global = {
|
||||||
|
# Load the plugin handling Let's Encrypt request
|
||||||
|
# lua-load /etc/haproxy/plugins/haproxy-acme-validation-plugin-0.1.1/acme-http01-webroot.lua
|
||||||
|
|
||||||
# Silence a warning issued by haproxy. Using 2048
|
# Silence a warning issued by haproxy. Using 2048
|
||||||
# instead of the default 1024 makes the connection stronger.
|
# instead of the default 1024 makes the connection stronger.
|
||||||
"tune.ssl.default-dh-param" = 2048;
|
"tune.ssl.default-dh-param" = 2048;
|
||||||
|
@ -49,25 +281,77 @@ rec {
|
||||||
condition = "!{ ssl_fc }";
|
condition = "!{ ssl_fc }";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
backend = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
https = {
|
https = (
|
||||||
mode = "http";
|
let
|
||||||
bind = {
|
r = (
|
||||||
addr = "*:443";
|
[{
|
||||||
ssl = true;
|
mode = "http";
|
||||||
crt = certPath;
|
bind = {
|
||||||
};
|
addr = "*:443";
|
||||||
} // optionalAttrs (debug) {
|
ssl = true;
|
||||||
log-format = "%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %sslv %sslc %[ssl_fc_cipherlist_str]";
|
crt = certPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
http-request = {
|
||||||
|
set-header = [
|
||||||
|
"X-Forwarded-Port %[dst_port]"
|
||||||
|
"X-Forwarded-For %[src]"
|
||||||
|
];
|
||||||
|
add-header = [
|
||||||
|
"X-Forwarded-Proto https"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
http-response = [
|
||||||
|
''set-header Strict-Transport-Security "max-age=15552000; includeSubDomains; preload;"''
|
||||||
|
];
|
||||||
|
|
||||||
|
# acl = flatten (mapAttrsToList (name: config:
|
||||||
|
# assert assertHasAttr name ["frontend" "acl"] config;
|
||||||
|
# config.frontend.acl
|
||||||
|
# ) sites);
|
||||||
|
# use_backend = mapAttrsToList (name: config:
|
||||||
|
# assert assertHasAttr name ["frontend" "use_backend"] config;
|
||||||
|
# nameValuePair name config.frontend.use_backend
|
||||||
|
# ) sites;
|
||||||
|
}]
|
||||||
|
++ (mapAttrsToList (name: config:
|
||||||
|
assert assertHasAttr name ["frontend"] config;
|
||||||
|
#(filterAttrs (k: v: k != "use_backend" && k != "acl")
|
||||||
|
# (mapAttrsRecursive
|
||||||
|
# (ks: v: optionalAttrs (hasAttrByPath ["frontend" "use_backend"] v) (setAttrByPath ["frontend" "use_backend"] (nameValuePair name v.frontend.use_backend)))
|
||||||
|
# config.frontend
|
||||||
|
(updateByPath ["frontend" "use_backend"] (x: (nameValuePair name x)) config).frontend
|
||||||
|
#)
|
||||||
|
) sites)
|
||||||
|
++ (mapAttrsToList (name: config:
|
||||||
|
if (hasAttr "debugHeaders" config && (getAttr "debugHeaders" config) != null) then {
|
||||||
|
option = "httplog";
|
||||||
|
http-request = {
|
||||||
|
capture = "req.hdrs len 512 if ${config.debugHeaders}";
|
||||||
|
};
|
||||||
|
log-format = ''"%ci:%cp [%tr] %ft [[%hr]] %hs %{+Q}r"'';
|
||||||
|
} else {}
|
||||||
|
) sites)
|
||||||
|
);
|
||||||
|
in
|
||||||
|
recursiveMerge r
|
||||||
|
)
|
||||||
|
// optionalAttrs (debug) {
|
||||||
|
log-format = ''"%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%Ta %ST %B %CC %CS %tsc %ac/%fc/%bc/%sc/%rc %sq/%bq %hr %hs %{+Q}r %sslv %sslc %[ssl_fc_cipherlist_str]"'';
|
||||||
};
|
};
|
||||||
} // optionalAttrs (stats != null)
|
} // optionalAttrs (stats != null)
|
||||||
(let
|
(let
|
||||||
stats_ = {
|
stats_ = {
|
||||||
|
enable = true;
|
||||||
port = 8404;
|
port = 8404;
|
||||||
uri = "/stats";
|
uri = "/stats";
|
||||||
refresh = "10s";
|
refresh = "10s";
|
||||||
prometheusUri = null;
|
prometheusUri = null;
|
||||||
|
hide-version = false;
|
||||||
} // stats;
|
} // stats;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
@ -75,107 +359,87 @@ rec {
|
||||||
bind = "localhost:${toString stats_.port}";
|
bind = "localhost:${toString stats_.port}";
|
||||||
mode = "http";
|
mode = "http";
|
||||||
stats = {
|
stats = {
|
||||||
enable = true;
|
enable = stats_.enable;
|
||||||
hide-version = false;
|
hide-version = stats_.hide-version;
|
||||||
uri = stats_.uri;
|
uri = stats_.uri;
|
||||||
refresh = stats_.refresh;
|
refresh = stats_.refresh;
|
||||||
} // optionalAttrs (stats_.prometheusUri != null) {
|
|
||||||
http-request = [
|
|
||||||
"use-service prometheus-exporter if { path ${stats_.prometheusUri} }"
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
|
} // optionalAttrs (stats_.prometheusUri != null) {
|
||||||
|
http-request = [
|
||||||
|
"use-service prometheus-exporter if { path ${stats_.prometheusUri} }"
|
||||||
|
];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
backend =
|
||||||
|
mapAttrs' (name: config:
|
||||||
|
assert assertMsg (hasAttr "backend" config) "no backend defined in config for site ${name}, found attr names: ${toString (attrNames config)}";
|
||||||
|
nameValuePair name config.backend)
|
||||||
|
sites;
|
||||||
|
# inherit backend;
|
||||||
|
# backend =
|
||||||
|
# let
|
||||||
|
# b = backend: nameValuePair backend.name {inherit (backend) options;};
|
||||||
|
# in
|
||||||
|
# listToAttrs (flatten (map (s: mapAttrsToList b s.backends) sites));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# mapIfHasAttr = f: attr: set:
|
||||||
|
# if hasAttr attr set
|
||||||
|
# then f (getAttr attr set)
|
||||||
|
# else set;
|
||||||
|
|
||||||
mkRule =
|
# Lua's import system requires the import path to be something like:
|
||||||
{ redirect ? false
|
#
|
||||||
, scheme ? "https"
|
# /nix/store/123-name/<package>/<file.lua>
|
||||||
, code ? null
|
#
|
||||||
, condition ? null
|
# Then the lua-prepend-path can be:
|
||||||
}:
|
#
|
||||||
concatStringsSep " " (flatten [
|
# /nix/store/123-name/?/<file.lua>
|
||||||
(optional redirect "redirect")
|
#
|
||||||
scheme
|
# Then when lua code imports <package>, it will search in the
|
||||||
(optional (code != null) "code ${toString code}")
|
# prepend paths and replace the question mark with the <package>
|
||||||
(optional (condition != null) "if ${condition}")
|
# name to get a match.
|
||||||
]);
|
#
|
||||||
|
# But the config.source is actually without the <package> name:
|
||||||
|
#
|
||||||
|
# /nix/store/123-name/<file.lua>
|
||||||
|
#
|
||||||
|
# This requires us to create a new directory structure and we're
|
||||||
|
# using a linkFarm for this.
|
||||||
|
# pluginLinks = configs:
|
||||||
|
# let
|
||||||
|
# mkLink = config: {
|
||||||
|
# inherit (config) name;
|
||||||
|
# path = config.source;
|
||||||
|
# };
|
||||||
|
|
||||||
mkBind =
|
# links = pkgs.linkFarm "haproxyplugins" (map mkLink configs);
|
||||||
{ addr
|
# in
|
||||||
, ssl ? false
|
# map (config:
|
||||||
, crt ? null
|
# "lua-prepend-path ${links}/?/${config.init}"
|
||||||
}:
|
# ) configs;
|
||||||
concatStringsSep " " (flatten [
|
|
||||||
addr
|
|
||||||
(optional ssl "ssl")
|
|
||||||
(optional (crt != null) "crt ${crt}")
|
|
||||||
]);
|
|
||||||
|
|
||||||
augmentedContent = fieldName: rules: set:
|
# loadPlugins = links: configs:
|
||||||
let
|
# let
|
||||||
print = {rule = k: v:
|
# mustLoad = config: hasAttr "load" config && config.load;
|
||||||
assert lib.assertMsg (isString v || isInt v) "cannot print key '${k}' of type '${typeOf v}', should be string or int instead";
|
# in
|
||||||
"${k} ${toString v}";};
|
# concatMap
|
||||||
|
# (config: optional (mustLoad config) "lua-load ${links}/${config.name}/${config.init}")
|
||||||
|
# configs;
|
||||||
|
|
||||||
matchingRule = k: v: findFirst (rule: rule.match k v) print rules;
|
# mkPlugins = configs:
|
||||||
|
# { name
|
||||||
|
# , init
|
||||||
|
# , source
|
||||||
|
# , load ? false
|
||||||
|
# }:
|
||||||
|
# let
|
||||||
|
# path = "lua-prepend-path ${links}/?/${init}"
|
||||||
|
|
||||||
augment = k: v:
|
# concatStringsSep " " (flatten [
|
||||||
let
|
# ]);
|
||||||
match = matchingRule k v;
|
|
||||||
rule = if hasAttr "rule" match then match.rule else null;
|
|
||||||
rules = if hasAttr "rules" match then match.rules else null;
|
|
||||||
in
|
|
||||||
if rule != null
|
|
||||||
then rule k v
|
|
||||||
else
|
|
||||||
assert lib.assertMsg (isAttrs v) "attempt to apply rules on key '${k}' which is a '${typeOf v}' but should be a set";
|
|
||||||
augmentedContent k rules v;
|
|
||||||
in
|
|
||||||
flatten (mapAttrsToList augment (
|
|
||||||
assert lib.assertMsg (isAttrs set) "attempt to apply rules on field ${fieldName} having type '${typeOf set}'";
|
|
||||||
set
|
|
||||||
));
|
|
||||||
|
|
||||||
# mkSection = name: config:
|
|
||||||
schema = [
|
|
||||||
{
|
|
||||||
match = k: v: k == "defaults";
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: v: k == "timeout";
|
|
||||||
rule = k: v: mapAttrsToList (k1: v1: "${k}.${k1} ${v1}") v;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: v: k == "global";
|
|
||||||
rules = [];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: v: k == "frontend";
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: v: true;
|
|
||||||
rules = [
|
|
||||||
{
|
|
||||||
match = k: v: k == "rules";
|
|
||||||
rule = k: v: map mkRule v;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: v: k == "bind" && isAttrs v;
|
|
||||||
rule = k: v: mkBind v;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
match = k: v: k == "http-request" || k == "http-response";
|
|
||||||
rule = k: v: v;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
render = config:
|
render = config:
|
||||||
concatStringsSep "\n" (augmentedContent name schema config);
|
concatStringsSep "\n" (augmentedContent "" schema [] config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,27 +3,15 @@
|
||||||
{ name
|
{ name
|
||||||
, configDir
|
, configDir
|
||||||
, configFile
|
, configFile
|
||||||
, user
|
, config
|
||||||
, group
|
|
||||||
, statsEnable ? false
|
|
||||||
, statsPort ? null
|
|
||||||
, prometheusStatsUri ? null
|
|
||||||
, certPath ? null
|
|
||||||
, frontends ? []
|
|
||||||
, backends ? []
|
|
||||||
, dependsOn ? {}
|
, dependsOn ? {}
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
inherit name configDir configFile;
|
inherit name configDir configFile;
|
||||||
inherit user group;
|
inherit (config) user group;
|
||||||
pkg = HaproxyConfig {
|
pkg = HaproxyConfig {
|
||||||
inherit configDir configFile;
|
inherit configDir configFile;
|
||||||
inherit user group;
|
inherit config;
|
||||||
inherit statsEnable statsPort;
|
|
||||||
inherit prometheusStatsUri;
|
|
||||||
inherit certPath;
|
|
||||||
|
|
||||||
inherit frontends backends;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inherit dependsOn;
|
inherit dependsOn;
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
{ stdenv
|
|
||||||
, pkgs
|
|
||||||
, lib
|
|
||||||
}:
|
|
||||||
{ serviceName
|
|
||||||
, servers ? []
|
|
||||||
, httpcheck ? null
|
|
||||||
, balance ? null
|
|
||||||
, phpFastcgi ? false
|
|
||||||
, phpDocroot ? null
|
|
||||||
, phpIndex ? "index.php"
|
|
||||||
, extraUseBackendConditions ? {}
|
|
||||||
, extraFrontendOptions ? []
|
|
||||||
, extraBackendOptions ? []
|
|
||||||
|
|
||||||
, debugHeaders ? false
|
|
||||||
}:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
with lib.lists;
|
|
||||||
with lib.attrsets;
|
|
||||||
let
|
|
||||||
indent = map (x: " " + x);
|
|
||||||
|
|
||||||
mkServer = i: s:
|
|
||||||
let
|
|
||||||
proto = optional phpFastcgi "proto fcgi";
|
|
||||||
in
|
|
||||||
concatStringsSep " " (
|
|
||||||
[
|
|
||||||
"server ${serviceName}${toString i} ${s.address}"
|
|
||||||
]
|
|
||||||
++ proto
|
|
||||||
++ (optional (hasAttr "check" s && s.check != null) (
|
|
||||||
concatStrings (["check"] ++ (map (k: if !hasAttr k s.check then "" else " ${k} ${getAttr k s.check}") ["inter" "downinter" "fall" "rise"]))
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
serverslines = imap1 mkServer servers;
|
|
||||||
|
|
||||||
backend =
|
|
||||||
(
|
|
||||||
concatStringsSep "\n" (
|
|
||||||
[
|
|
||||||
"backend ${serviceName}"
|
|
||||||
]
|
|
||||||
++ indent (
|
|
||||||
[
|
|
||||||
"mode http"
|
|
||||||
"option forwardfor"
|
|
||||||
]
|
|
||||||
++ extraBackendOptions
|
|
||||||
++ optional (balance != null) "balance ${balance}"
|
|
||||||
++ optional (httpcheck != null) "option httpchk ${httpcheck}"
|
|
||||||
++ optional phpFastcgi "use-fcgi-app ${serviceName}-php-fpm"
|
|
||||||
++ serverslines
|
|
||||||
)
|
|
||||||
++ [""]) # final newline
|
|
||||||
) +
|
|
||||||
(if !phpFastcgi then "" else ''
|
|
||||||
|
|
||||||
fcgi-app ${serviceName}-php-fpm
|
|
||||||
log-stderr global
|
|
||||||
docroot ${phpDocroot}
|
|
||||||
index ${phpIndex}
|
|
||||||
path-info ^(/.+\.php)(/.*)?$
|
|
||||||
'');
|
|
||||||
|
|
||||||
extraAclsCondition = concatStrings (mapAttrsToList (k: v: "\nacl acl_${serviceName}_${k} ${v}") extraUseBackendConditions);
|
|
||||||
|
|
||||||
extraAclsOr = concatStrings (mapAttrsToList (k: v: " OR acl_${serviceName}_${k}") extraUseBackendConditions);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
frontend = ''
|
|
||||||
acl acl_${serviceName} hdr_beg(host) ${serviceName}.${extraAclsCondition}
|
|
||||||
''
|
|
||||||
+ concatMapStrings (x: x + "\n") extraFrontendOptions
|
|
||||||
+ concatMapStrings (x: x + "\n") (optionals debugHeaders [
|
|
||||||
"option httplog"
|
|
||||||
"http-request capture req.hdrs len 512 if acl_${serviceName}${extraAclsOr}"
|
|
||||||
''log-format "%ci:%cp [%tr] %ft [[%hr]] %hs %{+Q}r"''
|
|
||||||
])
|
|
||||||
+ ''
|
|
||||||
use_backend ${serviceName} if acl_${serviceName}${extraAclsOr}
|
|
||||||
'';
|
|
||||||
|
|
||||||
inherit backend;
|
|
||||||
}
|
|
|
@ -6,6 +6,7 @@
|
||||||
, name ? "ttrss"
|
, name ? "ttrss"
|
||||||
, user ? "http"
|
, user ? "http"
|
||||||
, group ? "http"
|
, group ? "http"
|
||||||
|
, domain
|
||||||
, lock_directory
|
, lock_directory
|
||||||
, cache_directory
|
, cache_directory
|
||||||
, feed_icons_directory
|
, feed_icons_directory
|
||||||
|
@ -86,7 +87,7 @@ stdenv.mkDerivation rec {
|
||||||
|
|
||||||
buildCommand =
|
buildCommand =
|
||||||
let
|
let
|
||||||
configFile = pkgs.writeText "config.php" (asTtrssConfig (config "https://${name}.tiserbox.com/"));
|
configFile = pkgs.writeText "config.php" (asTtrssConfig (config "https://${name}.${domain}/"));
|
||||||
dr = dirOf document_root;
|
dr = dirOf document_root;
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
{ name
|
{ name
|
||||||
, user
|
, user
|
||||||
, group
|
, group
|
||||||
|
, domain
|
||||||
, serviceName
|
, serviceName
|
||||||
, document_root
|
, document_root
|
||||||
, lock_directory
|
, lock_directory
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
name = serviceName;
|
name = serviceName;
|
||||||
inherit document_root lock_directory cache_directory feed_icons_directory;
|
inherit document_root lock_directory cache_directory feed_icons_directory;
|
||||||
inherit user group;
|
inherit user group;
|
||||||
|
inherit domain;
|
||||||
|
|
||||||
inherit db_host db_port db_username db_password db_database;
|
inherit db_host db_port db_username db_password db_database;
|
||||||
inherit enabled_plugins;
|
inherit enabled_plugins;
|
||||||
|
|
Loading…
Reference in a new issue