667 lines
17 KiB
Nix
667 lines
17 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
jsonFormat = pkgs.formats.json {};
|
|
|
|
lbBackendType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Backend name";
|
|
};
|
|
|
|
address = mkOption {
|
|
type = types.str;
|
|
description = "Backend IP or hostname";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
description = "Backend port";
|
|
};
|
|
|
|
weight = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Backend weight (default: 1)";
|
|
};
|
|
|
|
admin_state = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Backend admin state (enabled, disabled, drain)";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbSessionPersistenceType = types.submodule {
|
|
options = {
|
|
type = mkOption {
|
|
type = types.enum [ "source_ip" "cookie" "app_cookie" ];
|
|
description = "Persistence strategy";
|
|
};
|
|
|
|
cookie_name = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Cookie name for cookie-based persistence";
|
|
};
|
|
|
|
timeout_seconds = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Persistence timeout in seconds";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbHttpHealthType = types.submodule {
|
|
options = {
|
|
method = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "HTTP method for health checks";
|
|
};
|
|
|
|
path = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "HTTP path for health checks";
|
|
};
|
|
|
|
expected_codes = mkOption {
|
|
type = types.listOf types.int;
|
|
default = [];
|
|
description = "Expected HTTP status codes";
|
|
};
|
|
|
|
host = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Host header for HTTP health checks";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbHealthCheckType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Health check name";
|
|
};
|
|
|
|
type = mkOption {
|
|
type = types.enum [ "tcp" "http" "https" "udp" "ping" ];
|
|
description = "Health check type";
|
|
};
|
|
|
|
interval_seconds = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Interval in seconds";
|
|
};
|
|
|
|
timeout_seconds = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Timeout in seconds";
|
|
};
|
|
|
|
healthy_threshold = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Healthy threshold";
|
|
};
|
|
|
|
unhealthy_threshold = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Unhealthy threshold";
|
|
};
|
|
|
|
http = mkOption {
|
|
type = types.nullOr lbHttpHealthType;
|
|
default = null;
|
|
description = "HTTP-specific health check configuration";
|
|
};
|
|
|
|
enabled = mkOption {
|
|
type = types.nullOr types.bool;
|
|
default = null;
|
|
description = "Enable or disable health check";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbPoolType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Pool name";
|
|
};
|
|
|
|
algorithm = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Pool algorithm (round_robin, least_connections, ip_hash, weighted_round_robin, random, maglev)";
|
|
};
|
|
|
|
protocol = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Pool protocol (tcp, udp, http, https)";
|
|
};
|
|
|
|
session_persistence = mkOption {
|
|
type = types.nullOr lbSessionPersistenceType;
|
|
default = null;
|
|
description = "Session persistence configuration";
|
|
};
|
|
|
|
backends = mkOption {
|
|
type = types.listOf lbBackendType;
|
|
default = [];
|
|
description = "Pool backends";
|
|
};
|
|
|
|
health_checks = mkOption {
|
|
type = types.listOf lbHealthCheckType;
|
|
default = [];
|
|
description = "Pool health checks";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbTlsType = types.submodule {
|
|
options = {
|
|
certificate_id = mkOption {
|
|
type = types.str;
|
|
description = "Certificate ID (FiberLB)";
|
|
};
|
|
|
|
min_version = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "TLS minimum version (tls_1_2, tls_1_3)";
|
|
};
|
|
|
|
cipher_suites = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = "TLS cipher suites";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbL7RuleType = types.submodule {
|
|
options = {
|
|
type = mkOption {
|
|
type = types.enum [ "host_name" "path" "file_type" "header" "cookie" "ssl_conn_has_sni" ];
|
|
description = "L7 rule type";
|
|
};
|
|
|
|
compare_type = mkOption {
|
|
type = types.nullOr (types.enum [ "equal_to" "regex" "starts_with" "ends_with" "contains" ]);
|
|
default = null;
|
|
description = "L7 compare type";
|
|
};
|
|
|
|
value = mkOption {
|
|
type = types.str;
|
|
description = "L7 rule match value";
|
|
};
|
|
|
|
key = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Header/cookie key for L7 rules";
|
|
};
|
|
|
|
invert = mkOption {
|
|
type = types.nullOr types.bool;
|
|
default = null;
|
|
description = "Invert the rule match";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbL7PolicyType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "L7 policy name";
|
|
};
|
|
|
|
position = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "L7 policy position (lower evaluates first)";
|
|
};
|
|
|
|
action = mkOption {
|
|
type = types.enum [ "redirect_to_pool" "redirect_to_url" "reject" ];
|
|
description = "L7 policy action";
|
|
};
|
|
|
|
redirect_url = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Redirect URL for redirect_to_url action";
|
|
};
|
|
|
|
redirect_pool = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Target pool name for redirect_to_pool action";
|
|
};
|
|
|
|
redirect_http_status_code = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Redirect or reject HTTP status code";
|
|
};
|
|
|
|
enabled = mkOption {
|
|
type = types.nullOr types.bool;
|
|
default = null;
|
|
description = "Enable or disable the policy";
|
|
};
|
|
|
|
rules = mkOption {
|
|
type = types.listOf lbL7RuleType;
|
|
default = [];
|
|
description = "L7 rules for this policy";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbListenerType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Listener name";
|
|
};
|
|
|
|
protocol = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Listener protocol (tcp, udp, http, https, terminated_https)";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
description = "Listener port";
|
|
};
|
|
|
|
default_pool = mkOption {
|
|
type = types.str;
|
|
description = "Default pool name";
|
|
};
|
|
|
|
tls = mkOption {
|
|
type = types.nullOr lbTlsType;
|
|
default = null;
|
|
description = "TLS configuration";
|
|
};
|
|
|
|
connection_limit = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Connection limit";
|
|
};
|
|
|
|
enabled = mkOption {
|
|
type = types.nullOr types.bool;
|
|
default = null;
|
|
description = "Enable or disable listener";
|
|
};
|
|
|
|
l7_policies = mkOption {
|
|
type = types.listOf lbL7PolicyType;
|
|
default = [];
|
|
description = "L7 policies for this listener";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Load balancer name";
|
|
};
|
|
|
|
org_id = mkOption {
|
|
type = types.str;
|
|
description = "Organization ID";
|
|
};
|
|
|
|
project_id = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Project ID (null for org-level)";
|
|
};
|
|
|
|
description = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Load balancer description";
|
|
};
|
|
|
|
pools = mkOption {
|
|
type = types.listOf lbPoolType;
|
|
default = [];
|
|
description = "Pools for this load balancer";
|
|
};
|
|
|
|
listeners = mkOption {
|
|
type = types.listOf lbListenerType;
|
|
default = [];
|
|
description = "Listeners for this load balancer";
|
|
};
|
|
};
|
|
};
|
|
|
|
dnsRecordType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Record name";
|
|
};
|
|
|
|
record_type = mkOption {
|
|
type = types.str;
|
|
description = "Record type (A, AAAA, CNAME, MX, TXT, SRV, NS, PTR, CAA)";
|
|
};
|
|
|
|
ttl = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "Record TTL (default: 300)";
|
|
};
|
|
|
|
data = mkOption {
|
|
type = types.attrsOf types.anything;
|
|
description = "Record data (type-specific)";
|
|
};
|
|
|
|
enabled = mkOption {
|
|
type = types.nullOr types.bool;
|
|
default = null;
|
|
description = "Enable or disable record";
|
|
};
|
|
};
|
|
};
|
|
|
|
dnsZoneType = types.submodule {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
description = "Zone name (example.com)";
|
|
};
|
|
|
|
org_id = mkOption {
|
|
type = types.str;
|
|
description = "Organization ID";
|
|
};
|
|
|
|
project_id = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Project ID (null for org-level)";
|
|
};
|
|
|
|
primary_ns = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Primary NS hostname";
|
|
};
|
|
|
|
admin_email = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Admin email for SOA";
|
|
};
|
|
|
|
refresh = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "SOA refresh";
|
|
};
|
|
|
|
retry = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "SOA retry";
|
|
};
|
|
|
|
expire = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "SOA expire";
|
|
};
|
|
|
|
minimum = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "SOA minimum";
|
|
};
|
|
|
|
records = mkOption {
|
|
type = types.listOf dnsRecordType;
|
|
default = [];
|
|
description = "Zone records";
|
|
};
|
|
};
|
|
};
|
|
|
|
dnsReverseZoneType = types.submodule {
|
|
options = {
|
|
org_id = mkOption {
|
|
type = types.str;
|
|
description = "Organization ID";
|
|
};
|
|
|
|
project_id = mkOption {
|
|
type = types.nullOr types.str;
|
|
default = null;
|
|
description = "Project ID (null for org-level)";
|
|
};
|
|
|
|
cidr = mkOption {
|
|
type = types.str;
|
|
description = "CIDR for the reverse zone";
|
|
};
|
|
|
|
ptr_pattern = mkOption {
|
|
type = types.str;
|
|
description = "PTR pattern (e.g., {4}-{3}-{2}-{1}.hosts.example.com.)";
|
|
};
|
|
|
|
ttl = mkOption {
|
|
type = types.nullOr types.int;
|
|
default = null;
|
|
description = "PTR TTL";
|
|
};
|
|
};
|
|
};
|
|
|
|
lbCfg = config.plasmacloud.lb;
|
|
dnsCfg = config.plasmacloud.dns;
|
|
|
|
lbConfigFile = jsonFormat.generate "plasmacloud-lb.json" {
|
|
load_balancers = lbCfg.loadBalancers;
|
|
};
|
|
lbConfigPath = lbCfg.configPath;
|
|
lbConfigRelative = removePrefix "/etc/" lbConfigPath;
|
|
|
|
dnsConfigFile = jsonFormat.generate "plasmacloud-dns.json" {
|
|
zones = dnsCfg.zones;
|
|
reverse_zones = dnsCfg.reverseZones;
|
|
};
|
|
dnsConfigPath = dnsCfg.configPath;
|
|
dnsConfigRelative = removePrefix "/etc/" dnsConfigPath;
|
|
|
|
in {
|
|
options.plasmacloud.lb = {
|
|
enable = mkEnableOption "PlasmaCloud load balancer declarations";
|
|
|
|
endpoint = mkOption {
|
|
type = types.str;
|
|
default = "http://127.0.0.1:7000";
|
|
description = "FiberLB gRPC endpoint";
|
|
};
|
|
|
|
loadBalancers = mkOption {
|
|
type = types.listOf lbType;
|
|
default = [];
|
|
description = "Load balancer declarations";
|
|
};
|
|
|
|
configPath = mkOption {
|
|
type = types.str;
|
|
default = "/etc/plasmacloud/lb.json";
|
|
description = "Path for rendered load balancer config";
|
|
};
|
|
|
|
applyOnBoot = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Apply declarations at boot";
|
|
};
|
|
|
|
applyOnChange = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Apply declarations when the config file changes";
|
|
};
|
|
|
|
prune = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Delete load balancer resources not declared in config";
|
|
};
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = pkgs.plasmacloud-reconciler or (throw "plasmacloud-reconciler package not found");
|
|
description = "Reconciler package for load balancer declarations";
|
|
};
|
|
};
|
|
|
|
options.plasmacloud.dns = {
|
|
enable = mkEnableOption "PlasmaCloud DNS declarations";
|
|
|
|
endpoint = mkOption {
|
|
type = types.str;
|
|
default = "http://127.0.0.1:6000";
|
|
description = "FlashDNS gRPC endpoint";
|
|
};
|
|
|
|
zones = mkOption {
|
|
type = types.listOf dnsZoneType;
|
|
default = [];
|
|
description = "DNS zone declarations";
|
|
};
|
|
|
|
reverseZones = mkOption {
|
|
type = types.listOf dnsReverseZoneType;
|
|
default = [];
|
|
description = "Reverse DNS zone declarations";
|
|
};
|
|
|
|
configPath = mkOption {
|
|
type = types.str;
|
|
default = "/etc/plasmacloud/dns.json";
|
|
description = "Path for rendered DNS config";
|
|
};
|
|
|
|
applyOnBoot = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Apply declarations at boot";
|
|
};
|
|
|
|
applyOnChange = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Apply declarations when the config file changes";
|
|
};
|
|
|
|
prune = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Delete DNS resources not declared in config";
|
|
};
|
|
|
|
package = mkOption {
|
|
type = types.package;
|
|
default = pkgs.plasmacloud-reconciler or (throw "plasmacloud-reconciler package not found");
|
|
description = "Reconciler package for DNS declarations";
|
|
};
|
|
};
|
|
|
|
config = mkMerge [
|
|
(mkIf lbCfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = hasPrefix "/etc/" lbConfigPath;
|
|
message = "plasmacloud.lb.configPath must be under /etc";
|
|
}
|
|
];
|
|
|
|
environment.etc."${lbConfigRelative}".source = lbConfigFile;
|
|
|
|
systemd.services.plasmacloud-lb-apply = {
|
|
description = "Apply PlasmaCloud load balancer declarations";
|
|
after = [ "network-online.target" ] ++ (optional config.services.fiberlb.enable "fiberlb.service");
|
|
wants = [ "network-online.target" ] ++ (optional config.services.fiberlb.enable "fiberlb.service");
|
|
wantedBy = optional lbCfg.applyOnBoot "multi-user.target";
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = "${lbCfg.package}/bin/plasmacloud-reconciler lb --config ${lbConfigPath} --endpoint ${lbCfg.endpoint}${optionalString lbCfg.prune " --prune"}";
|
|
};
|
|
};
|
|
|
|
systemd.paths.plasmacloud-lb-apply = mkIf lbCfg.applyOnChange {
|
|
wantedBy = [ "multi-user.target" ];
|
|
pathConfig = {
|
|
PathChanged = lbConfigPath;
|
|
};
|
|
};
|
|
})
|
|
|
|
(mkIf dnsCfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = hasPrefix "/etc/" dnsConfigPath;
|
|
message = "plasmacloud.dns.configPath must be under /etc";
|
|
}
|
|
];
|
|
|
|
environment.etc."${dnsConfigRelative}".source = dnsConfigFile;
|
|
|
|
systemd.services.plasmacloud-dns-apply = {
|
|
description = "Apply PlasmaCloud DNS declarations";
|
|
after = [ "network-online.target" ] ++ (optional config.services.flashdns.enable "flashdns.service");
|
|
wants = [ "network-online.target" ] ++ (optional config.services.flashdns.enable "flashdns.service");
|
|
wantedBy = optional dnsCfg.applyOnBoot "multi-user.target";
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
ExecStart = "${dnsCfg.package}/bin/plasmacloud-reconciler dns --config ${dnsConfigPath} --endpoint ${dnsCfg.endpoint}${optionalString dnsCfg.prune " --prune"}";
|
|
};
|
|
};
|
|
|
|
systemd.paths.plasmacloud-dns-apply = mkIf dnsCfg.applyOnChange {
|
|
wantedBy = [ "multi-user.target" ];
|
|
pathConfig = {
|
|
PathChanged = dnsConfigPath;
|
|
};
|
|
};
|
|
})
|
|
];
|
|
}
|