My homelab infra & documentation
  • Python 59.9%
  • Shell 20.4%
  • HTML 16.2%
  • PowerShell 2.7%
  • CSS 0.4%
  • Other 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
NocFA cc85f7198f
Preserve file mode when snapshotting the OpenClaw config
The live ~/.openclaw/openclaw.json is 0600, but the snapshot was landing
0644 on every run. cp does not preserve mode -- it creates a new destination
as 0666 & ~umask, and umask here is 022 -- so the script silently widened the
permissions of the thing it was copying. Caught by the AIDE FIM as a
0600 to 0644 drift.

The chmod is not redundant with -p. cp onto an existing file keeps the
destination's mode, so once the snapshot had been created 0644 the -p alone
would have left it that way forever; it could never self-heal.
2026-07-26 17:12:27 +01:00
.github/ISSUE_TEMPLATE Add GitHub issue templates for bugs and feature requests 2026-02-03 17:20:14 +00:00
agent Add SilverBullet shared workspace and surface it on the dashboard 2026-07-25 22:18:48 +01:00
archive Memory architecture redesign: retire extractor + observer, ship matrix-capture, fix discord-capture 2026-05-08 03:22:51 +01:00
configs Move homelab alerting to the private Discord server 2026-07-25 18:17:00 +01:00
dashboard Report noc-tux display status as headless instead of unlocked 2026-07-25 18:17:24 +01:00
docs Drop stale TeamSpeak launchctl refs, ignore data snapshots 2026-04-22 23:36:23 +01:00
launchagents Rotate noc-claw launchd logs, periodic writers only 2026-07-26 17:12:19 +01:00
linux Stop wireplumber retrying BlueZ on headless noc-tux 2026-07-26 17:12:07 +01:00
onedrive-migration Add Seafile self-hosted file sync service 2026-05-03 01:13:57 +01:00
scripts Route both Gatus instances through the suppressor, keep notifiers direct 2026-07-26 01:46:02 +01:00
services Preserve file mode when snapshotting the OpenClaw config 2026-07-26 17:12:27 +01:00
setup fix setup-linux.sh: configure dual push (GitHub + Codeberg) and branch tracking 2026-02-18 00:25:30 +00:00
windows Update READMEs to match what is actually deployed 2026-07-26 01:54:06 +01:00
.gitattributes Add apt health watchdog, media/Plex tooling, restore git-crypt rules 2026-07-25 09:03:17 +01:00
.gitignore Stop tracking docs/context (private agent context) 2026-06-13 23:47:16 +01:00
.gitleaks.toml Move Discord mention target from plaintext scripts into encrypted webhooks env 2026-07-04 19:38:10 +01:00
.gitleaksignore Move homelab alerting to the private Discord server 2026-07-25 18:17:00 +01:00
.sops.yaml feat: media pipeline scripts, agent linux support, SOPS auto-encrypt 2026-02-14 05:18:47 +00:00
.stignore Remove unused Seafile and refresh dashboard status map 2026-06-13 23:03:48 +01:00
LICENSE Add apt health watchdog, media/Plex tooling, restore git-crypt rules 2026-07-25 09:03:17 +01:00
README.md Update READMEs to match what is actually deployed 2026-07-26 01:54:06 +01:00

License: MIT

noc-homelab

A four-machine homelab running macOS, Linux, and a VPS, connected over Tailscale. Media streaming, Matrix communications, a malware-analysis sandbox, an on-device AI stack, game streaming, and monitoring — all driven from a central dashboard and a per-host agent API.

Dashboard

Machines

Machine OS Role Connectivity
noc-local macOS Dashboard host, sole writable git clone, Forgejo, Cloudflare tunnels LAN + Tailscale
noc-tux Ubuntu 24.04 LTS Media pipeline, Matrix, observability, CAPE sandbox. Headless (multi-user.target) LAN + Tailscale 100.91.104.124
noc-claw macOS (M4) AI stack — OpenClaw agent, gbrain, on-device MLX, log-triage Tailscale 100.95.102.128
noc-baguette AlmaLinux 9 OVH VPS, rathole tunnel server Public + Tailscale 100.96.57.116

