Files
nixpkgs/docs/guides/port-management.md
m3tm3re 44485c4c72 docs: update zellij-ps to reflect project switcher functionality
- Update package description and fix mainProgram typo
- Rewrite documentation to describe project switching, not process viewing
- Add PROJECT_FOLDERS configuration and usage examples
- Update all references across docs (README, guides, module overviews)
2025-12-30 15:42:52 +01:00

8.7 KiB

Port Management Guide

Managing service ports across multiple hosts with the m3ta.ports module.

Overview

The port management module provides a centralized way to define service ports that can have host-specific overrides. This prevents port conflicts and makes it easy to manage services across multiple machines.

Basic Usage

Enable Port Management

{config, ...}: {
  m3ta.ports = {
    enable = true;

    # Define default ports
    definitions = {
      nginx = 80;
      grafana = 3000;
      prometheus = 9090;
      homepage = 8080;
    };

    # Define host-specific overrides
    hostOverrides = {
      laptop = {
        nginx = 8080;       # Override on laptop
        homepage = 3001;    # Override on laptop
      };
      server = {
        homepage = 3002;    # Override on server
      };
    };

    # Set current host (determines which overrides to use)
    currentHost = config.networking.hostName;
  };
}

Using Ports

{config, ...}: {
  services.nginx = {
    enable = true;
    httpConfig = ''
      server {
        listen ${toString (config.m3ta.ports.get "nginx")};
        root /var/www;
      }
    '';
  };

  services.grafana = {
    enable = true;
    settings.server.http_port = config.m3ta.ports.get "grafana";
  };
}

Module Options

m3ta.ports.enable

Enable port management module.

  • Type: boolean
  • Default: false

m3ta.ports.definitions

Default port definitions.

  • Type: attrsOf int
  • Default: {}
definitions = {
  nginx = 80;
  grafana = 3000;
  prometheus = 9090;
};

m3ta.ports.hostOverrides

Host-specific port overrides.

  • Type: attrsOf (attrsOf int)
  • Default: {}
hostOverrides = {
  laptop = {
    nginx = 8080;
    grafana = 3001;
  };
  server = {
    grafana = 3002;
  };
};

m3ta.ports.currentHost

Current hostname. Determines which overrides to apply.

  • Type: string
  • Example: config.networking.hostName
currentHost = "laptop";  # Use laptop overrides

m3ta.ports.generateEnvVars (Home Manager only)

Generate environment variables from ports.

  • Type: boolean
  • Default: false (Home Manager)
  • NixOS: Not available

When enabled, generates environment variables like:

  • PORT_NGINX=8080
  • PORT_GRAFANA=3000

Functions

config.m3ta.ports.get "service"

Get port for a service with host-specific override.

services.nginx = {
  port = config.m3ta.ports.get "nginx";
};

If current host is laptop and hostOverrides.laptop.nginx = 8080, returns 8080. If no override, returns default 80.

config.m3ta.ports.getHostPorts "hostname"

Get all ports for a specific host.

# Get all ports for laptop
laptopPorts = config.m3ta.ports.getHostPorts "laptop";
# Returns: { nginx = 8080; grafana = 3000; ... }

config.m3ta.ports.listServices

List all defined service names.

allServices = config.m3ta.ports.listServices;
# Returns: ["nginx" "grafana" "prometheus" "homepage"]

Examples

NixOS Configuration

{config, ...}: {
  # Define ports
  m3ta.ports = {
    enable = true;
    definitions = {
      nginx = 80;
      grafana = 3000;
      prometheus = 9090;
      loki = 3100;
      promtail = 9080;
    };
    hostOverrides.laptop = {
      nginx = 8080;
      grafana = 3001;
    };
    currentHost = config.networking.hostName;
  };

  # Use ports
  services.nginx = {
    enable = true;
    httpConfig = ''
      server {
        listen ${toString (config.m3ta.ports.get "nginx")};
        root /var/www;
      }
    '';
  };

  services.grafana = {
    enable = true;
    settings.server.http_port = config.m3ta.ports.get "grafana";
  };

  services.prometheus = {
    enable = true;
    port = config.m3ta.ports.get "prometheus";
  };

  services.loki = {
    enable = true;
    configuration.http_listen_port = config.m3ta.ports.get "loki";
  };
}

Home Manager Configuration

{config, ...}: {
  # Define ports
  m3ta.ports = {
    enable = true;
    definitions = {
      dev-server = 3000;
      nextjs = 3001;
      vite = 5173;
    };
    hostOverrides.desktop = {
      vite = 5174;
    };
    currentHost = "desktop";
    generateEnvVars = true;  # Generate env vars
  };

  # Ports are now available as env vars
  # PORT_DEV_SERVER=3000
  # PORT_NEXTJS=3001
  # PORT_VITE=5174

  home.sessionVariables = {
    DEV_PORT = toString (config.m3ta.ports.get "dev-server");
  };
}

