{ config, lib, pkgs, ... }: let cfg = config.services.iam; tomlFormat = pkgs.formats.toml { }; iamConfigFile = tomlFormat.generate "iam.toml" { server = { addr = "0.0.0.0:${toString cfg.port}"; http_addr = "0.0.0.0:${toString cfg.httpPort}"; }; logging.level = "info"; store = { backend = cfg.storeBackend; flaredb_endpoint = if cfg.flaredbAddr != null then cfg.flaredbAddr else "127.0.0.1:2479"; flaredb_namespace = "iam"; single_node = cfg.singleNode; } // lib.optionalAttrs (cfg.databaseUrl != null) { database_url = cfg.databaseUrl; }; cluster = lib.optionalAttrs (cfg.chainfireAddr != null) { chainfire_endpoint = cfg.chainfireAddr; }; }; in { options.services.iam = { enable = lib.mkEnableOption "iam service"; port = lib.mkOption { type = lib.types.port; default = 50080; description = "Port for iam gRPC API"; }; httpPort = lib.mkOption { type = lib.types.port; default = 8083; description = "Port for IAM HTTP REST API"; }; chainfireAddr = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "ChainFire endpoint address (host:port) for cluster coordination"; 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"; }; storeBackend = lib.mkOption { type = lib.types.enum [ "flaredb" "postgres" "sqlite" "memory" ]; default = "flaredb"; description = "IAM store backend. Use memory only for test/dev."; }; databaseUrl = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "SQL database URL for IAM store (required when storeBackend is postgres/sqlite)."; example = "postgres://iam:secret@10.0.0.10:5432/iam"; }; singleNode = lib.mkOption { type = lib.types.bool; default = false; description = "Enable single-node mode (required when store backend is SQLite)"; }; dataDir = lib.mkOption { type = lib.types.path; default = "/var/lib/iam"; description = "Data directory for iam"; }; adminToken = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "Admin token injected as IAM_ADMIN_TOKEN for privileged IAM APIs."; }; settings = lib.mkOption { type = lib.types.attrs; default = {}; description = "Additional configuration settings"; }; package = lib.mkOption { type = lib.types.package; default = pkgs.iam-server or (throw "iam-server package not found"); description = "Package to use for iam"; }; }; config = lib.mkIf cfg.enable { # Create system user users.users.iam = { isSystemUser = true; group = "iam"; description = "IAM service user"; home = cfg.dataDir; }; users.groups.iam = {}; # Create systemd service systemd.services.iam = { description = "IAM Identity and Access Management Service"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "chainfire.service" "flaredb.service" ]; wants = [ "chainfire.service" "flaredb.service" ]; environment = lib.mkMerge [ { CHAINFIRE_ENDPOINT = if cfg.chainfireAddr != null then cfg.chainfireAddr else "127.0.0.1:2379"; FLAREDB_ENDPOINT = if cfg.flaredbAddr != null then cfg.flaredbAddr else "127.0.0.1:2479"; IAM_STORE_BACKEND = cfg.storeBackend; } (lib.mkIf (cfg.databaseUrl != null) { IAM_DATABASE_URL = cfg.databaseUrl; }) (lib.mkIf cfg.singleNode { IAM_SINGLE_NODE = "true"; }) (lib.mkIf (cfg.storeBackend == "memory") { IAM_ALLOW_MEMORY_BACKEND = "1"; }) (lib.mkIf (cfg.adminToken != null) { IAM_ADMIN_TOKEN = cfg.adminToken; }) ]; serviceConfig = { Type = "simple"; User = "iam"; Group = "iam"; Restart = "on-failure"; RestartSec = "10s"; # State directory management StateDirectory = "iam"; StateDirectoryMode = "0750"; # Security hardening NoNewPrivileges = true; PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; ReadWritePaths = [ cfg.dataDir ]; # Start command ExecStart = "${cfg.package}/bin/iam-server --config ${iamConfigFile}"; }; }; }; }