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