hyperhive/nix/modules/hive-forge.nix
müde 608de57924 hive-forge: default to pkgs.forgejo (15.x), expose package option
nixpkgs's services.forgejo defaults to forgejo-lts (11.0.13 today);
LTS lags far enough behind that any prior non-LTS run against the
same state dir leaves the DB at a migration the LTS binary can't
read ('database newer than binary, refusing to start'). default to
the latest release line and let operators opt down to LTS by
overriding services.hive-forge.package.
2026-05-17 01:29:19 +02:00

155 lines
5.3 KiB
Nix

{
pkgs,
lib,
config,
...
}:
let
cfg = config.services.hive-forge;
in
{
# Private Forgejo for hyperhive agents, wrapped in a nixos-container
# so it doesn't fight any `services.forgejo` the operator already
# runs on the host. The container shares the host network namespace
# (`privateNetwork = false`) so agents reach the forge at
# `http://localhost:<httpPort>` without any extra plumbing —
# nixos-container is just here for state + systemd-unit isolation,
# not network isolation.
#
# Container name is `hive-forge` (not `h-*`), so hive-c0re's
# lifecycle scanner ignores it; the operator manages it via the
# standard `nixos-container` CLI.
#
# State lives at `/var/lib/nixos-containers/hive-forge/var/lib/forgejo/`
# and survives container restart / host reboot. To wipe, destroy the
# container.
options.services.hive-forge = {
enable = lib.mkEnableOption "hive-forge private Forgejo (in a nixos-container) 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). Change this if you already have
another forgejo bound to 3000.
'';
};
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 <sshPort> git@<domain>:<owner>/<repo>.git`.
'';
};
domain = lib.mkOption {
type = lib.types.str;
default = "localhost";
example = "forge.internal";
description = ''
Hostname used in repo clone URLs the forge advertises. The
container shares host netns so `localhost` works for any
agent on the same host; set a real hostname when you want
clones from outside the host to look canonical.
'';
};
package = lib.mkOption {
type = lib.types.package;
default = pkgs.forgejo;
defaultText = lib.literalExpression "pkgs.forgejo";
description = ''
Forgejo package to run inside the container. Defaults to
`pkgs.forgejo` (the latest release line) rather than the
nixpkgs-module default of `pkgs.forgejo-lts`, because LTS
lags far behind on schema and the DB easily ends up "newer
than the binary" if the operator ever ran a non-LTS forgejo
against the same state dir. Override to `pkgs.forgejo-lts`
if you actively want the slower release train.
'';
};
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 container shares host netns, so this is the only
firewall layer that matters.)
'';
};
};
config = lib.mkIf cfg.enable {
containers.hive-forge = {
autoStart = true;
ephemeral = false;
# Share host netns — forgejo's HTTP / SSH listeners then look
# exactly like a host-side service, no port forwarding dance,
# and agent containers (which also share host netns) reach it
# via plain `localhost`.
privateNetwork = false;
config =
{ pkgs, ... }:
{
system.stateVersion = "25.11";
services.forgejo = {
enable = true;
package = cfg.package;
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 seeds agent users via
# `nixos-container run hive-forge -- forgejo admin
# user create …`.
service = {
DISABLE_REGISTRATION = true;
REQUIRE_SIGNIN_VIEW = false;
};
repository = {
DEFAULT_BRANCH = "main";
DEFAULT_PRIVATE = "private";
};
log.LEVEL = "Warn";
# F3 (federation) computes its data dir relative to the
# forgejo binary, which lands in the read-only nix
# store and crashes anything that touches the F3
# subsystem — including `forgejo admin user create`,
# which init-ses F3 even when ENABLED=false. Pin the
# path absolute alongside the disable so the init
# resolution succeeds before the flag is checked.
"F3" = {
ENABLED = false;
PATH = "/var/lib/forgejo/data/f3";
};
};
};
environment.systemPackages = [ pkgs.forgejo ];
};
};
networking.firewall = lib.mkIf cfg.openFirewall {
allowedTCPPorts = [
cfg.httpPort
cfg.sshPort
];
};
};
}