noc-tux boots headless — GNOME is not running at boot and is started on demand by xrdp (bound to the Tailscale address only), then reaped 5 minutes after disconnect.

Architecture

noc-local (macOS)                          noc-tux (Ubuntu, headless)
┌──────────────────────┐                   ┌──────────────────────────┐
│  Dashboard :8080     │◄── Agent API ───► │  Agent :8080             │
│  Forgejo :3090       │    (Tailscale)    │  Matrix Stack (Docker)   │
│  Copyparty / Maloja  │                   │  Media Pipeline          │
│  TeamSpeak 6         │                   │    (Zurg → Rclone →      │
│  Caddy / Segments    │                   │     FileBot → Emby/Plex) │
│  Cloudflare tunnels  │                   │  CAPE malware sandbox    │
│  Syncthing (hub)     │                   │  Loki / Grafana / Prom   │
│  alert-suppressor    │                   │  CrowdSec LAPI           │
└──────────────────────┘                   │  SilverBullet            │
           ▲                                │  Rathole client ────────┼──► noc-baguette
           │ Agent API                      └──────────────────────────┘        │
           ▼                                          ▲                          │
┌──────────────────────┐                              │ Syncthing                │
│  noc-claw (macOS)    │──────────────────────────────┘                          ▼
│  OpenClaw + gbrain   │                              ┌──────────────────────┐
│  MLX :8181           │                              │  noc-baguette (VPS)  │
│  log-triage :8182    │                              │  Rathole Server      │
└──────────────────────┘                              │   :2333 (Tailscale)  │
                                                      │   :23512/udp public  │
                                                      └──────────────────────┘

The dashboard on noc-local polls the agent API on noc-tux and noc-claw for live service status. Service control (start/stop/restart) is forwarded through the agents, which manage systemd units, LaunchAgents, and Docker containers. agent/config.yaml is the single catalogue for every host; each agent filters it by platform.node().

File sync is hub-and-spoke: noc-local is the only writable git clone, and Syncthing distributes the working tree to the two spokes. Edits made on a host propagate back to noc-local for commit. Detailed operational notes live in basic-memory.

Services

noc-local

Service Port Manager Description
Dashboard 8080 launchd Central control plane
Forgejo 3090 launchd Self-hosted Git forge (git.nocfa.net), the origin remote
Copyparty 8081 launchd File server with web UI
Maloja / Multi-Scrobbler 42010 / 9078 launchd Music scrobble server + aggregator
TeamSpeak 6 9987 Docker Voice chat + screen share
Gatus 3001 Docker Uptime checks (🟡 alerts)
Syncthing 8384 launchd File sync hub
Caddy 80/443 launchd Reverse proxy
Segments 7765 launchd Task tracker + web UI
alert-suppressor 9188 launchd Maintenance-mode mute for Discord alerting
Glances / Netdata 61999 / 19999 launchd System metrics
CrowdSec agent launchd Forwards alerts to noc-tux LAPI
Cloudflare tunnels launchd All public-facing edge endpoints
trivy-scan / dns-audit launchd timers Image CVE scan, Cloudflare DNS exposure audit

Also hosted here but tracked in their own repos: noc-debrid (:8090, Real-Debrid control plane), mdsf-crew (API/bot/web + Postgres/pgbouncer), mdsf-org, voiceseq, DMM.

noc-tux

