Compare commits
134 Commits
Author | SHA1 | Date |
---|---|---|
niten | f9c38a8479 | |
niten | b0b280c231 | |
niten | d604090b94 | |
niten | 6d08c1a790 | |
niten | 60d5d50336 | |
niten | 68c8052e0c | |
niten | 7bdae87de3 | |
niten | e6109489d4 | |
niten | 751ee6be5d | |
niten | b1197db920 | |
niten | cc0a8b5cba | |
niten | fe27dc3f3d | |
niten | 62e4276a86 | |
niten | 38640140c8 | |
niten | c1c67ecc80 | |
niten | b0c7b7bd88 | |
niten | 2d2f1099ea | |
niten | 9e36dfc6ca | |
niten | c5d1d11137 | |
niten | 16a105a24e | |
niten | 168dc68251 | |
niten | 031600e175 | |
niten | 083defd692 | |
niten | ace843b59d | |
niten | e8a191908e | |
niten | 2a27c0f7b1 | |
niten | 45b15e65fd | |
niten | 92be492607 | |
niten | f7a5a43d30 | |
niten | 2c206d394b | |
niten | 7e533a6d6f | |
niten | dd2df768f1 | |
niten | c405597d81 | |
niten | 4196468cf8 | |
niten | 1186de6c36 | |
niten | 42f486a6fc | |
niten | 8ba0e155d3 | |
niten | 12d62caf25 | |
niten | 8e08f115c5 | |
niten | 9f96e0fa62 | |
niten | a33c958625 | |
niten | 216e84c94e | |
niten | 893538d442 | |
niten | f142ce296c | |
niten | 1aea58945d | |
niten | deb3e455bd | |
niten | 09e6262e83 | |
niten | 078a150080 | |
niten | e8ad5ae2ca | |
niten | 3b89330f81 | |
niten | 26b130ceab | |
niten | 9b20a19e27 | |
niten | 72a33bd452 | |
niten | a169209046 | |
niten | c884d59f47 | |
niten | e770842fa6 | |
niten | cf4b24da99 | |
niten | 6015d8eaf0 | |
niten | 04263beb7f | |
niten | d4044d886a | |
niten | 88bcd94803 | |
niten | fdc658a263 | |
niten | 10e78ba89f | |
niten | 2d96202a58 | |
niten | e37fe381d5 | |
niten | 7ae766bd8a | |
niten | 70a1a6d6af | |
niten | 9d031308aa | |
niten | a241c56c59 | |
niten | cd1c104bce | |
niten | a46b71e72b | |
niten | 56f4e49df0 | |
niten | 356c452d66 | |
niten | c629223e85 | |
niten | 19f117a14b | |
niten | 1c0d4b207a | |
niten | 5079561573 | |
niten | 1e5cdcba79 | |
Niten | 169cf84263 | |
niten | 130086b6a8 | |
Niten | 28e16e19e4 | |
Niten | 7fcbc0bddb | |
niten | db5c0b7cd1 | |
niten | 0890bb7b1e | |
niten | 37bd62f950 | |
niten | 49009f67e7 | |
niten | a72e23a198 | |
niten | ad5570b7c3 | |
niten | 7ed8b64466 | |
niten | f3312f8fce | |
niten | 1e1bd7e3b9 | |
niten | dbf6cd6337 | |
niten | cea913f1fe | |
niten | 169dee3939 | |
niten | bfcd26ae7e | |
niten | 59a0f02001 | |
niten | 92962b83ed | |
niten | b2d3ef23f2 | |
niten | 930f12ab17 | |
niten | 83b85a7495 | |
niten | f5e5e263ef | |
niten | e546c70eaa | |
niten | 421ecd1f65 | |
niten | e2f29e8f44 | |
niten | 42332121be | |
niten | 5fa5877579 | |
niten | 3d585efebe | |
niten | d347309d5e | |
niten | 7028e169d3 | |
niten | ebf7d59e30 | |
niten | 7b318d0aaf | |
niten | 306087355c | |
niten | 8b21b53e4b | |
niten | f82dc851f1 | |
niten | f7966d3fee | |
niten | cb039ceabd | |
niten | 8b3f771c23 | |
niten | 860bb22034 | |
niten | 4549d51c7b | |
niten | 5ca65c3ee6 | |
niten | 9865877695 | |
niten | 977ae77a76 | |
niten | c4ec7d8e04 | |
niten | 00d3019934 | |
niten | 97babc3947 | |
niten | 882bfe1d53 | |
niten | 0971f01d66 | |
niten | 65f7f1c542 | |
niten | ed86976d84 | |
niten | e2ce36d2f8 | |
niten | 2720ce9be9 | |
niten | 93bc54ec67 | |
niten | bae4f4ddef | |
niten | 179dda3ec6 |
11
flake.nix
11
flake.nix
|
@ -2,9 +2,16 @@
|
|||
description = "Fudo Nix Helper Functions";
|
||||
|
||||
outputs = { self, ... }: {
|
||||
overlay = import ./overlay.nix;
|
||||
overlays = rec {
|
||||
default = lib;
|
||||
lib = import ./overlay.nix;
|
||||
};
|
||||
|
||||
nixosModule = import ./module.nix;
|
||||
nixosModules = rec {
|
||||
default = fudo;
|
||||
fudo = import ./module.nix;
|
||||
lib = { ... }: { config.nixpkgs.overlays = [ self.overlays.default ]; };
|
||||
};
|
||||
|
||||
lib = import ./lib.nix;
|
||||
};
|
||||
|
|
|
@ -1,109 +1,108 @@
|
|||
{ config, lib, pkgs, ... } @ toplevel:
|
||||
{ config, lib, pkgs, ... }@toplevel:
|
||||
|
||||
with lib;
|
||||
let
|
||||
hostname = config.instance.hostname;
|
||||
|
||||
domainOpts = { name, ... }: let
|
||||
domain = name;
|
||||
in {
|
||||
options = with types; {
|
||||
admin-email = mkOption {
|
||||
type = str;
|
||||
description = "Domain administrator email.";
|
||||
default = "admin@${domain}";
|
||||
};
|
||||
|
||||
extra-domains = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of domains to add to this certificate.";
|
||||
default = [];
|
||||
};
|
||||
|
||||
local-copies = let
|
||||
localCopyOpts = { name, ... }: let
|
||||
copy = name;
|
||||
in {
|
||||
options = with types; let
|
||||
target-path = "/run/ssl-certificates/${domain}/${copy}";
|
||||
in {
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User to which this copy belongs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Group to which this copy belongs.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
service = mkOption {
|
||||
type = str;
|
||||
description = "systemd job to copy certs.";
|
||||
default = "fudo-acme-${domain}-${copy}-certs.service";
|
||||
};
|
||||
|
||||
certificate = mkOption {
|
||||
type = str;
|
||||
description = "Full path to the local copy certificate.";
|
||||
default = "${target-path}/cert.pem";
|
||||
};
|
||||
|
||||
full-certificate = mkOption {
|
||||
type = str;
|
||||
description = "Full path to the local copy certificate.";
|
||||
default = "${target-path}/fullchain.pem";
|
||||
};
|
||||
|
||||
chain = mkOption {
|
||||
type = str;
|
||||
description = "Full path to the local copy certificate.";
|
||||
default = "${target-path}/chain.pem";
|
||||
};
|
||||
|
||||
private-key = mkOption {
|
||||
type = str;
|
||||
description = "Full path to the local copy certificate.";
|
||||
default = "${target-path}/key.pem";
|
||||
};
|
||||
|
||||
dependent-services = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of systemd services depending on this copy.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
part-of = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of systemd targets to which this copy belongs.";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
domainOpts = { name, ... }:
|
||||
let domain = name;
|
||||
in {
|
||||
options = with types; {
|
||||
admin-email = mkOption {
|
||||
type = str;
|
||||
description = "Domain administrator email.";
|
||||
default = "admin@${domain}";
|
||||
};
|
||||
|
||||
extra-domains = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of domains to add to this certificate.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
local-copies = let
|
||||
localCopyOpts = { name, ... }:
|
||||
let copy = name;
|
||||
in {
|
||||
options = with types;
|
||||
let target-path = "/run/ssl-certificates/${domain}/${copy}";
|
||||
in {
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User to which this copy belongs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Group to which this copy belongs.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
service = mkOption {
|
||||
type = str;
|
||||
description = "systemd job to copy certs.";
|
||||
default = "fudo-acme-${domain}-${copy}-certs.service";
|
||||
};
|
||||
|
||||
certificate = mkOption {
|
||||
type = str;
|
||||
description = "Full path to the local copy certificate.";
|
||||
default = "${target-path}/cert.pem";
|
||||
};
|
||||
|
||||
full-certificate = mkOption {
|
||||
type = str;
|
||||
description = "Full path to the local copy certificate.";
|
||||
default = "${target-path}/fullchain.pem";
|
||||
};
|
||||
|
||||
chain = mkOption {
|
||||
type = str;
|
||||
description = "Full path to the local copy certificate.";
|
||||
default = "${target-path}/chain.pem";
|
||||
};
|
||||
|
||||
private-key = mkOption {
|
||||
type = str;
|
||||
description = "Full path to the local copy certificate.";
|
||||
default = "${target-path}/key.pem";
|
||||
};
|
||||
|
||||
dependent-services = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of systemd services depending on this copy.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
part-of = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of systemd targets to which this copy belongs.";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
in mkOption {
|
||||
type = attrsOf (submodule localCopyOpts);
|
||||
description = "Map of copies to make for use by services.";
|
||||
default = { };
|
||||
};
|
||||
in mkOption {
|
||||
type = attrsOf (submodule localCopyOpts);
|
||||
description = "Map of copies to make for use by services.";
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
head-or-null = lst: if (lst == []) then null else head lst;
|
||||
head-or-null = lst: if (lst == [ ]) then null else head lst;
|
||||
rm-service-ext = filename:
|
||||
head-or-null (builtins.match "^(.+)\.service$" filename);
|
||||
head-or-null (builtins.match "^(.+).service$" filename);
|
||||
|
||||
concatMapAttrs = f: attrs:
|
||||
foldr (a: b: a // b) {} (mapAttrsToList f attrs);
|
||||
concatMapAttrs = f: attrs: foldr (a: b: a // b) { } (mapAttrsToList f attrs);
|
||||
|
||||
cfg = config.fudo.acme;
|
||||
hasLocalDomains = hasAttr hostname cfg.host-domains;
|
||||
localDomains = if hasLocalDomains then
|
||||
cfg.host-domains.${hostname} else {};
|
||||
localDomains = if hasLocalDomains then cfg.host-domains.${hostname} else { };
|
||||
|
||||
optionalStringOr = str: default: if (str != null) then str else default;
|
||||
|
||||
optionalStringOr = str: default:
|
||||
if (str != null) then str else default;
|
||||
|
||||
in {
|
||||
options.fudo.acme = with types; {
|
||||
host-domains = mkOption {
|
||||
|
@ -121,22 +120,22 @@ in {
|
|||
# default = "/run/acme-challenge";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
config = {
|
||||
security.acme.certs = mapAttrs (domain: domainOpts: {
|
||||
# email = domainOpts.admin-email;
|
||||
# webroot = cfg.challenge-path;
|
||||
# group = "nginx";
|
||||
# extraDomainNames = domainOpts.extra-domains;
|
||||
}) localDomains;
|
||||
# security.acme.certs = mapAttrs (domain: domainOpts:
|
||||
# {
|
||||
# # email = domainOpts.admin-email;
|
||||
# # webroot = cfg.challenge-path;
|
||||
# # group = "nginx";
|
||||
# # extraDomainNames = domainOpts.extra-domains;
|
||||
# }) localDomains;
|
||||
|
||||
# Assume that if we're acquiring SSL certs, we have a real IP for the
|
||||
# host. nginx must have an acme dir for security.acme to work.
|
||||
services.nginx = mkIf hasLocalDomains {
|
||||
enable = true;
|
||||
recommendedTlsSettings = true;
|
||||
virtualHosts = let
|
||||
server-path = "/.well-known/acme-challenge";
|
||||
virtualHosts = let serverPath = "/.well-known/acme-challenge";
|
||||
in (mapAttrs (domain: domainOpts: {
|
||||
# THIS IS A HACK. Getting redundant paths. So if {domain} is configured
|
||||
# somewhere else, assume ACME is already set.
|
||||
|
@ -152,7 +151,7 @@ in {
|
|||
serverName = "_";
|
||||
default = true;
|
||||
locations = {
|
||||
${server-path} = {
|
||||
"${serverPath}" = {
|
||||
root = cfg.challenge-path;
|
||||
extraConfig = "auth_basic off;";
|
||||
};
|
||||
|
@ -163,80 +162,86 @@ in {
|
|||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||
|
||||
|
||||
systemd = {
|
||||
tmpfiles = mkIf hasLocalDomains {
|
||||
rules = let
|
||||
copies = concatMapAttrs (domain: domainOpts:
|
||||
domainOpts.local-copies) localDomains;
|
||||
copies = concatMapAttrs (domain: domainOpts: domainOpts.local-copies)
|
||||
localDomains;
|
||||
perms = copyOpts: if (copyOpts.group != null) then "0550" else "0500";
|
||||
copy-paths = mapAttrsToList (copy: copyOpts:
|
||||
let
|
||||
dir-entry = copyOpts: file: "d \"${dirOf file}\" ${perms copyOpts} ${copyOpts.user} ${optionalStringOr copyOpts.group "-"} - -";
|
||||
dir-entry = copyOpts: file:
|
||||
''
|
||||
d "${dirOf file}" ${perms copyOpts} ${copyOpts.user} ${
|
||||
optionalStringOr copyOpts.group "-"
|
||||
} - -'';
|
||||
in map (dir-entry copyOpts) [
|
||||
copyOpts.certificate
|
||||
copyOpts.full-certificate
|
||||
copyOpts.chain
|
||||
copyOpts.private-key
|
||||
]) copies;
|
||||
in (unique (concatMap (i: unique i) copy-paths)) ++ [
|
||||
"d \"${cfg.challenge-path}\" 755 acme nginx - -"
|
||||
];
|
||||
in (unique (concatMap (i: unique i) copy-paths))
|
||||
++ [ ''d "${cfg.challenge-path}" 755 acme nginx - -'' ];
|
||||
};
|
||||
|
||||
services = concatMapAttrs (domain: domainOpts:
|
||||
concatMapAttrs (copy: copyOpts: let
|
||||
key-perms = copyOpts: if (copyOpts.group != null) then "0440" else "0400";
|
||||
source = config.security.acme.certs.${domain}.directory;
|
||||
target = copyOpts.path;
|
||||
owners =
|
||||
if (copyOpts.group != null) then
|
||||
concatMapAttrs (copy: copyOpts:
|
||||
let
|
||||
key-perms = copyOpts:
|
||||
if (copyOpts.group != null) then "0440" else "0400";
|
||||
source = config.security.acme.certs.${domain}.directory;
|
||||
target = copyOpts.path;
|
||||
owners = if (copyOpts.group != null) then
|
||||
"${copyOpts.user}:${copyOpts.group}"
|
||||
else copyOpts.user;
|
||||
dirs = unique [
|
||||
(dirOf copyOpts.certificate)
|
||||
(dirOf copyOpts.full-certificate)
|
||||
(dirOf copyOpts.chain)
|
||||
(dirOf copyOpts.private-key)
|
||||
];
|
||||
install-certs = pkgs.writeShellScript "fudo-install-${domain}-${copy}-certs.sh" ''
|
||||
${concatStringsSep "\n" (map (dir: ''
|
||||
mkdir -p ${dir}
|
||||
chown ${owners} ${dir}
|
||||
'') dirs)}
|
||||
cp ${source}/cert.pem ${copyOpts.certificate}
|
||||
chmod 0444 ${copyOpts.certificate}
|
||||
chown ${owners} ${copyOpts.certificate}
|
||||
else
|
||||
copyOpts.user;
|
||||
dirs = unique [
|
||||
(dirOf copyOpts.certificate)
|
||||
(dirOf copyOpts.full-certificate)
|
||||
(dirOf copyOpts.chain)
|
||||
(dirOf copyOpts.private-key)
|
||||
];
|
||||
install-certs =
|
||||
pkgs.writeShellScript "fudo-install-${domain}-${copy}-certs.sh" ''
|
||||
${concatStringsSep "\n" (map (dir: ''
|
||||
mkdir -p ${dir}
|
||||
chown ${owners} ${dir}
|
||||
'') dirs)}
|
||||
cp ${source}/cert.pem ${copyOpts.certificate}
|
||||
chmod 0444 ${copyOpts.certificate}
|
||||
chown ${owners} ${copyOpts.certificate}
|
||||
|
||||
cp ${source}/full.pem ${copyOpts.full-certificate}
|
||||
chmod 0444 ${copyOpts.full-certificate}
|
||||
chown ${owners} ${copyOpts.full-certificate}
|
||||
cp ${source}/full.pem ${copyOpts.full-certificate}
|
||||
chmod 0444 ${copyOpts.full-certificate}
|
||||
chown ${owners} ${copyOpts.full-certificate}
|
||||
|
||||
cp ${source}/chain.pem ${copyOpts.chain}
|
||||
chmod 0444 ${copyOpts.chain}
|
||||
chown ${owners} ${copyOpts.chain}
|
||||
cp ${source}/chain.pem ${copyOpts.chain}
|
||||
chmod 0444 ${copyOpts.chain}
|
||||
chown ${owners} ${copyOpts.chain}
|
||||
|
||||
cp ${source}/key.pem ${copyOpts.private-key}
|
||||
chmod ${key-perms copyOpts} ${copyOpts.private-key}
|
||||
chown ${owners} ${copyOpts.private-key}
|
||||
'';
|
||||
cp ${source}/key.pem ${copyOpts.private-key}
|
||||
chmod ${key-perms copyOpts} ${copyOpts.private-key}
|
||||
chown ${owners} ${copyOpts.private-key}
|
||||
'';
|
||||
|
||||
service-name = rm-service-ext copyOpts.service;
|
||||
in {
|
||||
${service-name} = {
|
||||
description = "Copy ${domain} ACME certs for ${copy}.";
|
||||
after = [ "acme-${domain}.service" ];
|
||||
before = copyOpts.dependent-services;
|
||||
wantedBy = [ "multi-user.target" ] ++ copyOpts.dependent-services;
|
||||
partOf = copyOpts.part-of;
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = install-certs;
|
||||
RemainAfterExit = true;
|
||||
StandardOutput = "journal";
|
||||
service-name = rm-service-ext copyOpts.service;
|
||||
in {
|
||||
${service-name} = {
|
||||
description = "Copy ${domain} ACME certs for ${copy}.";
|
||||
after = [ "acme-${domain}.service" ];
|
||||
before = copyOpts.dependent-services;
|
||||
wantedBy = [ "multi-user.target" ] ++ copyOpts.dependent-services;
|
||||
partOf = copyOpts.part-of;
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = install-certs;
|
||||
RemainAfterExit = true;
|
||||
StandardOutput = "journal";
|
||||
};
|
||||
};
|
||||
};
|
||||
}) domainOpts.local-copies) localDomains;
|
||||
}) domainOpts.local-copies) localDomains;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,18 +9,17 @@ let
|
|||
get-basename = filename:
|
||||
head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename));
|
||||
|
||||
format-json-file = filename: pkgs.stdenv.mkDerivation {
|
||||
name = "formatted-${get-basename filename}";
|
||||
phases = [ "installPhase" ];
|
||||
buildInputs = with pkgs; [ python ];
|
||||
installPhase = "python -mjson.tool ${filename} > $out";
|
||||
};
|
||||
|
||||
format-json-file = filename:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "formatted-${get-basename filename}";
|
||||
phases = [ "installPhase" ];
|
||||
buildInputs = with pkgs; [ python3 ];
|
||||
installPhase = "python -mjson.tool ${filename} > $out";
|
||||
};
|
||||
|
||||
admin-passwd-file =
|
||||
pkgs.lib.passwd.stablerandom-passwd-file
|
||||
"adguard-dns-proxy-admin"
|
||||
config.instance.build-seed;
|
||||
pkgs.lib.passwd.stablerandom-passwd-file "adguard-dns-proxy-admin"
|
||||
config.instance.build-seed;
|
||||
|
||||
filterOpts = {
|
||||
options = with types; {
|
||||
|
@ -41,50 +40,45 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
generate-config = { dns,
|
||||
http,
|
||||
filters,
|
||||
verbose,
|
||||
upstream-dns,
|
||||
bootstrap-dns,
|
||||
blocked-hosts,
|
||||
enable-dnssec,
|
||||
local-domain-name,
|
||||
... }: {
|
||||
bind_host = http.listen-ip;
|
||||
bind_port = http.listen-port;
|
||||
users = [
|
||||
{
|
||||
generate-config = { dns, http, filters, verbose, upstream-dns, bootstrap-dns
|
||||
, blocked-hosts, enable-dnssec, local-domain-name, ... }: {
|
||||
bind_host = http.listen-ip;
|
||||
bind_port = http.listen-port;
|
||||
users = [{
|
||||
name = "admin";
|
||||
password = pkgs.lib.passwd.bcrypt-passwd
|
||||
"adguard-dns-proxy-admin"
|
||||
password = pkgs.lib.passwd.bcrypt-passwd "adguard-dns-proxy-admin"
|
||||
admin-passwd-file;
|
||||
}
|
||||
];
|
||||
auth_attempts = 5;
|
||||
block_auth_min = 30;
|
||||
web_session_ttl = 720;
|
||||
dns = {
|
||||
bind_hosts = dns.listen-ips;
|
||||
port = dns.listen-port;
|
||||
upstream_dns = upstream-dns;
|
||||
bootstrap_dns = bootstrap-dns;
|
||||
blocking_mode = "default";
|
||||
blocked_hosts = blocked-hosts;
|
||||
enable_dnssec = enable-dnssec;
|
||||
local_domain_name = local-domain-name;
|
||||
}];
|
||||
auth_attempts = 5;
|
||||
block_auth_min = 30;
|
||||
web_session_ttl = 720;
|
||||
dns = {
|
||||
bind_hosts = dns.listen-ips;
|
||||
port = dns.listen-port;
|
||||
upstream_dns = upstream-dns;
|
||||
bootstrap_dns = bootstrap-dns;
|
||||
enable_dnssec = enable-dnssec;
|
||||
local_domain_name = local-domain-name;
|
||||
protection_enabled = true;
|
||||
blocking_mode = "default";
|
||||
blocked_hosts = blocked-hosts;
|
||||
filtering_enabled = true;
|
||||
parental_enabled = false;
|
||||
safesearch_enabled = false;
|
||||
use_private_ptr_resolvers = cfg.dns.reverse-dns != [ ];
|
||||
local_ptr_upstreams = cfg.dns.reverse-dns;
|
||||
};
|
||||
tls.enabled = false;
|
||||
filters = imap1 (i: filter: {
|
||||
enabled = true;
|
||||
name = filter.name;
|
||||
url = filter.url;
|
||||
}) filters;
|
||||
dhcp.enabled = false;
|
||||
clients = [ ];
|
||||
verbose = verbose;
|
||||
schema_version = 10;
|
||||
};
|
||||
tls.enabled = false;
|
||||
filters = imap1 (i: filter: {
|
||||
enabled = true;
|
||||
name = filter.name;
|
||||
url = filter.url;
|
||||
}) filters;
|
||||
dhcp.enabled = false;
|
||||
clients = [];
|
||||
verbose = verbose;
|
||||
schema_version = 10;
|
||||
};
|
||||
|
||||
generate-config-file = opts:
|
||||
format-json-file (pkgs.writeText "adguard-dns-proxy-config.yaml"
|
||||
|
@ -106,6 +100,13 @@ in {
|
|||
description = "Port on which to listen for DNS queries.";
|
||||
default = 53;
|
||||
};
|
||||
|
||||
reverse-dns = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"DNS servers on which to perform reverse lookups for private addresses (if any).";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
http = {
|
||||
|
@ -127,7 +128,8 @@ in {
|
|||
default = [
|
||||
{
|
||||
name = "AdGuard DNS filter";
|
||||
url = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt";
|
||||
url =
|
||||
"https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt";
|
||||
}
|
||||
{
|
||||
name = "AdAway Default Blocklist";
|
||||
|
@ -141,17 +143,25 @@ in {
|
|||
name = "OISD.NL Blocklist";
|
||||
url = "https://abp.oisd.nl/";
|
||||
}
|
||||
{
|
||||
name = "FireBog Easy Privacy";
|
||||
url = "https://v.firebog.net/hosts/Easyprivacy.txt";
|
||||
}
|
||||
{
|
||||
name = "FireBog Easy Ads";
|
||||
url = "https://v.firebog.net/hosts/Easylist.txt";
|
||||
}
|
||||
{
|
||||
name = "FireBog Easy Admiral";
|
||||
url = "https://v.firebog.net/hosts/Admiral.txt";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
blocked-hosts = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of hosts to explicitly block.";
|
||||
default = [
|
||||
"version.bind"
|
||||
"id.server"
|
||||
"hostname.bind"
|
||||
];
|
||||
default = [ "version.bind" "id.server" "hostname.bind" ];
|
||||
};
|
||||
|
||||
enable-dnssec = mkOption {
|
||||
|
@ -193,7 +203,8 @@ in {
|
|||
|
||||
allowed-networks = mkOption {
|
||||
type = nullOr (listOf str);
|
||||
description = "Optional list of networks with which this job may communicate.";
|
||||
description =
|
||||
"Optional list of networks with which this job may communicate.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
|
@ -220,9 +231,7 @@ in {
|
|||
group = cfg.user;
|
||||
};
|
||||
|
||||
groups.${cfg.user} = {
|
||||
members = [ cfg.user ];
|
||||
};
|
||||
groups.${cfg.user} = { members = [ cfg.user ]; };
|
||||
};
|
||||
|
||||
fudo = {
|
||||
|
@ -234,39 +243,40 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
system.services.adguard-dns-proxy = let
|
||||
cfg-path = "/run/adguard-dns-proxy/config.yaml";
|
||||
in {
|
||||
description = "DNS Proxy for ad filtering and DNS-over-HTTPS lookups.";
|
||||
wantedBy = [ "default.target" ];
|
||||
after = [ "syslog.target" ];
|
||||
requires = [ "network.target" ];
|
||||
privateNetwork = false;
|
||||
requiredCapabilities = optional upgrade-perms "CAP_NET_BIND_SERVICE";
|
||||
restartWhen = "always";
|
||||
addressFamilies = null;
|
||||
networkWhitelist = cfg.allowed-networks;
|
||||
user = mkIf upgrade-perms cfg.user;
|
||||
runtimeDirectory = "adguard-dns-proxy";
|
||||
stateDirectory = "adguard-dns-proxy";
|
||||
preStart = ''
|
||||
cp ${generate-config-file cfg} ${cfg-path};
|
||||
chown $USER ${cfg-path};
|
||||
chmod u+w ${cfg-path};
|
||||
'';
|
||||
system.services.adguard-dns-proxy =
|
||||
let cfg-path = "/run/adguard-dns-proxy/config.yaml";
|
||||
in {
|
||||
description =
|
||||
"DNS Proxy for ad filtering and DNS-over-HTTPS lookups.";
|
||||
wantedBy = [ "default.target" ];
|
||||
after = [ "syslog.target" ];
|
||||
requires = [ "network.target" ];
|
||||
privateNetwork = false;
|
||||
requiredCapabilities = optional upgrade-perms "CAP_NET_BIND_SERVICE";
|
||||
restartWhen = "always";
|
||||
addressFamilies = null;
|
||||
networkWhitelist = cfg.allowed-networks;
|
||||
user = mkIf upgrade-perms cfg.user;
|
||||
runtimeDirectory = "adguard-dns-proxy";
|
||||
stateDirectory = "adguard-dns-proxy";
|
||||
preStart = ''
|
||||
cp ${generate-config-file cfg} ${cfg-path};
|
||||
chown $USER ${cfg-path};
|
||||
chmod u+w ${cfg-path};
|
||||
'';
|
||||
|
||||
execStart = let
|
||||
args = [
|
||||
"--no-check-update"
|
||||
"--work-dir /var/lib/adguard-dns-proxy"
|
||||
"--pidfile /run/adguard-dns-proxy/adguard-dns-proxy.pid"
|
||||
"--host ${cfg.http.listen-ip}"
|
||||
"--port ${toString cfg.http.listen-port}"
|
||||
"--config ${cfg-path}"
|
||||
];
|
||||
arg-string = concatStringsSep " " args;
|
||||
in "${pkgs.adguardhome}/bin/adguardhome ${arg-string}";
|
||||
};
|
||||
execStart = let
|
||||
args = [
|
||||
"--no-check-update"
|
||||
"--work-dir /var/lib/adguard-dns-proxy"
|
||||
"--pidfile /run/adguard-dns-proxy/adguard-dns-proxy.pid"
|
||||
"--host ${cfg.http.listen-ip}"
|
||||
"--port ${toString cfg.http.listen-port}"
|
||||
"--config ${cfg-path}"
|
||||
];
|
||||
arg-string = concatStringsSep " " args;
|
||||
in "${pkgs.adguardhome}/bin/adguardhome ${arg-string}";
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -68,28 +68,27 @@ let
|
|||
environment.systemPackages = [ kadminLocal ];
|
||||
|
||||
systemd = {
|
||||
tmpfiles.rules =
|
||||
[ "f ${cfg.kdc.database} 0700 ${cfg.user} ${cfg.group} - -" ];
|
||||
|
||||
services = {
|
||||
heimdal-kdc = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
description =
|
||||
"Heimdal Kerberos Key Distribution Center (primary ticket server).";
|
||||
path = with pkgs; [ heimdal ];
|
||||
serviceConfig = {
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
# PrivateMounts = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelTunables = true;
|
||||
# ProtectSystem = true;
|
||||
ProtectHostname = true;
|
||||
# ProtectHome = true;
|
||||
|
||||
ProtectClock = true;
|
||||
ProtectKernelLogs = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
# LockPersonality = true;
|
||||
# PermissionsStartOnly = true;
|
||||
PermissionsStartOnly = false;
|
||||
LimitNOFILE = 4096;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
@ -97,39 +96,37 @@ let
|
|||
RestartSec = "5s";
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
SecureBits = "keep-caps";
|
||||
ExecStartPre = let
|
||||
chownScript = pkgs.writeShellScript "kerberos-chown.sh" ''
|
||||
${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} ${cfg.kdc.database}
|
||||
${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} ${cfg.kdc.state-directory}/kerberos.log
|
||||
'';
|
||||
in "+${chownScript}";
|
||||
ExecStart = let
|
||||
ips = if (cfg.kdc.bind-addresses != [ ]) then
|
||||
cfg.kdc.bind-addresses
|
||||
else
|
||||
[ "0.0.0.0" ];
|
||||
bindClause = "--addresses=${concatStringsSep "," ips}";
|
||||
in "${pkgs.heimdal}/libexec/heimdal/kdc --config-file=${kdcConf} --ports=88 ${bindClause}";
|
||||
in "${pkgs.heimdal}/libexec/kdc --config-file=${kdcConf} --ports=88 ${bindClause}";
|
||||
};
|
||||
};
|
||||
|
||||
heimdal-kadmind = {
|
||||
wantedBy = [ "heimdal-kdc.service" ];
|
||||
after = [ "heimdal-kdc.service" ];
|
||||
bindsTo = [ "heimdal-kdc.service" ];
|
||||
description = "Heimdal Kerberos Administration Server.";
|
||||
path = with pkgs; [ heimdal ];
|
||||
serviceConfig = {
|
||||
# StandardInput = "socket";
|
||||
# StandardOutput = "socket";
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
# PrivateMounts = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelTunables = true;
|
||||
# ProtectSystem = true;
|
||||
ProtectHostname = true;
|
||||
# ProtectHome = true;
|
||||
ProtectClock = true;
|
||||
ProtectKernelLogs = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
# LockPersonality = true;
|
||||
# PermissionsStartOnly = true;
|
||||
LimitNOFILE = 4096;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
@ -138,7 +135,7 @@ let
|
|||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
SecureBits = "keep-caps";
|
||||
ExecStart = concatStringsSep " " [
|
||||
"${pkgs.heimdal}/libexec/heimdal/kadmind"
|
||||
"${pkgs.heimdal}/libexec/kadmind"
|
||||
"--config-file=${kdcConf}"
|
||||
"--keytab=${cfg.kdc.primary.keytabs.kadmind}"
|
||||
"--realm=${cfg.realm}"
|
||||
|
@ -149,16 +146,11 @@ let
|
|||
heimdal-kpasswdd = {
|
||||
wantedBy = [ "heimdal-kdc.service" ];
|
||||
after = [ "heimdal-kdc.service" ];
|
||||
bindsTo = [ "heimdal-kdc.service" ];
|
||||
description = "Heimdal Kerberos Password Server.";
|
||||
path = with pkgs; [ heimdal ];
|
||||
serviceConfig = {
|
||||
# This wasn't working:
|
||||
# StandardInput = "socket";
|
||||
# StandardOutput = "socket";
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
# PrivateMounts = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = true;
|
||||
|
@ -178,7 +170,7 @@ let
|
|||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
SecureBits = "keep-caps";
|
||||
ExecStart = concatStringsSep " " [
|
||||
"${pkgs.heimdal}/libexec/heimdal/kpasswdd"
|
||||
"${pkgs.heimdal}/libexec/kpasswdd"
|
||||
"--config-file=${kdcConf}"
|
||||
"--keytab=${cfg.kdc.primary.keytabs.kpasswdd}"
|
||||
"--realm=${cfg.realm}"
|
||||
|
@ -187,9 +179,7 @@ let
|
|||
};
|
||||
|
||||
heimdal-hprop = mkIf hasSecondary {
|
||||
wantedBy = [ "heimdal-kdc.service" ];
|
||||
bindsTo = [ "heimdal-kdc.service" ];
|
||||
after = [ "heimdal-kdc.service" ];
|
||||
wantedBy = [ "multi-user.service" ];
|
||||
description =
|
||||
"Service to propagate the KDC database to secondary servers.";
|
||||
path = with pkgs; [ heimdal ];
|
||||
|
@ -198,21 +188,8 @@ let
|
|||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
Type = "oneshot";
|
||||
Restart = "on-failure";
|
||||
RuntimeDirectory = "heimdal-hprop";
|
||||
# ExecStartPre = let
|
||||
# convertCmd = concatStringsSep " " [
|
||||
# "${pkgs.kdcConvertDatabase}/bin/kdc-convert-database"
|
||||
# "--config-file=${kdcConf}"
|
||||
# "--key=${cfg.kdc.master-key-file}"
|
||||
# "--format=db3"
|
||||
# "--realm=${cfg.realm}"
|
||||
# "--output=${staging-db}"
|
||||
# "--verbose"
|
||||
# ];
|
||||
# in pkgs.writeShellScript "convert-kdc-database.sh" ''
|
||||
# ${convertCmd}
|
||||
# ls $RUNTIME_DIRECTORY
|
||||
# '';
|
||||
ExecStartPre = pkgs.writeShellScript "kdc-prepare-hprop-dump.sh"
|
||||
(concatStringsSep " " [
|
||||
"${pkgs.heimdal}/bin/kadmin"
|
||||
|
@ -220,12 +197,13 @@ let
|
|||
"--config-file=${kdcConf}"
|
||||
"--"
|
||||
"dump"
|
||||
"--format=MIT"
|
||||
"$(echo ${staging-db})"
|
||||
"--format=Heimdal"
|
||||
"${staging-db}"
|
||||
]);
|
||||
|
||||
ExecStart = pkgs.writeShellScript "kdc-hprop.sh"
|
||||
(concatStringsSep " " ([
|
||||
"${pkgs.heimdal}/libexec/heimdal/hprop"
|
||||
"${pkgs.heimdal}/libexec/hprop"
|
||||
''--master-key="${cfg.kdc.master-key-file}"''
|
||||
#''--database="(echo "${staging-db}")"''
|
||||
"--database=sqlite:${cfg.kdc.database}"
|
||||
|
@ -239,36 +217,12 @@ let
|
|||
};
|
||||
|
||||
paths.heimdal-hprop = mkIf hasSecondary {
|
||||
wantedBy = [ "heimdal-kdc.service" ];
|
||||
bindsTo = [ "heimdal-kdc.service" ];
|
||||
after = [ "heimdal-kdc.service" ];
|
||||
pathConfig = { PathModified = cfg.kdc.database; };
|
||||
wantedBy = [ "heimdal-hprop.service" ];
|
||||
bindsTo = [ "heimdal-hprop.service" ];
|
||||
pathConfig.PathModified = cfg.kdc.database;
|
||||
};
|
||||
};
|
||||
|
||||
# services.xinetd = {
|
||||
# enable = true;
|
||||
|
||||
# services = [
|
||||
# {
|
||||
# name = "kadmin";
|
||||
# user = cfg.user;
|
||||
# server = "${pkgs.heimdal}/libexec/heimdal/kadmind";
|
||||
# protocol = "tcp";
|
||||
# serverArgs =
|
||||
# "--config-file=${kdcConf} --keytab=${cfg.kdc.primary.keytabs.kadmind}";
|
||||
# }
|
||||
# {
|
||||
# name = "kpasswd";
|
||||
# user = cfg.user;
|
||||
# server = "${pkgs.heimdal}/libexec/heimdal/kpasswdd";
|
||||
# protocol = "udp";
|
||||
# serverArgs =
|
||||
# "--config-file=${kdcConf} --keytab=${cfg.kdc.primary.keytabs.kpasswdd}";
|
||||
# }
|
||||
# ];
|
||||
# };
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 88 749 ];
|
||||
allowedUDPPorts = [ 88 464 ];
|
||||
|
@ -310,32 +264,31 @@ let
|
|||
isSystemUser = true;
|
||||
group = cfg.group;
|
||||
};
|
||||
groups."${cfg.group}" = { members = [ cfg.user ]; };
|
||||
groups."${cfg.group}".members = [ cfg.user ];
|
||||
};
|
||||
|
||||
systemd = {
|
||||
|
||||
tmpfiles.rules =
|
||||
[ "f ${cfg.kdc.database} 0700 ${cfg.user} ${cfg.group} - -" ];
|
||||
|
||||
services = {
|
||||
heimdal-kdc-secondary = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
description =
|
||||
"Heimdal Kerberos Key Distribution Center (secondary ticket server).";
|
||||
path = with pkgs; [ heimdal ];
|
||||
serviceConfig = {
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
# PrivateMounts = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelTunables = true;
|
||||
# ProtectSystem = true;
|
||||
ProtectHostname = true;
|
||||
# ProtectHome = true;
|
||||
ProtectClock = true;
|
||||
ProtectKernelLogs = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
# LockPersonality = true;
|
||||
# PermissionsStartOnly = true;
|
||||
PermissionsStartOnly = false;
|
||||
LimitNOFILE = 4096;
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
|
@ -343,50 +296,72 @@ let
|
|||
RestartSec = "5s";
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
SecureBits = "keep-caps";
|
||||
ExecStartPre = let
|
||||
chownScript = pkgs.writeShellScript "kerberos-chown.sh" ''
|
||||
${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} ${cfg.kdc.database}
|
||||
${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} ${cfg.kdc.state-directory}/kerberos.log
|
||||
'';
|
||||
in "+${chownScript}";
|
||||
ExecStart = let
|
||||
ips = if (cfg.kdc.bind-addresses != [ ]) then
|
||||
cfg.kdc.bind-addresses
|
||||
else
|
||||
[ "0.0.0.0" ];
|
||||
bindClause = "--addresses=${concatStringsSep "," ips}";
|
||||
in "${pkgs.heimdal}/libexec/heimdal/kdc --config-file=${kdcConf} --ports=88 ${bindClause}";
|
||||
in "${pkgs.heimdal}/libexec/kdc --config-file=${kdcConf} --ports=88 ${bindClause}";
|
||||
};
|
||||
unitConfig.ConditionPathExists = [ cfg.kdc.database ];
|
||||
};
|
||||
|
||||
heimdal-hpropd = {
|
||||
wantedBy = [ "heimdal-kdc-secondary.service" ];
|
||||
after = [ "heimdal-kdc-secondary.service" ];
|
||||
bindsTo = [ "heimdal-kdc-secondary.service" ];
|
||||
"heimdal-hpropd@" = {
|
||||
description = "Heimdal propagation listener server.";
|
||||
path = with pkgs; [ heimdal ];
|
||||
path = with pkgs; [ coreutils heimdal ];
|
||||
serviceConfig = {
|
||||
StandardInput = "socket";
|
||||
StandardOutput = "socket";
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
# PrivateMounts = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelTunables = true;
|
||||
# ProtectSystem = true;
|
||||
ProtectHostname = true;
|
||||
# ProtectHome = true;
|
||||
ProtectClock = true;
|
||||
ProtectKernelLogs = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
# LockPersonality = true;
|
||||
# PermissionsStartOnly = true;
|
||||
LimitNOFILE = 4096;
|
||||
LimitNOFILE = "4096";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
# Server will retry -- this results in stacking
|
||||
Restart = "no";
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
SecureBits = "keep-caps";
|
||||
ExecStart = concatStringsSep " " [
|
||||
"${pkgs.heimdal}/libexec/heimdal/hpropd"
|
||||
"--database=sqlite:${cfg.kdc.database}"
|
||||
"--keytab=${cfg.kdc.secondary.keytabs.hpropd}"
|
||||
];
|
||||
ReadWritePaths = [ "${dirOf cfg.kdc.database}" ];
|
||||
StateDirectory = "hemidal-hpropd";
|
||||
ExecStartPre = pkgs.writeShellScript "heimdal-prepare-db.sh"
|
||||
"cp ${cfg.kdc.database} $STATE_DIRECTORY/realm.db";
|
||||
ExecStart = let
|
||||
startScript = pkgs.writeShellScript "launch-heimdal-hpropd.sh"
|
||||
(concatStringsSep " " [
|
||||
"${pkgs.heimdal}/libexec/hpropd"
|
||||
"--database=sqlite:$STATE_DIRECTORY/realm.db"
|
||||
"--keytab=${cfg.kdc.secondary.keytabs.hpropd}"
|
||||
]);
|
||||
in "${startScript}";
|
||||
ExecStartPost = pkgs.writeShellScript "heimdal-restore-db.sh" ''
|
||||
chown ${cfg.user}:${cfg.group} $STATE_DIRECTORY/realm.db
|
||||
mv $STATE_DIRECTORY/realm.db ${cfg.kdc.database}
|
||||
'';
|
||||
};
|
||||
unitConfig.ConditionPathExists =
|
||||
[ cfg.kdc.database cfg.kdc.secondary.keytabs.hpropd ];
|
||||
};
|
||||
};
|
||||
|
||||
sockets.heimdal-hpropd = {
|
||||
wantedBy = [ "sockets.target" ];
|
||||
socketConfig = {
|
||||
ListenStream = "0.0.0.0:754";
|
||||
Accept = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{ config, lib, pkgs, ... } @ toplevel:
|
||||
{ config, lib, pkgs, ... }@toplevel:
|
||||
|
||||
with lib;
|
||||
let
|
||||
|
@ -12,7 +12,8 @@ in {
|
|||
|
||||
required-services = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of systemd units on which the DNS backplane job depends.";
|
||||
description =
|
||||
"List of systemd units on which the DNS backplane job depends.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
|
@ -54,11 +55,12 @@ in {
|
|||
|
||||
password-file = mkOption {
|
||||
type = str;
|
||||
description = "File containing password for DNS backplane database user.";
|
||||
description =
|
||||
"File containing password for DNS backplane database user.";
|
||||
};
|
||||
|
||||
ssl-mode = mkOption {
|
||||
type = enum ["no" "yes" "full" "try" "require"];
|
||||
type = enum [ "no" "yes" "full" "try" "require" ];
|
||||
description = "SSL connection mode.";
|
||||
default = "require";
|
||||
};
|
||||
|
@ -86,9 +88,7 @@ in {
|
|||
home = backplane-dns-home;
|
||||
createHome = true;
|
||||
};
|
||||
groups.${cfg.group} = {
|
||||
members = [ cfg.user ];
|
||||
};
|
||||
groups.${cfg.group} = { members = [ cfg.user ]; };
|
||||
};
|
||||
|
||||
fudo.system.services = {
|
||||
|
@ -110,12 +110,12 @@ in {
|
|||
environment = {
|
||||
FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane-server;
|
||||
FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane-role.role;
|
||||
FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane-role.password-file;
|
||||
FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE =
|
||||
cfg.backplane-role.password-file;
|
||||
|
||||
FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.database.host;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.database.database;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_USERNAME =
|
||||
cfg.database.username;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_USERNAME = cfg.database.username;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE =
|
||||
cfg.database.password-file;
|
||||
FUDO_DNS_BACKPLANE_DATABASE_USE_SSL = cfg.database.ssl-mode;
|
||||
|
|
|
@ -7,20 +7,19 @@ let
|
|||
|
||||
host-secrets = config.fudo.secrets.host-secrets.${hostname};
|
||||
|
||||
generate-auth-file = name: files: let
|
||||
make-entry = name: passwd-file:
|
||||
''("${name}" . "${readFile passwd-file}")'';
|
||||
entries = mapAttrsToList make-entry files;
|
||||
content = concatStringsSep "\n" entries;
|
||||
in pkgs.writeText "${name}-backplane-auth.scm" "'(${content})";
|
||||
generate-auth-file = name: files:
|
||||
let
|
||||
make-entry = name: passwd-file:
|
||||
''("${name}" . "${readFile passwd-file}")'';
|
||||
entries = mapAttrsToList make-entry files;
|
||||
content = concatStringsSep "\n" entries;
|
||||
in pkgs.writeText "${name}-backplane-auth.scm" "'(${content})";
|
||||
|
||||
host-auth-file = generate-auth-file "host"
|
||||
(mapAttrs (hostname: hostOpts: hostOpts.password-file)
|
||||
cfg.client-hosts);
|
||||
(mapAttrs (hostname: hostOpts: hostOpts.password-file) cfg.client-hosts);
|
||||
|
||||
service-auth-file = generate-auth-file "service"
|
||||
(mapAttrs (service: serviceOpts: serviceOpts.password-file)
|
||||
cfg.services);
|
||||
(mapAttrs (service: serviceOpts: serviceOpts.password-file) cfg.services);
|
||||
|
||||
clientHostOpts = { name, ... }: {
|
||||
options = with types; {
|
||||
|
@ -49,13 +48,13 @@ in {
|
|||
client-hosts = mkOption {
|
||||
type = attrsOf (submodule clientHostOpts);
|
||||
description = "List of backplane client options.";
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
services = mkOption {
|
||||
type = attrsOf (submodule serviceOpts);
|
||||
description = "List of backplane service options.";
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
backplane-hostname = mkOption {
|
||||
|
@ -85,8 +84,7 @@ in {
|
|||
|
||||
jabber = {
|
||||
environment = {
|
||||
FUDO_HOST_PASSWD_FILE =
|
||||
host-secrets.backplane-host-auth.target-file;
|
||||
FUDO_HOST_PASSWD_FILE = host-secrets.backplane-host-auth.target-file;
|
||||
FUDO_SERVICE_PASSWD_FILE =
|
||||
host-secrets.backplane-service-auth.target-file;
|
||||
# GUILE_AUTO_COMPILE = "0";
|
||||
|
@ -98,22 +96,21 @@ in {
|
|||
site-config = {
|
||||
auth_method = "external";
|
||||
extauth_program =
|
||||
# "${pkgs.guile}/bin/guile -s /run/backplane-auth.scm";
|
||||
"${pkgs.guile}/bin/guile -s ${pkgs.backplane-auth}/backplane-auth.scm";
|
||||
extauth_pool_size = 3;
|
||||
auth_use_cache = false;
|
||||
|
||||
modules = {
|
||||
mod_adhoc = {};
|
||||
mod_adhoc = { };
|
||||
# mod_caps = {};
|
||||
# mod_carboncopy = {};
|
||||
# mod_client_state = {};
|
||||
mod_configure = {};
|
||||
mod_configure = { };
|
||||
# mod_disco = {};
|
||||
mod_fail2ban = {};
|
||||
mod_fail2ban = { };
|
||||
# mod_last = {};
|
||||
mod_offline.access_max_user_messages = 5000;
|
||||
mod_ping = {};
|
||||
mod_ping = { };
|
||||
# mod_pubsub = {
|
||||
# access_createnode = "pubsub_createnode";
|
||||
# ignore_pep_from_offline = true;
|
||||
|
@ -124,9 +121,9 @@ in {
|
|||
# ];
|
||||
# };
|
||||
# mod_roster = {};
|
||||
mod_stream_mgmt = {};
|
||||
mod_time = {};
|
||||
mod_version = {};
|
||||
mod_stream_mgmt = { };
|
||||
mod_time = { };
|
||||
mod_version = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -200,51 +200,7 @@ in {
|
|||
locations."/" = {
|
||||
proxyPass = "http://127.0.0.1:8065";
|
||||
proxyWebsockets = true;
|
||||
|
||||
# extraConfig = ''
|
||||
# client_max_body_size 50M;
|
||||
# proxy_set_header Connection "";
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-By $server_addr:$server_port;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header X-Frame-Options SAMEORIGIN;
|
||||
# proxy_buffers 256 16k;
|
||||
# proxy_buffer_size 16k;
|
||||
# proxy_read_timeout 600s;
|
||||
# proxy_cache mattermost_cache;
|
||||
# proxy_cache_revalidate on;
|
||||
# proxy_cache_min_uses 2;
|
||||
# proxy_cache_use_stale timeout;
|
||||
# proxy_cache_lock on;
|
||||
# proxy_http_version 1.1;
|
||||
# '';
|
||||
};
|
||||
|
||||
# locations."~ /api/v[0-9]+/(users/)?websocket$" = {
|
||||
# proxyPass = "http://127.0.0.1:8065";
|
||||
|
||||
# extraConfig = ''
|
||||
# proxy_set_header Upgrade $http_upgrade;
|
||||
# proxy_set_header Connection "upgrade";
|
||||
# client_max_body_size 50M;
|
||||
# proxy_set_header Host $host;
|
||||
# proxy_set_header X-Real-IP $remote_addr;
|
||||
# proxy_set_header X-Forwarded-By $server_addr:$server_port;
|
||||
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
# proxy_set_header X-Forwarded-Proto $scheme;
|
||||
# proxy_set_header X-Frame-Options SAMEORIGIN;
|
||||
# proxy_buffers 256 16k;
|
||||
# proxy_buffer_size 16k;
|
||||
# client_body_timeout 60;
|
||||
# send_timeout 300;
|
||||
# lingering_timeout 5;
|
||||
# proxy_connect_timeout 90;
|
||||
# proxy_send_timeout 300;
|
||||
# proxy_read_timeout 90s;
|
||||
# '';
|
||||
# };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,13 +4,12 @@ with lib;
|
|||
let
|
||||
cfg = config.fudo.client.dns;
|
||||
|
||||
ssh-key-files =
|
||||
map (host-key: host-key.path) config.services.openssh.hostKeys;
|
||||
|
||||
ssh-key-args = concatStringsSep " " (map (file: "-f ${file}") ssh-key-files);
|
||||
hostname = config.instance.hostname;
|
||||
|
||||
in {
|
||||
options.fudo.client.dns = {
|
||||
enable = mkEnableOption "Enable Backplane DNS client.";
|
||||
|
||||
ipv4 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
|
@ -68,7 +67,7 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users = {
|
||||
users = {
|
||||
|
@ -98,37 +97,96 @@ in {
|
|||
timerConfig = { OnCalendar = cfg.frequency; };
|
||||
};
|
||||
|
||||
services.backplane-dns-client-pw-file = {
|
||||
enable = true;
|
||||
requiredBy = [ "backplane-dns-client.service" ];
|
||||
reloadIfChanged = true;
|
||||
serviceConfig = { Type = "oneshot"; };
|
||||
script = ''
|
||||
chmod 400 ${cfg.password-file}
|
||||
chown ${cfg.user} ${cfg.password-file}
|
||||
'';
|
||||
};
|
||||
|
||||
services.backplane-dns-client = {
|
||||
enable = true;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
StandardOutput = "journal";
|
||||
User = cfg.user;
|
||||
ExecStart = pkgs.writeShellScript "start-backplane-dns-client.sh" ''
|
||||
${pkgs.backplane-dns-client}/bin/backplane-dns-client ${
|
||||
optionalString cfg.ipv4 "-4"
|
||||
} ${optionalString cfg.ipv6 "-6"} ${
|
||||
optionalString cfg.sshfp ssh-key-args
|
||||
} ${
|
||||
optionalString (cfg.external-interface != null)
|
||||
"--interface=${cfg.external-interface}"
|
||||
} --domain=${cfg.domain} --server=${cfg.server} --password-file=${cfg.password-file}
|
||||
services = let sshfp-file = "/tmp/${hostname}-sshfp/fingerprints";
|
||||
in {
|
||||
backplane-dns-client-pw-file = {
|
||||
requiredBy = [ "backplane-dns-client.service" ];
|
||||
reloadIfChanged = true;
|
||||
serviceConfig = { Type = "oneshot"; };
|
||||
script = ''
|
||||
chmod 400 ${cfg.password-file}
|
||||
chown ${cfg.user} ${cfg.password-file}
|
||||
'';
|
||||
};
|
||||
# Needed to generate SSH fingerprinst
|
||||
path = [ pkgs.openssh ];
|
||||
reloadIfChanged = true;
|
||||
|
||||
backplane-dns-generate-sshfps = mkIf cfg.sshfp {
|
||||
requiredBy = [ "backplane-dns-client.service" ];
|
||||
before = [ "backplane-dns-client.service" ];
|
||||
path = with pkgs; [ coreutils openssh ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
PrivateDevices = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
ProtectHome = true;
|
||||
ProtectKernelLogs = true;
|
||||
#ProtectSystem = true;
|
||||
#LockPersonality = true;
|
||||
#PermissionsStartOnly = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
RestrictRealtime = true;
|
||||
LimitNOFILE = 1024;
|
||||
ReadWritePaths = [ (dirOf sshfp-file) ];
|
||||
};
|
||||
script = let
|
||||
keyPaths = map (key: key.path) config.services.openssh.hostKeys;
|
||||
keyGenCmds = map (path:
|
||||
''
|
||||
ssh-keygen -r hostname -f "${path}" | sed 's/hostname IN SSHFP '// >> ${sshfp-file}'')
|
||||
keyPaths;
|
||||
in ''
|
||||
[ -f ${sshfp-file} ] && rm -f ${sshfp-file}
|
||||
SSHFP_DIR=$(dirname ${sshfp-file})
|
||||
[ -d $SSHFP_DIR ] || mkdir $SSHFP_DIR
|
||||
chown ${cfg.user} $SSHFP_DIR
|
||||
chmod go-rwx $SSHFP_DIR
|
||||
${concatStringsSep "\n" keyGenCmds}
|
||||
chown ${cfg.user} ${sshfp-file}
|
||||
chmod 600 ${sshfp-file}
|
||||
'';
|
||||
};
|
||||
|
||||
backplane-dns-client = {
|
||||
enable = true;
|
||||
path = with pkgs; [ coreutils ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
StandardOutput = "journal";
|
||||
User = cfg.user;
|
||||
ExecStart = pkgs.writeShellScript "start-backplane-dns-client.sh" ''
|
||||
SSHFP_ARGS=""
|
||||
${optionalString cfg.sshfp ''
|
||||
while read LINE; do SSHFP_ARGS="$SSHFP_ARGS --ssh-fp=\"$LINE\""; done < ${sshfp-file}
|
||||
''}
|
||||
CMD="${pkgs.backplaneDnsClient}/bin/backplane-dns-client ${
|
||||
optionalString cfg.ipv4 "-4"
|
||||
} ${optionalString cfg.ipv6 "-6"} ${
|
||||
optionalString cfg.sshfp "$SSHFP_ARGS"
|
||||
} ${
|
||||
optionalString (cfg.external-interface != null)
|
||||
"--interface=${cfg.external-interface}"
|
||||
} --domain=${cfg.domain} --server=${cfg.server} --password-file=${cfg.password-file}"
|
||||
echo $CMD
|
||||
$CMD
|
||||
'';
|
||||
ExecStartPost = mkIf cfg.sshfp "rm ${sshfp-file}";
|
||||
PrivateDevices = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHostname = true;
|
||||
ProtectClock = true;
|
||||
ProtectHome = true;
|
||||
ProtectKernelLogs = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
ProtectSystem = true;
|
||||
LockPersonality = true;
|
||||
PermissionsStartOnly = true;
|
||||
RestrictRealtime = true;
|
||||
ReadOnlyPaths = [ sshfp-file ];
|
||||
LimitNOFILE = 1024;
|
||||
};
|
||||
reloadIfChanged = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -33,6 +33,7 @@ with lib; {
|
|||
./minecraft-clj.nix
|
||||
./minecraft-server.nix
|
||||
./netinfo-email.nix
|
||||
./nexus.nix
|
||||
./node-exporter.nix
|
||||
./nsd.nix
|
||||
./password.nix
|
||||
|
|
|
@ -20,6 +20,13 @@ let
|
|||
default = true;
|
||||
};
|
||||
|
||||
ksk = {
|
||||
key-file = mkOption {
|
||||
type = str;
|
||||
description = "Key-signing key for this zone.";
|
||||
};
|
||||
};
|
||||
|
||||
zone-definition = mkOption {
|
||||
type = submodule (import ../types/zone-definition.nix);
|
||||
description =
|
||||
|
@ -64,25 +71,30 @@ in {
|
|||
allowedUDPPorts = [ 53 ];
|
||||
};
|
||||
|
||||
fileSystems."/var/lib/nsd" = {
|
||||
device = cfg.state-directory;
|
||||
options = [ "bind" ];
|
||||
};
|
||||
# fileSystems."/var/lib/nsd" = {
|
||||
# device = cfg.state-directory;
|
||||
# options = [ "bind" ];
|
||||
# };
|
||||
|
||||
services.nsd = {
|
||||
enable = true;
|
||||
identity = cfg.identity;
|
||||
interfaces = cfg.listen-ips;
|
||||
# stateDir = cfg.state-directory;
|
||||
zones = mapAttrs' (dom: dom-cfg:
|
||||
let net-cfg = dom-cfg.zone-definition;
|
||||
in nameValuePair "${dom}." {
|
||||
dnssec = dom-cfg.dnssec;
|
||||
fudo = {
|
||||
nsd = {
|
||||
enable = true;
|
||||
identity = cfg.identity;
|
||||
interfaces = cfg.listen-ips;
|
||||
stateDirectory = cfg.state-directory;
|
||||
zones = mapAttrs' (dom: dom-cfg:
|
||||
let net-cfg = dom-cfg.zone-definition;
|
||||
in nameValuePair "${dom}." {
|
||||
dnssec = dom-cfg.dnssec;
|
||||
|
||||
data = pkgs.lib.dns.zoneToZonefile config.instance.build-timestamp dom
|
||||
dom-cfg.zone-definition;
|
||||
ksk.keyFile = dom-cfg.ksk.key-file;
|
||||
|
||||
}) cfg.domains;
|
||||
data =
|
||||
pkgs.lib.dns.zoneToZonefile config.instance.build-timestamp dom
|
||||
dom-cfg.zone-definition;
|
||||
|
||||
}) cfg.domains;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -48,11 +48,21 @@ let
|
|||
default = "admin@${domain}";
|
||||
};
|
||||
|
||||
grafana-hosts = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of hosts acting as Grafana metric analyzers. Requires prometheus hosts as well.";
|
||||
default = [ ];
|
||||
metrics = mkOption {
|
||||
type = nullOr (submodule {
|
||||
options = {
|
||||
grafana-host = mkOption {
|
||||
type = str;
|
||||
description = "Hostname of the Grafana Metrics Analysis tool.";
|
||||
};
|
||||
prometheus-host = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Hostname of the Prometheus Metrics Aggregator tool.";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = null;
|
||||
};
|
||||
|
||||
log-aggregator = mkOption {
|
||||
|
@ -120,8 +130,9 @@ let
|
|||
};
|
||||
|
||||
gssapi-realm = mkOption {
|
||||
type = str;
|
||||
type = nullOr str;
|
||||
description = "GSSAPI (i.e. Kerberos) realm of this domain.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
kerberos-master = mkOption {
|
||||
|
@ -145,13 +156,6 @@ let
|
|||
default = [ ];
|
||||
};
|
||||
|
||||
prometheus-hosts = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of hosts acting aas prometheus metric scrapers for hosts in this network.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
primary-nameserver = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Hostname of the primary nameserver for this domain.";
|
||||
|
@ -182,6 +186,28 @@ let
|
|||
description = "Name of the DNS zone associated with domain.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
nexus = {
|
||||
public-domains = mkOption {
|
||||
type = listOf str;
|
||||
description = "Nexus domains to which hosts in this domain belong.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
private-domains = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"Nexus private domains to which hosts in this domain belong.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
tailscale-domains = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"Nexus tailscale domains to which hosts in this domain belong.";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -123,19 +123,13 @@ in {
|
|||
passwordFile = cfg.database.password-file;
|
||||
type = "postgres";
|
||||
};
|
||||
domain = cfg.hostname;
|
||||
httpAddress = "127.0.0.1";
|
||||
httpPort = cfg.local-port;
|
||||
repositoryRoot = cfg.repository-dir;
|
||||
stateDir = cfg.state-dir;
|
||||
rootUrl = "https://${cfg.hostname}/";
|
||||
user = mkIf (cfg.user != null) cfg.user;
|
||||
ssh = {
|
||||
#enable = true;
|
||||
clonePort = cfg.ssh.listen-port;
|
||||
};
|
||||
settings = mkIf (cfg.ssh != null) {
|
||||
server = {
|
||||
START_SSH_SERVER = true;
|
||||
|
||||
# Displayed in the clone URL
|
||||
SSH_DOMAIN = cfg.hostname;
|
||||
SSH_PORT = mkForce cfg.ssh.listen-port;
|
||||
|
@ -143,6 +137,11 @@ in {
|
|||
# Actual ip/port on which to listen
|
||||
SSH_LISTEN_PORT = cfg.ssh.listen-port;
|
||||
SSH_LISTEN_HOST = cfg.ssh.listen-ip;
|
||||
|
||||
ROOT_URL = "https://${cfg.hostname}/";
|
||||
HTTP_ADDR = "127.0.0.1";
|
||||
HTTP_PORT = cfg.local-port;
|
||||
DOMAIN = cfg.hostname;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -105,34 +105,40 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
database = {
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description = "Database name.";
|
||||
default = "grafana";
|
||||
};
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Hostname of the database server.";
|
||||
default = "localhost";
|
||||
};
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "Database username.";
|
||||
default = "grafana";
|
||||
};
|
||||
password-file = mkOption {
|
||||
type = str;
|
||||
description = "File containing the database user's password.";
|
||||
};
|
||||
};
|
||||
oauth = let
|
||||
oauthOpts.options = {
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Host of the OAuth server.";
|
||||
};
|
||||
|
||||
ldap = mkOption {
|
||||
type = nullOr (submodule ldapOpts);
|
||||
description = "";
|
||||
client-id = mkOption {
|
||||
type = str;
|
||||
description = "Path to file containing the Grafana OAuth client ID.";
|
||||
};
|
||||
|
||||
client-secret = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Path to file containing the Grafana OAuth client secret.";
|
||||
};
|
||||
|
||||
slug = mkOption {
|
||||
type = str;
|
||||
description = "The application slug on the OAuth server.";
|
||||
};
|
||||
};
|
||||
in mkOption {
|
||||
type = nullOr (submodule oauthOpts);
|
||||
default = null;
|
||||
};
|
||||
|
||||
# ldap = mkOption {
|
||||
# type = nullOr (submodule ldapOpts);
|
||||
# description = "";
|
||||
# default = null;
|
||||
# };
|
||||
|
||||
admin-password-file = mkOption {
|
||||
type = str;
|
||||
description = "Path to a file containing the admin user's password.";
|
||||
|
@ -164,20 +170,16 @@ in {
|
|||
tmpfiles.rules =
|
||||
let grafana-user = config.systemd.services.grafana.serviceConfig.User;
|
||||
in [ "d ${cfg.state-directory} 0700 ${grafana-user} - - -" ];
|
||||
|
||||
services.grafana.serviceConfig = {
|
||||
EnvironmentFile = host-secrets.grafana-environment-file.target-file;
|
||||
};
|
||||
};
|
||||
|
||||
fudo.secrets.host-secrets.${hostname}.grafana-environment-file = {
|
||||
source-file = pkgs.writeText "grafana.env" ''
|
||||
${optionalString (cfg.ldap != null)
|
||||
''GRAFANA_LDAP_BIND_PASSWD="${cfg.ldap.bind-passwd}"''}
|
||||
'';
|
||||
target-file = "/run/metrics/grafana/auth-bind.passwd";
|
||||
user = config.systemd.services.grafana.serviceConfig.User;
|
||||
};
|
||||
# fudo.secrets.host-secrets.${hostname}.grafana-environment-file = {
|
||||
# source-file = pkgs.writeText "grafana.env" ''
|
||||
# ${optionalString (cfg.ldap != null)
|
||||
# ''GRAFANA_LDAP_BIND_PASSWD="${cfg.ldap.bind-passwd}"''}
|
||||
# '';
|
||||
# target-file = "/run/metrics/grafana/auth-bind.passwd";
|
||||
# user = config.systemd.services.grafana.serviceConfig.User;
|
||||
# };
|
||||
|
||||
services = {
|
||||
nginx = {
|
||||
|
@ -226,49 +228,31 @@ in {
|
|||
};
|
||||
|
||||
database = {
|
||||
host = cfg.database.hostname;
|
||||
name = cfg.database.name;
|
||||
user = cfg.database.user;
|
||||
password = "$__file{${cfg.database.password-file}}";
|
||||
type = "postgres";
|
||||
type = "sqlite3";
|
||||
path = "${cfg.state-directory}/database.sqlite";
|
||||
};
|
||||
|
||||
"ldap.auth" = mkIf (cfg.ldap != null) (let
|
||||
base = cfg.ldap.base-dn;
|
||||
auth = mkIf (!isNull cfg.oauth) {
|
||||
signout_redirect_url =
|
||||
"https://${cfg.oauth.hostname}/application/o/${cfg.oauth.slug}/end-session/";
|
||||
oauth_auto_login = true;
|
||||
};
|
||||
|
||||
config-file = pkgs.writeText "grafana-ldap.toml" ''
|
||||
[[servers]]
|
||||
host = "${concatStringsSep " " cfg.ldap.hosts}"
|
||||
port = 389
|
||||
start_tls = true
|
||||
|
||||
bind_dn = "uid=%s,ou=members,${base}"
|
||||
|
||||
search_filter = "(uid=%s)"
|
||||
search_base_dns = [ "ou=members,${base}" ]
|
||||
|
||||
group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"
|
||||
group_search_base_dns = ["ou=groups,${base}"]
|
||||
group_search_filter_user_attribute = "uid"
|
||||
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=admin,ou=groups,${base}"
|
||||
org_role = "Admin"
|
||||
grafana_admin = true
|
||||
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=*,ou=groups,${base}"
|
||||
org_role = "Viewer"
|
||||
'';
|
||||
in {
|
||||
"auth.generic_oauth" = mkIf (!isNull cfg.oauth) {
|
||||
name = "Authentik";
|
||||
enabled = true;
|
||||
allow_sign_up = true;
|
||||
config_file = "${config-file}";
|
||||
|
||||
# AUTH_LDAP_ENABLED = "true";
|
||||
# AUTH_LDAP_ALLOW_SIGN_UP = "true";
|
||||
# AUTH_LDAP_CONFIG_FILE = config-file;
|
||||
});
|
||||
client_id = "$__file{${cfg.oauth.client-id}}";
|
||||
client_secret = "$__file{${cfg.oauth.client-secret}}";
|
||||
scopes = "openid email profile";
|
||||
auth_url = "https://${cfg.oauth.hostname}/application/o/authorize/";
|
||||
token_url = "https://${cfg.oauth.hostname}/application/o/token/";
|
||||
api_url = "https://${cfg.oauth.hostname}/application/o/userinfo/";
|
||||
role_attribute_path = concatStringsSep " || " [
|
||||
"contains(groups[*], 'Metrics Admin') && 'Admin'"
|
||||
"contains(groups[*], 'Metrics Editor') && 'Editor'"
|
||||
"'Viewer'"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
provision = {
|
||||
|
|
|
@ -4,120 +4,114 @@ with lib;
|
|||
let
|
||||
hostname = config.instance.hostname;
|
||||
host-filesystems = config.fudo.hosts.${hostname}.encrypted-filesystems;
|
||||
|
||||
|
||||
optionalOrDefault = str: default: if (str != null) then str else default;
|
||||
|
||||
filesystemsToMountpointLists = mapAttrsToList
|
||||
(fs: fsOpts: fsOpts.mountpoints);
|
||||
filesystemsToMountpointLists =
|
||||
mapAttrsToList (fs: fsOpts: fsOpts.mountpoints);
|
||||
|
||||
concatMapAttrs = f: as: concatMap (i: i) (mapAttrsToList f as);
|
||||
|
||||
concatMapAttrsToList = f: attrs:
|
||||
concatMap (i: i) (mapAttrsToList f attrs);
|
||||
concatMapAttrsToList = f: attrs: concatMap (i: i) (mapAttrsToList f attrs);
|
||||
|
||||
in {
|
||||
config = {
|
||||
users.groups = let
|
||||
site-name = config.instance.local-site;
|
||||
site-hosts = filterAttrs
|
||||
(hostname: hostOpts: hostOpts.site == site-name)
|
||||
site-hosts = filterAttrs (hostname: hostOpts: hostOpts.site == site-name)
|
||||
config.fudo.hosts;
|
||||
site-mountpoints = concatMapAttrsToList
|
||||
(host: hostOpts: concatMapAttrsToList
|
||||
(fs: fsOpts: attrValues fsOpts.mountpoints)
|
||||
hostOpts.encrypted-filesystems)
|
||||
site-hosts;
|
||||
site-mountpoints = concatMapAttrsToList (host: hostOpts:
|
||||
concatMapAttrsToList (fs: fsOpts: attrValues fsOpts.mountpoints)
|
||||
hostOpts.encrypted-filesystems) site-hosts;
|
||||
in listToAttrs
|
||||
(map (mp: nameValuePair mp.group { members = mp.users; })
|
||||
site-mountpoints);
|
||||
(map (mp: nameValuePair mp.group { members = mp.users; }) site-mountpoints);
|
||||
|
||||
systemd = {
|
||||
# Ensure the mountpoints exist
|
||||
tmpfiles.rules = let
|
||||
mpPerms = mpOpts: if mpOpts.world-readable then "755" else "750";
|
||||
mountpointToPath = mp: mpOpts:
|
||||
"d '${mp}' ${mpPerms mpOpts} root ${optionalOrDefault mpOpts.group "-"} - -";
|
||||
filesystemsToMountpointLists = mapAttrsToList
|
||||
(fs: fsOpts: fsOpts.mountpoints);
|
||||
mountpointListsToPaths = concatMap
|
||||
(mps: mapAttrsToList mountpointToPath mps);
|
||||
"d '${mp}' ${mpPerms mpOpts} root ${
|
||||
optionalOrDefault mpOpts.group "-"
|
||||
} - -";
|
||||
filesystemsToMountpointLists =
|
||||
mapAttrsToList (fs: fsOpts: fsOpts.mountpoints);
|
||||
mountpointListsToPaths =
|
||||
concatMap (mps: mapAttrsToList mountpointToPath mps);
|
||||
in mountpointListsToPaths (filesystemsToMountpointLists host-filesystems);
|
||||
|
||||
# Actual mounts of decrypted filesystems
|
||||
mounts = let
|
||||
filesystems = mapAttrsToList
|
||||
(fs: opts: { filesystem = fs; opts = opts; })
|
||||
host-filesystems;
|
||||
filesystems = mapAttrsToList (fs: opts: {
|
||||
filesystem = fs;
|
||||
opts = opts;
|
||||
}) host-filesystems;
|
||||
|
||||
mounts = concatMap
|
||||
(fs: mapAttrsToList
|
||||
(mp: mp-opts:
|
||||
{
|
||||
what = "/dev/mapper/${fs.filesystem}";
|
||||
type = fs.opts.filesystem-type;
|
||||
where = mp;
|
||||
options = concatStringsSep "," (fs.opts.options ++ mp-opts.options);
|
||||
description = "${fs.opts.filesystem-type} filesystem on ${fs.filesystem} mounted to ${mp}";
|
||||
requires = [ "${fs.filesystem}-decrypt.service" ];
|
||||
partOf = [ "${fs.filesystem}.target" ];
|
||||
wantedBy = [ "${fs.filesystem}.target" ];
|
||||
})
|
||||
fs.opts.mountpoints)
|
||||
filesystems;
|
||||
mounts = concatMap (fs:
|
||||
mapAttrsToList (mp: mp-opts: {
|
||||
what = "/dev/mapper/${fs.filesystem}";
|
||||
type = fs.opts.filesystem-type;
|
||||
where = mp;
|
||||
options = concatStringsSep "," (fs.opts.options ++ mp-opts.options);
|
||||
description =
|
||||
"${fs.opts.filesystem-type} filesystem on ${fs.filesystem} mounted to ${mp}";
|
||||
requires = [ "${fs.filesystem}-decrypt.service" ];
|
||||
partOf = [ "${fs.filesystem}.target" ];
|
||||
wantedBy = [ "${fs.filesystem}.target" ];
|
||||
}) fs.opts.mountpoints) filesystems;
|
||||
in mounts;
|
||||
|
||||
# Jobs to decrypt the encrypted devices
|
||||
services = mapAttrs' (filesystem-name: opts:
|
||||
nameValuePair "${filesystem-name}-decrypt"
|
||||
{
|
||||
description = "Decrypt the ${filesystem-name} filesystem when the key is available at ${opts.key-path}";
|
||||
path = with pkgs; [ cryptsetup ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = pkgs.writeShellScript "decrypt-${filesystem-name}.sh" ''
|
||||
[ -e /dev/mapper/${filesystem-name} ] || cryptsetup open --type luks --key-file ${opts.key-path} ${opts.encrypted-device} ${filesystem-name}
|
||||
'';
|
||||
ExecStartPost = pkgs.writeShellScript "remove-${filesystem-name}-key.sh" ''
|
||||
nameValuePair "${filesystem-name}-decrypt" {
|
||||
description =
|
||||
"Decrypt the ${filesystem-name} filesystem when the key is available at ${opts.key-path}";
|
||||
path = with pkgs; [ cryptsetup ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = pkgs.writeShellScript "decrypt-${filesystem-name}.sh" ''
|
||||
[ -e /dev/mapper/${filesystem-name} ] || cryptsetup open --type ${opts.type} --key-file ${opts.key-path} ${opts.encrypted-device} ${filesystem-name}
|
||||
'';
|
||||
ExecStartPost = mkIf opts.remove-key
|
||||
(pkgs.writeShellScript "remove-${filesystem-name}-key.sh" ''
|
||||
rm ${opts.key-path}
|
||||
'';
|
||||
ExecStop = pkgs.writeShellScript "close-${filesystem-name}.sh" ''
|
||||
cryptsetup close /dev/mapper/${filesystem-name}
|
||||
'';
|
||||
};
|
||||
restartIfChanged = true;
|
||||
})
|
||||
host-filesystems;
|
||||
'');
|
||||
ExecStop = pkgs.writeShellScript "close-${filesystem-name}.sh" ''
|
||||
cryptsetup close /dev/mapper/${filesystem-name}
|
||||
'';
|
||||
};
|
||||
restartIfChanged = true;
|
||||
}) host-filesystems;
|
||||
|
||||
# Watch the path of the key, trigger decrypt when it's available
|
||||
paths = let
|
||||
decryption-jobs = mapAttrs' (filesystem-name: opts:
|
||||
nameValuePair "${filesystem-name}-decrypt"
|
||||
{
|
||||
wantedBy = [ "default.target" ];
|
||||
description = "Watch for decryption key, then decrypt the target filesystem.";
|
||||
pathConfig = {
|
||||
PathExists = opts.key-path;
|
||||
Unit = "${filesystem-name}-decrypt.service";
|
||||
};
|
||||
}) host-filesystems;
|
||||
nameValuePair "${filesystem-name}-decrypt" {
|
||||
wantedBy = [ "default.target" ];
|
||||
description =
|
||||
"Watch for decryption key, then decrypt the target filesystem.";
|
||||
pathConfig = {
|
||||
PathExists = opts.key-path;
|
||||
Unit = "${filesystem-name}-decrypt.service";
|
||||
};
|
||||
}) host-filesystems;
|
||||
|
||||
post-decryption-jobs = mapAttrs' (filesystem-name: opts:
|
||||
nameValuePair "${filesystem-name}-mount"
|
||||
{
|
||||
wantedBy = [ "default.target" ];
|
||||
description = "Mount ${filesystem-name} filesystems once the decrypted device is available.";
|
||||
pathConfig = {
|
||||
PathExists = "/dev/mapper/${filesystem-name}";
|
||||
Unit = "${filesystem-name}.target";
|
||||
};
|
||||
}) host-filesystems;
|
||||
nameValuePair "${filesystem-name}-mount" {
|
||||
wantedBy = [ "default.target" ];
|
||||
description =
|
||||
"Mount ${filesystem-name} filesystems once the decrypted device is available.";
|
||||
pathConfig = {
|
||||
PathExists = "/dev/mapper/${filesystem-name}";
|
||||
Unit = "${filesystem-name}.target";
|
||||
};
|
||||
}) host-filesystems;
|
||||
in decryption-jobs // post-decryption-jobs;
|
||||
|
||||
targets = mapAttrs (filesystem-name: opts:
|
||||
{
|
||||
description = "${filesystem-name} enabled and available.";
|
||||
}) host-filesystems;
|
||||
targets = mapAttrs (filesystem-name: opts: {
|
||||
description = "${filesystem-name} enabled and available.";
|
||||
}) host-filesystems;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,13 +43,13 @@ in {
|
|||
firewall = mkIf ((length host-cfg.external-interfaces) > 0) {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 22 2112 ]; # Make sure _at least_ SSH is allowed
|
||||
trustedInterfaces = let
|
||||
all-interfaces = attrNames config.networking.interfaces;
|
||||
in subtractLists host-cfg.external-interfaces all-interfaces;
|
||||
trustedInterfaces =
|
||||
let all-interfaces = attrNames config.networking.interfaces;
|
||||
in subtractLists host-cfg.external-interfaces all-interfaces;
|
||||
};
|
||||
|
||||
hostId = mkIf (host-cfg.machine-id != null)
|
||||
(substring 0 8 host-cfg.machine-id);
|
||||
hostId =
|
||||
mkIf (host-cfg.machine-id != null) (substring 0 8 host-cfg.machine-id);
|
||||
};
|
||||
|
||||
environment = {
|
||||
|
@ -78,13 +78,15 @@ in {
|
|||
mode = "0444";
|
||||
};
|
||||
|
||||
current-system-packages.text = with builtins; let
|
||||
packages = map (p: "${p.name}")
|
||||
config.environment.systemPackages;
|
||||
sorted-unique = sort lessThan (unique packages);
|
||||
in "${concatStringsSep "\n" sorted-unique}\n";
|
||||
current-system-packages.text = with builtins;
|
||||
let
|
||||
packages = map (p: "${p.name}") config.environment.systemPackages;
|
||||
sorted-unique = sort lessThan (unique packages);
|
||||
in ''
|
||||
${concatStringsSep "\n" sorted-unique}
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
systemPackages = with pkgs;
|
||||
mkIf (host-cfg.docker-server) [ docker nix-prefetch-docker ];
|
||||
};
|
||||
|
@ -105,10 +107,9 @@ in {
|
|||
};
|
||||
|
||||
programs.adb.enable = host-cfg.android-dev;
|
||||
users.groups.adbusers = mkIf host-cfg.android-dev {
|
||||
members = config.instance.local-admins;
|
||||
};
|
||||
users.groups.adbusers =
|
||||
mkIf host-cfg.android-dev { members = config.instance.local-admins; };
|
||||
|
||||
boot.tmpOnTmpfs = host-cfg.tmp-on-tmpfs;
|
||||
boot.tmp.useTmpfs = host-cfg.tmp-on-tmpfs;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,47 +5,46 @@ let
|
|||
hostname = config.instance.hostname;
|
||||
|
||||
host-secrets = config.fudo.secrets.host-secrets.${hostname};
|
||||
|
||||
siteOpts = { ... }: with types; {
|
||||
options = {
|
||||
enableACME = mkOption {
|
||||
type = bool;
|
||||
description = "Use ACME to get SSL certificates for this site.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Hostname of this server.";
|
||||
};
|
||||
|
||||
site-config = mkOption {
|
||||
type = attrs;
|
||||
description = "Site-specific configuration.";
|
||||
siteOpts = { ... }:
|
||||
with types; {
|
||||
options = {
|
||||
enableACME = mkOption {
|
||||
type = bool;
|
||||
description = "Use ACME to get SSL certificates for this site.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Hostname of this server.";
|
||||
};
|
||||
|
||||
site-config = mkOption {
|
||||
type = attrs;
|
||||
description = "Site-specific configuration.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config-dir = dirOf cfg.config-file;
|
||||
|
||||
concatMapAttrs = f: attrs:
|
||||
foldr (a: b: a // b) {} (mapAttrs f attrs);
|
||||
concatMapAttrs = f: attrs: foldr (a: b: a // b) { } (mapAttrs f attrs);
|
||||
|
||||
concatMapAttrsToList = f: attr:
|
||||
concatMap (i: i) (attrValues (mapAttrs f attr));
|
||||
|
||||
host-domains = config.fudo.acme.host-domains.${hostname};
|
||||
|
||||
hostCerts = host: let
|
||||
cert-copy = host-domains.${host}.local-copies.ejabberd;
|
||||
in [
|
||||
cert-copy.certificate
|
||||
cert-copy.private-key
|
||||
# cert-copy.full-certificate
|
||||
];
|
||||
hostCerts = host:
|
||||
let cert-copy = host-domains.${host}.local-copies.ejabberd;
|
||||
in [
|
||||
cert-copy.certificate
|
||||
cert-copy.private-key
|
||||
# cert-copy.full-certificate
|
||||
];
|
||||
|
||||
hostCertService = host:
|
||||
host-domains.${host}.local-copies.ejabberd.service;
|
||||
hostCertService = host: host-domains.${host}.local-copies.ejabberd.service;
|
||||
|
||||
config-file-template = let
|
||||
jabber-config = {
|
||||
|
@ -60,8 +59,7 @@ let
|
|||
|
||||
acl.admin = {
|
||||
user = concatMap
|
||||
(admin: map (site: "${admin}@${site}")
|
||||
(attrNames cfg.sites))
|
||||
(admin: map (site: "${admin}@${site}") (attrNames cfg.sites))
|
||||
cfg.admins;
|
||||
};
|
||||
|
||||
|
@ -77,48 +75,41 @@ let
|
|||
starttls = true;
|
||||
starttls_required = true;
|
||||
};
|
||||
in
|
||||
if (cfg.listen-ips != null) then
|
||||
map (ip: { ip = ip; } // common)
|
||||
cfg.listen-ips
|
||||
else [ common ];
|
||||
in if (cfg.listen-ips != null) then
|
||||
map (ip: { ip = ip; } // common) cfg.listen-ips
|
||||
else
|
||||
[ common ];
|
||||
|
||||
certfiles = concatMapAttrsToList
|
||||
(site: siteOpts:
|
||||
if (siteOpts.enableACME) then
|
||||
(hostCerts siteOpts.hostname)
|
||||
else [])
|
||||
certfiles = concatMapAttrsToList (site: siteOpts:
|
||||
if (siteOpts.enableACME) then (hostCerts siteOpts.hostname) else [ ])
|
||||
cfg.sites;
|
||||
|
||||
host_config =
|
||||
mapAttrs (site: siteOpts: siteOpts.site-config)
|
||||
cfg.sites;
|
||||
host_config = mapAttrs (site: siteOpts: siteOpts.site-config) cfg.sites;
|
||||
};
|
||||
|
||||
|
||||
config-file = builtins.toJSON jabber-config;
|
||||
in pkgs.lib.text.format-json-file
|
||||
(pkgs.writeText "ejabberd.config.yml.template" config-file);
|
||||
(pkgs.writeText "ejabberd.config.yml.template" config-file);
|
||||
|
||||
enter-secrets = template: secrets: target: let
|
||||
secret-swappers = map
|
||||
(secret: "sed s/${secret}/\$${secret}/g")
|
||||
secrets;
|
||||
swapper = concatStringsSep " | " secret-swappers;
|
||||
in pkgs.writeShellScript "ejabberd-generate-config.sh" ''
|
||||
[ -f \$${target} ] && rm -f ${target}
|
||||
echo "Copying from ${template} to ${target}"
|
||||
touch ${target}
|
||||
chmod go-rwx ${target}
|
||||
chmod u+rw ${target}
|
||||
cat ${template} | ${swapper} > ${target}
|
||||
echo "Copying from ${template} to ${target} completed"
|
||||
'';
|
||||
enter-secrets = template: secrets: target:
|
||||
let
|
||||
secret-swappers = map (secret: "sed s/${secret}/\$${secret}/g") secrets;
|
||||
swapper = concatStringsSep " | " secret-swappers;
|
||||
in pkgs.writeShellScript "ejabberd-generate-config.sh" ''
|
||||
[ -f \$''${target} ] && rm -f ${target}
|
||||
echo "Copying from ${template} to ${target}"
|
||||
touch ${target}
|
||||
chmod go-rwx ${target}
|
||||
chmod u+rw ${target}
|
||||
cat ${template} | ${swapper} > ${target}
|
||||
echo "Copying from ${template} to ${target} completed"
|
||||
'';
|
||||
|
||||
cfg = config.fudo.jabber;
|
||||
|
||||
log-dir = "${cfg.state-directory}/logs";
|
||||
spool-dir = "${cfg.state-directory}/spool";
|
||||
|
||||
|
||||
in {
|
||||
options.fudo.jabber = with types; {
|
||||
enable = mkEnableOption "Enable ejabberd server.";
|
||||
|
@ -128,7 +119,7 @@ in {
|
|||
description = "IPs on which to listen for Jabber connections.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to listen for Jabber connections.";
|
||||
|
@ -150,7 +141,7 @@ in {
|
|||
admins = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of admin users for the server.";
|
||||
default = [];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
sites = mkOption {
|
||||
|
@ -160,8 +151,9 @@ in {
|
|||
|
||||
secret-files = mkOption {
|
||||
type = attrsOf str;
|
||||
description = "Map of secret-name to file. File contents will be subbed for the name in the config.";
|
||||
default = {};
|
||||
description =
|
||||
"Map of secret-name to file. File contents will be subbed for the name in the config.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
config-file = mkOption {
|
||||
|
@ -189,27 +181,20 @@ in {
|
|||
environment = mkOption {
|
||||
type = attrsOf str;
|
||||
description = "Environment variables to set for the ejabberd daemon.";
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users = {
|
||||
users.${cfg.user} = {
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.${cfg.user} = { isSystemUser = true; };
|
||||
|
||||
groups.${cfg.group} = {
|
||||
members = [ cfg.user ];
|
||||
};
|
||||
groups.${cfg.group} = { members = [ cfg.user ]; };
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 5222 5223 5269 8010 ];
|
||||
};
|
||||
networking.firewall = { allowedTCPPorts = [ 5222 5223 5269 8010 ]; };
|
||||
|
||||
fudo = let
|
||||
host-fqdn = config.instance.host-fqdn;
|
||||
fudo = let host-fqdn = config.instance.host-fqdn;
|
||||
in {
|
||||
acme.host-domains.${hostname} = mapAttrs' (site: siteOpts:
|
||||
nameValuePair siteOpts.hostname {
|
||||
|
@ -235,35 +220,39 @@ in {
|
|||
"d ${config-dir} 0700 ${cfg.user} ${cfg.group} - -"
|
||||
"d ${cfg.state-directory} 0750 ${cfg.user} ${cfg.group} - -"
|
||||
];
|
||||
|
||||
|
||||
services = {
|
||||
ejabberd = {
|
||||
wants =
|
||||
map hostCertService
|
||||
(mapAttrsToList (_: siteOpts: siteOpts.hostname) cfg.sites);
|
||||
wants = map hostCertService
|
||||
(mapAttrsToList (_: siteOpts: siteOpts.hostname) cfg.sites);
|
||||
requires = [ "ejabberd-config-generator.service" ];
|
||||
environment = cfg.environment;
|
||||
};
|
||||
|
||||
ejabberd-config-generator = let
|
||||
config-generator =
|
||||
enter-secrets config-file-template (attrNames cfg.secret-files) cfg.config-file;
|
||||
enter-secrets config-file-template (attrNames cfg.secret-files)
|
||||
cfg.config-file;
|
||||
in {
|
||||
description = "Generate ejabberd config file containing passwords.";
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
ExecStart = "${config-generator}";
|
||||
ExecStartPost = pkgs.writeShellScript "protect-ejabberd-config.sh" ''
|
||||
chown ${cfg.user}:${cfg.group} ${cfg.config-file}
|
||||
chmod 0400 ${cfg.config-file}
|
||||
'';
|
||||
ExecStartPost =
|
||||
pkgs.writeShellScript "protect-ejabberd-config.sh" ''
|
||||
chown ${cfg.user}:${cfg.group} ${cfg.config-file}
|
||||
chmod 0400 ${cfg.config-file}
|
||||
'';
|
||||
EnvironmentFile = host-secrets.ejabberd-password-env.target-file;
|
||||
};
|
||||
unitConfig.ConditionPathExists =
|
||||
[ host-secrets.ejabberd-password-env.target-file ];
|
||||
requires = [ host-secrets.ejabberd-password-env.service ];
|
||||
after = [ host-secrets.ejabberd-password-env.service ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
services.ejabberd = {
|
||||
enable = true;
|
||||
|
||||
|
|
|
@ -31,22 +31,27 @@ let
|
|||
else
|
||||
"/home/${user-opts.primary-group}/${username}";
|
||||
|
||||
userLdif = base: name: group-map: opts: ''
|
||||
dn: uid=${name},ou=members,${base}
|
||||
uid: ${name}
|
||||
objectClass: account
|
||||
objectClass: shadowAccount
|
||||
objectClass: posixAccount
|
||||
cn: ${opts.common-name}
|
||||
uidNumber: ${toString (opts.uid)}
|
||||
gidNumber: ${toString (getUserGidNumber opts group-map)}
|
||||
homeDirectory: ${mkHomeDir name opts}
|
||||
description: ${opts.description}
|
||||
shadowLastChange: 12230
|
||||
shadowMax: 99999
|
||||
shadowWarning: 7
|
||||
userPassword: ${opts.ldap-hashed-passwd}
|
||||
'';
|
||||
userLdif = base: name: group-map: opts:
|
||||
''
|
||||
dn: uid=${name},ou=members,${base}
|
||||
uid: ${name}
|
||||
objectClass: shadowAccount
|
||||
objectClass: posixAccount
|
||||
objectClass: inetOrgPerson
|
||||
cn: ${opts.common-name}
|
||||
uidNumber: ${toString (opts.uid)}
|
||||
gidNumber: ${toString (getUserGidNumber opts group-map)}
|
||||
homeDirectory: ${mkHomeDir name opts}
|
||||
description: ${opts.description}
|
||||
shadowLastChange: 12230
|
||||
shadowMax: 99999
|
||||
shadowWarning: 7
|
||||
userPassword: ${opts.ldap-hashed-passwd}
|
||||
mail: ${if (opts.email != null) then opts.email else ""}
|
||||
sn: ${if (opts.surname != null) then opts.surname else name}
|
||||
'' + (optionalString (opts.given-name != null) ''
|
||||
givenName: ${opts.given-name}
|
||||
'');
|
||||
|
||||
systemUserLdif = base: name: opts: ''
|
||||
dn: cn=${name},${base}
|
||||
|
@ -243,10 +248,10 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 389 636 ];
|
||||
allowedUDPPorts = [ 389 ];
|
||||
};
|
||||
# networking.firewall = {
|
||||
# allowedTCPPorts = [ 389 636 ];
|
||||
# allowedUDPPorts = [ 389 ];
|
||||
# };
|
||||
|
||||
systemd = let
|
||||
user = config.services.openldap.user;
|
||||
|
@ -408,7 +413,11 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
declarativeContents = {
|
||||
declarativeContents = let
|
||||
usersWithPasswords =
|
||||
filterAttrs (_: userOpts: userOpts.ldap-hashed-passwd != null)
|
||||
cfg.users;
|
||||
in {
|
||||
"${cfg.base}" = ''
|
||||
dn: ${cfg.base}
|
||||
objectClass: top
|
||||
|
@ -431,7 +440,7 @@ in {
|
|||
|
||||
${systemUsersLdif cfg.base cfg.system-users}
|
||||
${groupsLdif cfg.base cfg.groups}
|
||||
${usersLdif cfg.base cfg.groups cfg.users}
|
||||
${usersLdif cfg.base cfg.groups usersWithPasswords}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,7 +6,8 @@ let
|
|||
|
||||
join-lines = concatStringsSep "\n";
|
||||
|
||||
traceout = out: builtins.trace out out;
|
||||
inherit (pkgs.lib.ip)
|
||||
getNetworkBase maskFromV32Network networkMinIp networkMaxIp;
|
||||
|
||||
in {
|
||||
|
||||
|
@ -14,6 +15,11 @@ in {
|
|||
|
||||
enable = mkEnableOption "Enable local network configuration (DHCP & DNS).";
|
||||
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "Path at which to store server state.";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = "The domain to use for the local network.";
|
||||
|
@ -92,13 +98,14 @@ in {
|
|||
default = [ ];
|
||||
};
|
||||
|
||||
zone-definition = let
|
||||
zoneOpts = import ../types/zone-definition.nix { inherit lib; };
|
||||
in mkOption {
|
||||
type = submodule zoneOpts;
|
||||
description = "Definition of network zone to be served by local server.";
|
||||
default = { };
|
||||
};
|
||||
zone-definition =
|
||||
let zoneOpts = import ../types/zone-definition.nix { inherit lib; };
|
||||
in mkOption {
|
||||
type = submodule zoneOpts;
|
||||
description =
|
||||
"Definition of network zone to be served by local server.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
extra-records = mkOption {
|
||||
type = listOf str;
|
||||
|
@ -109,47 +116,70 @@ in {
|
|||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
fudo.system.hostfile-entries = let
|
||||
other-hosts = filterAttrs
|
||||
(hostname: hostOpts: hostname != config.instance.hostname)
|
||||
fudo.system.hostfile-entries = let
|
||||
other-hosts =
|
||||
filterAttrs (hostname: hostOpts: hostname != config.instance.hostname)
|
||||
cfg.zone-definition.hosts;
|
||||
in mapAttrs' (hostname: hostOpts:
|
||||
nameValuePair hostOpts.ipv4-address ["${hostname}.${cfg.domain}" hostname])
|
||||
other-hosts;
|
||||
|
||||
services.dhcpd4 = let
|
||||
zone = cfg.zone-definition;
|
||||
in {
|
||||
nameValuePair hostOpts.ipv4-address [
|
||||
"${hostname}.${cfg.domain}"
|
||||
hostname
|
||||
]) other-hosts;
|
||||
|
||||
services.kea.dhcp4 = {
|
||||
enable = true;
|
||||
|
||||
machines = mapAttrsToList (hostname: hostOpts: {
|
||||
ethernetAddress = hostOpts.mac-address;
|
||||
hostName = hostname;
|
||||
ipAddress = hostOpts.ipv4-address;
|
||||
}) (filterAttrs (host: hostOpts:
|
||||
hostOpts.mac-address != null && hostOpts.ipv4-address != null)
|
||||
zone.hosts);
|
||||
|
||||
interfaces = cfg.dhcp-interfaces;
|
||||
|
||||
extraConfig = ''
|
||||
subnet ${pkgs.lib.ip.getNetworkBase cfg.network} netmask ${
|
||||
pkgs.lib.ip.maskFromV32Network cfg.network
|
||||
} {
|
||||
authoritative;
|
||||
option subnet-mask ${pkgs.lib.ip.maskFromV32Network cfg.network};
|
||||
option broadcast-address ${pkgs.lib.ip.networkMaxIp cfg.network};
|
||||
option routers ${cfg.gateway};
|
||||
option domain-name-servers ${concatStringsSep " " cfg.dns-servers};
|
||||
option domain-name "${cfg.domain}";
|
||||
option domain-search "${
|
||||
concatStringsSep " " ([ cfg.domain ] ++ cfg.search-domains)
|
||||
}";
|
||||
range ${pkgs.lib.ip.networkMinIp cfg.dhcp-dynamic-network} ${
|
||||
pkgs.lib.ip.networkMaxButOneIp cfg.dhcp-dynamic-network
|
||||
};
|
||||
}
|
||||
'';
|
||||
settings = {
|
||||
interfaces-config.interfaces = cfg.dhcp-interfaces;
|
||||
valid-lifetime = 4000;
|
||||
rebind-timer = 2000;
|
||||
renew-timer = 1000;
|
||||
option-data = let joinList = concatStringsSep ", ";
|
||||
in [
|
||||
{
|
||||
name = "domain-name-servers";
|
||||
data = joinList cfg.dns-servers;
|
||||
}
|
||||
{
|
||||
name = "subnet-mask";
|
||||
data = maskFromV32Network cfg.network;
|
||||
}
|
||||
{
|
||||
name = "broadcast-address";
|
||||
data = networkMaxIp cfg.network;
|
||||
}
|
||||
{
|
||||
name = "routers";
|
||||
data = cfg.gateway;
|
||||
}
|
||||
{
|
||||
name = "domain-name";
|
||||
data = cfg.domain;
|
||||
}
|
||||
{
|
||||
name = "domain-search";
|
||||
data = joinList ([ cfg.domain ] ++ cfg.search-domains);
|
||||
}
|
||||
];
|
||||
subnet4 = [{
|
||||
pools = [{
|
||||
pool = let
|
||||
minIp = networkMinIp cfg.dhcp-dynamic-network;
|
||||
maxIp = networkMaxIp cfg.dhcp-dynamic-network;
|
||||
in "${minIp} - ${maxIp}";
|
||||
}];
|
||||
subnet = cfg.network;
|
||||
reservations = let
|
||||
hostsWithMac = filterAttrs (_: hostOpts:
|
||||
!isNull hostOpts.mac-address && !isNull hostOpts.ipv4-address)
|
||||
cfg.zone-definition.hosts;
|
||||
in mapAttrsToList (hostname:
|
||||
{ mac-address, ipv4-address, ... }: {
|
||||
hw-address = mac-address;
|
||||
# hostName = hostname;
|
||||
ip-address = ipv4-address;
|
||||
}) hostsWithMac;
|
||||
}];
|
||||
};
|
||||
};
|
||||
|
||||
services.bind = let
|
||||
|
@ -179,19 +209,22 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
filterRedundantIps = official-hosts: hosts: let
|
||||
host-by-ip = groupBy (hostOpts: hostOpts.ipv4-address) hosts;
|
||||
in filter (hostOpts:
|
||||
if (length (getAttr hostOpts.ipv4-address host-by-ip) == 1) then
|
||||
true
|
||||
else elem hostOpts.hostname official-hosts) hosts;
|
||||
filterRedundantIps = official-hosts: hosts:
|
||||
let host-by-ip = groupBy (hostOpts: hostOpts.ipv4-address) hosts;
|
||||
in filter (hostOpts:
|
||||
if (length (getAttr hostOpts.ipv4-address host-by-ip) == 1) then
|
||||
true
|
||||
else
|
||||
elem hostOpts.hostname official-hosts) hosts;
|
||||
ipTo24Block = ip:
|
||||
concatStringsSep "." (reverseList (take 3 (splitString "." ip)));
|
||||
hostsByBlock = official-hosts:
|
||||
groupBy (host-data: ipTo24Block host-data.ipv4-address)
|
||||
(filterRedundantIps official-hosts (attrValues zone.hosts));
|
||||
(filterRedundantIps official-hosts (attrValues zone.hosts));
|
||||
hostPtrRecord = host-data:
|
||||
"${last (splitString "." host-data.ipv4-address)} IN PTR ${host-data.hostname}.${cfg.domain}.";
|
||||
"${
|
||||
last (splitString "." host-data.ipv4-address)
|
||||
} IN PTR ${host-data.hostname}.${cfg.domain}.";
|
||||
|
||||
blockZones = official-hosts:
|
||||
mapAttrsToList blockHostsToZone (hostsByBlock official-hosts);
|
||||
|
@ -213,16 +246,18 @@ in {
|
|||
|
||||
domain-name = config.instance.local-domain;
|
||||
|
||||
domain-hosts =
|
||||
attrNames
|
||||
(filterAttrs (_: hostOpts:
|
||||
hostOpts.domain == domain-name)
|
||||
config.fudo.hosts);
|
||||
domain-hosts = attrNames
|
||||
(filterAttrs (_: hostOpts: hostOpts.domain == domain-name)
|
||||
config.fudo.hosts);
|
||||
|
||||
in {
|
||||
enable = true;
|
||||
cacheNetworks = [ cfg.network "localhost" "localnets" ];
|
||||
forwarders = [ "${cfg.recursive-resolver.host} port ${toString cfg.recursive-resolver.port}" ];
|
||||
forwarders = [
|
||||
"${cfg.recursive-resolver.host} port ${
|
||||
toString cfg.recursive-resolver.port
|
||||
}"
|
||||
];
|
||||
listenOn = cfg.dns-listen-ips;
|
||||
listenOnIpv6 = cfg.dns-listen-ipv6s;
|
||||
extraOptions = concatStringsSep "\n" [
|
||||
|
@ -235,43 +270,11 @@ in {
|
|||
master = true;
|
||||
name = cfg.domain;
|
||||
file = let
|
||||
zone-data = pkgs.lib.dns.zoneToZonefile
|
||||
config.instance.build-timestamp
|
||||
cfg.domain
|
||||
zone;
|
||||
zone-data =
|
||||
pkgs.lib.dns.zoneToZonefile config.instance.build-timestamp
|
||||
cfg.domain zone;
|
||||
in pkgs.writeText "zone-${cfg.domain}" zone-data;
|
||||
# file = pkgs.writeText "${cfg.domain}-zone" ''
|
||||
# @ IN SOA ns1.${cfg.domain}. hostmaster.${cfg.domain}. (
|
||||
# ${toString config.instance.build-timestamp}
|
||||
# 5m
|
||||
# 2m
|
||||
# 6w
|
||||
# 5m)
|
||||
|
||||
# $TTL 1h
|
||||
|
||||
# @ IN NS ns1.${cfg.domain}.
|
||||
|
||||
# $ORIGIN ${cfg.domain}.
|
||||
|
||||
# $TTL 30m
|
||||
|
||||
# ${optionalString (zone.gssapi-realm != null)
|
||||
# ''_kerberos IN TXT "${zone.gssapi-realm}"''}
|
||||
|
||||
# ${join-lines
|
||||
# (imap1 (i: server-ip: "ns${toString i} IN A ${server-ip}")
|
||||
# cfg.dns-servers)}
|
||||
# ${join-lines (mapAttrsToList hostARecord zone.hosts)}
|
||||
# ${join-lines (mapAttrsToList hostSshFpRecords zone.hosts)}
|
||||
# ${join-lines (mapAttrsToList cnameRecord zone.aliases)}
|
||||
# ${join-lines zone.verbatim-dns-records}
|
||||
# ${pkgs.lib.dns.srvRecordsToBindZone zone.srv-records}
|
||||
# ${join-lines cfg.extra-records}
|
||||
# '';
|
||||
}] ++ (optionals
|
||||
cfg.enable-reverse-mappings
|
||||
(blockZones domain-hosts));
|
||||
}] ++ (optionals cfg.enable-reverse-mappings (blockZones domain-hosts));
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,22 +4,52 @@ with lib;
|
|||
let cfg = config.fudo.mail-server;
|
||||
|
||||
in {
|
||||
options.fudo.mail-server.clamav = {
|
||||
options.fudo.mail-server.clamav = with types; {
|
||||
enable = mkOption {
|
||||
description = "Enable virus scanning with ClamAV.";
|
||||
type = types.bool;
|
||||
type = bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "Path at which to store the ClamAV database.";
|
||||
default = "/var/lib/clamav";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.enable && cfg.clamav.enable) {
|
||||
|
||||
users = {
|
||||
users.clamav = {
|
||||
isSystemUser = true;
|
||||
uid = config.ids.uids.clamav;
|
||||
home = mkForce cfg.clamav.state-directory;
|
||||
description = "ClamAV daemon user";
|
||||
group = "clamav";
|
||||
};
|
||||
groups.clamav = {
|
||||
members = [ "clamav" ];
|
||||
gid = config.ids.gids.clamav;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules =
|
||||
[ "d ${cfg.clamav.state-directory} 0750 clamav clamav - -" ];
|
||||
|
||||
services.clamav = {
|
||||
daemon = {
|
||||
enable = true;
|
||||
settings = { PhishingScanURLs = "no"; };
|
||||
settings = {
|
||||
PhishingScanURLs = "no";
|
||||
DatabaseDirectory = mkForce cfg.clamav.state-directory;
|
||||
User = "clamav";
|
||||
};
|
||||
};
|
||||
updater = {
|
||||
enable = true;
|
||||
settings = { DatabaseDirectory = mkForce cfg.clamav.state-directory; };
|
||||
};
|
||||
updater.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,32 +9,32 @@ let
|
|||
let
|
||||
dkim_key = "${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.key";
|
||||
dkim_txt = "${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.txt";
|
||||
in
|
||||
''
|
||||
if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ]
|
||||
then
|
||||
${cfg.dkim.package}/bin/opendkim-genkey -s "${cfg.dkim.selector}" \
|
||||
-d "${dom}" \
|
||||
--bits="${toString cfg.dkim.key-bits}" \
|
||||
--directory="${cfg.dkim.key-directory}"
|
||||
mv "${cfg.dkim.key-directory}/${cfg.dkim.selector}.private" "${dkim_key}"
|
||||
mv "${cfg.dkim.key-directory}/${cfg.dkim.selector}.txt" "${dkim_txt}"
|
||||
echo "Generated key for domain ${dom} selector ${cfg.dkim.selector}"
|
||||
fi
|
||||
'';
|
||||
in ''
|
||||
if [ ! -f "${dkim_key}" ] || [ ! -f "${dkim_txt}" ]; then
|
||||
${cfg.dkim.package}/bin/opendkim-genkey -s "${cfg.dkim.selector}" \
|
||||
-d "${dom}" \
|
||||
--bits="${toString cfg.dkim.key-bits}" \
|
||||
--directory="${cfg.dkim.key-directory}"
|
||||
mv "${cfg.dkim.key-directory}/${cfg.dkim.selector}.private" "${dkim_key}"
|
||||
mv "${cfg.dkim.key-directory}/${cfg.dkim.selector}.txt" "${dkim_txt}"
|
||||
echo "Generated key for domain ${dom} selector ${cfg.dkim.selector}"
|
||||
fi
|
||||
'';
|
||||
|
||||
createAllCerts = lib.concatStringsSep "\n" (map createDomainDkimCert cfg.local-domains);
|
||||
createAllCerts =
|
||||
lib.concatStringsSep "\n" (map createDomainDkimCert cfg.local-domains);
|
||||
|
||||
keyTable = pkgs.writeText "opendkim-KeyTable"
|
||||
(lib.concatStringsSep "\n" (lib.flip map cfg.local-domains
|
||||
(dom: "${dom} ${dom}:${cfg.dkim.selector}:${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.key")));
|
||||
keyTable = pkgs.writeText "opendkim-KeyTable" (lib.concatStringsSep "\n"
|
||||
(lib.flip map cfg.local-domains (dom:
|
||||
"${dom} ${dom}:${cfg.dkim.selector}:${cfg.dkim.key-directory}/${dom}.${cfg.dkim.selector}.key")));
|
||||
signingTable = pkgs.writeText "opendkim-SigningTable"
|
||||
(lib.concatStringsSep "\n" (lib.flip map cfg.local-domains (dom: "${dom} ${dom}")));
|
||||
(lib.concatStringsSep "\n"
|
||||
(lib.flip map cfg.local-domains (dom: "${dom} ${dom}")));
|
||||
|
||||
dkim = config.services.opendkim;
|
||||
args = [ "-f" "-l" ] ++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ];
|
||||
in
|
||||
{
|
||||
args = [ "-f" "-l" ]
|
||||
++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ];
|
||||
in {
|
||||
|
||||
options.fudo.mail-server.dkim = {
|
||||
signing = mkOption {
|
||||
|
@ -59,12 +59,12 @@ in
|
|||
type = types.int;
|
||||
default = 2048;
|
||||
description = ''
|
||||
How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
|
||||
How many bits in generated DKIM keys. RFC6376 advises minimum 1024-bit keys.
|
||||
|
||||
If you have already deployed a key with a different number of bits than specified
|
||||
here, then you should use a different selector (dkimSelector). In order to get
|
||||
this package to generate a key with the new number of bits, you will either have to
|
||||
change the selector or delete the old key file.
|
||||
If you have already deployed a key with a different number of bits than specified
|
||||
here, then you should use a different selector (dkimSelector). In order to get
|
||||
this package to generate a key with the new number of bits, you will either have to
|
||||
change the selector or delete the old key file.
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -81,16 +81,16 @@ in
|
|||
selector = cfg.dkim.selector;
|
||||
domains = "csl:${builtins.concatStringsSep "," cfg.local-domains}";
|
||||
configFile = pkgs.writeText "opendkim.conf" (''
|
||||
Canonicalization relaxed/simple
|
||||
UMask 0002
|
||||
Socket ${dkim.socket}
|
||||
KeyTable file:${keyTable}
|
||||
SigningTable file:${signingTable}
|
||||
'' + (lib.optionalString cfg.debug ''
|
||||
Syslog yes
|
||||
SyslogSuccess yes
|
||||
LogWhy yes
|
||||
''));
|
||||
Canonicalization relaxed/simple
|
||||
UMask 0002
|
||||
Socket ${dkim.socket}
|
||||
KeyTable file:${keyTable}
|
||||
SigningTable file:${signingTable}
|
||||
'' + (lib.optionalString cfg.debug ''
|
||||
Syslog yes
|
||||
SyslogSuccess yes
|
||||
LogWhy yes
|
||||
''));
|
||||
};
|
||||
|
||||
users.users = {
|
||||
|
@ -99,16 +99,19 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
systemd.services.opendkim = {
|
||||
preStart = lib.mkForce createAllCerts;
|
||||
serviceConfig = {
|
||||
ExecStart = lib.mkForce "${cfg.dkim.package}/bin/opendkim ${escapeShellArgs args}";
|
||||
PermissionsStartOnly = lib.mkForce false;
|
||||
systemd = {
|
||||
tmpfiles.rules = [
|
||||
"d '${cfg.dkim.key-directory}' - ${config.services.opendkim.user} ${config.services.opendkim.group} - -"
|
||||
];
|
||||
services.opendkim = {
|
||||
preStart = lib.mkForce createAllCerts;
|
||||
serviceConfig = {
|
||||
ExecStart = lib.mkForce
|
||||
"${cfg.dkim.package}/bin/opendkim ${escapeShellArgs args}";
|
||||
PermissionsStartOnly = lib.mkForce false;
|
||||
ReadWritePaths = [ cfg.dkim.key-directory ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dkim.key-directory}' - ${config.services.opendkim.user} ${config.services.opendkim.group} - -"
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -33,30 +33,30 @@ let
|
|||
tls = yes
|
||||
tls_require_cert = try
|
||||
'';
|
||||
in
|
||||
pkgs.writeText "dovecot2-ldap-config.conf.template" ''
|
||||
uris = ${concatStringsSep " " ldap-cfg.server-urls}
|
||||
ldap_version = 3
|
||||
dn = ${ldap-cfg.reader-dn}
|
||||
dnpass = __LDAP_READER_PASSWORD__
|
||||
auth_bind = yes
|
||||
auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org
|
||||
base = dc=fudo,dc=org
|
||||
${ssl-config}
|
||||
'';
|
||||
in pkgs.writeText "dovecot2-ldap-config.conf.template" ''
|
||||
uris = ${concatStringsSep " " ldap-cfg.server-urls}
|
||||
ldap_version = 3
|
||||
dn = ${ldap-cfg.reader-dn}
|
||||
dnpass = __LDAP_READER_PASSWORD__
|
||||
auth_bind = yes
|
||||
auth_bind_userdn = uid=%u,ou=members,dc=fudo,dc=org
|
||||
base = dc=fudo,dc=org
|
||||
${ssl-config}
|
||||
'';
|
||||
|
||||
ldap-conf-generator = ldap-cfg: let
|
||||
template = ldap-conf-template ldap-cfg;
|
||||
target-dir = dirOf ldap-cfg.generated-ldap-config;
|
||||
target = ldap-cfg.generated-ldap-config;
|
||||
in pkgs.writeScript "dovecot2-ldap-password-swapper.sh" ''
|
||||
mkdir -p ${target-dir}
|
||||
touch ${target}
|
||||
chmod 600 ${target}
|
||||
chown ${config.services.dovecot2.user} ${target}
|
||||
LDAP_READER_PASSWORD=$( cat "${ldap-cfg.reader-password-file}" )
|
||||
sed 's/__LDAP_READER_PASSWORD__/$LDAP_READER_PASSWORD/' '${template}' > ${target}
|
||||
'';
|
||||
ldap-conf-generator = ldap-cfg:
|
||||
let
|
||||
template = ldap-conf-template ldap-cfg;
|
||||
target-dir = dirOf ldap-cfg.generated-ldap-config;
|
||||
target = ldap-cfg.generated-ldap-config;
|
||||
in pkgs.writeScript "dovecot2-ldap-password-swapper.sh" ''
|
||||
mkdir -p ${target-dir}
|
||||
touch ${target}
|
||||
chmod 600 ${target}
|
||||
chown ${config.services.dovecot2.user} ${target}
|
||||
LDAP_READER_PASSWORD=$( cat "${ldap-cfg.reader-password-file}" )
|
||||
sed 's/__LDAP_READER_PASSWORD__/$LDAP_READER_PASSWORD/' '${template}' > ${target}
|
||||
'';
|
||||
|
||||
ldap-passwd-entry = ldap-config: ''
|
||||
passdb {
|
||||
|
@ -69,7 +69,8 @@ let
|
|||
options = with types; {
|
||||
ca = mkOption {
|
||||
type = nullOr str;
|
||||
description = "The path to the CA cert used to sign the LDAP server certificate.";
|
||||
description =
|
||||
"The path to the CA cert used to sign the LDAP server certificate.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
|
@ -99,7 +100,8 @@ let
|
|||
|
||||
generated-ldap-config = mkOption {
|
||||
type = str;
|
||||
description = "Path at which to store the generated LDAP config file, including password.";
|
||||
description =
|
||||
"Path at which to store the generated LDAP config file, including password.";
|
||||
default = "/run/dovecot2/config/ldap.conf";
|
||||
};
|
||||
};
|
||||
|
@ -132,7 +134,7 @@ in {
|
|||
|
||||
services.prometheus.exporters.dovecot = mkIf cfg.monitoring.enable {
|
||||
enable = true;
|
||||
scopes = ["user" "global"];
|
||||
scopes = [ "user" "global" ];
|
||||
listenAddress = "127.0.0.1";
|
||||
port = cfg.monitoring.dovecot-listen-port;
|
||||
socketPath = "/var/run/dovecot2/old-stats";
|
||||
|
@ -295,9 +297,8 @@ in {
|
|||
};
|
||||
|
||||
systemd = {
|
||||
tmpfiles.rules = [
|
||||
"d ${sieve-path} 750 ${dovecot-user} ${cfg.mail-group} - -"
|
||||
];
|
||||
tmpfiles.rules =
|
||||
[ "d ${sieve-path} 750 ${dovecot-user} ${cfg.mail-group} - -" ];
|
||||
|
||||
services.dovecot2.preStart = ''
|
||||
rm -f ${sieve-path}/*
|
||||
|
@ -307,7 +308,7 @@ in {
|
|||
done
|
||||
|
||||
${optionalString (cfg.dovecot.ldap != null)
|
||||
(ldap-conf-generator cfg.dovecot.ldap)}
|
||||
(ldap-conf-generator cfg.dovecot.ldap)}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/bash
|
||||
set -o errexit
|
||||
exec rspamc -h /run/rspamd/worker-controller.sock learn_ham
|
||||
exec rspamc -h /run/rspamd/worker-controller.sock learn_ham
|
||||
|
|
|
@ -8,48 +8,40 @@ let
|
|||
|
||||
# The final newline is important
|
||||
write-entries = filename: entries:
|
||||
let
|
||||
entries-string = (concatStringsSep "\n" entries);
|
||||
let entries-string = (concatStringsSep "\n" entries);
|
||||
in builtins.toFile filename ''
|
||||
${entries-string}
|
||||
'';
|
||||
|
||||
make-user-aliases = entries:
|
||||
concatStringsSep "\n"
|
||||
(mapAttrsToList (user: aliases:
|
||||
concatStringsSep "\n"
|
||||
(map (alias: "${alias} ${user}") aliases))
|
||||
entries);
|
||||
concatStringsSep "\n" (mapAttrsToList (user: aliases:
|
||||
concatStringsSep "\n" (map (alias: "${alias} ${user}") aliases))
|
||||
entries);
|
||||
|
||||
make-alias-users = domains: entries:
|
||||
concatStringsSep "\n"
|
||||
(flatten
|
||||
(mapAttrsToList (alias: users:
|
||||
(map (domain:
|
||||
"${alias}@${domain} ${concatStringsSep "," users}")
|
||||
domains))
|
||||
entries));
|
||||
concatStringsSep "\n" (flatten (mapAttrsToList (alias: users:
|
||||
(map (domain: "${alias}@${domain} ${concatStringsSep "," users}")
|
||||
domains)) entries));
|
||||
|
||||
policyd-spf = pkgs.writeText "policyd-spf.conf" (
|
||||
cfg.postfix.policy-spf-extra-config
|
||||
+ (lib.optionalString cfg.debug ''
|
||||
debugLevel = 4
|
||||
''));
|
||||
policyd-spf = pkgs.writeText "policyd-spf.conf"
|
||||
(cfg.postfix.policy-spf-extra-config + (lib.optionalString cfg.debug ''
|
||||
debugLevel = 4
|
||||
''));
|
||||
|
||||
submission-header-cleanup-rules = pkgs.writeText "submission_header_cleanup_rules" (''
|
||||
# Removes sensitive headers from mails handed in via the submission port.
|
||||
# See https://thomas-leister.de/mailserver-debian-stretch/
|
||||
# Uses "pcre" style regex.
|
||||
submission-header-cleanup-rules =
|
||||
pkgs.writeText "submission_header_cleanup_rules" (''
|
||||
# Removes sensitive headers from mails handed in via the submission port.
|
||||
# See https://thomas-leister.de/mailserver-debian-stretch/
|
||||
# Uses "pcre" style regex.
|
||||
|
||||
/^Received:/ IGNORE
|
||||
/^X-Originating-IP:/ IGNORE
|
||||
/^X-Mailer:/ IGNORE
|
||||
/^User-Agent:/ IGNORE
|
||||
/^X-Enigmail:/ IGNORE
|
||||
'');
|
||||
/^Received:/ IGNORE
|
||||
/^X-Originating-IP:/ IGNORE
|
||||
/^X-Mailer:/ IGNORE
|
||||
/^User-Agent:/ IGNORE
|
||||
/^X-Enigmail:/ IGNORE
|
||||
'');
|
||||
blacklist-postfix-entry = sender: "${sender} REJECT";
|
||||
blacklist-postfix-file = filename: entries:
|
||||
write-entries filename entries;
|
||||
blacklist-postfix-file = filename: entries: write-entries filename entries;
|
||||
sender-blacklist-file = blacklist-postfix-file "reject_senders"
|
||||
(map blacklist-postfix-entry cfg.sender-blacklist);
|
||||
recipient-blacklist-file = blacklist-postfix-file "reject_recipients"
|
||||
|
@ -57,12 +49,13 @@ let
|
|||
|
||||
# A list of domains for which we accept mail
|
||||
virtual-mailbox-map-file = write-entries "virtual_mailbox_map"
|
||||
(map (domain: "@${domain} OK") (cfg.local-domains ++ [cfg.domain]));
|
||||
(map (domain: "@${domain} OK") (cfg.local-domains ++ [ cfg.domain ]));
|
||||
|
||||
sender-login-map-file = let
|
||||
escapeDot = (str: replaceStrings ["."] ["\\."] str);
|
||||
in write-entries "sender_login_maps"
|
||||
(map (domain: "/^(.*)@${escapeDot domain}$/ \${1}") (cfg.local-domains ++ [cfg.domain]));
|
||||
sender-login-map-file =
|
||||
let escapeDot = (str: replaceStrings [ "." ] [ "\\." ] str);
|
||||
in write-entries "sender_login_maps"
|
||||
(map (domain: "/^(.*)@${escapeDot domain}$/ \${1}")
|
||||
(cfg.local-domains ++ [ cfg.domain ]));
|
||||
|
||||
mapped-file = name: "hash:/var/lib/postfix/conf/${name}";
|
||||
|
||||
|
@ -112,7 +105,7 @@ in {
|
|||
domain = cfg.domain;
|
||||
origin = cfg.domain;
|
||||
hostname = cfg.mail-hostname;
|
||||
destination = ["localhost" "localhost.localdomain"];
|
||||
destination = [ "localhost" "localhost.localdomain" ];
|
||||
# destination = ["localhost" "localhost.localdomain" cfg.hostname] ++
|
||||
# cfg.local-domains;;
|
||||
|
||||
|
@ -132,25 +125,27 @@ in {
|
|||
virtual = ''
|
||||
${make-user-aliases cfg.user-aliases}
|
||||
|
||||
${make-alias-users ([cfg.domain] ++ cfg.local-domains) cfg.alias-users}
|
||||
${make-alias-users ([ cfg.domain ] ++ cfg.local-domains)
|
||||
cfg.alias-users}
|
||||
'';
|
||||
|
||||
sslCert = cfg.postfix.ssl-certificate;
|
||||
sslKey = cfg.postfix.ssl-private-key;
|
||||
|
||||
config = {
|
||||
virtual_mailbox_domains = cfg.local-domains ++ [cfg.domain];
|
||||
virtual_mailbox_domains = cfg.local-domains ++ [ cfg.domain ];
|
||||
# virtual_mailbox_base = "${cfg.mail-directory}/";
|
||||
virtual_mailbox_maps = mapped-file "virtual_mailbox_map";
|
||||
|
||||
virtual_uid_maps = "static:${toString cfg.mail-user-id}";
|
||||
virtual_gid_maps = "static:${toString config.users.groups."${cfg.mail-group}".gid}";
|
||||
virtual_gid_maps =
|
||||
"static:${toString config.users.groups."${cfg.mail-group}".gid}";
|
||||
|
||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
||||
|
||||
# NOTE: it's important that this ends with /, to indicate Maildir format!
|
||||
# mail_spool_directory = "${cfg.mail-directory}/";
|
||||
message_size_limit = toString(cfg.message-size-limit * 1024 * 1024);
|
||||
message_size_limit = toString (cfg.message-size-limit * 1024 * 1024);
|
||||
|
||||
smtpd_banner = "${cfg.mail-hostname} ESMTP NO UCE";
|
||||
|
||||
|
@ -176,7 +171,8 @@ in {
|
|||
recipient_delimiter = "+";
|
||||
|
||||
milter_protocol = "6";
|
||||
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
|
||||
milter_mail_macros =
|
||||
"i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
|
||||
|
||||
smtpd_milters = [
|
||||
"unix:/run/rspamd/rspamd-milter.sock"
|
||||
|
@ -218,11 +214,8 @@ in {
|
|||
"reject_non_fqdn_recipient"
|
||||
];
|
||||
|
||||
smtpd_helo_restrictions = [
|
||||
"permit_mynetworks"
|
||||
"reject_invalid_hostname"
|
||||
"permit"
|
||||
];
|
||||
smtpd_helo_restrictions =
|
||||
[ "permit_mynetworks" "reject_invalid_hostname" "permit" ];
|
||||
|
||||
# Handled by submission
|
||||
smtpd_tls_security_level = "may";
|
||||
|
@ -230,44 +223,27 @@ in {
|
|||
smtpd_tls_eecdh_grade = "ultra";
|
||||
|
||||
# Disable obselete protocols
|
||||
smtpd_tls_protocols = [
|
||||
"TLSv1.2"
|
||||
"TLSv1.1"
|
||||
"!TLSv1"
|
||||
"!SSLv2"
|
||||
"!SSLv3"
|
||||
];
|
||||
smtp_tls_protocols = [
|
||||
"TLSv1.2"
|
||||
"TLSv1.1"
|
||||
"!TLSv1"
|
||||
"!SSLv2"
|
||||
"!SSLv3"
|
||||
];
|
||||
smtpd_tls_mandatory_protocols = [
|
||||
"TLSv1.2"
|
||||
"TLSv1.1"
|
||||
"!TLSv1"
|
||||
"!SSLv2"
|
||||
"!SSLv3"
|
||||
];
|
||||
smtp_tls_mandatory_protocols = [
|
||||
"TLSv1.2"
|
||||
"TLSv1.1"
|
||||
"!TLSv1"
|
||||
"!SSLv2"
|
||||
"!SSLv3"
|
||||
];
|
||||
smtpd_tls_protocols =
|
||||
[ "TLSv1.2" "TLSv1.1" "!TLSv1" "!SSLv2" "!SSLv3" ];
|
||||
smtp_tls_protocols = [ "TLSv1.2" "TLSv1.1" "!TLSv1" "!SSLv2" "!SSLv3" ];
|
||||
smtpd_tls_mandatory_protocols =
|
||||
[ "TLSv1.2" "TLSv1.1" "!TLSv1" "!SSLv2" "!SSLv3" ];
|
||||
smtp_tls_mandatory_protocols =
|
||||
[ "TLSv1.2" "TLSv1.1" "!TLSv1" "!SSLv2" "!SSLv3" ];
|
||||
|
||||
smtp_tls_ciphers = "high";
|
||||
smtpd_tls_ciphers = "high";
|
||||
smtp_tls_mandatory_ciphers = "high";
|
||||
smtpd_tls_mandatory_ciphers = "high";
|
||||
|
||||
smtpd_tls_mandatory_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
|
||||
smtpd_tls_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
|
||||
smtp_tls_mandatory_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
|
||||
smtp_tls_exclude_ciphers = ["MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL"];
|
||||
smtpd_tls_mandatory_exclude_ciphers =
|
||||
[ "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL" ];
|
||||
smtpd_tls_exclude_ciphers =
|
||||
[ "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL" ];
|
||||
smtp_tls_mandatory_exclude_ciphers =
|
||||
[ "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL" ];
|
||||
smtp_tls_exclude_ciphers =
|
||||
[ "MD5" "DES" "ADH" "RC4" "PSD" "SRP" "3DES" "eNULL" "aNULL" ];
|
||||
|
||||
tls_preempt_cipherlist = "yes";
|
||||
|
||||
|
@ -286,8 +262,10 @@ in {
|
|||
smtpd_sasl_security_options = "noanonymous";
|
||||
smtpd_sasl_local_domain = cfg.domain;
|
||||
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
||||
smtpd_sender_restrictions = "reject_sender_login_mismatch,reject_unknown_sender_domain";
|
||||
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||
smtpd_sender_restrictions =
|
||||
"reject_sender_login_mismatch,reject_unknown_sender_domain";
|
||||
smtpd_recipient_restrictions =
|
||||
"reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
||||
cleanup_service_name = "submission-header-cleanup";
|
||||
};
|
||||
|
||||
|
@ -297,7 +275,11 @@ in {
|
|||
privileged = true;
|
||||
chroot = false;
|
||||
command = "spawn";
|
||||
args = [ "user=nobody" "argv=${pkgs.pypolicyd-spf}/bin/policyd-spf" "${policyd-spf}"];
|
||||
args = [
|
||||
"user=nobody"
|
||||
"argv=${pkgs.pypolicyd-spf}/bin/policyd-spf"
|
||||
"${policyd-spf}"
|
||||
];
|
||||
};
|
||||
"submission-header-cleanup" = {
|
||||
type = "unix";
|
||||
|
@ -305,7 +287,8 @@ in {
|
|||
chroot = false;
|
||||
maxproc = 0;
|
||||
command = "cleanup";
|
||||
args = ["-o" "header_checks=pcre:${submission-header-cleanup-rules}"];
|
||||
args =
|
||||
[ "-o" "header_checks=pcre:${submission-header-cleanup-rules}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -313,9 +296,9 @@ in {
|
|||
# Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work
|
||||
systemd.services.postfix = {
|
||||
after = [ "dovecot2.service" ]
|
||||
++ (lib.optional cfg.dkim.signing "opendkim.service");
|
||||
++ (lib.optional cfg.dkim.signing "opendkim.service");
|
||||
requires = [ "dovecot2.service" ]
|
||||
++ (lib.optional cfg.dkim.signing "opendkim.service");
|
||||
++ (lib.optional cfg.dkim.signing "opendkim.service");
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.fudo.mail-server;
|
||||
let cfg = config.fudo.mail-server;
|
||||
|
||||
in {
|
||||
config = mkIf cfg.enable {
|
||||
|
@ -73,7 +72,7 @@ in {
|
|||
mode = "0666";
|
||||
}
|
||||
];
|
||||
includes = [];
|
||||
includes = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -87,6 +86,7 @@ in {
|
|||
requires = [ "rspamd.service" ];
|
||||
};
|
||||
|
||||
users.extraUsers.${config.services.postfix.user}.extraGroups = [ config.services.rspamd.group ];
|
||||
users.extraUsers.${config.services.postfix.user}.extraGroups =
|
||||
[ config.services.rspamd.group ];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -73,6 +73,18 @@ let
|
|||
default = 25565;
|
||||
};
|
||||
|
||||
query-port = mkOption {
|
||||
type = port;
|
||||
description = "Port for queries.";
|
||||
default = 25566;
|
||||
};
|
||||
|
||||
rcon-port = mkOption {
|
||||
type = port;
|
||||
description = "Port for remote commands.";
|
||||
default = 25567;
|
||||
};
|
||||
|
||||
difficulty = mkOption {
|
||||
type = enum [ "peaceful" "easy" "medium" "hard" ];
|
||||
description = "Difficulty setting of this server.";
|
||||
|
@ -120,6 +132,12 @@ let
|
|||
description = "Allow player-vs-player combat.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = package;
|
||||
description = "PaperMC package to launch.";
|
||||
default = pkgs.papermc;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -144,6 +162,8 @@ let
|
|||
server-port = worldOpts.port;
|
||||
hardcore = worldOpts.hardcore;
|
||||
pvp = worldOpts.pvp;
|
||||
"query.port" = worldOpts.query-port;
|
||||
"rcon.port" = worldOpts.rcon-port;
|
||||
};
|
||||
|
||||
toProps = attrs:
|
||||
|
@ -202,7 +222,9 @@ in {
|
|||
groups."${cfg.group}" = { members = [ cfg.user ]; };
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 25555 ];
|
||||
networking.firewall.allowedTCPPorts = [ 25555 ] ++ (lib.concatMap
|
||||
(worldOpts: with worldOpts; [ port query-port rcon-port ])
|
||||
(attrNames cfg.worlds));
|
||||
|
||||
systemd = {
|
||||
tmpfiles.rules = map (worldOpts:
|
||||
|
@ -226,7 +248,8 @@ in {
|
|||
cp -f ${props-file} ${stateDir}/server.properties
|
||||
cp -f ${eula-file} ${stateDir}/eula.txt
|
||||
mkdir -p ${stateDir}/plugins
|
||||
cp -f ${witchcraft-plugin} ${stateDir}/plugins/witchcraft-plugin.jar
|
||||
# Version not working...
|
||||
# cp -f ${witchcraft-plugin} ${stateDir}/plugins/witchcraft-plugin.jar
|
||||
chmod u+w ${stateDir}/server.properties
|
||||
'';
|
||||
|
||||
|
@ -237,7 +260,7 @@ in {
|
|||
++ (optionals (worldOpts.allocated-memory >= 12) highMemFlags);
|
||||
flagStr = concatStringsSep " " flags;
|
||||
in pkgs.writeShellScript "mc-start-${sanitizedName}.sh"
|
||||
"${pkgs.papermc}/bin/minecraft-server ${flagStr}";
|
||||
"${worldOpts.package}/bin/minecraft-server ${flagStr}";
|
||||
|
||||
in nameValuePair serverName {
|
||||
enable = worldOpts.enable;
|
||||
|
|
|
@ -4,53 +4,82 @@ with lib;
|
|||
let cfg = config.fudo.minecraft-server;
|
||||
|
||||
in {
|
||||
options.fudo.minecraft-server = {
|
||||
options.fudo.minecraft-server = with types; {
|
||||
enable = mkEnableOption "Start a minecraft server.";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
type = package;
|
||||
description = "Minecraft package to use.";
|
||||
default = pkgs.minecraft-current;
|
||||
};
|
||||
|
||||
data-dir = mkOption {
|
||||
type = types.path;
|
||||
type = path;
|
||||
description = "Path at which to store minecraft data.";
|
||||
};
|
||||
|
||||
world-name = mkOption {
|
||||
type = types.str;
|
||||
type = str;
|
||||
description = "Name of the server world (used in saves etc).";
|
||||
};
|
||||
|
||||
motd = mkOption {
|
||||
type = types.str;
|
||||
type = str;
|
||||
description = "Welcome message for newcomers.";
|
||||
default = "Welcome to Minecraft! Have fun building...";
|
||||
};
|
||||
|
||||
game-mode = mkOption {
|
||||
type = types.enum [ "survival" "creative" "adventure" "spectator" ];
|
||||
type = enum [ "survival" "creative" "adventure" "spectator" ];
|
||||
description = "Game mode of the server.";
|
||||
default = "survival";
|
||||
};
|
||||
|
||||
difficulty = mkOption {
|
||||
type = types.int;
|
||||
type = int;
|
||||
description = "Difficulty level, where 0 is peaceful and 3 is hard.";
|
||||
default = 2;
|
||||
};
|
||||
|
||||
allow-cheats = mkOption {
|
||||
type = types.bool;
|
||||
type = bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
allow-pvp = mkOption {
|
||||
type = bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
allocated-memory = mkOption {
|
||||
type = types.int;
|
||||
type = int;
|
||||
description = "Memory (in GB) to allocate to the Minecraft server.";
|
||||
default = 2;
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = port;
|
||||
description = "Port on which to run the Minecraft server.";
|
||||
default = 25565;
|
||||
};
|
||||
|
||||
query-port = mkOption {
|
||||
type = port;
|
||||
description = "Port for queries.";
|
||||
default = 25566;
|
||||
};
|
||||
|
||||
rcon-port = mkOption {
|
||||
type = port;
|
||||
description = "Port for remote commands.";
|
||||
default = 25567;
|
||||
};
|
||||
|
||||
world-seed = mkOption {
|
||||
type = nullOr int;
|
||||
description = "Seed to use while generating the world.";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
@ -64,10 +93,15 @@ in {
|
|||
declarative = true;
|
||||
serverProperties = {
|
||||
level-name = cfg.world-name;
|
||||
level-seed = toString cfg.world-seed;
|
||||
motd = cfg.motd;
|
||||
difficulty = cfg.difficulty;
|
||||
gamemode = cfg.game-mode;
|
||||
allow-cheats = true;
|
||||
allow-cheats = cfg.allow-cheats;
|
||||
server-port = cfg.port;
|
||||
"rcon.port" = cfg.rcon-port;
|
||||
"query.port" = cfg.query-port;
|
||||
pvp = cfg.allow-pvp;
|
||||
};
|
||||
jvmOpts = let
|
||||
opts = [
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
let
|
||||
domainOpts = { name, ... }: {
|
||||
options = with types; {
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
servers = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of servers for this Nexus domain.";
|
||||
};
|
||||
|
||||
dns-servers = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of DNS servers for this Nexus domain.";
|
||||
};
|
||||
|
||||
gssapi-realm = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
trusted-networks = mkOption {
|
||||
type = listOf str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
records = let
|
||||
recordOpts = { name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description = "Name of this record.";
|
||||
default = name;
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
type = str;
|
||||
description = "Record type of this record.";
|
||||
};
|
||||
|
||||
content = mkOption {
|
||||
type = str;
|
||||
description = "Data associated with this record.";
|
||||
};
|
||||
};
|
||||
};
|
||||
in mkOption {
|
||||
type = listOf (submodule recordOpts);
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.nexus = with types; {
|
||||
domains = mkOption {
|
||||
type = attrsOf (submodule domainOpts);
|
||||
description = "Nexus domain configurations.";
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
}
|
312
lib/fudo/nsd.nix
312
lib/fudo/nsd.nix
|
@ -1,8 +1,6 @@
|
|||
# ## NOTE:
|
||||
## This is a copy of the upstream version, which allows for overriding the state directory
|
||||
|
||||
## OBSOLETE
|
||||
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
@ -10,9 +8,9 @@ with lib;
|
|||
let
|
||||
cfg = config.fudo.nsd;
|
||||
|
||||
username = "nsd";
|
||||
stateDir = cfg.stateDir;
|
||||
pidFile = stateDir + "/var/nsd.pid";
|
||||
username = cfg.user;
|
||||
stateDir = cfg.stateDirectory;
|
||||
pidFile = "${stateDir}/run/nsd.pid";
|
||||
|
||||
# build nsd with the options needed for the given config
|
||||
nsdPkg = pkgs.nsd.override {
|
||||
|
@ -44,6 +42,7 @@ let
|
|||
postBuild = ''
|
||||
echo "checking zone files"
|
||||
cd $out/zones
|
||||
|
||||
for zoneFile in *; do
|
||||
echo "|- checking zone '$out/zones/$zoneFile'"
|
||||
${nsdPkg}/sbin/nsd-checkzone "$zoneFile" "$zoneFile" || {
|
||||
|
@ -52,9 +51,11 @@ let
|
|||
echo Escaping them is not needed any more. Please make sure \
|
||||
to unescape them where they prefix a variable name.
|
||||
fi
|
||||
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
echo "checking configuration file"
|
||||
# Save original config file including key references...
|
||||
cp $out/nsd.conf{,.orig}
|
||||
|
@ -79,16 +80,20 @@ let
|
|||
server:
|
||||
chroot: "${stateDir}"
|
||||
username: ${username}
|
||||
|
||||
# The directory for zonefile: files. The daemon chdirs here.
|
||||
zonesdir: "${stateDir}"
|
||||
|
||||
# the list of dynamically added zones.
|
||||
database: "${stateDir}/var/nsd.db"
|
||||
pidfile: "${pidFile}"
|
||||
xfrdfile: "${stateDir}/var/xfrd.state"
|
||||
xfrdir: "${stateDir}/tmp"
|
||||
zonelistfile: "${stateDir}/var/zone.list"
|
||||
|
||||
# interfaces
|
||||
${forEach " ip-address: " cfg.interfaces}
|
||||
|
||||
ip-freebind: ${yesOrNo cfg.ipFreebind}
|
||||
hide-version: ${yesOrNo cfg.hideVersion}
|
||||
identity: "${cfg.identity}"
|
||||
|
@ -111,13 +116,16 @@ let
|
|||
${maybeString "version: " cfg.version}
|
||||
xfrd-reload-timeout: ${toString cfg.xfrdReloadTimeout}
|
||||
zonefiles-check: ${yesOrNo cfg.zonefilesCheck}
|
||||
|
||||
${maybeString "rrl-ipv4-prefix-length: " cfg.ratelimit.ipv4PrefixLength}
|
||||
${maybeString "rrl-ipv6-prefix-length: " cfg.ratelimit.ipv6PrefixLength}
|
||||
rrl-ratelimit: ${toString cfg.ratelimit.ratelimit}
|
||||
${maybeString "rrl-slip: " cfg.ratelimit.slip}
|
||||
rrl-size: ${toString cfg.ratelimit.size}
|
||||
rrl-whitelist-ratelimit: ${toString cfg.ratelimit.whitelistRatelimit}
|
||||
|
||||
${keyConfigFile}
|
||||
|
||||
remote-control:
|
||||
control-enable: ${yesOrNo cfg.remoteControl.enable}
|
||||
control-key-file: "${cfg.remoteControl.controlKeyFile}"
|
||||
|
@ -126,7 +134,9 @@ let
|
|||
control-port: ${toString cfg.remoteControl.port}
|
||||
server-key-file: "${cfg.remoteControl.serverKeyFile}"
|
||||
server-cert-file: "${cfg.remoteControl.serverCertFile}"
|
||||
|
||||
${concatStrings (mapAttrsToList zoneConfigFile zoneConfigs)}
|
||||
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
|
@ -159,13 +169,16 @@ let
|
|||
${maybeString "outgoing-interface: " zone.outgoingInterface}
|
||||
${forEach " rrl-whitelist: " zone.rrlWhitelist}
|
||||
${maybeString "zonestats: " zone.zoneStats}
|
||||
|
||||
${maybeToString "max-refresh-time: " zone.maxRefreshSecs}
|
||||
${maybeToString "min-refresh-time: " zone.minRefreshSecs}
|
||||
${maybeToString "max-retry-time: " zone.maxRetrySecs}
|
||||
${maybeToString "min-retry-time: " zone.minRetrySecs}
|
||||
|
||||
allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback}
|
||||
${forEach " allow-notify: " zone.allowNotify}
|
||||
${forEach " request-xfr: " zone.requestXFR}
|
||||
|
||||
${forEach " notify: " zone.notify}
|
||||
notify-retry: ${toString zone.notifyRetry}
|
||||
${forEach " provide-xfr: " zone.provideXFR}
|
||||
|
@ -194,7 +207,7 @@ let
|
|||
allowAXFRFallback = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
If NSD as secondary server should be allowed to AXFR if the primary
|
||||
server does not allow IXFR.
|
||||
'';
|
||||
|
@ -208,21 +221,24 @@ let
|
|||
"10.0.0.1-10.0.0.5 my_tsig_key_name"
|
||||
"10.0.3.4&255.255.0.0 BLOCKED"
|
||||
];
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Listed primary servers are allowed to notify this secondary server.
|
||||
<screen><![CDATA[
|
||||
Format: <ip> <key-name | NOKEY | BLOCKED>
|
||||
<ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges:
|
||||
* 10.0.0.0/24 # via subnet size
|
||||
* 10.0.0.0&255.255.255.0 # via subnet mask
|
||||
* 10.0.0.1-10.0.0.254 # via range
|
||||
|
||||
Format: `<ip> <key-name | NOKEY | BLOCKED>`
|
||||
|
||||
`<ip>` either a plain IPv4/IPv6 address or range.
|
||||
Valid patters for ranges:
|
||||
* `10.0.0.0/24`: via subnet size
|
||||
* `10.0.0.0&255.255.255.0`: via subnet mask
|
||||
* `10.0.0.1-10.0.0.254`: via range
|
||||
|
||||
A optional port number could be added with a '@':
|
||||
* 2001:1234::1@1234
|
||||
<key-name | NOKEY | BLOCKED>
|
||||
* <key-name> will use the specified TSIG key
|
||||
* NOKEY no TSIG signature is required
|
||||
* BLOCKED notifies from non-listed or blocked IPs will be ignored
|
||||
* ]]></screen>
|
||||
* `2001:1234::1@1234`
|
||||
|
||||
`<key-name | NOKEY | BLOCKED>`
|
||||
* `<key-name>` will use the specified TSIG key
|
||||
* `NOKEY` no TSIG signature is required
|
||||
* `BLOCKED`notifies from non-listed or blocked IPs will be ignored
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -235,7 +251,7 @@ let
|
|||
# to default values, breaking the parent inheriting function.
|
||||
type = types.attrsOf types.anything;
|
||||
default = { };
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Children zones inherit all options of their parents. Attributes
|
||||
defined in a child will overwrite the ones of its parent. Only
|
||||
leaf zones will be actually served. This way it's possible to
|
||||
|
@ -248,29 +264,29 @@ let
|
|||
data = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
The actual zone data. This is the content of your zone file.
|
||||
Use imports or pkgs.lib.readFile if you don't want this data in your config file.
|
||||
'';
|
||||
};
|
||||
|
||||
dnssec = mkEnableOption "DNSSEC";
|
||||
dnssec = mkEnableOption (lib.mdDoc "DNSSEC");
|
||||
|
||||
dnssecPolicy = {
|
||||
algorithm = mkOption {
|
||||
type = types.str;
|
||||
default = "RSASHA256";
|
||||
description = "Which algorithm to use for DNSSEC";
|
||||
description = lib.mdDoc "Which algorithm to use for DNSSEC";
|
||||
};
|
||||
keyttl = mkOption {
|
||||
type = types.str;
|
||||
default = "1h";
|
||||
description = "TTL for dnssec records";
|
||||
description = lib.mdDoc "TTL for dnssec records";
|
||||
};
|
||||
coverage = mkOption {
|
||||
type = types.str;
|
||||
default = "1y";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
The length of time to ensure that keys will be correct; no action will be taken to create new keys to be activated after this time.
|
||||
'';
|
||||
};
|
||||
|
@ -282,7 +298,7 @@ let
|
|||
postPublish = "1w";
|
||||
rollPeriod = "1mo";
|
||||
};
|
||||
description = "Key policy for zone signing keys";
|
||||
description = lib.mdDoc "Key policy for zone signing keys";
|
||||
};
|
||||
ksk = mkOption {
|
||||
type = keyPolicy;
|
||||
|
@ -292,14 +308,22 @@ let
|
|||
postPublish = "1mo";
|
||||
rollPeriod = "0";
|
||||
};
|
||||
description = "Key policy for key signing keys";
|
||||
description = lib.mdDoc "Key policy for key signing keys";
|
||||
};
|
||||
};
|
||||
|
||||
ksk = {
|
||||
keyFile = mkOption {
|
||||
type = types.str;
|
||||
description =
|
||||
"Location of the zone key-signing key file on the local host.";
|
||||
};
|
||||
};
|
||||
|
||||
maxRefreshSecs = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Limit refresh time for secondary zones. This is the timer which
|
||||
checks to see if the zone has to be refetched when it expires.
|
||||
Normally the value from the SOA record is used, but this option
|
||||
|
@ -310,7 +334,7 @@ let
|
|||
minRefreshSecs = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Limit refresh time for secondary zones.
|
||||
'';
|
||||
};
|
||||
|
@ -318,7 +342,7 @@ let
|
|||
maxRetrySecs = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Limit retry time for secondary zones. This is the timeout after
|
||||
a failed fetch attempt for the zone. Normally the value from
|
||||
the SOA record is used, but this option restricts that value.
|
||||
|
@ -328,7 +352,7 @@ let
|
|||
minRetrySecs = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Limit retry time for secondary zones.
|
||||
'';
|
||||
};
|
||||
|
@ -337,23 +361,24 @@ let
|
|||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
This primary server will notify all given secondary servers about
|
||||
zone changes.
|
||||
<screen><![CDATA[
|
||||
Format: <ip> <key-name | NOKEY>
|
||||
<ip> a plain IPv4/IPv6 address with on optional port number (ip@port)
|
||||
<key-name | NOKEY>
|
||||
* <key-name> sign notifies with the specified key
|
||||
* NOKEY don't sign notifies
|
||||
]]></screen>
|
||||
|
||||
Format: `<ip> <key-name | NOKEY>`
|
||||
|
||||
`<ip>` a plain IPv4/IPv6 address with on optional port number (ip@port)
|
||||
|
||||
`<key-name | NOKEY>`
|
||||
- `<key-name>` sign notifies with the specified key
|
||||
- `NOKEY` don't sign notifies
|
||||
'';
|
||||
};
|
||||
|
||||
notifyRetry = mkOption {
|
||||
type = types.int;
|
||||
default = 5;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Specifies the number of retries for failed notifies. Set this along with notify.
|
||||
'';
|
||||
};
|
||||
|
@ -362,8 +387,8 @@ let
|
|||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "2000::1@1234";
|
||||
description = ''
|
||||
This address will be used for zone-transfere requests if configured
|
||||
description = lib.mdDoc ''
|
||||
This address will be used for zone-transfer requests if configured
|
||||
as a secondary server or notifications in case of a primary server.
|
||||
Supply either a plain IPv4 or IPv6 address with an optional port
|
||||
number (ip@port).
|
||||
|
@ -374,17 +399,17 @@ let
|
|||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
|
||||
address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40
|
||||
address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40
|
||||
'';
|
||||
};
|
||||
|
||||
requestXFR = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = ''
|
||||
Format: <code>[AXFR|UDP] <ip-address> <key-name | NOKEY></code>
|
||||
description = lib.mdDoc ''
|
||||
Format: `[AXFR|UDP] <ip-address> <key-name | NOKEY>`
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -403,7 +428,7 @@ let
|
|||
"all"
|
||||
]);
|
||||
default = [ ];
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Whitelists the given rrl-types.
|
||||
'';
|
||||
};
|
||||
|
@ -412,7 +437,7 @@ let
|
|||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "%s";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
When set to something distinct to null NSD is able to collect
|
||||
statistics per zone. All statistics of this zone(s) will be added
|
||||
to the group specified by this given name. Use "%s" to use the zones
|
||||
|
@ -427,19 +452,20 @@ let
|
|||
options = {
|
||||
keySize = mkOption {
|
||||
type = types.int;
|
||||
description = "Key size in bits";
|
||||
description = lib.mdDoc "Key size in bits";
|
||||
};
|
||||
prePublish = mkOption {
|
||||
type = types.str;
|
||||
description = "How long in advance to publish new keys";
|
||||
description = lib.mdDoc "How long in advance to publish new keys";
|
||||
};
|
||||
postPublish = mkOption {
|
||||
type = types.str;
|
||||
description = "How long after deactivation to keep a key in the zone";
|
||||
description =
|
||||
lib.mdDoc "How long after deactivation to keep a key in the zone";
|
||||
};
|
||||
rollPeriod = mkOption {
|
||||
type = types.str;
|
||||
description = "How frequently to change keys";
|
||||
description = lib.mdDoc "How frequently to change keys";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -450,20 +476,42 @@ let
|
|||
dnssec = dnssecZones != { };
|
||||
|
||||
dnssecTools = pkgs.bind.override { enablePython = true; };
|
||||
ldnsTools = pkgs.ldns.examples;
|
||||
|
||||
signZones = optionalString dnssec ''
|
||||
signZones = let
|
||||
dnssecZoneNames = attrNames dnssecZones;
|
||||
mkdirCmds = map (zone: ''
|
||||
mkdir -p ${stateDir}/dnssec/${zone}
|
||||
chown ${username}:${username} ${stateDir}/dnssec/${zone}
|
||||
chmod 0600 ${stateDir}/dnssec/${zone}
|
||||
'') (attrNames dnssecZones);
|
||||
in optionalString dnssec ''
|
||||
mkdir -p ${stateDir}/dnssec
|
||||
chown ${username}:${username} ${stateDir}/dnssec
|
||||
chmod 0600 ${stateDir}/dnssec
|
||||
${concatStrings (mapAttrsToList signZone dnssecZones)}
|
||||
${concatStringsSep "\n" mkdirCmds}
|
||||
|
||||
${concatStringsSep "\n" (mapAttrsToList signZone dnssecZones)}
|
||||
'';
|
||||
signZone = name: zone: ''
|
||||
${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${
|
||||
policyFile name zone.dnssecPolicy
|
||||
} ${name}
|
||||
${dnssecTools}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
|
||||
${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
|
||||
${pkgs.nsdSignZone}/bin/nsd-sign-zone \
|
||||
--verbose \
|
||||
--domain=${name} \
|
||||
--ksk-file=${zone.ksk.keyFile} \
|
||||
--zsk-metadata=${stateDir}/dnssec/${name}/metadata.json \
|
||||
${stateDir}/zones/${name}
|
||||
${nsdPkg}/sbin/nsd-checkzone \
|
||||
${name} \
|
||||
${stateDir}/zones/${name}.signed &&
|
||||
mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
|
||||
'';
|
||||
# signZone = name: zone: ''
|
||||
# ${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${
|
||||
# policyFile name zone.dnssecPolicy
|
||||
# } ${name}
|
||||
# ${dnssecTools}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
|
||||
# ${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
|
||||
# '';
|
||||
policyFile = name: policy:
|
||||
pkgs.writeText "${name}.policy" ''
|
||||
zone ${name} {
|
||||
|
@ -484,20 +532,26 @@ in {
|
|||
# options are ordered alphanumerically
|
||||
options.fudo.nsd = {
|
||||
|
||||
enable = mkEnableOption "NSD authoritative DNS server";
|
||||
enable = mkEnableOption (lib.mdDoc "NSD authoritative DNS server");
|
||||
|
||||
stateDir = mkOption {
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
description = "Directory at which to store NSD state data.";
|
||||
description = "User as which to run the NSD server.";
|
||||
default = "nsd";
|
||||
};
|
||||
|
||||
stateDirectory = mkOption {
|
||||
type = types.str;
|
||||
description = "Location at which to store NSD state.";
|
||||
default = "/var/lib/nsd";
|
||||
};
|
||||
|
||||
bind8Stats = mkEnableOption "BIND8 like statistics";
|
||||
bind8Stats = mkEnableOption (lib.mdDoc "BIND8 like statistics");
|
||||
|
||||
dnssecInterval = mkOption {
|
||||
type = types.str;
|
||||
default = "1h";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
How often to check whether dnssec key rollover is required
|
||||
'';
|
||||
};
|
||||
|
@ -505,7 +559,7 @@ in {
|
|||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Extra nsd config.
|
||||
'';
|
||||
};
|
||||
|
@ -513,7 +567,7 @@ in {
|
|||
hideVersion = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Whether NSD should answer VERSION.BIND and VERSION.SERVER CHAOS class queries.
|
||||
'';
|
||||
};
|
||||
|
@ -521,7 +575,7 @@ in {
|
|||
identity = mkOption {
|
||||
type = types.str;
|
||||
default = "unidentified server";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Identify the server (CH TXT ID.SERVER entry).
|
||||
'';
|
||||
};
|
||||
|
@ -529,7 +583,7 @@ in {
|
|||
interfaces = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "127.0.0.0" "::1" ];
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
What addresses the server should listen to.
|
||||
'';
|
||||
};
|
||||
|
@ -537,7 +591,7 @@ in {
|
|||
ipFreebind = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Whether to bind to nonlocal addresses and interfaces that are down.
|
||||
Similar to ip-transparent.
|
||||
'';
|
||||
|
@ -546,7 +600,7 @@ in {
|
|||
ipTransparent = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Allow binding to non local addresses.
|
||||
'';
|
||||
};
|
||||
|
@ -554,7 +608,7 @@ in {
|
|||
ipv4 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Whether to listen on IPv4 connections.
|
||||
'';
|
||||
};
|
||||
|
@ -562,7 +616,7 @@ in {
|
|||
ipv4EDNSSize = mkOption {
|
||||
type = types.int;
|
||||
default = 4096;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Preferred EDNS buffer size for IPv4.
|
||||
'';
|
||||
};
|
||||
|
@ -570,7 +624,7 @@ in {
|
|||
ipv6 = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Whether to listen on IPv6 connections.
|
||||
'';
|
||||
};
|
||||
|
@ -578,7 +632,7 @@ in {
|
|||
ipv6EDNSSize = mkOption {
|
||||
type = types.int;
|
||||
default = 4096;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Preferred EDNS buffer size for IPv6.
|
||||
'';
|
||||
};
|
||||
|
@ -586,7 +640,7 @@ in {
|
|||
logTimeAscii = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Log time in ascii, if false then in unix epoch seconds.
|
||||
'';
|
||||
};
|
||||
|
@ -594,15 +648,15 @@ in {
|
|||
nsid = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
NSID identity (hex string, or "ascii_somestring").
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
type = types.port;
|
||||
default = 53;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Port the service should bind do.
|
||||
'';
|
||||
};
|
||||
|
@ -611,7 +665,7 @@ in {
|
|||
type = types.bool;
|
||||
default = pkgs.stdenv.isLinux;
|
||||
defaultText = literalExpression "pkgs.stdenv.isLinux";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Whether to enable SO_REUSEPORT on all used sockets. This lets multiple
|
||||
processes bind to the same port. This speeds up operation especially
|
||||
if the server count is greater than one and makes fast restarts less
|
||||
|
@ -622,18 +676,18 @@ in {
|
|||
rootServer = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Whether this server will be a root server (a DNS root server, you
|
||||
usually don't want that).
|
||||
'';
|
||||
};
|
||||
|
||||
roundRobin = mkEnableOption "round robin rotation of records";
|
||||
roundRobin = mkEnableOption (lib.mdDoc "round robin rotation of records");
|
||||
|
||||
serverCount = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Number of NSD servers to fork. Put the number of CPUs to use here.
|
||||
'';
|
||||
};
|
||||
|
@ -641,7 +695,7 @@ in {
|
|||
statistics = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Statistics are produced every number of seconds. Prints to log.
|
||||
If null no statistics are logged.
|
||||
'';
|
||||
|
@ -650,7 +704,7 @@ in {
|
|||
tcpCount = mkOption {
|
||||
type = types.int;
|
||||
default = 100;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Maximum number of concurrent TCP connections per server.
|
||||
'';
|
||||
};
|
||||
|
@ -658,7 +712,7 @@ in {
|
|||
tcpQueryCount = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Maximum number of queries served on a single TCP connection.
|
||||
0 means no maximum.
|
||||
'';
|
||||
|
@ -667,7 +721,7 @@ in {
|
|||
tcpTimeout = mkOption {
|
||||
type = types.int;
|
||||
default = 120;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
TCP timeout in seconds.
|
||||
'';
|
||||
};
|
||||
|
@ -675,7 +729,7 @@ in {
|
|||
verbosity = mkOption {
|
||||
type = types.int;
|
||||
default = 0;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Verbosity level.
|
||||
'';
|
||||
};
|
||||
|
@ -683,7 +737,7 @@ in {
|
|||
version = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
The version string replied for CH TXT version.server and version.bind
|
||||
queries. Will use the compiled package version on null.
|
||||
See hideVersion for enabling/disabling this responses.
|
||||
|
@ -693,7 +747,7 @@ in {
|
|||
xfrdReloadTimeout = mkOption {
|
||||
type = types.int;
|
||||
default = 1;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Number of seconds between reloads triggered by xfrd.
|
||||
'';
|
||||
};
|
||||
|
@ -701,7 +755,7 @@ in {
|
|||
zonefilesCheck = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Whether to check mtime of all zone files on start and sighup.
|
||||
'';
|
||||
};
|
||||
|
@ -713,14 +767,14 @@ in {
|
|||
algorithm = mkOption {
|
||||
type = types.str;
|
||||
default = "hmac-sha256";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Authentication algorithm for this key.
|
||||
'';
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Path to the file which contains the actual base64 encoded
|
||||
key. The key will be copied into "${stateDir}/private" before
|
||||
NSD starts. The copied file is only accessibly by the NSD
|
||||
|
@ -738,19 +792,19 @@ in {
|
|||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Define your TSIG keys here.
|
||||
'';
|
||||
};
|
||||
|
||||
ratelimit = {
|
||||
|
||||
enable = mkEnableOption "ratelimit capabilities";
|
||||
enable = mkEnableOption (lib.mdDoc "ratelimit capabilities");
|
||||
|
||||
ipv4PrefixLength = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
IPv4 prefix length. Addresses are grouped by netblock.
|
||||
'';
|
||||
};
|
||||
|
@ -758,7 +812,7 @@ in {
|
|||
ipv6PrefixLength = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
IPv6 prefix length. Addresses are grouped by netblock.
|
||||
'';
|
||||
};
|
||||
|
@ -766,7 +820,7 @@ in {
|
|||
ratelimit = mkOption {
|
||||
type = types.int;
|
||||
default = 200;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Max qps allowed from any query source.
|
||||
0 means unlimited. With an verbosity of 2 blocked and
|
||||
unblocked subnets will be logged.
|
||||
|
@ -776,7 +830,7 @@ in {
|
|||
slip = mkOption {
|
||||
type = types.nullOr types.int;
|
||||
default = null;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Number of packets that get discarded before replying a SLIP response.
|
||||
0 disables SLIP responses. 1 will make every response a SLIP response.
|
||||
'';
|
||||
|
@ -785,7 +839,7 @@ in {
|
|||
size = mkOption {
|
||||
type = types.int;
|
||||
default = 1000000;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Size of the hashtable. More buckets use more memory but lower
|
||||
the chance of hash hash collisions.
|
||||
'';
|
||||
|
@ -794,7 +848,7 @@ in {
|
|||
whitelistRatelimit = mkOption {
|
||||
type = types.int;
|
||||
default = 2000;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Max qps allowed from whitelisted sources.
|
||||
0 means unlimited. Set the rrl-whitelist option for specific
|
||||
queries to apply this limit instead of the default to them.
|
||||
|
@ -805,12 +859,12 @@ in {
|
|||
|
||||
remoteControl = {
|
||||
|
||||
enable = mkEnableOption "remote control via nsd-control";
|
||||
enable = mkEnableOption (lib.mdDoc "remote control via nsd-control");
|
||||
|
||||
controlCertFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/nsd/nsd_control.pem";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Path to the client certificate signed with the server certificate.
|
||||
This file is used by nsd-control and generated by nsd-control-setup.
|
||||
'';
|
||||
|
@ -819,7 +873,7 @@ in {
|
|||
controlKeyFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/nsd/nsd_control.key";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Path to the client private key, which is used by nsd-control
|
||||
but not by the server. This file is generated by nsd-control-setup.
|
||||
'';
|
||||
|
@ -828,15 +882,15 @@ in {
|
|||
interfaces = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "127.0.0.1" "::1" ];
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Which interfaces NSD should bind to for remote control.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
type = types.port;
|
||||
default = 8952;
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Port number for remote control operations (uses TLS over TCP).
|
||||
'';
|
||||
};
|
||||
|
@ -844,7 +898,7 @@ in {
|
|||
serverCertFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/nsd/nsd_server.pem";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Path to the server self signed certificate, which is used by the server
|
||||
but and by nsd-control. This file is generated by nsd-control-setup.
|
||||
'';
|
||||
|
@ -853,7 +907,7 @@ in {
|
|||
serverKeyFile = mkOption {
|
||||
type = types.path;
|
||||
default = "/etc/nsd/nsd_server.key";
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Path to the server private key, which is used by the server
|
||||
but not by nsd-control. This file is generated by nsd-control-setup.
|
||||
'';
|
||||
|
@ -886,6 +940,7 @@ in {
|
|||
};
|
||||
};
|
||||
};
|
||||
|
||||
"example.net." = {
|
||||
provideXFR = [ "10.3.2.1 NOKEY" ];
|
||||
data = '''
|
||||
|
@ -894,7 +949,7 @@ in {
|
|||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
description = lib.mdDoc ''
|
||||
Define your zones here. Zones can cascade other zones and therefore
|
||||
inherit settings from parent zones. Look at the definition of
|
||||
children to learn about inheritance and child zones.
|
||||
|
@ -918,14 +973,15 @@ in {
|
|||
etc."nsd/nsd.conf".source = "${configFile}/nsd.conf";
|
||||
};
|
||||
|
||||
users.groups.${username}.gid = config.ids.gids.nsd;
|
||||
|
||||
users.users.${username} = {
|
||||
description = "NSD service user";
|
||||
home = stateDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.nsd;
|
||||
group = username;
|
||||
users = {
|
||||
users."${username}" = {
|
||||
description = "NSD service user";
|
||||
home = stateDir;
|
||||
createHome = true;
|
||||
uid = config.ids.uids.nsd;
|
||||
group = username;
|
||||
};
|
||||
groups."${username}".gid = config.ids.gids.nsd;
|
||||
};
|
||||
|
||||
systemd.services.nsd = {
|
||||
|
@ -947,19 +1003,24 @@ in {
|
|||
preStart = ''
|
||||
rm -Rf "${stateDir}/private/"
|
||||
rm -Rf "${stateDir}/tmp/"
|
||||
|
||||
mkdir -m 0700 -p "${stateDir}/private"
|
||||
mkdir -m 0700 -p "${stateDir}/tmp"
|
||||
mkdir -m 0700 -p "${stateDir}/var"
|
||||
|
||||
cat > "${stateDir}/don't touch anything in here" << EOF
|
||||
Everything in this directory except NSD's state in var and dnssec
|
||||
is automatically generated and will be purged and redeployed by
|
||||
the nsd.service pre-start script.
|
||||
EOF
|
||||
|
||||
chown ${username}:${username} -R "${stateDir}/private"
|
||||
chown ${username}:${username} -R "${stateDir}/tmp"
|
||||
chown ${username}:${username} -R "${stateDir}/var"
|
||||
|
||||
rm -rf "${stateDir}/zones"
|
||||
cp -rL "${nsdEnv}/zones" "${stateDir}/zones"
|
||||
|
||||
${copyKeys}
|
||||
'';
|
||||
};
|
||||
|
@ -968,6 +1029,7 @@ in {
|
|||
description = "Automatic DNSSEC key rollover";
|
||||
|
||||
wantedBy = [ "nsd.service" ];
|
||||
before = [ "nsd.service" ];
|
||||
|
||||
timerConfig = {
|
||||
OnActiveSec = cfg.dnssecInterval;
|
||||
|
@ -981,6 +1043,22 @@ in {
|
|||
wantedBy = [ "nsd.service" ];
|
||||
before = [ "nsd.service" ];
|
||||
|
||||
preStart = let
|
||||
zoneRotateCmd = zone:
|
||||
let zoneDir = "${stateDir}/dnssec/${zone}";
|
||||
in ''
|
||||
mkdir -p ${zoneDir}
|
||||
${pkgs.nsdRotateKeys}/bin/nsd-rotate-keys \
|
||||
--key-directory=${zoneDir} \
|
||||
--validity-period=30 \
|
||||
--period-overlap=10 \
|
||||
--metadata=${zoneDir}/metadata.json \
|
||||
--verbose \
|
||||
${zone}
|
||||
'';
|
||||
zoneRotateCmds = map zoneRotateCmd (lib.attrNames dnssecZones);
|
||||
in lib.concatStringsSep "\n" zoneRotateCmds;
|
||||
|
||||
script = signZones;
|
||||
|
||||
postStop = ''
|
||||
|
|
|
@ -151,6 +151,12 @@ in {
|
|||
options.fudo.postgresql = with types; {
|
||||
enable = mkEnableOption "Fudo PostgreSQL Server";
|
||||
|
||||
package = mkOption {
|
||||
type = package;
|
||||
description = "Which package to use for Postgresql server.";
|
||||
default = pkgs.postgresql_15_gssapi;
|
||||
};
|
||||
|
||||
ssl-private-key = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Location of the server SSL private key.";
|
||||
|
@ -247,7 +253,7 @@ in {
|
|||
|
||||
networking.firewall.allowedTCPPorts = [ 5432 ];
|
||||
|
||||
environment.systemPackages = with pkgs; [ postgresql_11_gssapi ];
|
||||
environment.systemPackages = with pkgs; [ cfg.package ];
|
||||
|
||||
users.groups = {
|
||||
${cfg.socket-group} = { members = [ "postgres" ] ++ cfg.local-users; };
|
||||
|
@ -255,7 +261,7 @@ in {
|
|||
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
package = pkgs.postgresql_11_gssapi;
|
||||
package = cfg.package;
|
||||
enableTCPIP = true;
|
||||
ensureDatabases = mapAttrsToList (name: value: name) cfg.databases;
|
||||
ensureUsers = ((mapAttrsToList (username: attrs: {
|
||||
|
|
|
@ -39,13 +39,27 @@ let
|
|||
{ source-file, target-file, user, group, permissions, ... }: {
|
||||
description =
|
||||
"decrypt secret ${secret-name} at ${target-host}:${target-file}.";
|
||||
wantedBy = [ cfg.secret-target ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requiredBy = [ cfg.secret-target ];
|
||||
requires = [ "local-fs.target" ];
|
||||
before = [ cfg.secret-target ];
|
||||
after = [ "local-fs.target" ];
|
||||
restartIfChanged = true;
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStartPre =
|
||||
pkgs.writeShellScript "fudo-secret-prep-${secret-name}.sh" ''
|
||||
if [ ! -d ${dirOf target-file} ]; then
|
||||
mkdir -p ${dirOf target-file}
|
||||
chown ${user}:${group} ${dirOf target-file}
|
||||
chmod ${if (group == null) then "0550" else "0500"} ${
|
||||
dirOf target-file
|
||||
}
|
||||
fi
|
||||
'';
|
||||
ExecStart =
|
||||
let host-master-key = config.fudo.hosts.${target-host}.master-key;
|
||||
let host-master-key = config.fudo.hosts."${target-host}".master-key;
|
||||
in decrypt-script {
|
||||
inherit secret-name source-file target-host target-file
|
||||
host-master-key user group permissions;
|
||||
|
@ -218,11 +232,11 @@ in {
|
|||
cfg.secret-paths;
|
||||
|
||||
in {
|
||||
tmpfiles.rules = host-secret-paths ++ build-secret-paths;
|
||||
tmpfiles.rules = unique (host-secret-paths ++ build-secret-paths);
|
||||
|
||||
services = host-secret-services // {
|
||||
fudo-secrets-watcher = mkIf (length cfg.secret-paths > 0) {
|
||||
wantedBy = [ "default.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description =
|
||||
"Ensure access for group ${cfg.secret-group} to fudo secret paths.";
|
||||
serviceConfig = {
|
||||
|
@ -241,12 +255,12 @@ in {
|
|||
${strip-ext cfg.secret-target} = {
|
||||
description =
|
||||
"Target indicating that all Fudo secrets are available.";
|
||||
wantedBy = [ "default.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
};
|
||||
|
||||
paths.fudo-secrets-watcher = mkIf (length cfg.secret-paths > 0) {
|
||||
wantedBy = [ "default.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
description = "Watch fudo secret paths, and correct perms on changes.";
|
||||
pathConfig = {
|
||||
PathChanged = cfg.secret-paths;
|
||||
|
|
|
@ -139,9 +139,31 @@ let
|
|||
|
||||
local-gateway = mkOption {
|
||||
type = nullOr str;
|
||||
description = "If this is a NAT site, this should point to the host acting as network gateway.";
|
||||
description =
|
||||
"If this is a NAT site, this should point to the host acting as network gateway.";
|
||||
default = null;
|
||||
};
|
||||
nexus = {
|
||||
public-domains = mkOption {
|
||||
type = listOf str;
|
||||
description = "Nexus domains to which hosts in this domain belong.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
private-domains = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"Nexus private domains to which hosts in this domain belong.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
tailscale-domains = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"Nexus tailscale domains to which hosts in this domain belong.";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -4,21 +4,22 @@ with lib;
|
|||
let
|
||||
cfg = config.fudo.slynk;
|
||||
|
||||
initScript = port: load-paths: let
|
||||
load-path-string =
|
||||
concatStringsSep " " (map (path: "\"${path}\"") load-paths);
|
||||
in pkgs.writeText "slynk.lisp" ''
|
||||
(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))
|
||||
(ql:quickload :slynk)
|
||||
(setf asdf:*central-registry*
|
||||
(append asdf:*central-registry*
|
||||
(list ${load-path-string})))
|
||||
(slynk:create-server :port ${toString port} :dont-close t)
|
||||
(dolist (var '("LD_LIBRARY_PATH"))
|
||||
(format t "~S: ~S~%" var (sb-unix::posix-getenv var)))
|
||||
initScript = port: load-paths:
|
||||
let
|
||||
load-path-string =
|
||||
concatStringsSep " " (map (path: ''"${path}"'') load-paths);
|
||||
in pkgs.writeText "slynk.lisp" ''
|
||||
(load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname)))
|
||||
(ql:quickload :slynk)
|
||||
(setf asdf:*central-registry*
|
||||
(append asdf:*central-registry*
|
||||
(list ${load-path-string})))
|
||||
(slynk:create-server :port ${toString port} :dont-close t)
|
||||
(dolist (var '("LD_LIBRARY_PATH"))
|
||||
(format t "~S: ~S~%" var (sb-unix::posix-getenv var)))
|
||||
|
||||
(loop (sleep 60))
|
||||
'';
|
||||
(loop (sleep 60))
|
||||
'';
|
||||
|
||||
lisp-libs = with pkgs.lispPackages; [
|
||||
alexandria
|
||||
|
@ -47,14 +48,15 @@ in {
|
|||
systemd.user.services.slynk = {
|
||||
description = "Slynk Common Lisp server.";
|
||||
|
||||
serviceConfig = let
|
||||
load-paths = (map (pkg: "${pkg}/lib/common-lisp/") lisp-libs);
|
||||
in {
|
||||
ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init";
|
||||
ExecStart = "${pkgs.sbcl}/bin/sbcl --load ${initScript cfg.port load-paths}";
|
||||
Restart = "on-failure";
|
||||
PIDFile = "/run/slynk.$USERNAME.pid";
|
||||
};
|
||||
serviceConfig =
|
||||
let load-paths = (map (pkg: "${pkg}/lib/common-lisp/") lisp-libs);
|
||||
in {
|
||||
ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init";
|
||||
ExecStart =
|
||||
"${pkgs.sbcl}/bin/sbcl --load ${initScript cfg.port load-paths}";
|
||||
Restart = "on-failure";
|
||||
PIDFile = "/run/slynk.$USERNAME.pid";
|
||||
};
|
||||
|
||||
path = with pkgs; [
|
||||
gcc
|
||||
|
@ -62,9 +64,7 @@ in {
|
|||
file
|
||||
];
|
||||
|
||||
environment = {
|
||||
LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib";
|
||||
};
|
||||
environment = { LD_LIBRARY_PATH = "${pkgs.openssl.out}/lib"; };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,13 +6,12 @@ let
|
|||
|
||||
cfg = config.fudo.webmail;
|
||||
|
||||
base-data-path = cfg.state-directory;
|
||||
|
||||
webmail-user = cfg.user;
|
||||
webmail-group = cfg.group;
|
||||
|
||||
base-data-path = "/run/rainloop";
|
||||
|
||||
concatMapAttrs = f: attrs:
|
||||
foldr (a: b: a // b) {} (mapAttrsToList f attrs);
|
||||
concatMapAttrs = f: attrs: foldr (a: b: a // b) { } (mapAttrsToList f attrs);
|
||||
|
||||
fastcgi-conf = builtins.toFile "fastcgi.conf" ''
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
|
@ -57,142 +56,144 @@ let
|
|||
'';
|
||||
})) cfg.sites;
|
||||
|
||||
siteOpts = { site-host, ... }: with types; {
|
||||
options = {
|
||||
title = mkOption {
|
||||
type = str;
|
||||
description = "Webmail site title";
|
||||
example = "My Webmail";
|
||||
};
|
||||
|
||||
debug = mkOption {
|
||||
type = bool;
|
||||
description = "Turn debug logs on.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
mail-server = mkOption {
|
||||
type = str;
|
||||
description = "Mail server from which to send & recieve email.";
|
||||
default = "mail.fudo.org";
|
||||
};
|
||||
|
||||
favicon = mkOption {
|
||||
type = str;
|
||||
description = "URL of the site favicon";
|
||||
example = "https://www.somepage.com/fav.ico";
|
||||
};
|
||||
|
||||
messages-per-page = mkOption {
|
||||
type = int;
|
||||
description = "Default number of messages to show per page";
|
||||
default = 30;
|
||||
};
|
||||
|
||||
max-upload-size = mkOption {
|
||||
type = int;
|
||||
description = "Size limit in MB for uploaded files";
|
||||
default = 30;
|
||||
};
|
||||
|
||||
theme = mkOption {
|
||||
type = str;
|
||||
description = "Default theme to use for this webmail site.";
|
||||
default = "Default";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = "Domain for which the server acts as webmail server";
|
||||
};
|
||||
|
||||
edit-mode = mkOption {
|
||||
type = enum [ "Plain" "Html" "PlainForced" "HtmlForced" ];
|
||||
description = "Default text editing mode for email";
|
||||
default = "Html";
|
||||
};
|
||||
|
||||
layout-mode = mkOption {
|
||||
type = enum [ "side" "bottom" ];
|
||||
description = "Layout mode to use for email preview.";
|
||||
default = "side";
|
||||
};
|
||||
|
||||
enable-threading = mkOption {
|
||||
type = bool;
|
||||
description = "Whether to enable threading for email.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
enable-mobile = mkOption {
|
||||
type = bool;
|
||||
description = "Whether to enable a mobile site view.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
database = mkOption {
|
||||
type = nullOr (submodule databaseOpts);
|
||||
description = "Database configuration for storing contact data.";
|
||||
example = {
|
||||
name = "my_db";
|
||||
host = "db.domain.com";
|
||||
user = "my_user";
|
||||
password-file = /path/to/some/file.pw;
|
||||
siteOpts = { name, ... }:
|
||||
with types; {
|
||||
options = {
|
||||
title = mkOption {
|
||||
type = str;
|
||||
description = "Webmail site title";
|
||||
example = "My Webmail";
|
||||
};
|
||||
default = null;
|
||||
};
|
||||
|
||||
admin-email = mkOption {
|
||||
type = str;
|
||||
description = "Email of administrator of this site.";
|
||||
default = "admin@fudo.org";
|
||||
debug = mkOption {
|
||||
type = bool;
|
||||
description = "Turn debug logs on.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
mail-server = mkOption {
|
||||
type = str;
|
||||
description = "Mail server from which to send & recieve email.";
|
||||
default = "mail.fudo.org";
|
||||
};
|
||||
|
||||
favicon = mkOption {
|
||||
type = str;
|
||||
description = "URL of the site favicon";
|
||||
example = "https://www.somepage.com/fav.ico";
|
||||
};
|
||||
|
||||
messages-per-page = mkOption {
|
||||
type = int;
|
||||
description = "Default number of messages to show per page";
|
||||
default = 30;
|
||||
};
|
||||
|
||||
max-upload-size = mkOption {
|
||||
type = int;
|
||||
description = "Size limit in MB for uploaded files";
|
||||
default = 30;
|
||||
};
|
||||
|
||||
theme = mkOption {
|
||||
type = str;
|
||||
description = "Default theme to use for this webmail site.";
|
||||
default = "Default";
|
||||
};
|
||||
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = "Domain for which the server acts as webmail server";
|
||||
};
|
||||
|
||||
edit-mode = mkOption {
|
||||
type = enum [ "Plain" "Html" "PlainForced" "HtmlForced" ];
|
||||
description = "Default text editing mode for email";
|
||||
default = "Html";
|
||||
};
|
||||
|
||||
layout-mode = mkOption {
|
||||
type = enum [ "side" "bottom" ];
|
||||
description = "Layout mode to use for email preview.";
|
||||
default = "side";
|
||||
};
|
||||
|
||||
enable-threading = mkOption {
|
||||
type = bool;
|
||||
description = "Whether to enable threading for email.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
enable-mobile = mkOption {
|
||||
type = bool;
|
||||
description = "Whether to enable a mobile site view.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
database = mkOption {
|
||||
type = nullOr (submodule databaseOpts);
|
||||
description = "Database configuration for storing contact data.";
|
||||
example = {
|
||||
name = "my_db";
|
||||
host = "db.domain.com";
|
||||
user = "my_user";
|
||||
password-file = /path/to/some/file.pw;
|
||||
};
|
||||
default = null;
|
||||
};
|
||||
|
||||
admin-email = mkOption {
|
||||
type = str;
|
||||
description = "Email of administrator of this site.";
|
||||
default = "admin@fudo.org";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
databaseOpts = { ... }: with types; {
|
||||
options = {
|
||||
type = mkOption {
|
||||
type = enum [ "pgsql" "mysql" ];
|
||||
description = "Driver to use when connecting to the database.";
|
||||
default = "pgsql";
|
||||
};
|
||||
databaseOpts = { ... }:
|
||||
with types; {
|
||||
options = {
|
||||
type = mkOption {
|
||||
type = enum [ "pgsql" "mysql" ];
|
||||
description = "Driver to use when connecting to the database.";
|
||||
default = "pgsql";
|
||||
};
|
||||
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Name of host running the database.";
|
||||
example = "my-db.domain.com";
|
||||
};
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Name of host running the database.";
|
||||
example = "my-db.domain.com";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = int;
|
||||
description = "Port on which the database server is listening.";
|
||||
default = 5432;
|
||||
};
|
||||
port = mkOption {
|
||||
type = int;
|
||||
description = "Port on which the database server is listening.";
|
||||
default = 5432;
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Name of the database containing contact info. <user> must have access.";
|
||||
default = "rainloop_webmail";
|
||||
};
|
||||
name = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Name of the database containing contact info. <user> must have access.";
|
||||
default = "rainloop_webmail";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which to connect to the database.";
|
||||
default = "webmail";
|
||||
};
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which to connect to the database.";
|
||||
default = "webmail";
|
||||
};
|
||||
|
||||
password-file = mkOption {
|
||||
type = nullOr str;
|
||||
description = ''
|
||||
Password to use when connecting to the database.
|
||||
password-file = mkOption {
|
||||
type = nullOr str;
|
||||
description = ''
|
||||
Password to use when connecting to the database.
|
||||
|
||||
If unset, a random password will be generated.
|
||||
'';
|
||||
If unset, a random password will be generated.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
in {
|
||||
options.fudo.webmail = with types; {
|
||||
|
@ -211,6 +212,11 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
state-directory = mkOption {
|
||||
type = str;
|
||||
description = "The path at which to store server state.";
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = str;
|
||||
description = "User as which webmail will run.";
|
||||
|
@ -240,9 +246,8 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
security.acme.certs = mapAttrs
|
||||
(site: site-cfg: { email = site-cfg.admin-email; })
|
||||
cfg.sites;
|
||||
security.acme.certs =
|
||||
mapAttrs (site: site-cfg: { email = site-cfg.admin-email; }) cfg.sites;
|
||||
|
||||
services = {
|
||||
phpfpm = {
|
||||
|
@ -298,11 +303,12 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
fudo.secrets.host-secrets.${hostname} = concatMapAttrs
|
||||
(site: site-cfg: let
|
||||
fudo.secrets.host-secrets.${hostname} = concatMapAttrs (site: site-cfg:
|
||||
let
|
||||
|
||||
site-config-file = builtins.toFile "${site}-rainloop.cfg"
|
||||
(import ./include/rainloop.nix lib site site-cfg site-packages.${site}.version);
|
||||
(import ./include/rainloop.nix lib site site-cfg
|
||||
site-packages.${site}.version);
|
||||
|
||||
domain-config-file = builtins.toFile "${site}-domain.cfg" ''
|
||||
imap_host = "${site-cfg.mail-server}"
|
||||
|
@ -325,13 +331,13 @@ in {
|
|||
in {
|
||||
"${site}-site-config" = {
|
||||
source-file = site-config-file;
|
||||
target-file = "/var/run/webmail/rainloop/site-${site}-rainloop.cfg";
|
||||
target-file = "/run/webmail/rainloop/site-${site}-rainloop.cfg";
|
||||
user = cfg.user;
|
||||
};
|
||||
|
||||
"${site}-domain-config" = {
|
||||
source-file = domain-config-file;
|
||||
target-file = "/var/run/webmail/rainloop/domain-${site}-rainloop.cfg";
|
||||
target-file = "/run/webmail/rainloop/domain-${site}-rainloop.cfg";
|
||||
user = cfg.user;
|
||||
};
|
||||
}) cfg.sites;
|
||||
|
@ -341,8 +347,10 @@ in {
|
|||
webmail-init = let
|
||||
link-configs = concatStringsSep "\n" (mapAttrsToList (site: site-cfg:
|
||||
let
|
||||
cfg-file = config.fudo.secrets.host-secrets.${hostname}."${site}-site-config".target-file;
|
||||
domain-cfg-file = config.fudo.secrets.host-secrets.${hostname}."${site}-domain-config".target-file;
|
||||
cfg-file =
|
||||
config.fudo.secrets.host-secrets.${hostname}."${site}-site-config".target-file;
|
||||
domain-cfg-file =
|
||||
config.fudo.secrets.host-secrets.${hostname}."${site}-domain-config".target-file;
|
||||
in ''
|
||||
${pkgs.coreutils}/bin/mkdir -p ${base-data-path}/${site}/_data_/_default_/configs
|
||||
${pkgs.coreutils}/bin/cp ${cfg-file} ${base-data-path}/${site}/_data_/_default_/configs/application.ini
|
||||
|
|
|
@ -1,27 +1,32 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
zoneOpts =
|
||||
import ../types/zone-definition.nix { inherit lib; };
|
||||
let zoneOpts = import ../types/zone-definition.nix { inherit lib; };
|
||||
in {
|
||||
options.fudo.zones = with types; mkOption {
|
||||
type = attrsOf (submodule zoneOpts);
|
||||
description = "A map of network zone to zone definition.";
|
||||
default = { };
|
||||
};
|
||||
options.fudo.zones = with types;
|
||||
mkOption {
|
||||
type = attrsOf (submodule zoneOpts);
|
||||
description = "A map of network zone to zone definition.";
|
||||
default = { };
|
||||
};
|
||||
|
||||
config = let
|
||||
domain-name = config.instance.local-domain;
|
||||
# FIXME: ipv6?
|
||||
local-networks = config.instance.local-networks;
|
||||
net-names = map (network: "ipv4:${network}")
|
||||
local-networks;
|
||||
local-net-string = concatStringsSep " " net-names;
|
||||
in {
|
||||
fudo.zones.${domain-name}.verbatim-dns-records = [
|
||||
''@ IN TXT "v=spf1 mx ${local-net-string} -all"''
|
||||
''@ IN SPF "v=spf1 mx ${local-net-string} -all"''
|
||||
];
|
||||
};
|
||||
# config = let
|
||||
# domainName = config.instance.local-domain;
|
||||
# zoneName = config.fudo.domains."${domainName}".zone;
|
||||
# isLocal = network: hasPrefix "::1/" network || hasPrefix "127." network;
|
||||
# localNetworks =
|
||||
# filter (network: !(isLocal network)) config.instance.local-networks;
|
||||
# makeName = network:
|
||||
# if !isNull (builtins.match ":" network) then
|
||||
# "ip6:${network}"
|
||||
# else
|
||||
# "ip4:${network}";
|
||||
# netNames = map makeName localNetworks;
|
||||
# localNetString = concatStringsSep " " netNames;
|
||||
# in {
|
||||
# fudo.zones."${zoneName}".verbatim-dns-records = [
|
||||
# ''@ IN TXT "v=spf1 mx ${localNetString} -all"''
|
||||
# ''@ IN SPF "v=spf1 mx ${localNetString} -all"''
|
||||
# ];
|
||||
# };
|
||||
}
|
||||
|
|
|
@ -4,47 +4,38 @@ with lib;
|
|||
let
|
||||
cfg = config.informis.cl-gemini;
|
||||
|
||||
feedOpts = { ... }: with types; {
|
||||
options = {
|
||||
url = mkOption {
|
||||
type = str;
|
||||
description = "Base URI of the feed, i.e. the URI corresponding to the feed path.";
|
||||
example = "gemini://my.server/path/to/feedfiles";
|
||||
};
|
||||
feedOpts = { ... }:
|
||||
with types; {
|
||||
options = {
|
||||
url = mkOption {
|
||||
type = str;
|
||||
description =
|
||||
"Base URI of the feed, i.e. the URI corresponding to the feed path.";
|
||||
example = "gemini://my.server/path/to/feedfiles";
|
||||
};
|
||||
|
||||
title = mkOption {
|
||||
type = str;
|
||||
description = "Title of given feed.";
|
||||
example = "My Fancy Feed";
|
||||
};
|
||||
title = mkOption {
|
||||
type = str;
|
||||
description = "Title of given feed.";
|
||||
example = "My Fancy Feed";
|
||||
};
|
||||
|
||||
path = mkOption {
|
||||
type = str;
|
||||
description = "Path to Gemini files making up the feed.";
|
||||
example = "/path/to/feed";
|
||||
path = mkOption {
|
||||
type = str;
|
||||
description = "Path to Gemini files making up the feed.";
|
||||
example = "/path/to/feed";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
ensure-certificates = hostname: user: key: cert: pkgs.writeShellScript "ensure-gemini-certificates.sh" ''
|
||||
if [[ ! -e ${key} ]]; then
|
||||
TARGET_CERT_DIR=$(${pkgs.coreutils}/bin/dirname ${cert})
|
||||
TARGET_KEY_DIR=$(${pkgs.coreutils}/bin/dirname ${key})
|
||||
if [[ ! -d $TARGET_CERT_DIR ]]; then mkdir -p $TARGET_CERT_DIR; fi
|
||||
if [[ ! -d $TARGET_KEY_DIR ]]; then mkdir -p $TARGET_KEY_DIR; fi
|
||||
${pkgs.openssl}/bin/openssl req -new -subj "/CN=.${hostname}" -addext "subjectAltName = DNS:${hostname}, DNS:.${hostname}" -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -days 3650 -nodes -out ${cert} -keyout ${key}
|
||||
${pkgs.coreutils}/bin/chown -R ${user}:nogroup ${cert}
|
||||
${pkgs.coreutils}/bin/chown -R ${user}:nogroup ${key}
|
||||
${pkgs.coreutils}/bin/chmod 0444 ${cert}
|
||||
${pkgs.coreutils}/bin/chmod 0400 ${key}
|
||||
fi
|
||||
'';
|
||||
|
||||
generate-feeds = feeds:
|
||||
let
|
||||
feed-strings = mapAttrsToList (feed-name: opts:
|
||||
"(cl-gemini:register-feed :name \"${feed-name}\" :title \"${opts.title}\" :path \"${opts.path}\" :base-uri \"${opts.url}\")") feeds;
|
||||
in pkgs.writeText "gemini-local-feeds.lisp" (concatStringsSep "\n" feed-strings);
|
||||
''
|
||||
(cl-gemini:register-feed :name "${feed-name}" :title "${opts.title}" :path "${opts.path}" :base-uri "${opts.url}")'')
|
||||
feeds;
|
||||
in pkgs.writeText "gemini-local-feeds.lisp"
|
||||
(concatStringsSep "\n" feed-strings);
|
||||
|
||||
in {
|
||||
options.informis.cl-gemini = with types; {
|
||||
|
@ -58,7 +49,8 @@ in {
|
|||
|
||||
hostname = mkOption {
|
||||
type = str;
|
||||
description = "Hostname at which the server is available (for generating the SSL certificate).";
|
||||
description =
|
||||
"Hostname at which the server is available (for generating the SSL certificate).";
|
||||
example = "my.hostname.com";
|
||||
};
|
||||
|
||||
|
@ -108,7 +100,8 @@ in {
|
|||
|
||||
feeds = mkOption {
|
||||
type = attrsOf (submodule feedOpts);
|
||||
description = "Feeds to generate and make available (as eg. /feed/name.xml).";
|
||||
description =
|
||||
"Feeds to generate and make available (as eg. /feed/name.xml).";
|
||||
example = {
|
||||
diary = {
|
||||
title = "My Diary";
|
||||
|
@ -116,7 +109,7 @@ in {
|
|||
url = "gemini://my.host/blog-path/";
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
|
||||
textfiles-archive = mkOption {
|
||||
|
@ -141,18 +134,22 @@ in {
|
|||
|
||||
systemd.services = {
|
||||
cl-gemini = {
|
||||
description = "cl-gemini Gemini server (https://gemini.circumlunar.space/)";
|
||||
description =
|
||||
"cl-gemini Gemini server (https://gemini.circumlunar.space/)";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStartPre = "${ensure-certificates cfg.hostname cfg.user cfg.ssl-private-key cfg.ssl-certificate}";
|
||||
ExecStart = "${pkgs.cl-gemini}/bin/launch-server.sh";
|
||||
ExecStartPre =
|
||||
"${ensure-certificates cfg.hostname cfg.user cfg.ssl-private-key
|
||||
cfg.ssl-certificate}";
|
||||
ExecStart = "${pkgs.cl-gemini}/bin/cl-gemini-launcher";
|
||||
Restart = "on-failure";
|
||||
PIDFile = "/run/cl-gemini.$USERNAME.uid";
|
||||
User = cfg.user;
|
||||
};
|
||||
|
||||
environment = {
|
||||
GEMINI_SLYNK_PORT = mkIf (cfg.slynk-port != null) (toString cfg.slynk-port);
|
||||
GEMINI_SLYNK_PORT =
|
||||
mkIf (cfg.slynk-port != null) (toString cfg.slynk-port);
|
||||
GEMINI_LISTEN_IP = cfg.server-ip;
|
||||
GEMINI_PRIVATE_KEY = cfg.ssl-private-key;
|
||||
GEMINI_CERTIFICATE = cfg.ssl-certificate;
|
||||
|
@ -161,14 +158,11 @@ in {
|
|||
GEMINI_TEXTFILES_ROOT = cfg.textfiles-archive;
|
||||
GEMINI_FEEDS = "${generate-feeds cfg.feeds}";
|
||||
|
||||
CL_SOURCE_REGISTRY = pkgs.lib.lisp.lisp-source-registry pkgs.cl-gemini;
|
||||
CL_SOURCE_REGISTRY =
|
||||
pkgs.lib.lisp.lisp-source-registry pkgs.cl-gemini;
|
||||
};
|
||||
|
||||
path = with pkgs; [
|
||||
gcc
|
||||
file
|
||||
getent
|
||||
];
|
||||
path = with pkgs; [ gcc file getent ];
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
|
182
lib/lib/dns.nix
182
lib/lib/dns.nix
|
@ -7,16 +7,17 @@ let
|
|||
pthru = obj: builtins.trace obj obj;
|
||||
|
||||
remove-blank-lines = str:
|
||||
concatStringsSep "\n\n"
|
||||
(filter builtins.isString
|
||||
(builtins.split "\n\n\n+" str));
|
||||
concatStringsSep "\n\n" (filter builtins.isString (builtins.split ''
|
||||
|
||||
n-spaces = n:
|
||||
concatStringsSep "" (builtins.genList (_: " ") n);
|
||||
|
||||
pad-to-length = strlen: str: let
|
||||
spaces = n-spaces (strlen - (stringLength str));
|
||||
in str + spaces;
|
||||
|
||||
+'' str));
|
||||
|
||||
n-spaces = n: concatStringsSep "" (builtins.genList (_: " ") n);
|
||||
|
||||
pad-to-length = strlen: str:
|
||||
let spaces = n-spaces (strlen - (stringLength str));
|
||||
in str + spaces;
|
||||
|
||||
record-matcher = builtins.match "^([^;].*) IN ([A-Z][A-Z0-9]*) (.+)$";
|
||||
|
||||
|
@ -24,32 +25,35 @@ let
|
|||
|
||||
max-int = foldr (a: b: if (a < b) then b else a) 0;
|
||||
|
||||
make-zone-formatter = zonedata: let
|
||||
lines = splitString "\n" zonedata;
|
||||
records = filter is-record lines;
|
||||
split-records = map record-matcher records;
|
||||
index-strlen = i: record: stringLength (elemAt record i);
|
||||
record-index-maxlen = i: max-int (map (index-strlen i) split-records);
|
||||
in record-formatter (record-index-maxlen 0) (record-index-maxlen 1);
|
||||
make-zone-formatter = zonedata:
|
||||
let
|
||||
lines = splitString "\n" zonedata;
|
||||
records = filter is-record lines;
|
||||
split-records = map record-matcher records;
|
||||
index-strlen = i: record: stringLength (elemAt record i);
|
||||
record-index-maxlen = i: max-int (map (index-strlen i) split-records);
|
||||
in record-formatter (record-index-maxlen 0) (record-index-maxlen 1);
|
||||
|
||||
record-formatter = name-max: type-max: let
|
||||
name-padder = pad-to-length name-max;
|
||||
type-padder = pad-to-length type-max;
|
||||
in record-line: let
|
||||
record-parts = record-matcher record-line;
|
||||
in
|
||||
if (record-parts == null) then
|
||||
record-formatter = name-max: type-max:
|
||||
let
|
||||
name-padder = pad-to-length name-max;
|
||||
type-padder = pad-to-length type-max;
|
||||
in record-line:
|
||||
let record-parts = record-matcher record-line;
|
||||
in if (record-parts == null) then
|
||||
record-line
|
||||
else (let
|
||||
name = elemAt record-parts 0;
|
||||
type = elemAt record-parts 1;
|
||||
data = elemAt record-parts 2;
|
||||
in "${name-padder name} IN ${type-padder type} ${data}");
|
||||
else
|
||||
(let
|
||||
name = elemAt record-parts 0;
|
||||
type = elemAt record-parts 1;
|
||||
data = elemAt record-parts 2;
|
||||
in "${name-padder name} IN ${type-padder type} ${data}");
|
||||
|
||||
format-zone = zonedata: let
|
||||
formatter = make-zone-formatter zonedata;
|
||||
lines = splitString "\n" zonedata;
|
||||
in concatStringsSep "\n" (map formatter lines);
|
||||
format-zone = zonedata:
|
||||
let
|
||||
formatter = make-zone-formatter zonedata;
|
||||
lines = splitString "\n" zonedata;
|
||||
in concatStringsSep "\n" (map formatter lines);
|
||||
|
||||
makeSrvRecords = protocol: service: records:
|
||||
join-lines (map (record:
|
||||
|
@ -61,13 +65,10 @@ let
|
|||
join-lines (mapAttrsToList (makeSrvRecords protocol) services);
|
||||
|
||||
makeMetricRecords = metric-type: records:
|
||||
join-lines
|
||||
(map (record:
|
||||
"${metric-type}._metrics._tcp IN SRV ${
|
||||
toString record.priority
|
||||
} ${
|
||||
toString record.weight
|
||||
} ${toString record.port} ${record.host}.") records);
|
||||
join-lines (map (record:
|
||||
"${metric-type}._metrics._tcp IN SRV ${toString record.priority} ${
|
||||
toString record.weight
|
||||
} ${toString record.port} ${record.host}.") records);
|
||||
|
||||
srvRecordOpts = with types; {
|
||||
options = {
|
||||
|
@ -96,33 +97,31 @@ let
|
|||
};
|
||||
};
|
||||
|
||||
hostRecords = hostname: nethost-data: let
|
||||
sshfp-records = map (sshfp: "${hostname} IN SSHFP ${sshfp}")
|
||||
nethost-data.sshfp-records;
|
||||
a-record = optional (nethost-data.ipv4-address != null)
|
||||
"${hostname} IN A ${nethost-data.ipv4-address}";
|
||||
aaaa-record = optional (nethost-data.ipv6-address != null)
|
||||
"${hostname} IN AAAA ${nethost-data.ipv6-address}";
|
||||
description-record = optional (nethost-data.description != null)
|
||||
''${hostname} IN TXT "${nethost-data.description}"'';
|
||||
in join-lines (a-record ++
|
||||
aaaa-record ++
|
||||
sshfp-records ++
|
||||
description-record);
|
||||
hostRecords = hostname: nethost-data:
|
||||
let
|
||||
sshfp-records =
|
||||
map (sshfp: "${hostname} IN SSHFP ${sshfp}") nethost-data.sshfp-records;
|
||||
a-record = optional (nethost-data.ipv4-address != null)
|
||||
"${hostname} IN A ${nethost-data.ipv4-address}";
|
||||
aaaa-record = optional (nethost-data.ipv6-address != null)
|
||||
"${hostname} IN AAAA ${nethost-data.ipv6-address}";
|
||||
description-record = optional (nethost-data.description != null)
|
||||
''${hostname} IN TXT "${nethost-data.description}"'';
|
||||
in join-lines
|
||||
(a-record ++ aaaa-record ++ sshfp-records ++ description-record);
|
||||
|
||||
cnameRecord = alias: host: "${alias} IN CNAME ${host}";
|
||||
|
||||
dmarcRecord = dmarc-email:
|
||||
optionalString (dmarc-email != null)
|
||||
''_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"'';
|
||||
optionalString (dmarc-email != null) ''
|
||||
_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"'';
|
||||
|
||||
mxRecords = mxs: map (mx: "@ IN MX 10 ${mx}.") mxs;
|
||||
|
||||
nsRecords = map (ns-host: "@ IN NS ${ns-host}");
|
||||
|
||||
flatmapAttrsToList = f: attrs:
|
||||
foldr (a: b: a ++ b) [] (mapAttrsToList f attrs);
|
||||
|
||||
foldr (a: b: a ++ b) [ ] (mapAttrsToList f attrs);
|
||||
|
||||
srvRecordPair = domain: protocol: service: record: {
|
||||
"_${service}._${protocol}.${domain}" =
|
||||
|
@ -131,38 +130,44 @@ let
|
|||
} ${record.host}.";
|
||||
};
|
||||
|
||||
domain-records = dom: zone: ''
|
||||
$ORIGIN ${dom}.
|
||||
$TTL ${zone.default-ttl}
|
||||
domain-records = dom: zone:
|
||||
let
|
||||
defaultHostRecords = optionalString (zone.default-host != null)
|
||||
(hostRecords "@" zone.default-host);
|
||||
in ''
|
||||
$ORIGIN ${dom}.
|
||||
$TTL ${zone.default-ttl}
|
||||
|
||||
${optionalString (zone.default-host != null)
|
||||
"@ IN A ${zone.default-host}"}
|
||||
${defaultHostRecords}
|
||||
|
||||
${join-lines (mxRecords zone.mx)}
|
||||
${join-lines (mxRecords zone.mx)}
|
||||
|
||||
${dmarcRecord zone.dmarc-report-address}
|
||||
${dmarcRecord zone.dmarc-report-address}
|
||||
|
||||
${optionalString (zone.gssapi-realm != null)
|
||||
${optionalString (zone.gssapi-realm != null)
|
||||
''_kerberos IN TXT "${zone.gssapi-realm}"''}
|
||||
|
||||
${join-lines (nsRecords zone.nameservers)}
|
||||
${join-lines (nsRecords zone.nameservers)}
|
||||
|
||||
${join-lines (mapAttrsToList makeSrvProtocolRecords zone.srv-records)}
|
||||
${join-lines (mapAttrsToList makeSrvProtocolRecords zone.srv-records)}
|
||||
|
||||
${join-lines (mapAttrsToList makeMetricRecords zone.metric-records)}
|
||||
${join-lines (mapAttrsToList makeMetricRecords zone.metric-records)}
|
||||
|
||||
$TTL ${zone.host-record-ttl}
|
||||
$TTL ${zone.host-record-ttl}
|
||||
|
||||
${join-lines (mapAttrsToList hostRecords zone.hosts)}
|
||||
${join-lines (mapAttrsToList hostRecords zone.hosts)}
|
||||
|
||||
${join-lines (mapAttrsToList cnameRecord zone.aliases)}
|
||||
${join-lines (mapAttrsToList cnameRecord zone.aliases)}
|
||||
|
||||
${join-lines zone.verbatim-dns-records}
|
||||
${join-lines zone.verbatim-dns-records}
|
||||
|
||||
${join-lines (mapAttrsToList
|
||||
(subdom: subdomCfg: domain-records "${subdom}.${dom}" subdomCfg)
|
||||
zone.subdomains)}
|
||||
'';
|
||||
${join-lines (mapAttrsToList
|
||||
(subdom: subdomCfg: domain-records "${subdom}.${dom}" subdomCfg)
|
||||
zone.subdomains)}
|
||||
'';
|
||||
|
||||
concatMapAttrs = f: attrs:
|
||||
concatMap (x: x) (mapAttrsToList (key: val: f key val) attrs);
|
||||
|
||||
in rec {
|
||||
|
||||
|
@ -171,27 +176,24 @@ in rec {
|
|||
srvRecordsToBindZone = srvRecords:
|
||||
join-lines (mapAttrsToList makeSrvProtocolRecords srvRecords);
|
||||
|
||||
concatMapAttrs = f: attrs:
|
||||
concatMap (x: x) (mapAttrsToList (key: val: f key val) attrs);
|
||||
|
||||
srvRecordsToPairs = domain: srvRecords:
|
||||
srvRecordsToPairs = domain: srvRecords:
|
||||
listToAttrs (concatMapAttrs (protocol: services:
|
||||
concatMapAttrs
|
||||
(service: records: map (srvRecordPair domain protocol service) records) services)
|
||||
srvRecords);
|
||||
(service: records: map (srvRecordPair domain protocol service) records)
|
||||
services) srvRecords);
|
||||
|
||||
zoneToZonefile = timestamp: dom: zone:
|
||||
remove-blank-lines (format-zone ''
|
||||
$ORIGIN ${dom}.
|
||||
$TTL ${zone.default-ttl}
|
||||
$ORIGIN ${dom}.
|
||||
$TTL ${zone.default-ttl}
|
||||
|
||||
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
|
||||
${toString timestamp}
|
||||
30m
|
||||
2m
|
||||
3w
|
||||
5m)
|
||||
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
|
||||
${toString timestamp}
|
||||
30m
|
||||
2m
|
||||
3w
|
||||
5m)
|
||||
|
||||
${domain-records dom zone}
|
||||
'');
|
||||
${domain-records dom zone}
|
||||
'');
|
||||
}
|
||||
|
|
|
@ -39,8 +39,10 @@ in rec {
|
|||
in foldr (a: b: a + b) 0 (imap0 (i: el: (leftShift el (i * 8))) els);
|
||||
|
||||
intToIpv4 = int:
|
||||
concatStringsSep "."
|
||||
(map (i: toString (bitAnd (rightShift int (i * 8)) 255)) [ 3 2 1 0 ]);
|
||||
let
|
||||
toComponent = i: toString (bitAnd (rightShift int (i * 8)) 255);
|
||||
components = map toComponent [ 3 2 1 0 ];
|
||||
in concatStringsSep "." components;
|
||||
|
||||
maskFromV32Network = network:
|
||||
let
|
||||
|
@ -49,7 +51,7 @@ in rec {
|
|||
in intToIpv4
|
||||
(leftShift (rightShift fullMask insignificantBits) insignificantBits);
|
||||
|
||||
networkMinIp = network: intToIpv4 (1 + (ipv4ToInt (getNetworkBase network)));
|
||||
networkMinIp = network: intToIpv4 (ipv4ToInt (getNetworkBase network));
|
||||
|
||||
networkMaxIp = network:
|
||||
intToIpv4 (rightPadBits (ipv4ToInt (getNetworkBase network))
|
||||
|
@ -57,15 +59,14 @@ in rec {
|
|||
|
||||
# To avoid broadcast IP...
|
||||
networkMaxButOneIp = network:
|
||||
intToIpv4 ((rightPadBits (ipv4ToInt (getNetworkBase network))
|
||||
(32 - (getNetworkMask network))) - 1);
|
||||
intToIpv4 ((ipv4ToInt (networkMaxIp network)) - 1);
|
||||
|
||||
ipv4OnNetwork = ip: network:
|
||||
let
|
||||
ip-int = ipv4ToInt ip;
|
||||
net-min = networkMinIp network;
|
||||
net-max = networkMaxIp network;
|
||||
in (ip-int >= networkMinIp) && (ip-int <= networkMaxIp);
|
||||
netMin = networkMinIp network;
|
||||
netMax = networkMaxIp network;
|
||||
in (ip-int >= (ipv4ToInt netMin)) && (ip-int <= (ipv4ToInt netMax));
|
||||
|
||||
getNetworkMask = network: toInt (elemAt (splitString "/" network) 1);
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
{ pkgs, ... }:
|
||||
|
||||
with pkgs.lib;
|
||||
rec {
|
||||
gather-dependencies = pkg: unique (pkg.propagatedBuildInputs ++ (concatMap gather-dependencies pkg.propagatedBuildInputs));
|
||||
|
||||
lisp-source-registry = pkg: concatStringsSep ":" (map (p: "${p}//") (gather-dependencies pkg));
|
||||
with pkgs.lib; rec {
|
||||
gather-dependencies = pkg:
|
||||
unique (pkg.propagatedBuildInputs
|
||||
++ (concatMap gather-dependencies pkg.propagatedBuildInputs));
|
||||
|
||||
lisp-source-registry = pkg:
|
||||
concatStringsSep ":" (map (p: "${p}//") (gather-dependencies pkg));
|
||||
}
|
||||
|
|
|
@ -2,54 +2,55 @@
|
|||
|
||||
with pkgs.lib;
|
||||
let
|
||||
hash-ldap-passwd-pkg = name: passwd-file: pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-ldap-passwd";
|
||||
hash-ldap-passwd-pkg = name: passwd-file:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-ldap-passwd";
|
||||
|
||||
phases = [ "installPhase" ];
|
||||
phases = [ "installPhase" ];
|
||||
|
||||
buildInputs = with pkgs; [ openldap ];
|
||||
buildInputs = with pkgs; [ openldap ];
|
||||
|
||||
installPhase = let
|
||||
passwd = removeSuffix "\n" (readFile passwd-file);
|
||||
in ''
|
||||
slappasswd -s ${passwd} | tr -d '\n' > $out
|
||||
'';
|
||||
};
|
||||
installPhase = let passwd = removeSuffix "\n" (readFile passwd-file);
|
||||
in ''
|
||||
slappasswd -s ${passwd} | tr -d '\n' > $out
|
||||
'';
|
||||
};
|
||||
|
||||
hash-ldap-passwd = name: passwd-file:
|
||||
readFile "${hash-ldap-passwd-pkg name passwd-file}";
|
||||
|
||||
generate-random-passwd = name: length: pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-random-passwd";
|
||||
generate-random-passwd = name: length:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-random-passwd";
|
||||
|
||||
phases = [ "installPhase" ];
|
||||
phases = [ "installPhase" ];
|
||||
|
||||
buildInputs = with pkgs; [ pwgen ];
|
||||
buildInputs = with pkgs; [ pwgen ];
|
||||
|
||||
installPhase = ''
|
||||
pwgen --secure --num-passwords=1 ${toString length} | tr -d '\n' > $out
|
||||
'';
|
||||
};
|
||||
installPhase = ''
|
||||
pwgen --secure --num-passwords=1 ${toString length} | tr -d '\n' > $out
|
||||
'';
|
||||
};
|
||||
|
||||
bcrypt-passwd-pkg = name: passwd-file: pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-bcrypt";
|
||||
bcrypt-passwd-pkg = name: passwd-file:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-bcrypt";
|
||||
|
||||
phases = [ "installPhase" ];
|
||||
phases = [ "installPhase" ];
|
||||
|
||||
buildInputs = with pkgs; [ apacheHttpd ];
|
||||
buildInputs = with pkgs; [ apacheHttpd ];
|
||||
|
||||
installPhase = let
|
||||
passwd = removeSuffix "\n" (readFile passwd-file);
|
||||
in ''
|
||||
htpasswd -bnBC 10 "" ${passwd} | tr -d ':\n' | sed 's/$2y/$2a/' > $out
|
||||
'';
|
||||
};
|
||||
installPhase = let passwd = removeSuffix "\n" (readFile passwd-file);
|
||||
in ''
|
||||
htpasswd -bnBC 10 "" ${passwd} | tr -d ':\n' | sed 's/$2y/$2a/' > $out
|
||||
'';
|
||||
};
|
||||
|
||||
bcrypt-passwd = name: passwd-file:
|
||||
readFile "${bcrypt-passwd-pkg name passwd-file}";
|
||||
|
||||
|
||||
generate-stablerandom-passwd = name: { seed, length ? 20, ... }:
|
||||
generate-stablerandom-passwd = name:
|
||||
{ seed, length ? 20, ... }:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "${name}-stablerandom-passwd";
|
||||
|
||||
|
@ -59,7 +60,9 @@ let
|
|||
|
||||
installPhase = ''
|
||||
echo "${name}-${seed}" > seedfile
|
||||
pwgen --secure --num-passwords=1 -H seedfile ${toString length} | tr -d '\n' > $out
|
||||
pwgen --secure --num-passwords=1 -H seedfile ${
|
||||
toString length
|
||||
} | tr -d '\n' > $out
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
|
@ -5,13 +5,12 @@ let
|
|||
get-basename-without-hash = filename:
|
||||
head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename));
|
||||
|
||||
format-json-file = filename: pkgs.stdenv.mkDerivation {
|
||||
name = "formatted-${get-basename-without-hash filename}";
|
||||
phases = [ "installPhase" ];
|
||||
buildInputs = with pkgs; [ python ];
|
||||
installPhase = "python -mjson.tool ${filename} > $out";
|
||||
};
|
||||
format-json-file = filename:
|
||||
pkgs.stdenv.mkDerivation {
|
||||
name = "formatted-${get-basename-without-hash filename}";
|
||||
phases = [ "installPhase" ];
|
||||
buildInputs = with pkgs; [ python3 ];
|
||||
installPhase = "python -mjson.tool ${filename} > $out";
|
||||
};
|
||||
|
||||
in {
|
||||
inherit format-json-file;
|
||||
}
|
||||
in { inherit format-json-file; }
|
||||
|
|
|
@ -4,20 +4,21 @@ with lib;
|
|||
let passwd = import ../passwd.nix { inherit lib; };
|
||||
|
||||
in rec {
|
||||
encryptedFSOpts = { ... }:
|
||||
encryptedFSOpts = { name, ... }:
|
||||
let
|
||||
mountpoint = { mp, ... }: {
|
||||
mountpoint = { name, ... }: {
|
||||
options = with types; {
|
||||
mountpoint = mkOption {
|
||||
type = str;
|
||||
description = "Path at which to mount the filesystem.";
|
||||
default = mp;
|
||||
default = name;
|
||||
};
|
||||
|
||||
options = mkOption {
|
||||
type = listOf str;
|
||||
description =
|
||||
"List of filesystem options specific to this mountpoint (eg: subvol).";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
|
@ -59,6 +60,18 @@ in rec {
|
|||
'';
|
||||
};
|
||||
|
||||
type = mkOption {
|
||||
type = enum [ "luks" "luks2" ];
|
||||
description = "Type of the LUKS encryption.";
|
||||
default = "luks";
|
||||
};
|
||||
|
||||
remove-key = mkOption {
|
||||
type = bool;
|
||||
description = "Remove key once the filesystem is decrypted.";
|
||||
default = true;
|
||||
};
|
||||
|
||||
filesystem-type = mkOption {
|
||||
type = str;
|
||||
description = "Filesystem type of the decrypted filesystem.";
|
||||
|
@ -67,6 +80,7 @@ in rec {
|
|||
options = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of filesystem options with which to mount.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
mountpoints = mkOption {
|
||||
|
@ -243,6 +257,13 @@ in rec {
|
|||
default = true;
|
||||
};
|
||||
|
||||
fudo-system = mkOption {
|
||||
type = bool;
|
||||
description =
|
||||
"Whether the host is a Fudo host (i.e. needs generated keys) without being a NixOS system.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
arch = mkOption {
|
||||
type = str;
|
||||
description = "System architecture of the system.";
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
rec {
|
||||
with lib; rec {
|
||||
systemUserOpts = { name, ... }: {
|
||||
options = with lib.types; {
|
||||
username = mkOption {
|
||||
|
@ -23,93 +22,105 @@ rec {
|
|||
};
|
||||
};
|
||||
|
||||
userOpts = { name, ... }: let
|
||||
username = name;
|
||||
in {
|
||||
options = with lib.types; {
|
||||
username = mkOption {
|
||||
type = str;
|
||||
description = "The user's login name.";
|
||||
default = username;
|
||||
};
|
||||
userOpts = { name, ... }:
|
||||
let username = name;
|
||||
in {
|
||||
options = with lib.types; {
|
||||
username = mkOption {
|
||||
type = str;
|
||||
description = "The user's login name.";
|
||||
default = username;
|
||||
};
|
||||
|
||||
uid = mkOption {
|
||||
type = int;
|
||||
description = "Unique UID number for the user.";
|
||||
};
|
||||
uid = mkOption {
|
||||
type = int;
|
||||
description = "Unique UID number for the user.";
|
||||
};
|
||||
|
||||
common-name = mkOption {
|
||||
type = str;
|
||||
description = "The user's common or given name.";
|
||||
};
|
||||
common-name = mkOption {
|
||||
type = str;
|
||||
description = "The user's common or given name.";
|
||||
};
|
||||
|
||||
primary-group = mkOption {
|
||||
type = str;
|
||||
description = "Primary group to which the user belongs.";
|
||||
};
|
||||
primary-group = mkOption {
|
||||
type = str;
|
||||
description = "Primary group to which the user belongs.";
|
||||
};
|
||||
|
||||
login-shell = mkOption {
|
||||
type = nullOr shellPackage;
|
||||
description = "The user's preferred shell.";
|
||||
};
|
||||
login-shell = mkOption {
|
||||
type = nullOr shellPackage;
|
||||
description = "The user's preferred shell.";
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
type = str;
|
||||
default = "Fudo Member";
|
||||
description = "A description of this user's role.";
|
||||
};
|
||||
description = mkOption {
|
||||
type = str;
|
||||
default = "Fudo Member";
|
||||
description = "A description of this user's role.";
|
||||
};
|
||||
|
||||
ldap-hashed-passwd = mkOption {
|
||||
type = nullOr str;
|
||||
description =
|
||||
"LDAP-formatted hashed password, used for email and other services. Use slappasswd to generate the properly-formatted password.";
|
||||
default = null;
|
||||
};
|
||||
ldap-hashed-passwd = mkOption {
|
||||
type = nullOr str;
|
||||
description =
|
||||
"LDAP-formatted hashed password, used for email and other services. Use slappasswd to generate the properly-formatted password.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
login-hashed-passwd = mkOption {
|
||||
type = nullOr str;
|
||||
description =
|
||||
"Hashed password for shell, used for shell access to hosts. Use mkpasswd to generate the properly-formatted password.";
|
||||
default = null;
|
||||
};
|
||||
login-hashed-passwd = mkOption {
|
||||
type = nullOr str;
|
||||
description =
|
||||
"Hashed password for shell, used for shell access to hosts. Use mkpasswd to generate the properly-formatted password.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
ssh-authorized-keys = mkOption {
|
||||
type = listOf str;
|
||||
description = "SSH public keys this user can use to log in.";
|
||||
default = [ ];
|
||||
};
|
||||
ssh-authorized-keys = mkOption {
|
||||
type = listOf str;
|
||||
description = "SSH public keys this user can use to log in.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
home-directory = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Default home directory for the given user.";
|
||||
default = null;
|
||||
};
|
||||
home-directory = mkOption {
|
||||
type = nullOr str;
|
||||
description = "Default home directory for the given user.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
k5login = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of Kerberos principals that map to this user.";
|
||||
default = [ ];
|
||||
};
|
||||
k5login = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of Kerberos principals that map to this user.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
ssh-keys = mkOption {
|
||||
type = listOf (submodule sshKeyOpts);
|
||||
description = "Path to the user's public and private key files.";
|
||||
default = [];
|
||||
};
|
||||
ssh-keys = mkOption {
|
||||
type = listOf (submodule sshKeyOpts);
|
||||
description = "Path to the user's public and private key files.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
email = mkOption {
|
||||
type = nullOr str;
|
||||
description = "User's primary email address.";
|
||||
default = null;
|
||||
};
|
||||
email = mkOption {
|
||||
type = nullOr str;
|
||||
description = "User's primary email address.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
email-aliases = mkOption {
|
||||
type = listOf str;
|
||||
description = "Email aliases that should map to this user.";
|
||||
default = [];
|
||||
email-aliases = mkOption {
|
||||
type = listOf str;
|
||||
description = "Email aliases that should map to this user.";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
given-name = mkOption {
|
||||
type = nullOr str;
|
||||
description = "User's given name.";
|
||||
default = null;
|
||||
};
|
||||
|
||||
surname = mkOption {
|
||||
type = nullOr str;
|
||||
description = "User's surname.";
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
groupOpts = { name, ... }: {
|
||||
options = with lib.types; {
|
||||
|
|
|
@ -50,11 +50,8 @@ let
|
|||
nameservers = mkOption {
|
||||
type = listOf str;
|
||||
description = "List of zone nameservers.";
|
||||
example = [
|
||||
"ns1.fudo.org."
|
||||
"10.0.0.1"
|
||||
];
|
||||
default = [];
|
||||
example = [ "ns1.fudo.org." "10.0.0.1" ];
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
srv-records = mkOption {
|
||||
|
@ -91,12 +88,10 @@ let
|
|||
port = 443;
|
||||
}
|
||||
];
|
||||
rspamd = [
|
||||
{
|
||||
host = "mail-host.my-domain.com";
|
||||
port = 443;
|
||||
}
|
||||
];
|
||||
rspamd = [{
|
||||
host = "mail-host.my-domain.com";
|
||||
port = 443;
|
||||
}];
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
|
@ -127,7 +122,7 @@ let
|
|||
};
|
||||
|
||||
default-host = mkOption {
|
||||
type = nullOr str;
|
||||
type = nullOr (submodule networkHostOpts);
|
||||
description =
|
||||
"IP of the host which will act as the default server for this domain, if any.";
|
||||
default = null;
|
||||
|
@ -165,7 +160,7 @@ let
|
|||
subdomains = mkOption {
|
||||
type = attrsOf (submodule zoneOpts);
|
||||
description = "Subdomains of the current zone.";
|
||||
default = {};
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue