photoncloud-monorepo/nix/modules/plasmavmc.nix

278 lines
9.2 KiB
Nix

{ config, lib, pkgs, ... }:
let
cfg = config.services.plasmavmc;
coronafsEnabled = lib.hasAttrByPath [ "services" "coronafs" "enable" ] config && config.services.coronafs.enable;
coronafsDataDir =
if coronafsEnabled && lib.hasAttrByPath [ "services" "coronafs" "dataDir" ] config
then toString config.services.coronafs.dataDir
else null;
tomlFormat = pkgs.formats.toml { };
plasmavmcConfigFile = tomlFormat.generate "plasmavmc.toml" {
addr = "0.0.0.0:${toString cfg.port}";
http_addr = "0.0.0.0:${toString cfg.httpPort}";
log_level = "info";
auth = {
iam_server_addr =
if cfg.iamAddr != null
then cfg.iamAddr
else "127.0.0.1:50080";
};
};
in
{
options.services.plasmavmc = {
enable = lib.mkEnableOption "plasmavmc service";
mode = lib.mkOption {
type = lib.types.enum [ "server" "agent" "all-in-one" ];
default = "all-in-one";
description = "PlasmaVMC operating mode: server (control-plane), agent (compute), or all-in-one";
};
port = lib.mkOption {
type = lib.types.port;
default = 50082;
description = "Port for plasmavmc gRPC API";
};
httpPort = lib.mkOption {
type = lib.types.port;
default = 8084;
description = "Port for plasmavmc HTTP REST API";
};
prismnetAddr = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "PrismNET service endpoint address (host:port)";
example = "10.0.0.1:50081";
};
iamAddr = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "IAM service endpoint address (host:port)";
example = "10.0.0.1:50080";
};
chainfireAddr = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "ChainFire endpoint address (host:port) for cluster coordination only";
example = "10.0.0.1:2379";
};
flaredbAddr = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "FlareDB endpoint address (host:port) for metadata/user data";
example = "10.0.0.1:2479";
};
controlPlaneAddr = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "PlasmaVMC control-plane gRPC endpoint (host:port) for agent heartbeats.";
example = "10.0.0.11:50082";
};
advertiseAddr = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Advertised PlasmaVMC gRPC endpoint for scheduler forwarding (host:port).";
example = "10.0.0.21:50082";
};
lightningstorAddr = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "LightningStor gRPC endpoint (host:port) for VM image artifacts.";
example = "10.0.0.11:50086";
};
coronafsEndpoint = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "CoronaFS HTTP endpoint used to provision and export managed VM volumes.";
example = "http://10.0.0.11:50088";
};
managedVolumeRoot = lib.mkOption {
type = lib.types.path;
default = "/var/lib/plasmavmc/managed-volumes";
description = "Local root directory used for PlasmaVMC managed raw volumes.";
};
sharedLiveMigration = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Whether this node advertises shared-storage live migration capability.";
};
cephMonitors = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "Optional Ceph monitor endpoints used to enable Ceph RBD volumes.";
example = [ "10.0.0.31:6789" "10.0.0.32:6789" "10.0.0.33:6789" ];
};
cephClusterId = lib.mkOption {
type = lib.types.str;
default = "default";
description = "Ceph cluster identifier expected by registered Ceph RBD volumes.";
};
cephUser = lib.mkOption {
type = lib.types.str;
default = "admin";
description = "Ceph user passed to QEMU RBD attachments.";
};
cephSecret = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Optional Ceph secret used for QEMU RBD attachments.";
};
nodeId = lib.mkOption {
type = lib.types.str;
default = config.networking.hostName;
description = "Node identifier used when running in agent/all-in-one mode.";
};
nodeName = lib.mkOption {
type = lib.types.str;
default = config.networking.hostName;
description = "Node display name used in PlasmaVMC heartbeats.";
};
heartbeatIntervalSeconds = lib.mkOption {
type = lib.types.int;
default = 5;
description = "Heartbeat interval for PlasmaVMC agents.";
};
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/plasmavmc";
description = "Data directory for plasmavmc";
};
settings = lib.mkOption {
type = lib.types.attrs;
default = {};
description = "Additional configuration settings";
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.plasmavmc-server or (throw "plasmavmc-server package not found");
description = "Package to use for plasmavmc";
};
};
config = lib.mkIf cfg.enable {
# Create system user
users.users.plasmavmc = {
isSystemUser = true;
group = "plasmavmc";
description = "PlasmaVMC service user";
home = cfg.dataDir;
extraGroups = [ "kvm" ] ++ lib.optional coronafsEnabled "coronafs";
};
users.groups.plasmavmc = {};
# Create systemd service
systemd.services.plasmavmc = {
description = "PlasmaVMC Virtual Machine Compute Service";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "prismnet.service" "flaredb.service" "chainfire.service" ];
wants = [ "prismnet.service" "flaredb.service" "chainfire.service" ];
path = [ pkgs.qemu pkgs.coreutils ];
environment = lib.mkMerge [
{
PLASMAVMC_MODE = cfg.mode;
PLASMAVMC_STORAGE_BACKEND = "flaredb";
PLASMAVMC_FLAREDB_ENDPOINT = if cfg.flaredbAddr != null then cfg.flaredbAddr else "127.0.0.1:2479";
PLASMAVMC_QEMU_PATH = "${pkgs.qemu}/bin/qemu-system-x86_64";
PLASMAVMC_RUNTIME_DIR = "/run/libvirt/plasmavmc";
PLASMAVMC_IMAGE_CACHE_DIR = "${toString cfg.dataDir}/images";
PLASMAVMC_MANAGED_VOLUME_ROOT = toString cfg.managedVolumeRoot;
PLASMAVMC_SHARED_LIVE_MIGRATION = lib.boolToString cfg.sharedLiveMigration;
}
(lib.mkIf (cfg.prismnetAddr != null) {
PRISMNET_ENDPOINT = "http://${cfg.prismnetAddr}";
})
(lib.mkIf (cfg.chainfireAddr != null) {
PLASMAVMC_CHAINFIRE_ENDPOINT = "http://${cfg.chainfireAddr}";
PLASMAVMC_STATE_WATCHER = "1";
})
(lib.mkIf (cfg.lightningstorAddr != null) {
PLASMAVMC_LIGHTNINGSTOR_ENDPOINT = cfg.lightningstorAddr;
})
(lib.mkIf (cfg.coronafsEndpoint != null) {
PLASMAVMC_CORONAFS_ENDPOINT = cfg.coronafsEndpoint;
})
(lib.mkIf (cfg.cephMonitors != [ ]) {
PLASMAVMC_CEPH_MONITORS = lib.concatStringsSep "," cfg.cephMonitors;
PLASMAVMC_CEPH_CLUSTER_ID = cfg.cephClusterId;
PLASMAVMC_CEPH_USER = cfg.cephUser;
})
(lib.mkIf (cfg.cephSecret != null) {
PLASMAVMC_CEPH_SECRET = cfg.cephSecret;
})
(lib.mkIf (cfg.mode != "server") {
PLASMAVMC_NODE_ID = cfg.nodeId;
PLASMAVMC_NODE_NAME = cfg.nodeName;
PLASMAVMC_NODE_HEARTBEAT_INTERVAL_SECS = toString cfg.heartbeatIntervalSeconds;
})
(lib.mkIf (cfg.controlPlaneAddr != null) {
PLASMAVMC_CONTROL_PLANE_ADDR = cfg.controlPlaneAddr;
})
(lib.mkIf (cfg.advertiseAddr != null) {
PLASMAVMC_ENDPOINT_ADVERTISE = cfg.advertiseAddr;
})
(lib.mkIf (cfg.mode == "server") {
PLASMAVMC_NODE_HEALTH_MONITOR_INTERVAL_SECS = "5";
PLASMAVMC_NODE_HEARTBEAT_TIMEOUT_SECS = "30";
})
];
serviceConfig = {
Type = "simple";
User = "plasmavmc";
Group = "plasmavmc";
SupplementaryGroups = [ "kvm" ] ++ lib.optional coronafsEnabled "coronafs";
Restart = "on-failure";
RestartSec = "10s";
# State directory management
StateDirectory = "plasmavmc";
StateDirectoryMode = "0750";
RuntimeDirectory = "libvirt";
RuntimeDirectoryMode = "0755";
# Security hardening - relaxed for KVM access
NoNewPrivileges = false; # Needed for KVM
PrivateTmp = true;
ProtectSystem = "strict";
ProtectHome = true;
ReadWritePaths =
[ cfg.dataDir "/run/libvirt" cfg.managedVolumeRoot ]
++ lib.optionals (coronafsDataDir != null) [ coronafsDataDir ];
DeviceAllow = [ "/dev/kvm rw" ];
# Start command
ExecStart = "${cfg.package}/bin/plasmavmc-server --config ${plasmavmcConfigFile}";
};
};
systemd.tmpfiles.rules = [
"d ${builtins.dirOf (toString cfg.managedVolumeRoot)} 0755 plasmavmc plasmavmc -"
"d ${toString cfg.managedVolumeRoot} 0750 plasmavmc plasmavmc -"
];
};
}