With Custom Modules

Using ports with custom modules (e.g., m3ta.mem0):

{config, ...}: {
  # Define ports
  m3ta.ports = {
    enable = true;
    definitions = {
      mem0 = 8000;
      qdrant = 6333;
    };
    hostOverrides.laptop = {
      mem0 = 8080;
    };
    currentHost = config.networking.hostName;
  };

  # Use with mem0 module
  m3ta.mem0 = {
    enable = true;
    port = config.m3ta.ports.get "mem0";  # 8000 or 8080 on laptop
  };

  # Use with qdrant service
  services.qdrant = {
    enable = true;
    port = config.m3ta.ports.get "qdrant";
  };
}

Port File Generation

Generate a JSON file with all ports:

{config, pkgs, ...}: {
  m3ta.ports = {
    enable = true;
    definitions = {
      service1 = 80;
      service2 = 443;
    };
    currentHost = config.networking.hostName;
  };

  # Generate port file
  environment.etc."m3ta/ports.json".text = builtins.toJSON (
    config.m3ta.ports.getHostPorts config.networking.hostName
  );
}

Advanced Usage

Conditional Configuration

{config, ...}: {
  services.nginx = {
    enable = true;

    # Only open firewall if binding to non-localhost
    httpConfig = let
      port = config.m3ta.ports.get "nginx";
    in ''
      server {
        listen ${toString port};
      }
    '';
  };

  networking.firewall.allowedTCPPorts =
    if config.m3ta.ports.get "nginx" == 80
    then [80]
    else [];
}

Port Ranges

definitions = {
  service-start = 8000;
  service-end = 8999;
};

# Use in config
services.my-app = {
  portRange = [
    config.m3ta.ports.get "service-start"
    config.m3ta.ports.get "service-end"
  ];
};

Dynamic Port Allocation

{config, ...}: {
  m3ta.ports = {
    enable = true;
    definitions = {
      # Reserve port ranges
      app-range-start = 9000;
      app-range-end = 9999;
    };
    currentHost = config.networking.hostName;
  };

  # Calculate next available port
  services.my-app = {
    port = config.m3ta.ports.get "app-range-start" + 0;
  };

  services.my-other-app = {
    port = config.m3ta.ports.get "app-range-start" + 1;
  };
}

Best Practices

Use Descriptive Service Names

# Good
definitions = {
  nginx = 80;
  grafana = 3000;
  prometheus-ui = 9090;
  prometheus-push = 9091;
};

# Avoid
definitions = {
  p1 = 80;
  p2 = 3000;
  p3 = 9090;
};
definitions = {
  # Monitoring stack
  grafana = 3000;
  prometheus = 9090;
  loki = 3100;
  promtail = 9080;

  # Web services
  nginx = 80;
  homepage = 8080;

  # Databases
  postgres = 5432;
  redis = 6379;
  qdrant = 6333;
};

Document Overrides

hostOverrides = {
  # Laptop: Running multiple dev servers, use higher ports
  laptop = {
    nginx = 8080;
    dev-server = 3000;
  };

  # Server: Production, use standard ports
  server = {
    nginx = 80;
    dev-server = null;  # Disable on server
  };
};

Handle Missing Ports

services.some-service = {
  enable = true;
  port = config.m3ta.ports.get "some-service" or 8080;
};

Troubleshooting

Service Not Found

Error: Service "foo" not defined

Solution: Add service to definitions:

definitions = {
  foo = 8080;
};

Current Host Not Set

Error: currentHost not set

Solution: Set currentHost:

currentHost = config.networking.hostName;

Port Conflict

Issue: Two services trying to use same port.

Solution: Define both in port management:

definitions = {
  service1 = 8080;
  service2 = 8081;  # Different port
};

Migration from Hardcoded Ports

Before

services.nginx = {
  enable = true;
  httpConfig = ''
    server {
      listen 80;
    }
  '';
};

services.grafana = {
  enable = true;
  settings.server.http_port = 3000;
};

After

m3ta.ports = {
  enable = true;
  definitions = {
    nginx = 80;
    grafana = 3000;
  };
  currentHost = config.networking.hostName;
};

services.nginx = {
  enable = true;
  httpConfig = ''
    server {
      listen ${toString (config.m3ta.ports.get "nginx")};
    }
  '';
};

services.grafana = {
  enable = true;
  settings.server.http_port = config.m3ta.ports.get "grafana";
};

Next Steps