Files
nixpkgs/modules/home-manager/ports.nix
2025-10-05 09:19:29 +02:00

262 lines
7.2 KiB
Nix

# 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_<SERVICE_NAME> (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;
};
};
};
}