Service Port Manager Description
Agent 8080 systemd (user) Service control API
Matrix (Synapse, Element, MAS, Traefik, Postgres, Coturn, LiveKit) 443 / — Docker matrix.nocfa.net + element.nocfa.net
Emby / Plex / Jellyfin 8096 / 32400 / 8097 systemd Media streaming
Dispatcharr (+ celery, celerybeat, daphne) 9191 / 8001 systemd IPTV/EPG manager, proxy, HDHomeRun output
Zurg + Rclone 9999 / — systemd (user) Real-Debrid WebDAV → FUSE at /mnt/zurg
Navidrome (amsync) 4533 Docker Apple-Music-synced library
CAPE (web, processor, rooter, dns, inetsim, novnc) 8000 systemd Malware sandbox, isolated KVM guest, Tailscale-only
SilverBullet 3010 Docker Shared plan/checklist workspace (tailnet, :7000 via serve)
alert-suppressor 9188 systemd Maintenance-mode mute for Discord alerting
Authentik (server, worker, redis, postgres) Docker SSO / identity
Open Notebook (+ SurrealDB, Ollama, TTS) 8502 / 5055 Docker Research notebook, local TTS podcasts
Sunshine / xrdp 47990 / 3389 systemd Game streaming; remote desktop (Tailscale-only)
Gatus / Glances / Netdata 3001 / 61999 / 19999 systemd Health + metrics (🔵 alerts)
Loki + Grafana + Prometheus 3100 / 3000 / 9090 Docker Log + metric aggregation
Alloy systemd Log shipper → Loki
CrowdSec LAPI + firewall-bouncer 8150 systemd Central alerting, active blocking
Suricata systemd Network IDS
Arcane 3552 Docker Docker management UI
Caddy (websites) / Cloudflared / Forgejo runner systemd (user) looney.eu hosting + CI

noc-claw

The AI stack. See basic-memory architecture/noc-claw-ai-stack-open-claw-pi-gbrain before touching any of it.

Service Port Manager Description
OpenClaw (Lamb) launchd Agent with native Discord + Matrix channels; patch-guardian and status generator keep custom runtime patches applied across updates
gbrain + gbrain-viz — / 8185 launchd Knowledge graph on PostgreSQL 17, plus a 3D graph viewer
MLX server 8181 launchd mlx_lm.server serving mlx-community/gemma-3-4b-it-4bit
log-triage 8182 launchd MLX-backed CrowdSec alert enricher
discord-capture / matrix-capture launchd Conversation capture into gbrain
lamb-health-ledger launchd Agent health metrics uploader
Ollama 11434 launchd On-demand models, 60s idle-unload
Glances / Netdata / CrowdSec agent 61999 / 19999 / — launchd Metrics + IDS agent

noc-baguette

Service Port Manager Description
Rathole Server 2333/tcp systemd Tunnel control (Tailscale-only)
Forgejo SSH 2222/tcp rathole git-only SSH (ssh.git.nocfa.net)
Resonite 23512/udp rathole Resonite headless server tunnel
Attack-surface scanner systemd timer Weekly nuclei + testssl + ssh-audit

Media Pipeline

Real-Debrid Cloud
       │ (API poll every 10s)
       ▼
Zurg (WebDAV :9999) ──► Rclone FUSE (/mnt/zurg)
       │
       ▼ on_library_update hook
library-update.sh
  ├── FileBot: movies → media/movies/
  ├── FileBot: shows  → media/shows/
  ├── Emby /Library/Refresh
  └── Plex /library/sections/all/refresh
       │
       ▼
Emby (8096) + Plex (32400) + Jellyfin (8097)

Content stays in the Real-Debrid cloud — no local storage needed. A zurg-healthcheck timer runs every 5 minutes comparing the Real-Debrid API torrent count against what Zurg is serving and restarts the stack on divergence. A separate media-pipeline-watchdog catches the soft-dead failure modes (a wedged Dispatcharr celery worker caused a 10-day IPTV outage that no service-up check would have spotted). plextraktsync mirrors Plex watch state to Trakt.

CAPE Malware Sandbox

CAPEv2 on noc-tux. Windows samples detonate inside an isolated KVM guest with no route to the internet or the LAN — INetSim provides a fake internet on the isolated bridge, and iptables drops guest→LAN/tailnet egress. Static + dynamic analysis, VirusTotal auto-upload, an LLM-written verdict panel (the risk rating itself is computed deterministically, the model only writes the explanation), and in-browser remote control of the live guest via Guacamole. Web UI is Tailscale-only.

Chosen over DRAKVUF, which hard-requires a Xen dom0 and would have been mutually exclusive with the existing KVM + Docker stack on that box.

Matrix Stack

Self-hosted Matrix homeserver at matrix.nocfa.net, Element Web at element.nocfa.net, deployed via the MASH ansible playbook as Docker containers. Traefik handles TLS, Coturn handles TURN/STUN, LiveKit backs Element Call, and Matrix Authentication Service handles OIDC login. Admin UI at matrix.nocfa.net/synapse-admin/.

