photoncloud-monorepo/nix/modules/nix-nos/topology.nix
centra 3eeb303dcb feat: Batch commit for T039.S3 deployment
Includes all pending changes needed for nixos-anywhere:
- fiberlb: L7 policy, rule, certificate types
- deployer: New service for cluster management
- nix-nos: Generic network modules
- Various service updates and fixes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 04:34:51 +09:00

148 lines
4.3 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.nix-nos;
# Node definition type
nodeType = types.submodule {
options = {
role = mkOption {
type = types.enum [ "control-plane" "worker" ];
default = "worker";
description = "Node role in the cluster";
};
ip = mkOption {
type = types.str;
description = "IP address of the node";
};
services = mkOption {
type = types.listOf types.str;
default = [];
description = "List of services to run on this node";
example = [ "chainfire" "flaredb" "iam" ];
};
raftPort = mkOption {
type = types.port;
default = 2380;
description = "Raft port for consensus protocols";
};
apiPort = mkOption {
type = types.port;
default = 2379;
description = "API port for cluster services";
};
metadata = mkOption {
type = types.attrsOf types.anything;
default = {};
description = "Additional metadata for the node";
};
};
};
# Cluster definition type
clusterType = types.submodule {
options = {
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" ];
};
}
'';
};
bootstrapNode = mkOption {
type = types.nullOr types.str;
default = null;
description = "Name of the bootstrap node (first control-plane node if null)";
};
};
};
in {
options.nix-nos = {
enable = mkEnableOption "Nix-NOS declarative cluster management";
clusters = mkOption {
type = types.attrsOf clusterType;
default = {};
description = "Map of cluster names to their configurations";
};
# Helper function to generate cluster-config.json for a specific node
generateClusterConfig = mkOption {
type = types.functionTo types.attrs;
default = { hostname, clusterName ? "plasmacloud" }:
let
cluster = cfg.clusters.${clusterName} or (throw "Cluster ${clusterName} not found");
node = cluster.nodes.${hostname} or (throw "Node ${hostname} not found in cluster ${clusterName}");
# Determine bootstrap node
controlPlaneNodes = filter (n: cluster.nodes.${n}.role == "control-plane") (attrNames cluster.nodes);
bootstrapNodeName =
if cluster.bootstrapNode != null
then cluster.bootstrapNode
else head controlPlaneNodes;
isBootstrap = hostname == bootstrapNodeName;
# Leader URL (bootstrap node's API endpoint)
bootstrapNode = cluster.nodes.${bootstrapNodeName};
leaderUrl = "https://${bootstrapNode.ip}:${toString bootstrapNode.apiPort}";
# Initial peers for Raft cluster
initialPeers = map (nodeName: {
id = nodeName;
addr = "${cluster.nodes.${nodeName}.ip}:${toString cluster.nodes.${nodeName}.raftPort}";
}) controlPlaneNodes;
# FlareDB peers (all control-plane nodes)
flaredbPeers = map (nodeName:
"${cluster.nodes.${nodeName}.ip}:${toString (cluster.nodes.${nodeName}.apiPort + 100)}"
) controlPlaneNodes;
in {
node_id = hostname;
node_role = node.role;
bootstrap = isBootstrap;
cluster_name = cluster.name;
leader_url = leaderUrl;
raft_addr = "${node.ip}:${toString node.raftPort}";
initial_peers = initialPeers;
flaredb_peers = flaredbPeers;
services = node.services;
metadata = node.metadata;
};
description = "Function to generate cluster-config.json for a specific hostname";
};
};
config = mkIf cfg.enable {
# Ensure at least one cluster is defined
assertions = [
{
assertion = (builtins.length (attrNames cfg.clusters)) > 0;
message = "nix-nos.clusters must contain at least one cluster definition";
}
];
};
}