feat: Hermes Dashboard via m3-atlas Traefik (TLS + Netbird-only) #15

Merged
m3tam3re merged 1 commits from feat/hermes-dashboard-traefik into master 2026-05-11 16:09:53 +02:00
2 changed files with 30 additions and 5 deletions
+22
View File
@@ -43,6 +43,12 @@
dynamicConfigOptions = { dynamicConfigOptions = {
http = { http = {
services = { services = {
# ── Hermes Dashboard (m3-hermes over Netbird) ────────────────
hermes-dashboard = {
loadBalancer.servers = [
{url = "http://100.81.231.152:9119";}
];
};
dummy = { dummy = {
loadBalancer.servers = [ loadBalancer.servers = [
{url = "http://192.168.0.1";} # Diese URL wird nie verwendet {url = "http://192.168.0.1";} # Diese URL wird nie verwendet
@@ -50,6 +56,12 @@
}; };
}; };
middlewares = { middlewares = {
# Hermes Dashboard — Netbird mesh only
netbird-only = {
ipWhiteList = {
sourceRange = ["100.64.0.0/16"];
};
};
domain-redirect = { domain-redirect = {
redirectRegex = { redirectRegex = {
regex = "^https://www\\.m3tam3re\\.com(.*)"; regex = "^https://www\\.m3tam3re\\.com(.*)";
@@ -79,6 +91,16 @@
}; };
routers = { routers = {
# ── Hermes Dashboard — Netbird mesh only ─────────────────────
hermes-dashboard = {
rule = "Host(`dash.m3ta.dev`)";
service = "hermes-dashboard";
middlewares = ["netbird-only"];
entrypoints = ["websecure"];
tls = {
certResolver = "godaddy";
};
};
api = { api = {
rule = "Host(`r.m3tam3re.com`)"; rule = "Host(`r.m3tam3re.com`)";
service = "api@internal"; service = "api@internal";
@@ -4,7 +4,8 @@
inputs, inputs,
... ...
}: let }: let
# Netbird mesh VPN range — dashboard only accessible from mesh peers # Netbird mesh VPN range — dashboard only accessible from mesh peers.
# m3-atlas Traefik proxies to this port over Netbird.
netbirdRange = "100.64.0.0/16"; netbirdRange = "100.64.0.0/16";
# Reference the hermes-agent package from the running service config # Reference the hermes-agent package from the running service config
@@ -12,7 +13,11 @@
in { in {
# ── Hermes Dashboard systemd service ─────────────────────────────────── # ── Hermes Dashboard systemd service ───────────────────────────────────
# Web UI for managing Hermes Agent — sessions, config, kanban, cron, etc. # Web UI for managing Hermes Agent — sessions, config, kanban, cron, etc.
# Binds to 0.0.0.0:9119 but firewall restricts to Netbird mesh only. #
# Flow: Browser → dash.m3ta.dev (TLS via m3-atlas Traefik) → Netbird → :9119
#
# --insecure is required to bind 0.0.0.0 (hermes refuses non-localhost otherwise).
# Safe because firewall restricts port 9119 to Netbird mesh only.
systemd.services.hermes-dashboard = { systemd.services.hermes-dashboard = {
description = "Hermes Agent Web Dashboard"; description = "Hermes Agent Web Dashboard";
after = ["network.target" "hermes-agent.service"]; after = ["network.target" "hermes-agent.service"];
@@ -24,7 +29,7 @@ in {
User = "hermes"; User = "hermes";
Group = "hermes"; Group = "hermes";
ExecStart = "${hermesPkg}/bin/hermes dashboard --host 0.0.0.0 --port 9119 --no-open"; ExecStart = "${hermesPkg}/bin/hermes dashboard --host 0.0.0.0 --port 9119 --no-open --insecure";
# Environment matching the hermes-agent service # Environment matching the hermes-agent service
Environment = [ Environment = [
@@ -48,8 +53,6 @@ in {
# ── Firewall: Dashboard only from Netbird mesh ───────────────────────── # ── Firewall: Dashboard only from Netbird mesh ─────────────────────────
networking.firewall = { networking.firewall = {
# Use extraCommands for source-IP-restricted port (NixOS firewall
# allowedTCPPorts is all-or-nothing per port).
extraCommands = '' extraCommands = ''
# Allow Hermes Dashboard (9119/tcp) only from Netbird mesh VPN # Allow Hermes Dashboard (9119/tcp) only from Netbird mesh VPN
ip46tables -A nixos-fw -p tcp --dport 9119 -s ${netbirdRange} -j nixos-fw-accept ip46tables -A nixos-fw -p tcp --dport 9119 -s ${netbirdRange} -j nixos-fw-accept