diff --git a/modules/home-manager/ports.nix b/modules/home-manager/ports.nix new file mode 100644 index 0000000..7526ea9 --- /dev/null +++ b/modules/home-manager/ports.nix @@ -0,0 +1,261 @@ +# Home Manager Module for Port Management +# +# This module provides centralized port management for Home Manager configurations. +# Define ports once and use them consistently across user services, with support +# for host-specific overrides. +# +# Usage in your Home Manager configuration: +# +# # In your home.nix or flake: +# imports = [ inputs.m3ta-nixpkgs.homeManagerModules.default ]; +# +# m3ta.ports = { +# enable = true; +# +# # Define your default ports +# definitions = { +# vscodium = 8080; +# jupyter = 8888; +# dev-server = 3000; +# local-api = 8000; +# docs-preview = 4000; +# }; +# +# # Define host-specific overrides +# hostOverrides = { +# laptop = { +# dev-server = 3001; +# vscodium = 8081; +# }; +# desktop = { +# jupyter = 9999; +# }; +# }; +# +# # Set the current hostname +# currentHost = "laptop"; # Or use config.networking.hostName if available +# }; +# +# # Use ports in your configuration: +# home.file.".config/myapp/config.json".text = builtins.toJSON { +# port = config.m3ta.ports.get "dev-server"; +# }; +# +# # Generate environment variables: +# home.sessionVariables = { +# DEV_SERVER_PORT = toString (config.m3ta.ports.get "dev-server"); +# JUPYTER_PORT = toString (config.m3ta.ports.get "jupyter"); +# }; +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.m3ta.ports; + + # Import the ports library + portsLib = import ../../lib/ports.nix {inherit lib;}; + + # Create port helpers from the configuration + portHelpers = + if cfg.enable + then + portsLib.mkPortHelpers { + ports = cfg.definitions; + hostPorts = cfg.hostOverrides; + } + else null; +in { + options.m3ta.ports = { + enable = mkEnableOption "centralized port management for Home Manager"; + + definitions = mkOption { + type = types.attrsOf types.port; + default = {}; + example = literalExpression '' + { + vscodium = 8080; + jupyter = 8888; + dev-server = 3000; + local-api = 8000; + } + ''; + description = '' + Default port definitions for user services. + These ports will be used unless overridden by host-specific settings. + ''; + }; + + hostOverrides = mkOption { + type = types.attrsOf (types.attrsOf types.port); + default = {}; + example = literalExpression '' + { + laptop = { + dev-server = 3001; + vscodium = 8081; + }; + desktop = { + jupyter = 9999; + }; + } + ''; + description = '' + Host-specific port overrides. + Keys are hostnames, values are attribute sets of service-name to port mappings. + ''; + }; + + currentHost = mkOption { + type = types.nullOr types.str; + default = null; + example = "laptop"; + description = '' + The current hostname to use for port resolution. + Set to null to disable host-specific overrides. + + Note: In Home Manager, you may need to set this manually unless + you pass config.networking.hostName from your NixOS configuration. + ''; + }; + + # Computed option - provides access to port helpers + get = mkOption { + type = types.functionTo types.port; + readOnly = true; + internal = true; + description = '' + Function to get a port for a service. + Automatically uses the current host for overrides. + ''; + }; + + getForHost = mkOption { + type = types.functionTo (types.functionTo types.port); + readOnly = true; + internal = true; + description = '' + Function to get a port for a service on a specific host. + Usage: config.m3ta.ports.getForHost "hostname" "service" + ''; + }; + + all = mkOption { + type = types.attrsOf types.port; + readOnly = true; + internal = true; + description = '' + All ports for the current host (defaults merged with overrides). + ''; + }; + + allForHost = mkOption { + type = types.functionTo (types.attrsOf types.port); + readOnly = true; + internal = true; + description = '' + Function to get all ports for a specific host. + Usage: config.m3ta.ports.allForHost "hostname" + ''; + }; + + services = mkOption { + type = types.listOf types.str; + readOnly = true; + internal = true; + description = '' + List of all defined service names. + ''; + }; + + # Convenience option for generating shell variables + generateEnvVars = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Whether to automatically generate environment variables for all ports. + Variables will be named as: PORT_ (uppercase, dashes to underscores). + + Example: service "dev-server" becomes PORT_DEV_SERVER + ''; + }; + + envVarPrefix = mkOption { + type = types.str; + default = "PORT_"; + example = "MY_APP_PORT_"; + description = '' + Prefix for generated environment variables when generateEnvVars is enabled. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.definitions != {}; + message = "m3ta.ports.definitions must not be empty when m3ta.ports.enable is true"; + } + ]; + + m3ta.ports = { + # Function to get port for current host + get = service: + assert assertMsg (portHelpers != null) "Port helpers not initialized"; + assert assertMsg (cfg.definitions ? ${service}) "Service '${service}' not defined in m3ta.ports.definitions"; + portHelpers.getPort service cfg.currentHost; + + # Function to get port for specific host + getForHost = host: service: + assert assertMsg (portHelpers != null) "Port helpers not initialized"; + assert assertMsg (cfg.definitions ? ${service}) "Service '${service}' not defined in m3ta.ports.definitions"; + portHelpers.getPort service host; + + # All ports for current host + all = + if portHelpers != null + then portHelpers.getHostPorts cfg.currentHost + else {}; + + # Function to get all ports for specific host + allForHost = host: + assert assertMsg (portHelpers != null) "Port helpers not initialized"; + portHelpers.getHostPorts host; + + # List all services + services = + if portHelpers != null + then portHelpers.listServices + else []; + }; + + # Optional: Automatically generate environment variables for all ports + home.sessionVariables = mkIf cfg.generateEnvVars ( + let + # Convert service name to env var name: "dev-server" -> "DEV_SERVER" + toEnvVarName = service: + cfg.envVarPrefix + (lib.toUpper (builtins.replaceStrings ["-"] ["_"] service)); + in + builtins.listToAttrs ( + map (service: { + name = toEnvVarName service; + value = toString (cfg.get service); + }) + cfg.services + ) + ); + + # Create a JSON file with all ports for easy inspection + home.file.".config/m3ta/ports.json" = { + text = builtins.toJSON { + hostname = cfg.currentHost; + ports = cfg.all; + allDefinitions = cfg.definitions; + hostOverrides = cfg.hostOverrides; + }; + }; + }; +} diff --git a/modules/nixos/ports.nix b/modules/nixos/ports.nix new file mode 100644 index 0000000..5e1b14a --- /dev/null +++ b/modules/nixos/ports.nix @@ -0,0 +1,219 @@ +# NixOS Module for Port Management +# +# This module provides centralized port management across your NixOS systems. +# Define ports once and use them consistently across all services, with +# support for host-specific overrides. +# +# Usage in your NixOS configuration: +# +# # In your flake.nix or configuration.nix: +# imports = [ inputs.m3ta-nixpkgs.nixosModules.default ]; +# +# m3ta.ports = { +# enable = true; +# +# # Define your default ports +# definitions = { +# nginx = 80; +# grafana = 3000; +# prometheus = 9090; +# homepage = 8080; +# ssh = 22; +# }; +# +# # Define host-specific overrides +# hostOverrides = { +# laptop = { +# nginx = 8080; # Use non-privileged port on laptop +# ssh = 2222; +# }; +# server = { +# homepage = 3001; +# }; +# }; +# +# # Optionally set the current hostname for automatic port resolution +# currentHost = config.networking.hostName; +# }; +# +# # Use ports in your configuration: +# services.nginx.defaultHTTPListenPort = config.m3ta.ports.get "nginx"; +# services.grafana.settings.server.http_port = config.m3ta.ports.get "grafana"; +# +# # Or access all ports for the current host: +# environment.etc."my-ports.json".text = builtins.toJSON config.m3ta.ports.all; +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.m3ta.ports; + + # Import the ports library + portsLib = import ../../lib/ports.nix {inherit lib;}; + + # Create port helpers from the configuration + portHelpers = + if cfg.enable + then + portsLib.mkPortHelpers { + ports = cfg.definitions; + hostPorts = cfg.hostOverrides; + } + else null; +in { + options.m3ta.ports = { + enable = mkEnableOption "centralized port management"; + + definitions = mkOption { + type = types.attrsOf types.port; + default = {}; + example = literalExpression '' + { + nginx = 80; + grafana = 3000; + prometheus = 9090; + ssh = 22; + } + ''; + description = '' + Default port definitions for services. + These ports will be used unless overridden by host-specific settings. + ''; + }; + + hostOverrides = mkOption { + type = types.attrsOf (types.attrsOf types.port); + default = {}; + example = literalExpression '' + { + laptop = { + nginx = 8080; + ssh = 2222; + }; + server = { + nginx = 443; + }; + } + ''; + description = '' + Host-specific port overrides. + Keys are hostnames, values are attribute sets of service-name to port mappings. + ''; + }; + + currentHost = mkOption { + type = types.nullOr types.str; + default = config.networking.hostName; + defaultText = literalExpression "config.networking.hostName"; + example = "laptop"; + description = '' + The current hostname to use for port resolution. + Defaults to the system's hostname. + Set to null to disable host-specific overrides. + ''; + }; + + # Computed option - provides access to port helpers + get = mkOption { + type = types.functionTo types.port; + readOnly = true; + internal = true; + description = '' + Function to get a port for a service. + Automatically uses the current host for overrides. + ''; + }; + + getForHost = mkOption { + type = types.functionTo (types.functionTo types.port); + readOnly = true; + internal = true; + description = '' + Function to get a port for a service on a specific host. + Usage: config.m3ta.ports.getForHost "hostname" "service" + ''; + }; + + all = mkOption { + type = types.attrsOf types.port; + readOnly = true; + internal = true; + description = '' + All ports for the current host (defaults merged with overrides). + ''; + }; + + allForHost = mkOption { + type = types.functionTo (types.attrsOf types.port); + readOnly = true; + internal = true; + description = '' + Function to get all ports for a specific host. + Usage: config.m3ta.ports.allForHost "hostname" + ''; + }; + + services = mkOption { + type = types.listOf types.str; + readOnly = true; + internal = true; + description = '' + List of all defined service names. + ''; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.definitions != {}; + message = "m3ta.ports.definitions must not be empty when m3ta.ports.enable is true"; + } + ]; + + m3ta.ports = { + # Function to get port for current host + get = service: + assert assertMsg (portHelpers != null) "Port helpers not initialized"; + assert assertMsg (cfg.definitions ? ${service}) "Service '${service}' not defined in m3ta.ports.definitions"; + portHelpers.getPort service cfg.currentHost; + + # Function to get port for specific host + getForHost = host: service: + assert assertMsg (portHelpers != null) "Port helpers not initialized"; + assert assertMsg (cfg.definitions ? ${service}) "Service '${service}' not defined in m3ta.ports.definitions"; + portHelpers.getPort service host; + + # All ports for current host + all = + if portHelpers != null + then portHelpers.getHostPorts cfg.currentHost + else {}; + + # Function to get all ports for specific host + allForHost = host: + assert assertMsg (portHelpers != null) "Port helpers not initialized"; + portHelpers.getHostPorts host; + + # List all services + services = + if portHelpers != null + then portHelpers.listServices + else []; + }; + + # Optional: Create a JSON file with all ports for easy inspection + environment.etc."m3ta/ports.json" = mkIf cfg.enable { + text = builtins.toJSON { + hostname = cfg.currentHost; + ports = cfg.all; + allDefinitions = cfg.definitions; + hostOverrides = cfg.hostOverrides; + }; + mode = "0444"; + }; + }; +}