feat(exports): service access

This commit is contained in:
arcnmx 2024-02-19 17:34:39 -08:00
parent 91918b8061
commit 871b1c5b2d
69 changed files with 1317 additions and 509 deletions

View file

@ -0,0 +1,22 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.dnsmasq = { config, ... }: {
id = mkAlmostOptionDefault "dns";
nixos = {
serviceAttr = "dnsmasq";
};
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 53;
transport = "udp";
};
tcp = {
port = config.ports.default.port;
transport = "tcp";
};
};
};
}

View file

@ -0,0 +1,48 @@
{
config,
name,
lib,
...
}: let
inherit (lib.options) mkOption mkEnableOption;
cfg = config.exports;
systemConfig = config;
exportModule = {
config,
name,
...
}: {
options = with lib.types; {
enable = mkEnableOption "exported service";
name = mkOption {
type = str;
default = name;
};
serviceName = mkOption {
type = str;
default = name;
};
id = mkOption {
type = str;
default = cfg.services.${config.serviceName}.id/* or config.name*/;
};
};
};
in {
options.exports = with lib.types; {
exports = mkOption {
type = attrsOf (submoduleWith {
modules = [exportModule];
specialArgs = {
machine = name;
inherit systemConfig;
};
});
default = {};
};
};
config = {
_module.args.exports = cfg;
};
}

View file

@ -0,0 +1,18 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.freeipa = {
id = mkAlmostOptionDefault "freeipa";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 443;
protocol = "https";
};
redirect = {
port = 80;
protocol = "http";
};
};
};
}

View file

@ -0,0 +1,34 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.freepbx = {
id = mkAlmostOptionDefault "pbx";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
http = {
port = 80;
protocol = "http";
};
https = {
port = 443;
protocol = "https";
};
ucp = {
port = 8001;
protocol = "http";
};
ucp-ssl = {
port = 8003;
protocol = "https";
};
asterisk = {
port = 8088;
protocol = "http";
};
asterisk-ssl = {
port = 8089;
protocol = "https";
};
};
};
}

View file

@ -0,0 +1,45 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.modules) mkIf;
inherit (lib.attrsets) mapAttrs;
inherit (lib.lists) all imap0;
inherit (lib.trivial) id;
in {
config.exports.services.home-assistant = { config, ... }: let
mkAssertion = f: nixosConfig: let
cfg = nixosConfig.services.home-assistant;
in f nixosConfig cfg;
assertPort = nixosConfig: cfg: {
assertion = config.ports.default.port == cfg.config.http.server_port;
message = "port mismatch";
};
assertHomekitPort = let
portName = i: "homekit${toString i}";
mkAssertPort = i: homekit: config.ports.${portName i}.port or null == homekit.port;
in nixosConfig: cfg: {
assertion = all id (imap0 mkAssertPort cfg.config.homekit);
message = "homekit port mismatch";
};
in {
id = mkAlmostOptionDefault "home";
nixos = {
serviceAttr = "home-assistant";
assertions = mkIf config.enable [
(mkAssertion assertPort)
(mkAssertion assertHomekitPort)
];
};
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 8123;
protocol = "http";
};
homekit0 = {
port = 21063;
transport = "tcp";
};
# TODO: cast udp port range 32768 to 60999
};
};
}

View file

@ -0,0 +1,21 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.modules) mkIf;
in {
config.exports.services.invidious = { config, ... }: {
id = mkAlmostOptionDefault "yt";
nixos = {
serviceAttr = "invidious";
assertions = mkIf config.enable [
(nixosConfig: {
assertion = config.ports.default.port == nixosConfig.services.invidious.port;
message = "port mismatch";
})
];
};
ports.default = mapAlmostOptionDefaults {
port = 3000;
protocol = "http";
};
};
}

View file

@ -0,0 +1,35 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.kerberos = { config, ... }: {
id = "krb5";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 88;
transport = "tcp";
};
udp = {
port = config.ports.default.port;
transport = "udp";
};
kadmin = {
port = 749;
transport = "tcp";
};
kpasswd = {
port = 464;
transport = "tcp";
};
kpasswd-udp = {
port = config.ports.kpasswd.port;
transport = "udp";
};
ticket4 = {
enable = false;
port = 4444;
transport = "udp";
};
};
};
}

