- Python 59.9%
- Shell 20.4%
- HTML 16.2%
- PowerShell 2.7%
- CSS 0.4%
- Other 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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. |
||
| .github/ISSUE_TEMPLATE | ||
| agent | ||
| archive | ||
| configs | ||
| dashboard | ||
| docs | ||
| launchagents | ||
| linux | ||
| onedrive-migration | ||
| scripts | ||
| services | ||
| setup | ||
| windows | ||
| .gitattributes | ||
| .gitignore | ||
| .gitleaks.toml | ||
| .gitleaksignore | ||
| .sops.yaml | ||
| .stignore | ||
| LICENSE | ||
| README.md | ||
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.
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 muting —
maintenance-mode.sh on all 15mmutes a planned reboot. Both Gatus instances post through a localalert-suppressorproxy 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:
originon 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.