Rathole Tunnels

Game servers run on noc-tux behind NAT. Rathole punches outbound through the NAT to noc-baguette, exposing UDP/TCP ports publicly without opening the home router. The control channel (port 2333) is Tailscale-only — the VPS is not a jump box.

Observability & Security

  • Logs — Loki on noc-tux ingests from all hosts via Grafana Alloy, with a "Homelab Logs" dashboard provisioned in Grafana. Journald retention on noc-tux is capped at 8 GB / 60 days.
  • Metrics — Netdata parent on noc-tux streams from brew-installed children on both Macs. Prometheus scrapes the container stack.
  • Uptime — two Gatus instances (noc-local 🟡, noc-tux 🔵) cross-checking each other, alerting to Discord.
  • Alert mutingmaintenance-mode.sh on all 15m mutes a planned reboot. Both Gatus instances post through a local alert-suppressor proxy that spools what it swallows and posts a digest afterwards, so nothing is lost silently. The systemd/notifier scripts deliberately post direct to Discord and are not mutable — they are what tells you the proxy died.
  • IDS — CrowdSec LAPI v1.7.8 on noc-tux (Tailscale + LAN only) with an active firewall bouncer, plus Suricata. Both Macs run native agents in agent-only mode. Alerts fan out via Discord and an MLX-backed log-triage enricher on noc-claw.
  • File integrity — AIDE weekly on noc-tux, alerting on any change to the baseline.
  • External attack surface — weekly nuclei + testssl + ssh-audit run on noc-baguette, results to Discord on findings.
  • Backups — Kopia to a Hetzner Storage Box (noc-wardrobe), one repository for the fleet with per-machine source identity. Rollout in progress.

Secrets Management

git-crypt — transparent clean/smudge filter encryption. Plaintext on disk for direct service use; ciphertext in git objects. Patterns listed in .gitattributes (53 rules: configs/*.env, services/*/.env, both Gatus configs, etc.). gitleaks runs in the pre-commit hook as a defense-in-depth scan, alongside a repo sanity scan that verifies every git-crypt-matched file is genuinely ciphertext in the index before anything is pushed.

The git-crypt key lives in a separate private sync repo (noc-homelab-beads/, gitignored here). Fresh-clone procedure: git clone <url> && cd noc-homelab && git-crypt unlock /path/to/git-crypt.key.

Git Framework

  • noc-local is the only writable clone. Single remote: origin on self-hosted Forgejo. Public mirrors are updated manually, not by this repo.
  • Hosts (noc-tux, noc-claw) have no .git/. They receive working-tree updates via Syncthing and cannot commit. Edits made on a host propagate back to noc-local.
  • Pre-commit hook runs gitleaks against staged content. A leaked secret aborts the commit.
  • GPG-signed commits on all three machines.

Repository Structure

noc-homelab/
├── agent/                  REST API agent (canonical multi-host config)
│   ├── agent.py            Flask app, port 8080 (Linux) / 5005 (macOS)
│   ├── config.yaml         All hosts; agent filters by platform.node()
│   └── platforms/          Linux/macOS/Windows handlers
├── dashboard/              Control dashboard (runs on noc-local)
├── linux/                  Systemd units, scripts, native service configs
├── services/               Docker Compose stacks + per-service configs
├── configs/                Per-host service configs (most git-crypt-encrypted)
├── launchagents/           macOS LaunchAgent plists
├── scripts/                Utility scripts
├── setup/                  Per-machine bootstrap scripts
├── windows/                HISTORICAL — the decommissioned noc-winlocal pipeline
└── docs/                   Architecture and setup notes

Deployment

git clone ssh://[email protected]:2222/NocFA/noc-homelab.git
cd noc-homelab
git-crypt unlock /path/to/git-crypt.key
./setup/setup-linux.sh   # noc-tux
# or
./setup/setup-macos.sh   # noc-local / noc-claw

Setup scripts install dependencies, wire up the pre-commit hook, configure GPG signing, and lay down the systemd / launchd units relevant to the host.