{ config, lib, pkgs, ... }: let cfg = config.services.apigateway; tomlFormat = pkgs.formats.toml {}; authProviderType = lib.types.submodule { options = { name = lib.mkOption { type = lib.types.str; description = "Auth provider name"; }; providerType = lib.mkOption { type = lib.types.str; default = "grpc"; description = "Auth provider type (grpc)"; }; endpoint = lib.mkOption { type = lib.types.str; description = "Auth provider endpoint (e.g., http://127.0.0.1:9000)"; }; timeoutMs = lib.mkOption { type = lib.types.nullOr lib.types.int; default = null; description = "Auth provider timeout in milliseconds"; }; }; }; creditProviderType = lib.types.submodule { options = { name = lib.mkOption { type = lib.types.str; description = "Credit provider name"; }; providerType = lib.mkOption { type = lib.types.str; default = "grpc"; description = "Credit provider type (grpc)"; }; endpoint = lib.mkOption { type = lib.types.str; description = "Credit provider endpoint (e.g., http://127.0.0.1:9100)"; }; timeoutMs = lib.mkOption { type = lib.types.nullOr lib.types.int; default = null; description = "Credit provider timeout in milliseconds"; }; }; }; routeAuthType = lib.types.submodule { options = { provider = lib.mkOption { type = lib.types.str; description = "Auth provider name to use"; }; mode = lib.mkOption { type = lib.types.enum [ "disabled" "optional" "required" ]; default = "required"; description = "Auth enforcement mode"; }; failOpen = lib.mkOption { type = lib.types.bool; default = false; description = "Allow requests on auth failures"; }; }; }; routeCreditType = lib.types.submodule { options = { provider = lib.mkOption { type = lib.types.str; description = "Credit provider name to use"; }; mode = lib.mkOption { type = lib.types.enum [ "disabled" "optional" "required" ]; default = "required"; description = "Credit enforcement mode"; }; units = lib.mkOption { type = lib.types.int; default = 1; description = "Credit units to reserve per request"; }; failOpen = lib.mkOption { type = lib.types.bool; default = false; description = "Allow requests on credit failures"; }; commitOn = lib.mkOption { type = lib.types.enum [ "success" "always" "never" ]; default = "success"; description = "Credit commit policy"; }; attributes = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = {}; description = "Extra credit attributes"; }; }; }; routeType = lib.types.submodule { options = { name = lib.mkOption { type = lib.types.str; description = "Route name"; }; pathPrefix = lib.mkOption { type = lib.types.str; description = "Path prefix to match"; }; upstream = lib.mkOption { type = lib.types.str; description = "Upstream base URL"; }; stripPrefix = lib.mkOption { type = lib.types.bool; default = false; description = "Strip the path prefix before proxying"; }; timeoutMs = lib.mkOption { type = lib.types.nullOr lib.types.int; default = null; description = "Per-route upstream timeout in milliseconds"; }; auth = lib.mkOption { type = lib.types.nullOr routeAuthType; default = null; description = "Auth configuration for this route"; }; credit = lib.mkOption { type = lib.types.nullOr routeCreditType; default = null; description = "Credit configuration for this route"; }; }; }; baseConfig = { http_addr = "0.0.0.0:${toString cfg.port}"; log_level = "info"; }; toAuthProvider = provider: { name = provider.name; type = provider.providerType; endpoint = provider.endpoint; } // lib.optionalAttrs (provider.timeoutMs != null) { timeout_ms = provider.timeoutMs; }; toCreditProvider = provider: { name = provider.name; type = provider.providerType; endpoint = provider.endpoint; } // lib.optionalAttrs (provider.timeoutMs != null) { timeout_ms = provider.timeoutMs; }; toRouteAuth = auth: { provider = auth.provider; mode = auth.mode; fail_open = auth.failOpen; }; toRouteCredit = credit: { provider = credit.provider; mode = credit.mode; units = credit.units; fail_open = credit.failOpen; commit_on = credit.commitOn; attributes = credit.attributes; }; toRoute = route: { name = route.name; path_prefix = route.pathPrefix; upstream = route.upstream; strip_prefix = route.stripPrefix; } // lib.optionalAttrs (route.timeoutMs != null) { timeout_ms = route.timeoutMs; } // lib.optionalAttrs (route.auth != null) { auth = toRouteAuth route.auth; } // lib.optionalAttrs (route.credit != null) { credit = toRouteCredit route.credit; }; generatedConfig = { max_body_bytes = cfg.maxBodyBytes; auth_providers = map toAuthProvider cfg.authProviders; credit_providers = map toCreditProvider cfg.creditProviders; routes = map toRoute cfg.routes; }; configFile = tomlFormat.generate "apigateway.toml" ( lib.recursiveUpdate baseConfig (lib.recursiveUpdate generatedConfig cfg.settings) ); configPath = "/etc/apigateway/apigateway.toml"; in { options.services.apigateway = { enable = lib.mkEnableOption "apigateway service"; port = lib.mkOption { type = lib.types.port; default = 8080; description = "Port for the API gateway HTTP listener"; }; iamAddr = lib.mkOption { type = lib.types.nullOr lib.types.str; default = null; description = "IAM service endpoint (host:port) to auto-configure IAM auth provider"; }; dataDir = lib.mkOption { type = lib.types.path; default = "/var/lib/apigateway"; description = "Data directory for apigateway"; }; maxBodyBytes = lib.mkOption { type = lib.types.int; default = 16 * 1024 * 1024; description = "Maximum request body size in bytes"; }; authProviders = lib.mkOption { type = lib.types.listOf authProviderType; default = []; description = "Auth provider definitions"; }; creditProviders = lib.mkOption { type = lib.types.listOf creditProviderType; default = []; description = "Credit provider definitions"; }; routes = lib.mkOption { type = lib.types.listOf routeType; default = []; description = "API gateway routes"; }; settings = lib.mkOption { type = lib.types.attrs; default = {}; description = "Additional API gateway TOML settings (merged into apigateway.toml)"; }; package = lib.mkOption { type = lib.types.package; default = pkgs.apigateway-server or (throw "apigateway-server package not found"); description = "Package to use for apigateway"; }; }; config = lib.mkIf cfg.enable { services.apigateway.authProviders = lib.mkIf (cfg.iamAddr != null) [ { name = "iam"; providerType = "grpc"; endpoint = "http://${cfg.iamAddr}"; } ]; users.users.apigateway = { isSystemUser = true; group = "apigateway"; description = "API gateway service user"; home = cfg.dataDir; }; users.groups.apigateway = {}; systemd.services.apigateway = { description = "PlasmaCloud API Gateway"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { Type = "simple"; User = "apigateway"; Group = "apigateway"; Restart = "on-failure"; RestartSec = "10s"; StateDirectory = "apigateway"; StateDirectoryMode = "0750"; NoNewPrivileges = true; PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; ReadWritePaths = [ cfg.dataDir ]; ExecStart = "${cfg.package}/bin/apigateway-server --config ${configPath}"; }; }; environment.etc."apigateway/apigateway.toml".source = configFile; }; }