photoncloud-monorepo/nix/modules/plasmacloud-resources.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;
};
};
})
];
}