View file

@ -0,0 +1,37 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.modules) mkIf;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.keycloak = { config, ... }: {
id = mkAlmostOptionDefault "sso";
nixos = {
serviceAttr = "keycloak";
assertions = let
mkAssertion = f: nixosConfig: let
cfg = nixosConfig.services.keycloak;
in f nixosConfig cfg;
in mkIf config.enable [
(mkAssertion (nixosConfig: cfg: {
assertion = config.ports.${cfg.protocol}.port == cfg.port;
message = "port mismatch";
}))
(mkAssertion (nixosConfig: cfg: {
assertion = config.ports.${cfg.protocol}.enable;
message = "port enable mismatch";
}))
];
};
ports = mapAttrs (_: mapAlmostOptionDefaults) {
http = {
enable = !config.ports.https.enable;
port = 8080;
protocol = "http";
};
https = {
port = 8443;
protocol = "https";
};
};
};
}

View file

@ -0,0 +1,19 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.ldap = { config, ... }: {
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 389;
transport = "tcp";
};
ssl = {
port = 636;
ssl = true;
listen = "wan";
};
};
};
}

View file

@ -0,0 +1,38 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.modules) mkIf;
inherit (lib.attrsets) mapAttrs;
inherit (lib.lists) all imap0;
inherit (lib.trivial) id;
in {
config.exports.services.mosquitto = { config, ... }: {
id = mkAlmostOptionDefault "mqtt";
nixos = {
serviceAttr = "mosquitto";
assertions = mkIf config.enable [
(nixosConfig: let
cfg = nixosConfig.services.mosquitto;
portName = i:
if i == 0 then "default"
else "listener${toString i}";
mkAssertPort = i: listener: config.ports.${portName i}.port or null == listener.port;
in {
assertion = all id (imap0 mkAssertPort cfg.listeners);
message = "port mismatch";
})
];
};
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 1883;
transport = "tcp";
};
ssl = {
enable = false;
port = 8883;
ssl = true;
};
};
};
}

View file

@ -0,0 +1,18 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.motion = { config, ... }: {
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 8080;
protocol = "http";
};
stream = {
port = 8081;
protocol = "http";
};
};
};
}

View file

@ -0,0 +1,77 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.modules) mkIf;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.nfs = { config, ... }: let
mkAssertion = f: nixosConfig: let
cfg = nixosConfig.services.nfs;
in f nixosConfig cfg;
mkAssertionPort = portName: mkAssertion (nixosConfig: cfg: let
portAttr = "${portName}Port";
in {
assertion = mkAssertPort config.ports.${portName} cfg.server.${portAttr};
message = "${portAttr} mismatch";
});
mkAssertPort = port: cfgPort: let
cmpPort = if port.enable then port.port else null;
in cfgPort == cmpPort;
in {
nixos = {
serviceAttrPath = [ "services" "nfs" "server" ];
assertions = mkIf config.enable [
(mkAssertionPort "statd")
(mkAssertionPort "lockd")
(mkAssertionPort "mountd")
(mkAssertion (nixosConfig: cfg: {
assertion = nixosConfig.services.rpcbind.enable == config.ports.rpcbind.enable;
message = "rpcbind enable mismatch";
}))
];
};
# TODO: expose over wan
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 2049;
transport = "tcp";
};
udp = {
port = config.ports.default.port;
transport = "udp";
};
rpcbind = {
port = 111;
transport = "tcp";
};
rpcbind-udp = {
port = config.ports.rpcbind.port;
transport = "udp";
};
statd = {
port = 4000;
transport = "tcp";
};
statd-udp = {
port = config.ports.statd.port;
transport = "udp";
};
lockd = {
port = 4001;
transport = "tcp";
};
lockd-udp = {
port = config.ports.lockd.port;
transport = "udp";
};
mountd = {
port = 4002;
transport = "tcp";
};
mountd-udp = {
port = config.ports.mountd.port;
transport = "udp";
};
};
};
}

