# 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; }; }; }; }