photoncloud-monorepo/nix/modules/plasmacloud-cluster.nix
centra d6d96b8c37
Some checks failed
Nix CI / filter (push) Successful in 6s
Nix CI / gate () (push) Failing after 1s
Nix CI / gate (shared crates) (push) Has been skipped
Nix CI / build () (push) Has been skipped
Nix CI / ci-status (push) Failing after 1s
Move native runtime seed state into declarative Nix
2026-03-28 07:38:45 +09:00

221 lines
7.2 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.plasmacloud.cluster;
clusterConfigLib = import ./cluster-config-lib.nix { inherit lib; };
nodeType = clusterConfigLib.mkNodeType types;
nodeClassType = clusterConfigLib.mkNodeClassType types;
nodePoolType = clusterConfigLib.mkNodePoolType types;
enrollmentRuleType = clusterConfigLib.mkEnrollmentRuleType types;
hostDeploymentType = clusterConfigLib.mkHostDeploymentType types;
serviceType = clusterConfigLib.mkServiceType types;
mtlsPolicyType = clusterConfigLib.mkMtlsPolicyType types;
jsonFormat = pkgs.formats.json { };
# Generate cluster-config.json for the current node
generateClusterConfig = cluster:
clusterConfigLib.mkClusterConfig {
inherit cluster;
hostname = config.networking.hostName;
bootstrapNodeName = head cluster.bootstrap.initialPeers;
};
generatedNodeClusterConfig =
if cfg.nodes ? "${config.networking.hostName}" then
generateClusterConfig cfg
else
null;
generatedDeployerClusterState = clusterConfigLib.mkDeployerClusterState cfg;
in {
options.plasmacloud.cluster = {
enable = mkEnableOption "PlasmaCloud cluster configuration";
name = mkOption {
type = types.str;
default = "plasmacloud-cluster";
description = "Cluster name";
};
nodes = mkOption {
type = types.attrsOf nodeType;
default = {};
description = "Map of node names to their configurations";
example = literalExpression ''
{
"node01" = {
role = "control-plane";
ip = "10.0.1.10";
services = [ "chainfire" "flaredb" "iam" ];
};
}
'';
};
bootstrap = {
initialPeers = mkOption {
type = types.listOf types.str;
description = "Initial Raft peers for bootstrap (ordered list, first is bootstrap node)";
example = [ "node01" "node02" "node03" ];
};
};
bgp = {
asn = mkOption {
type = types.int;
description = "BGP AS number for the cluster";
example = 64512;
};
};
deployer = {
clusterId = mkOption {
type = types.nullOr types.str;
default = null;
description = "Cluster ID exported into deployer cluster state";
};
environment = mkOption {
type = types.nullOr types.str;
default = null;
description = "Optional environment label exported into deployer cluster state";
};
nodeClasses = mkOption {
type = types.attrsOf nodeClassType;
default = { };
description = "Reusable deployer node classes derived from Nix";
};
pools = mkOption {
type = types.attrsOf nodePoolType;
default = { };
description = "Reusable deployer node pools derived from Nix";
};
enrollmentRules = mkOption {
type = types.attrsOf enrollmentRuleType;
default = { };
description = "Deployer auto-enrollment rules derived from Nix";
};
hostDeployments = mkOption {
type = types.attrsOf hostDeploymentType;
default = { };
description = "Declarative host rollout objects derived from Nix";
};
services = mkOption {
type = types.attrsOf serviceType;
default = { };
description = "Scheduler-managed service definitions derived from Nix";
};
mtlsPolicies = mkOption {
type = types.attrsOf mtlsPolicyType;
default = { };
description = "Declarative mTLS policies derived from Nix";
};
};
generated = {
nodeClusterConfig = mkOption {
type = types.nullOr (types.attrsOf types.anything);
internal = true;
default = null;
description = "Resolved per-node cluster-config.json content";
};
deployerClusterState = mkOption {
type = types.attrsOf types.anything;
internal = true;
default = { };
description = "Resolved deployer cluster state exported from Nix";
};
};
};
config = mkIf cfg.enable {
# Assertions
assertions = [
{
assertion = (length (attrNames cfg.nodes)) > 0;
message = "plasmacloud.cluster.nodes must contain at least one node";
}
{
assertion = (length cfg.bootstrap.initialPeers) > 0;
message = "plasmacloud.cluster.bootstrap.initialPeers must contain at least one node";
}
{
assertion = all (peer: cfg.nodes ? "${peer}") cfg.bootstrap.initialPeers;
message = "All nodes in bootstrap.initialPeers must exist in cluster.nodes";
}
{
assertion = cfg.bgp.asn > 0 && cfg.bgp.asn < 4294967296;
message = "BGP ASN must be between 1 and 4294967295";
}
{
assertion = all (nodeName:
let
node = cfg.nodes.${nodeName};
in
node.pool == null || cfg.deployer.pools ? "${node.pool}"
) (attrNames cfg.nodes);
message = "All node pools referenced in plasmacloud.cluster.nodes must exist in plasmacloud.cluster.deployer.pools";
}
{
assertion = all (nodeName:
let
node = cfg.nodes.${nodeName};
in
node.nodeClass == null || cfg.deployer.nodeClasses ? "${node.nodeClass}"
) (attrNames cfg.nodes);
message = "All node classes referenced in plasmacloud.cluster.nodes must exist in plasmacloud.cluster.deployer.nodeClasses";
}
{
assertion = all (poolName:
let
pool = cfg.deployer.pools.${poolName};
in
pool.nodeClass == null || cfg.deployer.nodeClasses ? "${pool.nodeClass}"
) (attrNames cfg.deployer.pools);
message = "All deployer pools must reference existing deployer node classes";
}
{
assertion = all (ruleName:
let
rule = cfg.deployer.enrollmentRules.${ruleName};
in
(rule.pool == null || cfg.deployer.pools ? "${rule.pool}")
&& (rule.nodeClass == null || cfg.deployer.nodeClasses ? "${rule.nodeClass}")
) (attrNames cfg.deployer.enrollmentRules);
message = "All deployer enrollment rules must reference existing pools and node classes";
}
{
assertion = all (deploymentName:
let
deployment = cfg.deployer.hostDeployments.${deploymentName};
in
all (pool: cfg.deployer.pools ? "${pool}") deployment.selector.pools
&& all (nodeClass: cfg.deployer.nodeClasses ? "${nodeClass}") deployment.selector.nodeClasses
) (attrNames cfg.deployer.hostDeployments);
message = "All deployer host deployments must reference existing pools and node classes";
}
];
# Generate cluster-config.json for first-boot-automation
environment.etc."nixos/secrets/cluster-config.json" = mkIf (cfg.nodes ? "${config.networking.hostName}") {
text = builtins.toJSON generatedNodeClusterConfig;
mode = "0600";
};
plasmacloud.cluster.generated.nodeClusterConfig = generatedNodeClusterConfig;
plasmacloud.cluster.generated.deployerClusterState = generatedDeployerClusterState;
system.build.plasmacloudDeployerClusterState =
jsonFormat.generate "plasmacloud-deployer-cluster-state.json" generatedDeployerClusterState;
};
}