Compare commits

...

3 Commits

Author SHA1 Message Date
jeremy 78b824bb39 fix: pin gitea to 1.26.1 and disable Watchtower to prevent broken image pulls
Watchtower pulled gitea/gitea:latest three times (Mar 12, Apr 19, Apr 25)
and at least one pull resulted in a broken image with git binary failures.
Pin to stable gitea/gitea:1.26.1 and add watchtower.enable=false label
so the image is only updated by deliberate human action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 16:17:15 -05:00
jeremy a908973264 infra: rongorongo state-sync backend + Traefik basicAuth
Add a Flask backend service for cross-device state persistence, storing
JSON state on an NFS volume at /mnt/docker-data/configs/rongorongo/data.
Frontend Dockerfile/nginx.conf reorganised into frontend/ subdirectory;
frontend API calls now route to /api/* handled by the backend container
over a dedicated bridge network. Traefik basicAuth middleware added to
the public URL with the bcrypt hash deliberately kept out of git
(REPLACE_AT_DEPLOY placeholder in rongorongo.yml). Prometheus probe moved
from https-external to internal http://192.168.86.192:8181/health to
bypass the new auth wall.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 18:51:47 -05:00
jeremy 795908e1ef infra: deploy Rongorongo decipherment engine to hf-pdocker-01
Standalone Compose service running a locally-built nginx image that
serves a static Anthropic-API frontend. Traefik dynamic route added
for public HTTPS access. Blackbox probe added to prometheus.yml for
uptime monitoring. CLAUDE.md updated with service entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 13:10:47 -05:00
11 changed files with 2530 additions and 78 deletions
+44 -67
View File
@@ -4,14 +4,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Repository Overview
This repository manages a **homelab infrastructure** with self-hosted services running across 3 nodes using a **hybrid orchestration** model: Docker Swarm for core infrastructure services, standalone Docker Compose for isolated services.
This repository manages a **homelab infrastructure** with self-hosted services running across 3 nodes using **standalone Docker Compose** on all nodes. (Docker Swarm was fully dismantled on 2026-04-01.)
### Cluster Architecture
**Nodes:**
- **hf-pdocker-01** (192.168.86.192) - Swarm Manager, core infrastructure + Frigate NVR - **RESTORED (2026-03-08)**
- **hf-pdocker-02** (192.168.86.100) - Standalone Compose services (dev tools, utilities)
- **bart** (192.168.86.167) - Standalone Compose services (media stack, OpenVAS)
- **hf-pdocker-01** (192.168.86.192) - Core infrastructure + Frigate NVR - **RESTORED (2026-03-08)**
- **hf-pdocker-02** (192.168.86.100) - Dev tools, utilities
- **bart** (192.168.86.167) - Media stack, OpenVAS
**Synology NAS** (192.168.86.30) - Centralized NFS storage for all service data
@@ -20,47 +20,47 @@ This repository manages a **homelab infrastructure** with self-hosted services r
### Service Distribution
```
hf-pdocker-01 (Manager, 23GB RAM, i7-7700T, NVMe, HD 630 GPU) -- RESTORED (2026-03-08, hardware repaired)
hf-pdocker-01 (23GB RAM, i7-7700T, NVMe, HD 630 GPU) -- RESTORED (2026-03-08, hardware repaired)
├── [COMPOSE] Frigate NVR (:5000) -- OpenVINO GPU detector, 13 cameras active
├── [COMPOSE] Frigate-Notify v2 (:5199) -- MQTT->AI->Matrix notifications
├── [COMPOSE] clip-service (:5200) -- CLIP embeddings (ViT-L-14, CPU)
├── [COMPOSE] Open WebUI (:3010) -- AI chat UI (Ollama + Anthropic backends)
├── [COMPOSE] Home Assistant (:8123)
├── [COMPOSE] ESPHome (:6052)
├── [COMPOSE] Mosquitto (:1883, :8883)
├── [COMPOSE] Synapse/Matrix (:8008) + synapse-db (postgres) + wellknown (:8010)
├── [COMPOSE] Guacamole (:8199) + guacd + guacamole-db (mysql, LOCAL /var/lib/guacamole-db)
├── [COMPOSE] AI Agents (margaret:18790, spirit:18795 [infra ops], clippy:18800, bob:18810, travis:18815)
├── [COMPOSE] AI Agents (margaret:18790, spirit:18795 [infra ops])
├── [COMPOSE] Traefik (reverse proxy, ports 80/443/8090)
├── [COMPOSE] AdGuard Home (:3000, :53)
├── [COMPOSE] Monitoring (Grafana :3001, Prometheus :9095, Alertmanager :9093, InfluxDB :8086, Blackbox :9115, matrix-receiver :9094)
├── [COMPOSE] Farm Manager API (:8888) -- dashboard + API server
├── [COMPOSE] Farm Agent (:8889)
├── [SWARM] Telegraf (global), Node-exporter (global)
├── [COMPOSE] Rongorongo decipherment engine -- 2 containers (rongorongo-web :8181, rongorongo-api). Server-synced state at /mnt/docker-data/configs/rongorongo/data. Traefik basicAuth on rongorongo.hobbs.farm.
├── [REMOVED] VoiceCoach -- moved to GPU machine (192.168.86.150) 2026-04-04
├── [COMPOSE] Node-exporter (:9100) -- standalone with pid:host
├── [COMPOSE] Telegraf -- metrics collection to InfluxDB
├── [CRON] sync-frigate-to-nas (daily 2am), cleanup-frigate-media (weekly Sun 3am), restart-frigate (daily 4am)
├── 5TB USB disk at /mnt/frigate-recordings (Frigate recordings storage)
bart (Worker, 15GB RAM, i5-4570T, HDD)
bart (15GB RAM, i5-4570T, HDD)
├── [COMPOSE] Media Stack (Jellyfin, Sonarr, Radarr, SABnzbd, NZBHydra2, Jellyseerr)
├── [COMPOSE] OpenVAS (vulnerability scanner)
├── [COMPOSE] Onyx (:3005) -- AI document search/RAG (Ollama backend: llava:13b on 192.168.86.196)
├── [COMPOSE] Farm Agent (:8889)
├── [COMPOSE] Node-exporter (:9100) -- standalone with pid:host
├── [COMPOSE] Telegraf -- metrics collection to InfluxDB
├── [COMPOSE] Watchtower
├── [SWARM] Telegraf (global), Node-exporter (global)
hf-pdocker-02 (Worker, 7.6GB RAM, i7-6700T, HDD)
hf-pdocker-02 (7.6GB RAM, i7-6700T, HDD)
├── [COMPOSE] Unifi + unifi-db (MongoDB)
├── [COMPOSE] Gitea + gitea-db (PostgreSQL)
├── [COMPOSE] Semaphore + semaphore-db (MySQL)
├── [COMPOSE] Wiki.js + wikijs-db (PostgreSQL)
├── [COMPOSE] Vaultwarden, Actual Budget, MCPO, Trivy
├── [COMPOSE] SearXNG (:8888) -- self-hosted meta-search engine (Onyx web search backend)
├── [COMPOSE] Unstructured API (:8000) -- document parsing for Onyx file ingestion
├── [COMPOSE] n8n + n8n-db (PostgreSQL) -- Clippy workflow automation
├── [COMPOSE] NetClaw/Aaron (:1179) -- AI network agent
├── [COMPOSE] n8n + n8n-db (PostgreSQL) -- workflow automation
├── [COMPOSE] Farm Agent (:8889)
├── [COMPOSE] Node-exporter (:9100) -- standalone with pid:host
├── [COMPOSE] Telegraf -- metrics collection to InfluxDB
├── [COMPOSE] Watchtower
├── [SWARM] Telegraf (global), Node-exporter (global)
```
@@ -70,7 +70,7 @@ hf-pdocker-02 (Worker, 7.6GB RAM, i7-6700T, HDD)
You are working in a **WSL 2 environment** on Windows. Docker is NOT directly available in WSL - all Docker operations must be performed by connecting to the remote nodes via SSH.
**Critical:** Use the `docker-swarm-ops` agent (via Task tool) for ALL Docker operations. Do not attempt to run docker commands directly in bash - they will fail.
**Critical:** Docker is not available locally in WSL. All Docker operations must be performed by connecting to the remote nodes via SSH.
### Storage Architecture
@@ -84,7 +84,7 @@ All service data lives on the **Synology NAS** via NFS, mounted at `/mnt/docker-
├── traefik/ # Let's Encrypt certs
└── compose/ # Standalone compose files per node
├── hf-pdocker-01/{frigate,frigate-notify,clip-service,matrix,smarthome,guacamole,ai-agents,traefik,adguard}/
├── hf-pdocker-02/{unifi,gitea,semaphore,wikijs,vaultwarden,actual-budget,mcpo,trivy,netclaw,watchtower}/
├── hf-pdocker-02/{unifi,gitea,semaphore,wikijs,vaultwarden,actual-budget,mcpo,trivy,watchtower}/
└── bart/{media,openvas,watchtower}/
```
@@ -95,13 +95,10 @@ All service data lives on the **Synology NAS** via NFS, mounted at `/mnt/docker-
### Repository File Structure
```
stacks/ # Swarm stack definitions (deployed on hf-pdocker-01)
├── {service-name}/
│ ├── docker-compose.yml
│ └── .env (if needed)
stacks/ # LEGACY Swarm stack definitions (Swarm dismantled 2026-04-01, kept for reference)
compose/ # Standalone Compose definitions (deployed per-node)
├── hf-pdocker-01/ # Frigate
├── hf-pdocker-01/ # Core infrastructure + Frigate
├── hf-pdocker-02/ # Dev tools & utilities
│ ├── {service}/docker-compose.yml
│ └── {service}/.env
@@ -110,20 +107,15 @@ compose/ # Standalone Compose definitions (deployed per-node)
### Deployment Workflow
**Swarm services (stacks/):**
1. Edit stack file in `stacks/{service-name}/docker-compose.yml`
2. SCP to hf-pdocker-01 and redeploy: `docker stack deploy -c <file> <stack-name>`
**Standalone Compose services (compose/):**
**All services (compose/):**
1. Edit compose file in `compose/{node}/{service}/docker-compose.yml`
2. SCP to NAS path or node, then: `cd /mnt/docker-data/compose/{node}/{service} && docker compose up -d`
### Service Architecture Patterns
**Network Patterns:**
- **Swarm services**: Overlay networks for inter-service communication
- **Compose services**: Bridge networks for service isolation
- **Public services** (Traefik): Connect to `traefik-public` overlay + Traefik labels
- **Public services** (Traefik): Traefik labels + dynamic config files in `/mnt/docker-data/traefik/dynamic/`
**Database Patterns:**
- PostgreSQL: `postgres:15-alpine` or `postgres:16-alpine`
@@ -146,8 +138,8 @@ compose/ # Standalone Compose definitions (deployed per-node)
- Home Assistant: `yoda.hobbs.farm`
- Vaultwarden: `vault.hobbs.farm`
- Gitea: `git.hobbs.farm`
- Onyx: `onyx.hobbs.farm`
- Open WebUI: `open.hobbs.farm`
- VoiceCoach: `coach.hobbs.farm` (LiveKit WebRTC: `voice.hobbs.farm`)
- Rongorongo: `rongorongo.hobbs.farm`
### Internal Services by Node
@@ -155,7 +147,6 @@ compose/ # Standalone Compose definitions (deployed per-node)
- Frigate: :5000
- Frigate Alerts (v2): :5199 (web UI + REST API)
- clip-service: :5200 (CLIP embeddings, ViT-L-14, CPU)
- Open WebUI: :3010 (AI chat UI, public at open.hobbs.farm)
- Home Assistant: :8123
- ESPHome: :6052
- Mosquitto: :1883
@@ -170,9 +161,11 @@ compose/ # Standalone Compose definitions (deployed per-node)
- InfluxDB: :8086
- Blackbox Exporter: :9115
- matrix-alertmanager-receiver: :9094
- AI Agents (margaret :18790, spirit :18795 [infra ops — monitors #notifications, auto-remediates alerts], clippy :18800, bob :18810, travis :18815) -- LAN-accessible (bind=lan, 0.0.0.0)
- AI Agents (margaret :18790, spirit :18795 [infra ops — monitors #notifications, auto-remediates alerts]) -- LAN-accessible (bind=lan, 0.0.0.0)
- Farm Manager: :8888 (dashboard + API)
- Farm Agent: :8889
- Rongorongo: :8181 (rongorongo-web nginx; reverse-proxies /api/* to rongorongo-api Flask backend on the internal `rongorongo` bridge net). State + images at /mnt/docker-data/configs/rongorongo/data/. Public URL gated by Traefik basicAuth (creds in Vaultwarden).
- ~~VoiceCoach~~ -- moved to GPU machine (192.168.86.150) 2026-04-04
**hf-pdocker-02 (192.168.86.100):**
- Farm Agent: :8889
@@ -184,9 +177,6 @@ compose/ # Standalone Compose definitions (deployed per-node)
- Actual Budget: :5006
- MCPO: :8001
- Trivy: :4954
- SearXNG: :8888 (meta-search, Onyx web search backend)
- Unstructured API: :8000 (document parsing for Onyx file ingestion)
- NetClaw/Aaron: :1179 (internal only)
**bart (192.168.86.167):**
- Farm Agent: :8889
@@ -197,19 +187,27 @@ compose/ # Standalone Compose definitions (deployed per-node)
- NZBHydra2: :5076
- Jellyseerr: :5055
- OpenVAS: :9392
- Onyx: :3005 (AI document search/RAG)
**GPU Machine (192.168.86.196, RTX 3090):**
- Ollama: :11434 (vision LLM, llava:13b)
**GPU Machine (192.168.86.150, RTX 3090, 15GB RAM):**
- Ollama: :11434 (llama3.1:8b for VoiceCoach LLM, qwen2.5:14b, others)
- [COMPOSE] VoiceCoach (all services co-located):
- voicecoach-livekit: :7880/:7881, UDP 50000-50100 (public at voice.hobbs.farm)
- voicecoach-speaches-gpu: :8000 (GPU Whisper STT + Kokoro TTS, CUDA float16)
- voicecoach-speechbrain: :8001 (emotion analysis)
- voicecoach-token-server: :8080 (token + settings API)
- voicecoach-pipecat: pipeline agent (LLM via local Ollama llama3.1:8b)
- voicecoach-web: :8085 (public at coach.hobbs.farm)
- voicecoach-openclaw: :3100 (idle, kept for future Claude API routing)
- Compose: ~/voicecoach-speaches/docker-compose.yml
### Port Conflict Notes
Port conflicts already resolved:
- Port 8000: Now used by Unstructured API on hf-pdocker-02 (MCPO moved to 8001, Portainer moved elsewhere)
- Port 8000: Freed (MCPO moved to 8001, Portainer moved elsewhere)
- Port 3000: AdGuard Home (Gitea moved to 3003)
- Port 3001: Grafana (Semaphore moved to 3004)
- Port 5000: Frigate uses host networking (registry container stopped)
- AI Agent ports re-spaced (2026-03-15): openclaw 2026.3.13 uses gateway+2 and gateway+3 for browser/chrome-relay. Agents now spaced 5 apart: margaret=18790, spirit=18795, clippy=18800, bob=18810, travis=18815
- AI Agent ports re-spaced (2026-03-15): openclaw 2026.3.13 uses gateway+2 and gateway+3 for browser/chrome-relay. Agents spaced 5 apart: margaret=18790, spirit=18795
## Critical Configuration Preservation
@@ -235,20 +233,11 @@ Do NOT wait for the user to ask. Do NOT skip "because the change was small." Eve
## Common Operations
### Deploy/Update a Swarm Service
1. Edit `stacks/{service}/docker-compose.yml`
2. Use docker-swarm-ops agent to SCP and deploy:
```bash
docker stack deploy -c <compose-file> <stack-name>
```
### Deploy/Update a Compose Service
1. Edit `compose/{node}/{service}/docker-compose.yml`
2. Use docker-swarm-ops agent to:
2. SSH to the target node and run:
```bash
# On the target node:
cd /mnt/docker-data/compose/{node}/{service}
docker compose up -d
```
@@ -256,11 +245,7 @@ Do NOT wait for the user to ask. Do NOT skip "because the change was small." Eve
### Troubleshoot
```bash
# Swarm services (on hf-pdocker-01):
docker service ls
docker service logs {stack}_{service} --tail 100
# Compose services (on target node):
# On target node:
docker compose -f /mnt/docker-data/compose/{node}/{service}/docker-compose.yml logs --tail 100
docker ps --format "table {{.Names}}\t{{.Status}}"
```
@@ -268,13 +253,8 @@ docker ps --format "table {{.Names}}\t{{.Status}}"
### Check Cluster Health
```bash
# On hf-pdocker-01:
docker node ls # Swarm node status
docker service ls # Swarm services
docker stack ls # Deployed stacks
# On each node:
docker ps # Local containers (Compose + Swarm)
docker ps # Local containers
```
## Data Management
@@ -296,9 +276,6 @@ All persistent data on NAS at `/mnt/docker-data/`:
## Specialized Agents
### docker-swarm-ops
**Use for:** All Docker operations (Swarm + Compose) on any node
### vault-credential-manager
**Use for:** Storing/retrieving credentials from Vaultwarden
@@ -0,0 +1,15 @@
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD python -c "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health').status==200 else 1)" || exit 1
CMD ["gunicorn", "-w", "2", "-t", "60", "-b", "0.0.0.0:8000", "app:app"]
@@ -0,0 +1,139 @@
"""
Rongorongo state-sync backend.
Single-user. Stores state as one JSON file and images as content-addressed
files on disk. No DB, no auth — auth is enforced by Traefik basicAuth in
front of the whole app.
"""
import hashlib
import json
import mimetypes
import os
import tempfile
from pathlib import Path
from flask import Flask, abort, jsonify, request, send_file
DATA_DIR = Path(os.environ.get("RONGORONGO_DATA_DIR", "/data"))
STATE_FILE = DATA_DIR / "state.json"
IMAGES_DIR = DATA_DIR / "images"
MAX_IMAGE_BYTES = 25 * 1024 * 1024 # 25 MB per image
DATA_DIR.mkdir(parents=True, exist_ok=True)
IMAGES_DIR.mkdir(parents=True, exist_ok=True)
app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = MAX_IMAGE_BYTES + 1024
def _atomic_write(path: Path, data: bytes) -> None:
fd, tmp = tempfile.mkstemp(dir=path.parent, prefix=".tmp-", suffix=".part")
try:
with os.fdopen(fd, "wb") as fh:
fh.write(data)
os.replace(tmp, path)
except Exception:
try:
os.unlink(tmp)
except FileNotFoundError:
pass
raise
def _ext_for(mime: str) -> str:
if not mime:
return ".bin"
guess = mimetypes.guess_extension(mime) or ".bin"
# mimetypes returns .jpe for image/jpeg on some systems
return ".jpg" if guess == ".jpe" else guess
@app.get("/health")
def health():
return ("ok\n", 200, {"Content-Type": "text/plain"})
@app.get("/api/health")
def api_health():
return ("ok\n", 200, {"Content-Type": "text/plain"})
@app.get("/api/state")
def get_state():
if not STATE_FILE.exists():
return jsonify(
{
"images": [],
"glyphs": [],
"anchors": [],
"analyses": [],
"feasibilityRuns": [],
"confidenceHistory": {},
}
)
return send_file(STATE_FILE, mimetype="application/json")
@app.put("/api/state")
def put_state():
if not request.is_json:
abort(415, description="expected application/json")
payload = request.get_json(silent=False)
if not isinstance(payload, dict):
abort(400, description="state must be a JSON object")
body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
_atomic_write(STATE_FILE, body)
return jsonify({"ok": True, "bytes": len(body)})
def _find_image(image_hash: str) -> Path | None:
for p in IMAGES_DIR.glob(f"{image_hash}.*"):
return p
return None
@app.post("/api/images")
def upload_image():
f = request.files.get("file")
if f is None or f.filename == "":
abort(400, description="missing file")
data = f.read()
if len(data) == 0:
abort(400, description="empty file")
if len(data) > MAX_IMAGE_BYTES:
abort(413, description="image too large")
digest = hashlib.sha256(data).hexdigest()
mime = f.mimetype or "application/octet-stream"
existing = _find_image(digest)
if existing is None:
target = IMAGES_DIR / f"{digest}{_ext_for(mime)}"
_atomic_write(target, data)
return jsonify(
{"hash": digest, "name": f.filename, "mimeType": mime, "size": len(data)}
)
@app.get("/api/images/<image_hash>")
def get_image(image_hash: str):
if not all(c in "0123456789abcdef" for c in image_hash) or len(image_hash) != 64:
abort(400, description="invalid hash")
p = _find_image(image_hash)
if p is None:
abort(404)
mime, _ = mimetypes.guess_type(str(p))
return send_file(p, mimetype=mime or "application/octet-stream")
@app.delete("/api/images/<image_hash>")
def delete_image(image_hash: str):
if not all(c in "0123456789abcdef" for c in image_hash) or len(image_hash) != 64:
abort(400, description="invalid hash")
p = _find_image(image_hash)
if p is None:
return ("", 204)
p.unlink()
return ("", 204)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000)
@@ -0,0 +1,2 @@
flask==3.0.3
gunicorn==23.0.0
@@ -0,0 +1,60 @@
services:
rongorongo-api:
build:
context: ./backend
dockerfile: Dockerfile
image: rongorongo-api:latest
container_name: rongorongo-api
restart: unless-stopped
networks:
- rongorongo
volumes:
- /mnt/docker-data/configs/rongorongo/data:/data
mem_limit: 256M
memswap_limit: 256M
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/health').status==200 else 1)"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
labels:
- "com.centurylinklabs.watchtower.enable=false"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
rongorongo-web:
build:
context: ./frontend
dockerfile: Dockerfile
image: rongorongo-web:latest
container_name: rongorongo-web
restart: unless-stopped
depends_on:
- rongorongo-api
networks:
- rongorongo
ports:
- "8181:80"
mem_limit: 128M
memswap_limit: 128M
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
labels:
- "com.centurylinklabs.watchtower.enable=false"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
rongorongo:
driver: bridge
@@ -0,0 +1,14 @@
FROM nginx:1.25-alpine
RUN rm -rf /usr/share/nginx/html/*
COPY index.html /usr/share/nginx/html/index.html
COPY nginx.conf /etc/nginx/conf.d/default.conf
RUN chmod 644 /usr/share/nginx/html/index.html
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1/health || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,51 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
client_max_body_size 30m;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# CSP — allows Anthropic API calls and Google Fonts
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob:; connect-src 'self' https://api.anthropic.com; worker-src blob:;" always;
gzip on;
gzip_types text/html text/css application/javascript application/json;
gzip_min_length 1024;
location /health {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
location /api/ {
resolver 127.0.0.11 valid=10s;
set $backend "http://rongorongo-api:8000";
proxy_pass $backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_request_buffering off;
proxy_read_timeout 60s;
}
location / {
try_files $uri $uri/ /index.html;
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
@@ -1,8 +1,10 @@
services:
gitea:
image: gitea/gitea:latest
image: gitea/gitea:1.26.1
container_name: gitea
restart: unless-stopped
labels:
- "com.centurylinklabs.watchtower.enable=false"
ports:
- "3003:3000"
- "2222:22"
+4 -10
View File
@@ -57,7 +57,6 @@ scrape_configs:
- http://192.168.86.192:5000 # Frigate NVR (standalone, host network)
- http://192.168.86.192:5199/health # Frigate Notify
- http://192.168.86.192:5200/health # clip-service (CLIP embeddings)
- http://192.168.86.192:3010 # Open WebUI
- http://192.168.86.192:8888/health # Farm Manager API
- http://192.168.86.192:8889/health # Farm Agent
- http://192.168.86.192:8123 # Home Assistant
@@ -69,8 +68,10 @@ scrape_configs:
- http://192.168.86.192:3001 # Grafana
- http://192.168.86.192:9095 # Prometheus
- http://192.168.86.192:9093 # Alertmanager
- http://192.168.86.192:18815 # Travis (NemoClaw agent)
- http://192.168.86.192:18795 # Spirit (infra ops agent)
- http://192.168.86.192:8085 # VoiceCoach web client
- http://192.168.86.192:8181/health # Rongorongo (probe internal — Traefik basicAuth blocks external)
- http://192.168.86.192:7880 # VoiceCoach LiveKit server
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
@@ -93,9 +94,6 @@ scrape_configs:
- http://192.168.86.100:5006 # Actual Budget
- http://192.168.86.100:8001/openapi.json # MCPO
- http://192.168.86.100:8889/health # Farm Agent
- http://192.168.86.100:8888 # SearXNG
- http://192.168.86.100:8000/healthcheck # Unstructured API
- http://192.168.86.100:9000 # Portainer
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
@@ -135,10 +133,6 @@ scrape_configs:
- http://192.168.86.167:5055 # Jellyseerr
- http://192.168.86.167:9392 # OpenVAS
- http://192.168.86.167:8889/health # Farm Agent
- targets:
- http://192.168.86.167:3005 # Onyx
labels:
name: onyx
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
@@ -159,7 +153,7 @@ scrape_configs:
- https://yoda.hobbs.farm # Home Assistant
- https://vault.hobbs.farm # Vaultwarden
- https://git.hobbs.farm # Gitea
- https://open.hobbs.farm # Open WebUI
- https://coach.hobbs.farm # VoiceCoach web client
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
+35
View File
@@ -0,0 +1,35 @@
http:
routers:
rongorongo:
rule: "Host(`rongorongo.hobbs.farm`)"
entryPoints:
- websecure
middlewares:
- rongorongo-auth
- security-headers
service: rongorongo
tls:
certResolver: letsencrypt
rongorongo-http:
rule: "Host(`rongorongo.hobbs.farm`)"
entryPoints:
- web
middlewares:
- rongorongo-https-redirect
service: rongorongo
middlewares:
rongorongo-https-redirect:
redirectScheme:
scheme: https
permanent: true
rongorongo-auth:
basicAuth:
realm: "rongorongo.hobbs.farm"
# users entries are appended at deploy time as: <user>:<bcrypt-hash>
users:
- "REPLACE_AT_DEPLOY"
services:
rongorongo:
loadBalancer:
servers:
- url: "http://192.168.86.192:8181"