View file

@ -0,0 +1,43 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.plex = {
nixos.serviceAttr = "plex";
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
default = {
port = 32400;
protocol = "http";
};
roku = {
port = 8324;
transport = "tcp";
};
dlna-tcp = {
port = 32469;
transport = "tcp";
};
dlna-udp = {
port = 1900;
transport = "udp";
};
gdm0 = {
port = 32410;
transport = "udp";
};
gdm1 = {
port = 32412;
transport = "udp";
};
gdm2 = {
port = 32413;
transport = "udp";
};
gdm3 = {
port = 32414;
transport = "udp";
};
};
};
}

View file

@ -0,0 +1,27 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults;
inherit (lib.modules) mkIf;
in {
config.exports.services.postgresql = { config, ... }: let
mkAssertion = f: nixosConfig: let
cfg = nixosConfig.services.postgresql;
in f nixosConfig cfg;
in {
nixos = {
assertions = mkIf config.enable [
(mkAssertion (nixosConfig: cfg: {
assertion = config.ports.default.port == cfg.settings.port;
message = "port mismatch";
}))
(mkAssertion (nixosConfig: cfg: {
assertion = config.ports.default.enable == cfg.enableTCPIP;
message = "enableTCPIP mismatch";
}))
];
};
ports.default = mapAlmostOptionDefaults {
port = 5432;
transport = "tcp";
};
};
}

View file

@ -0,0 +1,12 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
in {
config.exports.services.proxmox = { config, ... }: {
id = mkAlmostOptionDefault "prox";
defaults.port.listen = mkAlmostOptionDefault "lan";
ports.default = mapAlmostOptionDefaults {
port = 8006;
protocol = "https";
};
};
}

View file

@ -0,0 +1,29 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.samba = {
id = mkAlmostOptionDefault "smb";
nixos.serviceAttr = "samba";
# TODO: expose over wan
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
port0 = {
port = 137;
transport = "udp";
};
port1 = {
port = 138;
transport = "udp";
};
port2 = {
port = 139;
transport = "tcp";
};
default = {
port = 445;
transport = "tcp";
};
};
};
}

View file

@ -0,0 +1,155 @@
{
config,
name,
lib,
gensokyo-zone,
...
}: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault;
inherit (lib.attrsets) mapAttrsToList getAttrFromPath;
inherit (lib.trivial) mapNullable;
inherit (lib.strings) concatStringsSep;
systemConfig = config;
portModule = {config, service, ...}: {
options = with lib.types; {
enable =
mkEnableOption "port"
// {
default = true;
};
listen = mkOption {
type = enum ["wan" "lan" "int" "localhost"];
};
protocol = mkOption {
type = nullOr (enum ["http" "https"]);
default = null;
};
transport = mkOption {
type = enum ["tcp" "udp" "unix"];
};
path = mkOption {
type = nullOr path;
default = null;
description = "unix socket path";
};
ssl = mkOption {
type = bool;
default = false;
};
port = mkOption {
type = nullOr int;
};
};
config = {
transport = mkMerge [
(mkIf (config.protocol == "http" || config.protocol == "https") (mkOptionDefault "tcp"))
(mkIf config.ssl (mkOptionDefault "tcp"))
];
ssl = mkIf (config.protocol == "https") (
mkAlmostOptionDefault true
);
listen = mkOptionDefault service.defaults.port.listen;
};
};
serviceModule = {
config,
name,
...
}: {
options = with lib.types; {
enable = mkEnableOption "hosted service";
name = mkOption {
type = str;
default = name;
};
id = mkOption {
type = str;
default = config.name;
};
ports = mkOption {
type = attrsOf (submoduleWith {
modules = [portModule];
specialArgs = {
service = config;
};
});
};
nixos = {
serviceAttr = mkOption {
type = nullOr str;
default = null;
};
serviceAttrPath = mkOption {
type = nullOr (listOf str);
};
assertions = mkOption {
type = listOf (functionTo attrs);
default = [ ];
};
};
defaults = {
port = {
listen = mkOption {
type = str;
default = "int";
};
};
};
};
config = {
nixos = {
serviceAttrPath = mkOptionDefault (
mapNullable (serviceAttr: ["services" config.nixos.serviceAttr]) config.nixos.serviceAttr
);
assertions = let
serviceConfig = getAttrFromPath config.nixos.serviceAttrPath;
mkAssertion = f: nixosConfig: let
cfg = serviceConfig nixosConfig;
in f nixosConfig cfg;
enableAssertion = nixosConfig: cfg: {
assertion = (! cfg ? enable) || (config.enable == cfg.enable);
message = "enable == nixosConfig.${concatStringsSep "." config.nixos.serviceAttrPath}.enable";
};
in [
(mkIf (config.nixos.serviceAttrPath != null) (
mkAssertion enableAssertion
))
];
};
};
};
nixosModule = {config, system, ...}: let
mapAssertion = service: a: let
res = a config;
in res // {
message = "system.exports.${service.name}: " + res.message or "assertion failed";
};
assertions = mapAttrsToList (_: service: map (mapAssertion service) service.nixos.assertions) system.exports.services;
in {
config = {
assertions = mkMerge assertions;
# TODO: export ports via firewall according to enable/listen/etc
};
};
in {
options.exports = with lib.types; {
services = mkOption {
type = attrsOf (submoduleWith {
modules = [serviceModule];
specialArgs = {
machine = name;
inherit systemConfig;
};
});
default = {};
};
};
config = {
modules = mkIf (config.type == "NixOS") [
nixosModule
];
};
}

