{ pkgs, lib, config, ... }: let cfg = config.services.hive-forge; in { # Thin wrapper around `services.forgejo` with hyperhive-friendly # defaults: sqlite (no extra service to manage), built-in SSH on a # non-22 port so it doesn't fight the host's sshd, registration off # (agents get accounts seeded out of band), and ports opened in the # firewall. # # Forge wiring into the agent containers is intentionally out of # scope here — containers already share the host network namespace, # so once this module is enabled an agent can reach the forge at # http://localhost: without any extra bind mount. The MCP # tool surface (open PR, list repos, etc.) lives in a separate # follow-up that the operator opts into per agent. options.services.hive-forge = { enable = lib.mkEnableOption "hive-forge — private Forgejo instance for hyperhive agents"; httpPort = lib.mkOption { type = lib.types.port; default = 3000; description = '' TCP port the forge serves HTTP on. Default 3000 sits outside hyperhive's claimed ranges (dashboard 7000, manager 8000, sub-agents 8100..8999) so they don't collide. ''; }; sshPort = lib.mkOption { type = lib.types.port; default = 2222; description = '' TCP port the forge's built-in SSH server listens on. Kept off 22 so it doesn't clash with the host's openssh. Agents push with `ssh -p git@:/.git`. ''; }; domain = lib.mkOption { type = lib.types.str; default = "localhost"; example = "forge.internal"; description = '' Hostname agents and operator dial the forge by. `localhost` is fine for a single-host setup since containers share the host netns; set a real hostname when you want clones from outside the host to look canonical. ''; }; openFirewall = lib.mkOption { type = lib.types.bool; default = true; description = '' Open `httpPort` + `sshPort` in the host firewall. Off when the forge should only be reachable from inside the host (the forge listens on all interfaces either way; this just gates external access). ''; }; }; config = lib.mkIf cfg.enable { services.forgejo = { enable = true; database.type = "sqlite3"; lfs.enable = true; settings = { server = { DOMAIN = cfg.domain; ROOT_URL = "http://${cfg.domain}:${toString cfg.httpPort}/"; HTTP_PORT = cfg.httpPort; START_SSH_SERVER = true; SSH_PORT = cfg.sshPort; SSH_LISTEN_PORT = cfg.sshPort; BUILTIN_SSH_SERVER_USER = "git"; DISABLE_SSH = false; }; # Registration off — operator creates agent users via # `forgejo admin user create` (or the API once seeded). # Agents that need access get a dedicated account + token. service = { DISABLE_REGISTRATION = true; REQUIRE_SIGNIN_VIEW = false; }; repository = { DEFAULT_BRANCH = "main"; DEFAULT_PRIVATE = "private"; }; # Quiet logs at idle; bump when debugging. log.LEVEL = "Warn"; }; }; networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.httpPort cfg.sshPort ]; }; # Convenience: drop the forgejo CLI on the host PATH so the # operator can `forgejo admin user create …` without hunting for # the wrapped binary. The forgejo service runs as its own user; # admin commands need `sudo -u forgejo`. environment.systemPackages = [ pkgs.forgejo ]; }; }