feat: herdr + agent network over netbird
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
# here, NOT in environment.systemPackages
|
||||
];
|
||||
programs.fish.enable = true;
|
||||
programs.mosh.enable = true;
|
||||
programs.nh = {
|
||||
enable = true;
|
||||
clean.enable = true;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
{lib, ...}: {
|
||||
imports = [
|
||||
./baserow.nix
|
||||
# ./baserow.nix
|
||||
./ghost.nix
|
||||
./honcho.nix
|
||||
./kestra.nix
|
||||
# ./honcho.nix
|
||||
./littlelink.nix
|
||||
./matomo.nix
|
||||
# ./matomo.nix
|
||||
./netbird.nix
|
||||
# ./n8n.nix
|
||||
# ./pangolin.nix
|
||||
./restreamer.nix
|
||||
./slash.nix
|
||||
./slash-nemoti.nix
|
||||
@@ -16,7 +14,7 @@
|
||||
];
|
||||
system.activationScripts.createPodmanNetworkWeb = lib.mkAfter ''
|
||||
if ! /run/current-system/sw/bin/podman network exists web; then
|
||||
/run/current-system/sw/bin/podman network create web --subnet=10.89.0.0/24 --internal
|
||||
/run/current-system/sw/bin/podman network create web --subnet=10.89.0.0/24 --gateway=10.89.0.1
|
||||
fi
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
environmentFiles = [config.age.secrets.ghost-env.path];
|
||||
ports = ["127.0.0.1:3002:2368"];
|
||||
volumes = ["ghost_data:/var/lib/ghost/content"];
|
||||
extraOptions = ["--add-host=mysql:10.89.0.1" "--ip=10.89.0.11" "--network=web"];
|
||||
extraOptions = ["--add-host=mysql:10.89.0.1" "--ip=10.89.0.11" "--network=web" "--dns=8.8.8.8"];
|
||||
};
|
||||
# Traefik configuration specific to ghost
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
|
||||
@@ -1,209 +0,0 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
serviceName = "honcho";
|
||||
image = "ghcr.io/plastic-labs/honcho:v3.0.6";
|
||||
|
||||
apiIp = "10.89.0.24";
|
||||
deriverIp = "10.89.0.25";
|
||||
redisIp = "10.89.0.26";
|
||||
|
||||
postgresHost = "10.89.0.1";
|
||||
postgresPort = config.m3ta.ports.get "postgres";
|
||||
honchoPort = config.m3ta.ports.get "honcho";
|
||||
|
||||
# m3-atlas Netbird mesh address, discovered from `netbird status -d`.
|
||||
# Binding the host port here keeps self-hosted Honcho off public interfaces.
|
||||
netbirdBindAddress = "100.81.142.56";
|
||||
netbirdRange = "100.64.0.0/16";
|
||||
|
||||
dbName = "honcho";
|
||||
dbUser = "honcho";
|
||||
redisName = "${serviceName}-redis";
|
||||
runtimeDirectory = "/run/${serviceName}";
|
||||
runtimeEnvFile = "${runtimeDirectory}/env";
|
||||
|
||||
# Keep auth disabled for the first deployment because Honcho clients need
|
||||
# generated JWTs. The JWT secret is still provisioned so enabling auth later is
|
||||
# a one-line change here plus client token generation.
|
||||
authUseAuth = false;
|
||||
|
||||
sharedEnvironment = {
|
||||
CACHE_ENABLED = "true";
|
||||
CACHE_URL = "redis://${redisName}:6379/0?suppress=true";
|
||||
LOG_LEVEL = "INFO";
|
||||
TELEMETRY_ENABLED = "false";
|
||||
VECTOR_STORE_MIGRATED = "false";
|
||||
VECTOR_STORE_TYPE = "pgvector";
|
||||
AUTH_USE_AUTH = lib.boolToString authUseAuth;
|
||||
};
|
||||
|
||||
sharedEnvironmentFiles = [
|
||||
runtimeEnvFile
|
||||
config.age.secrets."${serviceName}-selfhost-env".path
|
||||
];
|
||||
|
||||
webNetwork = ip: [
|
||||
"--add-host=postgres:${postgresHost}"
|
||||
"--network=web:ip=${ip}"
|
||||
];
|
||||
|
||||
# The shared web network is intentionally internal. API and deriver also join
|
||||
# this egress-only network so LLM provider calls can leave the host without
|
||||
# exposing any extra inbound ports.
|
||||
networksWithEgress = ip:
|
||||
(webNetwork ip)
|
||||
++ [
|
||||
"--network=${serviceName}-egress"
|
||||
];
|
||||
|
||||
apiHealthCmd = ''/app/.venv/bin/python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=2).read()"'';
|
||||
in {
|
||||
system.activationScripts.createPodmanNetworkHonchoEgress = lib.mkAfter ''
|
||||
if ! /run/current-system/sw/bin/podman network exists ${serviceName}-egress; then
|
||||
/run/current-system/sw/bin/podman network create ${serviceName}-egress
|
||||
fi
|
||||
'';
|
||||
|
||||
virtualisation.oci-containers.containers = {
|
||||
"${serviceName}-redis" = {
|
||||
image = "docker.io/redis:8.2";
|
||||
autoStart = true;
|
||||
volumes = ["${serviceName}_redis_data:/data"];
|
||||
extraOptions =
|
||||
(webNetwork redisIp)
|
||||
++ [
|
||||
"--health-cmd=redis-cli ping"
|
||||
"--health-interval=5s"
|
||||
"--health-timeout=5s"
|
||||
"--health-retries=5"
|
||||
];
|
||||
};
|
||||
|
||||
"${serviceName}-api" = {
|
||||
inherit image;
|
||||
autoStart = true;
|
||||
entrypoint = "sh";
|
||||
cmd = ["docker/entrypoint.sh"];
|
||||
environment = sharedEnvironment;
|
||||
environmentFiles = sharedEnvironmentFiles;
|
||||
ports = ["${netbirdBindAddress}:${toString honchoPort}:8000"];
|
||||
dependsOn = [redisName];
|
||||
extraOptions =
|
||||
(networksWithEgress apiIp)
|
||||
++ [
|
||||
"--health-cmd=${apiHealthCmd}"
|
||||
"--health-interval=5s"
|
||||
"--health-timeout=5s"
|
||||
"--health-retries=5"
|
||||
"--health-start-period=10s"
|
||||
];
|
||||
};
|
||||
|
||||
"${serviceName}-deriver" = {
|
||||
inherit image;
|
||||
autoStart = true;
|
||||
entrypoint = "/app/.venv/bin/python";
|
||||
cmd = ["-m" "src.deriver"];
|
||||
environment = sharedEnvironment;
|
||||
environmentFiles = sharedEnvironmentFiles;
|
||||
dependsOn = ["${serviceName}-api" redisName];
|
||||
extraOptions = networksWithEgress deriverIp;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services = {
|
||||
"${serviceName}-postgres-bootstrap" = {
|
||||
description = "Bootstrap Honcho PostgreSQL role, database, password, and pgvector";
|
||||
after = ["postgresql.service" "agenix.service"];
|
||||
requires = ["postgresql.service" "agenix.service"];
|
||||
before = ["${serviceName}-env.service" "podman-${serviceName}-api.service" "podman-${serviceName}-deriver.service"];
|
||||
requiredBy = ["podman-${serviceName}-api.service" "podman-${serviceName}-deriver.service"];
|
||||
path = [
|
||||
config.services.postgresql.package
|
||||
pkgs.coreutils
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "postgres";
|
||||
Group = "postgres";
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
test -s ${config.age.secrets."${serviceName}-selfhost-db-password".path}
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --dbname=postgres <<'SQL'
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE ROLE ${dbUser} LOGIN;
|
||||
EXCEPTION WHEN duplicate_object THEN
|
||||
NULL;
|
||||
END
|
||||
$$;
|
||||
|
||||
SELECT 'CREATE DATABASE ${dbName} OWNER ${dbUser}'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${dbName}')\gexec
|
||||
|
||||
ALTER DATABASE ${dbName} OWNER TO ${dbUser};
|
||||
\set honcho_password `cat ${config.age.secrets."${serviceName}-selfhost-db-password".path}`
|
||||
ALTER ROLE ${dbUser} WITH LOGIN PASSWORD :'honcho_password';
|
||||
SQL
|
||||
|
||||
psql -v ON_ERROR_STOP=1 --dbname=${dbName} <<'SQL'
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${dbUser};
|
||||
SQL
|
||||
'';
|
||||
};
|
||||
|
||||
"${serviceName}-env" = {
|
||||
description = "Generate Honcho runtime environment file with agenix secrets";
|
||||
after = ["agenix.service" "${serviceName}-postgres-bootstrap.service"];
|
||||
requires = ["agenix.service" "${serviceName}-postgres-bootstrap.service"];
|
||||
before = ["podman-${serviceName}-api.service" "podman-${serviceName}-deriver.service"];
|
||||
requiredBy = ["podman-${serviceName}-api.service" "podman-${serviceName}-deriver.service"];
|
||||
path = [
|
||||
pkgs.coreutils
|
||||
pkgs.python3
|
||||
];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
set -euo pipefail
|
||||
install -d -m 0750 ${runtimeDirectory}
|
||||
|
||||
db_password_encoded=$(
|
||||
python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip(), safe=""))' \
|
||||
< ${config.age.secrets."${serviceName}-selfhost-db-password".path}
|
||||
)
|
||||
jwt_secret=$(tr -d '\r\n' < ${config.age.secrets."${serviceName}-selfhost-jwt-secret".path})
|
||||
|
||||
umask 077
|
||||
cat > ${runtimeEnvFile} <<ENV
|
||||
DB_CONNECTION_URI=postgresql+psycopg://${dbUser}:$db_password_encoded@postgres:${toString postgresPort}/${dbName}
|
||||
AUTH_JWT_SECRET=$jwt_secret
|
||||
ENV
|
||||
'';
|
||||
};
|
||||
|
||||
"podman-${serviceName}-api" = {
|
||||
after = ["${serviceName}-env.service" "${serviceName}-postgres-bootstrap.service"];
|
||||
requires = ["${serviceName}-env.service" "${serviceName}-postgres-bootstrap.service"];
|
||||
};
|
||||
|
||||
"podman-${serviceName}-deriver" = {
|
||||
after = ["${serviceName}-env.service" "${serviceName}-postgres-bootstrap.service"];
|
||||
requires = ["${serviceName}-env.service" "${serviceName}-postgres-bootstrap.service"];
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.extraCommands = ''
|
||||
# Self-hosted Honcho API: only Netbird mesh peers may reach ${netbirdBindAddress}:${toString honchoPort}.
|
||||
ip46tables -A nixos-fw -p tcp --dport ${toString honchoPort} -s ${netbirdRange} -j nixos-fw-accept
|
||||
'';
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
{config, ...}: {
|
||||
virtualisation.oci-containers.containers."kestra" = {
|
||||
image = "docker.io/kestra/kestra:latest";
|
||||
environmentFiles = [config.age.secrets.kestra-env.path];
|
||||
cmd = ["server" "standalone" "--config" "/etc/config/application.yaml"];
|
||||
ports = ["127.0.0.1:3018:8080"];
|
||||
user = "root";
|
||||
volumes = [
|
||||
"/var/run/docker.sock:/var/run/docker.sock"
|
||||
"${config.age.secrets.kestra-config.path}:/etc/config/application.yaml"
|
||||
"kestra_data:/app/storage"
|
||||
"/tmp/kestra-wd:/tmp/kestra-wd"
|
||||
];
|
||||
extraOptions = ["--add-host=postgres:10.89.0.1" "--ip=10.89.0.18" "--network=web"];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /tmp/kestra-wd 0750 1000 1000 - -"
|
||||
];
|
||||
|
||||
# Traefik configuration specific to littlelink
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.kestra.loadBalancer.servers = [{url = "http://localhost:3018/";}];
|
||||
|
||||
routers.kestra = {
|
||||
rule = "Host(`k.m3ta.dev`)";
|
||||
tls = {certResolver = "godaddy";};
|
||||
service = "kestra";
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
@@ -121,7 +122,7 @@ in {
|
||||
|
||||
virtualisation.oci-containers.containers = {
|
||||
"${serviceName}-dashboard" = {
|
||||
image = "netbirdio/dashboard:latest";
|
||||
image = "docker.io/netbirdio/dashboard:latest";
|
||||
autoStart = true;
|
||||
environmentFiles = [config.age.secrets."${serviceName}-dashboard-env".path];
|
||||
extraOptions = [
|
||||
@@ -131,7 +132,7 @@ in {
|
||||
};
|
||||
|
||||
"${serviceName}-server" = {
|
||||
image = "netbirdio/netbird-server:latest";
|
||||
image = "docker.io/netbirdio/netbird-server:latest";
|
||||
autoStart = true;
|
||||
ports = ["${toString stunPort}:${toString stunPort}/udp"];
|
||||
environmentFiles = [config.age.secrets."${serviceName}-server-env".path];
|
||||
@@ -147,12 +148,15 @@ in {
|
||||
};
|
||||
|
||||
"${serviceName}-proxy" = {
|
||||
image = "netbirdio/reverse-proxy:latest";
|
||||
image = "docker.io/netbirdio/reverse-proxy:latest";
|
||||
autoStart = true;
|
||||
ports = ["${toString wireguardPort}:${toString wireguardPort}/udp"];
|
||||
volumes = [
|
||||
"${serviceName}_proxy_certs:/certs"
|
||||
];
|
||||
environment = {
|
||||
NB_PROXY_PRIVATE = "true";
|
||||
};
|
||||
environmentFiles = [config.age.secrets."${serviceName}-proxy-env".path];
|
||||
cmd = [
|
||||
"--domain=${proxyDomain}"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
./rustfs.nix
|
||||
./mysql.nix
|
||||
./netbird.nix
|
||||
./n8n.nix
|
||||
# ./n8n.nix
|
||||
./paperless.nix
|
||||
./postgres.nix
|
||||
./searx.nix
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
}: {
|
||||
services.gitea-actions-runner = {
|
||||
instances.default = {
|
||||
enable = true;
|
||||
enable = false;
|
||||
name = "${config.networking.hostName}-runner";
|
||||
url = "https://code.m3ta.dev";
|
||||
tokenFile = config.age.secrets.gitea-runner-token.path;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
{config, ...}: {
|
||||
{
|
||||
config,
|
||||
inputs,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
services.paperless = {
|
||||
enable = true;
|
||||
port = config.m3ta.ports.get "paperless";
|
||||
|
||||
@@ -18,17 +18,13 @@
|
||||
# Localhost connections (IPv4 and IPv6)
|
||||
host all postgres 127.0.0.1/32 scram-sha-256
|
||||
host all postgres ::1/128 scram-sha-256
|
||||
host outline outline 127.0.0.1/32 scram-sha-256
|
||||
host outline outline ::1/128 scram-sha-256
|
||||
host paperless paperless 127.0.0.1/32 scram-sha-256
|
||||
host paperless paperless ::1/128 scram-sha-256
|
||||
|
||||
# Podman network connections for Baserow
|
||||
host baserow baserow 10.89.0.0/24 scram-sha-256
|
||||
host kestra kestra 10.89.0.0/24 scram-sha-256
|
||||
host netbird netbird 10.89.0.0/24 scram-sha-256
|
||||
host authentik authentik 10.89.0.0/24 scram-sha-256
|
||||
host honcho honcho 10.89.0.0/24 scram-sha-256
|
||||
|
||||
# Deny all other connections
|
||||
local all all reject
|
||||
@@ -39,7 +35,7 @@
|
||||
services.postgresqlBackup = {
|
||||
enable = true;
|
||||
startAt = "03:10:00";
|
||||
databases = ["baserow" "paperless" "kestra" "authentik" "netbird" "honcho"];
|
||||
databases = ["baserow" "paperless" "authentik" "netbird"];
|
||||
};
|
||||
networking.firewall = {
|
||||
extraCommands = ''
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
enable = true;
|
||||
environmentFile = config.age.secrets.rustfs-env.path;
|
||||
settings = {
|
||||
# Reuse existing MinIO data directory
|
||||
RUSTFS_VOLUMES = "/var/storage/s3";
|
||||
|
||||
# Keep same ports as MinIO to avoid changing Traefik and client configs
|
||||
RUSTFS_ADDRESS = ":3008";
|
||||
RUSTFS_CONSOLE_ENABLE = "true";
|
||||
RUSTFS_CONSOLE_ADDRESS = ":3007";
|
||||
@@ -22,18 +20,18 @@
|
||||
|
||||
# Traefik configuration — same routes as before
|
||||
services.traefik.dynamicConfigOptions.http = {
|
||||
services.minio-console.loadBalancer.servers = [
|
||||
services.rustfs-console.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:3007/";
|
||||
}
|
||||
];
|
||||
services.minio.loadBalancer.servers = [
|
||||
services.rustfs.loadBalancer.servers = [
|
||||
{
|
||||
url = "http://localhost:3008/";
|
||||
}
|
||||
];
|
||||
|
||||
routers.minio = {
|
||||
routers.rustfs = {
|
||||
rule = "Host(`s3.m3tam3re.com`)";
|
||||
tls = {
|
||||
certResolver = "godaddy";
|
||||
@@ -41,13 +39,31 @@
|
||||
service = "minio";
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
routers.minio-console = {
|
||||
routers.rustfs-console = {
|
||||
rule = "Host(`s3c.m3ta.dev`)";
|
||||
tls = {
|
||||
certResolver = "godaddy";
|
||||
};
|
||||
service = "rustfs-console";
|
||||
entrypoints = "websecure";
|
||||
};
|
||||
routers.rustfs-old = {
|
||||
rule = "Host(`s3.m3ta.dev`)";
|
||||
tls = {
|
||||
certResolver = "godaddy";
|
||||
};
|
||||
service = "rustfs";
|
||||
entrypoints = "websecure";
|
||||
middlewares = ["subdomain-redirect"];
|
||||
};
|
||||
routers.rustfs-console-old = {
|
||||
rule = "Host(`minio.m3tam3re.com`)";
|
||||
tls = {
|
||||
certResolver = "godaddy";
|
||||
};
|
||||
service = "minio-console";
|
||||
service = "rustfs";
|
||||
entrypoints = "websecure";
|
||||
middlewares = ["subdomain-redirect"];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@
|
||||
};
|
||||
websecure = {
|
||||
address = ":443";
|
||||
# NetBird (signal/management gRPC, proxy mapping stream) requires
|
||||
# long-lived HTTP/2 streams — see https://docs.netbird.io/selfhosted/maintenance/configuration-files
|
||||
# Default readTimeout (60s) kills the streams every minute.
|
||||
transport.respondingTimeouts.readTimeout = "0s";
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -85,15 +89,6 @@
|
||||
};
|
||||
|
||||
routers = {
|
||||
# ── Hermes Dashboard — Netbird mesh only ─────────────────────
|
||||
hermes-dashboard = {
|
||||
rule = "Host(`dash.m3ta.dev`)";
|
||||
service = "hermes-dashboard";
|
||||
entrypoints = ["websecure"];
|
||||
tls = {
|
||||
certResolver = "godaddy";
|
||||
};
|
||||
};
|
||||
api = {
|
||||
rule = "Host(`r.m3tam3re.com`)";
|
||||
service = "api@internal";
|
||||
|
||||
Reference in New Issue
Block a user