View file

@ -0,0 +1,7 @@
{...}: {
config.exports.services.tailscale = {
id = "tail";
nixos.serviceAttr = "tailscale";
ports = {};
};
}

View file

@ -0,0 +1,47 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.attrsets) mapAttrs;
in {
config.exports.services.unifi = { config, ... }: {
nixos.serviceAttr = "unifi";
defaults.port.listen = mkAlmostOptionDefault "lan";
ports = mapAttrs (_: mapAlmostOptionDefaults) {
management = {
# remote login
port = 8443;
protocol = "https";
listen = "int";
};
uap = {
# UAP to inform controller
port = 8080;
transport = "tcp";
};
portal = {
# HTTP portal redirect, if guest portal is enabled
port = 8880;
protocol = "http";
};
portal-secure = {
# HTTPS portal redirect
port = 8843;
protocol = "https";
};
speedtest = {
# UniFi mobile speed test
port = 6789;
transport = "tcp";
};
stun = {
port = 3478;
transport = "udp";
listen = "wan";
};
discovery = {
# device discovery
port = 10001;
transport = "udp";
};
};
};
}

View file

@ -0,0 +1,22 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.modules) mkIf;
in {
config.exports.services.vouch-proxy = { config, ... }: {
id = mkAlmostOptionDefault "login";
defaults.port.listen = mkAlmostOptionDefault "localhost";
nixos = {
serviceAttr = "vouch-proxy";
assertions = mkIf config.enable [
(nixosConfig: {
assertion = config.ports.default.port == nixosConfig.services.vouch-proxy.settings.vouch.port;
message = "port mismatch";
})
];
};
ports.default = mapAlmostOptionDefaults {
port = 30746;
protocol = "http";
};
};
}

View file

@ -0,0 +1,21 @@
{lib, gensokyo-zone, ...}: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults mkAlmostOptionDefault;
inherit (lib.modules) mkIf;
in {
config.exports.services.zigbee2mqtt = { config, ... }: {
id = mkAlmostOptionDefault "z2m";
nixos = {
serviceAttr = "zigbee2mqtt";
assertions = mkIf config.enable [
(nixosConfig: {
assertion = config.ports.default.port == nixosConfig.services.zigbee2mqtt.settings.frontend.port;
message = "port mismatch";
})
];
};
ports.default = mapAlmostOptionDefaults {
port = 8072;
protocol = "http";
};
};
}