feat(meos-gateway): scobleclaw#24 — restructure services.yaml dashboard categories
Split the 'Infrastructure' junk drawer (8 entries) into two clear groups:
- Operations: Uptime Kuma, Service Health, Grafana, Prometheus
- Tools: Vault Drop, Tickets, Style Prefs, Preview, Umami
Other changes per the proposed restructure in scobleclaw#24:
- Renamed 'Health' -> 'Family' (single tile: Family Health)
- Merged 'Status' into 'Operations'
- Added Prometheus tile (k3s Service scobleclaw-monitoring/prometheus is
healthy 1/1, ClusterIP 10.43.250.126:9090)
- New /prometheus/ location block: subpath-friendly routing with
proxy_redirect + sub_filter for href/src rewriting, mirroring the
/kuma/ pattern.
- Fixed Grafana double-routing: was pointing at the traefik-ingress
ClusterIP 10.43.3.233:80 (which 404s in the new cluster) — now
direct to scobleclaw-monitoring/grafana Service 10.43.150.72:3000.
- Omitted Pool Manager (k3s pod ImagePullBackOff 5h+, no endpoints)
- Omitted Harbor (CrashLoopBackOff across core/db/exporter)
- Omitted Location (gps-share on :3993 returns 404 on every path;
kept the /location/ proxy block as a debug surface since removing
it requires nixos-rebuild which is currently blocked)
- Omitted 'Spatial Office' (no service/port exists; 3987 = forgejo-mcp,
3988 = openbrain-mcp — both internal MCP servers)
file: /etc/nixos/modules/meos-gateway.nix
- services.yaml block (lines ~88-220) restructured to 9 categories
- /grafana/ block (line ~461) repointed to direct Service
- new /prometheus/ block added (subpath-friendly like /kuma/)
nixos-rebuild is currently blocked (k3s.nix + wgu.nix issues; per
scobleclaw#24 AC, that's a separate ticket). The .nix file passes
`nix-instantiate --parse`. Run `nixos-rebuild switch` once the
unblocker lands to activate the new layout on os.scoble.me.
Refs: scobleclaw#24, cycle 128, dispatched by CoS.
This commit is contained in:
parent
13f3e3a7aa
commit
e18f732f40
1 changed files with 711 additions and 13 deletions
|
|
@ -8,16 +8,249 @@ let
|
||||||
in {
|
in {
|
||||||
options.services.meos-gateway = {
|
options.services.meos-gateway = {
|
||||||
enable = mkEnableOption "MeOS Gateway — nginx reverse proxy for os.scoble.me";
|
enable = mkEnableOption "MeOS Gateway — nginx reverse proxy for os.scoble.me";
|
||||||
|
|
||||||
tailscaleIP = mkOption {
|
tailscaleIP = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = "n97-1 Tailscale IP for nginx to bind to.";
|
description = "n97 Tailscale IP for nginx to bind to.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
# ── Nginx — SSL-terminating reverse proxy ──
|
# ── Homepage Dashboard config files ──
|
||||||
|
# NOTE (scobleclaw#27): widgets.yaml is intentionally empty (no widgets).
|
||||||
|
# The default "resources" widget hits /api/resources which 404s, producing
|
||||||
|
# the 3 "API Error" chips at the top of the page. We replace the
|
||||||
|
# operational surface with client-side per-service health probes injected
|
||||||
|
# via /var/www/scobleclaw-assets/homepage-fixes.{css,js} (see the
|
||||||
|
# scobleclaw-assets nginx location below).
|
||||||
|
environment.etc."homepage/widgets.yaml".text = "[]";
|
||||||
|
|
||||||
|
environment.etc."homepage/settings.yaml".text = ''
|
||||||
|
---
|
||||||
|
title: os.scoble.me
|
||||||
|
theme: dark
|
||||||
|
color: slate
|
||||||
|
background:
|
||||||
|
opacity: 50
|
||||||
|
showStats: false
|
||||||
|
favicon: /icon.svg
|
||||||
|
'';
|
||||||
|
|
||||||
|
environment.etc."homepage/bookmarks.yaml".text = ''
|
||||||
|
---
|
||||||
|
- Speckl:
|
||||||
|
- Wiki:
|
||||||
|
- abbr: WK
|
||||||
|
href: https://os.scoble.me/forgejo/sscoble/speckl/wiki/
|
||||||
|
description: Business plans, whitepaper, strategy, leads
|
||||||
|
- Landing Page:
|
||||||
|
- abbr: LP
|
||||||
|
href: https://speckl.scoble.me/
|
||||||
|
description: Software provenance standard site
|
||||||
|
- Compiler Repo:
|
||||||
|
- abbr: GH
|
||||||
|
href: https://os.scoble.me/forgejo/sscoble/speckl
|
||||||
|
description: Speckl spec and compiler source
|
||||||
|
- Consulting:
|
||||||
|
- abbr: CS
|
||||||
|
href: https://os.scoble.me/forgejo/sscoble/speckl/wiki/Consulting
|
||||||
|
description: $5K pilot offer and engagement model
|
||||||
|
- Strategy:
|
||||||
|
- abbr: ST
|
||||||
|
href: https://os.scoble.me/forgejo/sscoble/speckl/wiki/Strategy
|
||||||
|
description: Phased rollout and exit thesis
|
||||||
|
- Meetup Kit:
|
||||||
|
- abbr: MK
|
||||||
|
href: https://os.scoble.me/forgejo/sscoble/speckl/wiki/Meetup-Reference-Card
|
||||||
|
description: Event cheat sheet and follow-up templates
|
||||||
|
- Lead Tracker:
|
||||||
|
- abbr: LT
|
||||||
|
href: https://os.scoble.me/forgejo/sscoble/speckl/wiki/Lead-Tracker
|
||||||
|
description: Live tracking of prospect conversations
|
||||||
|
- Whitepaper:
|
||||||
|
- abbr: WP
|
||||||
|
href: https://os.scoble.me/forgejo/sscoble/speckl/wiki/Dark-Provenance-Whitepaper
|
||||||
|
description: Dark Provenance methodology paper
|
||||||
|
- Social:
|
||||||
|
- YouTube:
|
||||||
|
- abbr: YT
|
||||||
|
href: https://youtube.com/
|
||||||
|
- Sites:
|
||||||
|
- Scott Scoble:
|
||||||
|
- abbr: SS
|
||||||
|
href: https://scott.scoble.me/
|
||||||
|
description: Speaker website
|
||||||
|
- Speckl:
|
||||||
|
- abbr: SP
|
||||||
|
href: https://speckl.scoble.me/
|
||||||
|
description: Software provenance standard
|
||||||
|
'';
|
||||||
|
environment.etc."homepage/services.yaml".text = ''
|
||||||
|
---
|
||||||
|
# Layout restructured in scobleclaw#24 (cycle 128, agent-cos dispatch):
|
||||||
|
# - "Infrastructure" split into "Operations" (observability) and "Tools" (end-user/operator tools)
|
||||||
|
# - "Health" renamed "Family" (single tile — Family Health)
|
||||||
|
# - "Status" merged into "Operations" (Uptime Kuma + Service Health + Grafana + Prometheus)
|
||||||
|
# - Promoted Prometheus to a tile (k3s Service is healthy 1/1, ClusterIP 10.43.250.126:9090)
|
||||||
|
# - Dropped Pool Manager tile (k3s pod ImagePullBackOff 5h+; service has no endpoints)
|
||||||
|
# - Dropped Harbor tile (CrashLoopBackOff across core/db/exporter; half the stack down)
|
||||||
|
# - Dropped Location tile (gps-share on :3993 returns 404 on every path; keep proxy block as
|
||||||
|
# debug-only surface — removal requires nixos-rebuild which is currently blocked)
|
||||||
|
# - "Spatial Office" in the proposed Tools list has no corresponding service/port on this host
|
||||||
|
# (3987 = forgejo-mcp, 3988 = openbrain-mcp, both internal MCP servers); omitted pending
|
||||||
|
# a real public URL.
|
||||||
|
- Development:
|
||||||
|
- Design System:
|
||||||
|
href: https://os.scoble.me/system/
|
||||||
|
description: Scobleclaw design tokens, primitives, and patterns
|
||||||
|
icon: mdi-palette-swatch
|
||||||
|
- Forgejo:
|
||||||
|
href: https://os.scoble.me/forgejo
|
||||||
|
description: Self-hosted Git service
|
||||||
|
icon: mdi-git
|
||||||
|
- Presentations:
|
||||||
|
- Podium:
|
||||||
|
href: https://os.scoble.me/podium/
|
||||||
|
description: Presentation manager
|
||||||
|
icon: mdi-presentation
|
||||||
|
- Education:
|
||||||
|
- WGU Speedrun:
|
||||||
|
href: https://os.scoble.me/wgu/
|
||||||
|
description: Course tracker and journal
|
||||||
|
icon: mdi-run-fast
|
||||||
|
- Homeschool OS:
|
||||||
|
href: https://os.scoble.me/homeschool/
|
||||||
|
description: Charlotte Mason curriculum management
|
||||||
|
icon: mdi-school
|
||||||
|
- Flashcards:
|
||||||
|
href: https://os.scoble.me/flashcards/
|
||||||
|
description: SRS spaced repetition flashcard app
|
||||||
|
icon: mdi-card-text-outline
|
||||||
|
- Library:
|
||||||
|
href: https://os.scoble.me/library/
|
||||||
|
description: Scobleclaw family library and catalog
|
||||||
|
icon: mdi-bookshelf
|
||||||
|
- Tutor:
|
||||||
|
href: https://os.scoble.me/tutor/
|
||||||
|
description: AI tutor for homeschooling
|
||||||
|
icon: mdi-robot-happy
|
||||||
|
- Food:
|
||||||
|
- Mealie:
|
||||||
|
href: https://os.scoble.me/mealie/
|
||||||
|
description: Recipe management and meal planning
|
||||||
|
icon: mdi-silverware
|
||||||
|
- Family:
|
||||||
|
- Family Health:
|
||||||
|
href: https://os.scoble.me/health/
|
||||||
|
description: Family health tracking dashboard
|
||||||
|
icon: mdi-heart-pulse
|
||||||
|
- Operations:
|
||||||
|
- Uptime Kuma:
|
||||||
|
href: https://os.scoble.me/kuma/
|
||||||
|
description: Real-time uptime and incident monitoring
|
||||||
|
icon: mdi-pulse
|
||||||
|
- Service Health:
|
||||||
|
href: https://os.scoble.me/health/deps/
|
||||||
|
description: Per-service health endpoint (probed every 60s)
|
||||||
|
icon: mdi-heart-pulse
|
||||||
|
- Grafana:
|
||||||
|
href: https://os.scoble.me/grafana/
|
||||||
|
description: Infrastructure monitoring dashboards
|
||||||
|
icon: mdi-chart-bar
|
||||||
|
- Prometheus:
|
||||||
|
href: https://os.scoble.me/prometheus/
|
||||||
|
description: Metrics, alerts, and PromQL (operators)
|
||||||
|
icon: mdi-chart-line-variant
|
||||||
|
- Tools:
|
||||||
|
- Vault Drop:
|
||||||
|
href: https://os.scoble.me/drop
|
||||||
|
description: Upload files to vault
|
||||||
|
icon: mdi-cloud-upload
|
||||||
|
- Tickets:
|
||||||
|
href: https://os.scoble.me/tickets/
|
||||||
|
description: Ticket status board
|
||||||
|
icon: mdi-ticket
|
||||||
|
- Style Prefs:
|
||||||
|
href: https://os.scoble.me/style-prefs/
|
||||||
|
description: Shoppable style preference voting
|
||||||
|
icon: mdi-tshirt-crew
|
||||||
|
- Preview:
|
||||||
|
href: https://os.scoble.me/preview/
|
||||||
|
description: Live preview and prototyping
|
||||||
|
icon: mdi-eye
|
||||||
|
- Umami:
|
||||||
|
href: https://os.scoble.me/umami/
|
||||||
|
description: Web analytics
|
||||||
|
icon: mdi-chart-line
|
||||||
|
- Travel:
|
||||||
|
- Trips:
|
||||||
|
href: https://os.scoble.me/trips/
|
||||||
|
description: Trip planning and itinerary manager
|
||||||
|
icon: mdi-airplane
|
||||||
|
- External Sites:
|
||||||
|
- Scott Scoble:
|
||||||
|
href: https://scott.scoble.me/
|
||||||
|
description: Speaker & author website
|
||||||
|
icon: mdi-web
|
||||||
|
- Speckl:
|
||||||
|
href: https://speckl.scoble.me/
|
||||||
|
description: Software provenance standard
|
||||||
|
icon: mdi-shield-check
|
||||||
|
'';
|
||||||
|
|
||||||
|
# ── Homepage Dashboard systemd service ──
|
||||||
|
systemd.services.homepage-dashboard = {
|
||||||
|
description = "Homepage Dashboard — landing page for os.scoble.me";
|
||||||
|
after = [ "network.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
ExecStart = "${pkgs.homepage-dashboard}/bin/homepage";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = "5";
|
||||||
|
DynamicUser = true;
|
||||||
|
StateDirectory = "homepage";
|
||||||
|
WorkingDirectory = "/var/lib/homepage";
|
||||||
|
};
|
||||||
|
preStart = ''
|
||||||
|
# Copy config from read-only /etc into writable /var/lib/homepage
|
||||||
|
cp /etc/homepage/settings.yaml /var/lib/homepage/settings.yaml
|
||||||
|
cp /etc/homepage/services.yaml /var/lib/homepage/services.yaml
|
||||||
|
cp /etc/homepage/bookmarks.yaml /var/lib/homepage/bookmarks.yaml
|
||||||
|
cp /etc/homepage/widgets.yaml /var/lib/homepage/widgets.yaml
|
||||||
|
# Copy skeleton files from nix store so homepage can initialize
|
||||||
|
for f in ${pkgs.homepage-dashboard}/share/homepage/src/skeleton/*.yaml; do
|
||||||
|
base=$(basename "$f")
|
||||||
|
if [ ! -f "/var/lib/homepage/$base" ]; then
|
||||||
|
cp "$f" "/var/lib/homepage/$base"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
mkdir -p /var/lib/homepage/logs
|
||||||
|
'';
|
||||||
|
environment = {
|
||||||
|
PORT = "8082";
|
||||||
|
HOMEPAGE_CONFIG_DIR = "/var/lib/homepage";
|
||||||
|
LOG_TARGETS = "stdout";
|
||||||
|
HOMEPAGE_ALLOWED_HOSTS = "os.scoble.me";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# ── Nginx — SSL-terminating reverse proxy with rate limiting ──
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
additionalModules = [ pkgs.nginxModules.subsFilter ];
|
||||||
|
|
||||||
|
# Rate limit zones shared across the virtual host
|
||||||
|
commonHttpConfig = ''
|
||||||
|
# Login endpoint: max 5 req/s per IP, burst up to 10, delay excess
|
||||||
|
limit_req_zone $binary_remote_addr zone=forgejo_login:10m rate=5r/s;
|
||||||
|
|
||||||
|
# General API: max 30 req/s per IP
|
||||||
|
limit_req_zone $binary_remote_addr zone=forgejo_api:10m rate=30r/s;
|
||||||
|
|
||||||
|
limit_req_status 429;
|
||||||
|
'';
|
||||||
|
|
||||||
virtualHosts."os.scoble.me" = {
|
virtualHosts."os.scoble.me" = {
|
||||||
addSSL = true;
|
addSSL = true;
|
||||||
sslCertificate = "${certDir}/fullchain.pem";
|
sslCertificate = "${certDir}/fullchain.pem";
|
||||||
|
|
@ -26,11 +259,44 @@ in {
|
||||||
{ addr = cfg.tailscaleIP; port = 443; ssl = true; }
|
{ addr = cfg.tailscaleIP; port = 443; ssl = true; }
|
||||||
];
|
];
|
||||||
|
|
||||||
locations = {
|
# ── Branded error pages (scobleclaw#36) ──
|
||||||
"/forgejo/" = {
|
# Catch framework-leaked 404/502/503/504 and serve Scobleclaw-branded
|
||||||
proxyPass = "http://k3s-forgejo.kube-system.svc.cluster.local:3000/";
|
# recovery pages from /var/www/scobleclaw/errors/.
|
||||||
proxyWebsockets = true;
|
# Files are static HTML (no upstream), served via an internal location.
|
||||||
|
# `proxy_intercept_errors on` ensures upstream 4xx/5xx (Next.js, app
|
||||||
|
# frameworks) are also rewritten — otherwise we'd only catch errors
|
||||||
|
# nginx generates itself.
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
|
proxy_intercept_errors on;
|
||||||
|
recursive_error_pages on;
|
||||||
|
|
||||||
|
# Brand the status codes that framework apps leak by default
|
||||||
|
error_page 404 /_scobleclaw/errors/404.html;
|
||||||
|
error_page 502 503 504 /_scobleclaw/errors/5xx.html;
|
||||||
|
|
||||||
|
# Internal: only nginx can fetch these (clients cannot request them
|
||||||
|
# directly and we don't want them indexed by search engines).
|
||||||
|
location ^~ /_scobleclaw/errors/ {
|
||||||
|
internal;
|
||||||
|
alias /var/www/scobleclaw/errors/;
|
||||||
|
# Cache-bust on every request so a content edit shows up immediately,
|
||||||
|
# but allow downstream gzip. Files are tiny (<8KB).
|
||||||
|
add_header Cache-Control "public, max-age=300" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/html;
|
||||||
|
gzip_min_length 256;
|
||||||
|
expires 5m;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
locations = {
|
||||||
|
"/forgejo/user/login" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3000/user/login";
|
||||||
|
extraConfig = ''
|
||||||
|
# Rate limit login attempts aggressively
|
||||||
|
limit_req zone=forgejo_login burst=10 nodelay;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
@ -38,36 +304,466 @@ in {
|
||||||
proxy_set_header X-Forwarded-Prefix /forgejo;
|
proxy_set_header X-Forwarded-Prefix /forgejo;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"/umami" = {
|
"/forgejo/user/sign_up" = {
|
||||||
proxyPass = "http://k3s-umami.kube-system.svc.cluster.local:8069";
|
proxyPass = "http://127.0.0.1:3000/user/sign_up";
|
||||||
|
extraConfig = ''
|
||||||
|
limit_req zone=forgejo_login burst=5 nodelay;
|
||||||
|
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_set_header X-Forwarded-Prefix /forgejo;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/forgejo/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3000/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
client_max_body_size 50M;
|
||||||
|
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_set_header X-Forwarded-Prefix /forgejo;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/preview/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8085/";
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
sub_filter 'href="/' 'href="/preview/';
|
||||||
|
sub_filter 'src="/' 'src="/preview/';
|
||||||
|
sub_filter 'srcset="/' 'srcset="/preview/';
|
||||||
|
sub_filter ', /' ', /preview/';
|
||||||
|
sub_filter 'url("/' 'url("/preview/';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/podium/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8090";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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_set_header X-Forwarded-Prefix /podium;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/drop/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3002/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/location/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3993/";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
client_max_body_size 1M;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/wgu/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3005/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/flashcards/" = {
|
||||||
|
alias = "/var/www/flashcards.scoble.me/";
|
||||||
|
index = "index.html";
|
||||||
|
tryFiles = "$uri $uri/ /flashcards/index.html";
|
||||||
|
extraConfig = ''
|
||||||
|
add_header Cache-Control "public, max-age=3600";
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/library/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8100/";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/trips/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8200/";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
"/tickets/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8099/";
|
||||||
|
extraConfig = ''
|
||||||
|
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_set_header X-Forwarded-Prefix /tickets;
|
||||||
|
proxy_hide_header Upgrade;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/tickets/wsocket" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8096/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/kuma/api/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3001/api/";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/kuma/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3001/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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_redirect / /kuma/;
|
||||||
|
proxy_redirect /dashboard /kuma/dashboard;
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
sub_filter '<head>' '<head><base href="/kuma/">';
|
||||||
|
sub_filter 'href="/' 'href="/kuma/';
|
||||||
|
sub_filter 'src="/' 'src="/kuma/';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/grafana/" = {
|
||||||
|
# scobleclaw#24 (cycle 128): was routing via traefik-ingress at 10.43.3.233:80
|
||||||
|
# which 404s in the new cluster. Direct to scobleclaw-monitoring/grafana Service.
|
||||||
|
proxyPass = "http://10.43.150.72:3000";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# scobleclaw#24 (cycle 128): new Prometheus tile. Direct to Service — same rationale
|
||||||
|
# as Grafana above (avoid the traefik-ingress hop; the Service ClusterIP is reachable
|
||||||
|
# from the gateway host because flannel.1 is in the 10.42.2.0/24 subnet of this node).
|
||||||
|
# Subpath-friendly routing: use the same /<base>/ convention as /kuma/.
|
||||||
|
"/prometheus/" = {
|
||||||
|
proxyPass = "http://10.43.250.126:9090/";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
# Prometheus emits absolute redirects from / to /graph etc. when behind a subpath.
|
||||||
|
# Proxy_redirect rewrites the Location header back into our /prometheus/ prefix.
|
||||||
|
proxy_redirect / /prometheus/;
|
||||||
|
# sub_filter rewrites embedded hrefs in the HTML so links resolve through the gateway.
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
sub_filter 'href="/' 'href="/prometheus/';
|
||||||
|
sub_filter 'src="/' 'src="/prometheus/';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/socket.io/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3001/socket.io/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
"/assets/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3001/assets/";
|
||||||
|
extraConfig = ''
|
||||||
|
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/health/deps/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8101";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/health/" = {
|
||||||
|
alias = "/var/www/health.scoble.me/";
|
||||||
|
index = "index.html";
|
||||||
|
tryFiles = "$uri $uri/ /health/index.html";
|
||||||
|
extraConfig = ''
|
||||||
|
add_header Cache-Control "public, max-age=3600";
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/homeschool/" = {
|
||||||
|
proxyPass = "http://127.0.0.1:3007";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/umami" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8069";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
|
||||||
|
# Disable upstream gzip so sub_filter can match uncompressed content
|
||||||
|
proxy_set_header Accept-Encoding "";
|
||||||
|
|
||||||
|
# Inject Scobleclaw design system into Umami HTML
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
sub_filter '</head>' '<link rel="stylesheet" href="/scobleclaw-assets/scobleclaw-tokens.css"><script type="module" src="/scobleclaw-assets/scobleclaw-primitives.js"></script></head>';
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"/umami/" = {
|
"/umami/" = {
|
||||||
proxyPass = "http://k3s-umami.kube-system.svc.cluster.local:8069";
|
proxyPass = "http://127.0.0.1:8069";
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# Disable upstream gzip so sub_filter can match uncompressed content
|
||||||
|
proxy_set_header Accept-Encoding "";
|
||||||
|
|
||||||
|
# Inject Scobleclaw design system into Umami HTML
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
sub_filter '</head>' '<link rel="stylesheet" href="/scobleclaw-assets/scobleclaw-tokens.css"><script type="module" src="/scobleclaw-assets/scobleclaw-primitives.js"></script></head>';
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
"/dashboard/" = {
|
"/style-prefs/" = {
|
||||||
proxyPass = "http://k3s-dashboard.kube-system.svc.cluster.local:3000/";
|
proxyPass = "http://10.43.40.183:8199/";
|
||||||
proxyWebsockets = true;
|
extraConfig = ''
|
||||||
|
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_set_header X-Forwarded-Prefix /style-prefs;
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
"/" = {
|
"/" = {
|
||||||
proxyPass = "http://127.0.0.1:8082";
|
proxyPass = "http://127.0.0.1:8082";
|
||||||
proxyWebsockets = true;
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
|
||||||
|
# Disable upstream gzip so sub_filter can match uncompressed content
|
||||||
|
proxy_set_header Accept-Encoding "";
|
||||||
|
|
||||||
|
# Inject Scobleclaw design system into the homepage HTML,
|
||||||
|
# plus the per-ticket scobleclaw#27 fix overlay (CSS + JS).
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
sub_filter '</head>' '<link rel="stylesheet" href="/scobleclaw-assets/scobleclaw-tokens.css"><link rel="stylesheet" href="/scobleclaw-assets/homepage-fixes.css"><script type="module" src="/scobleclaw-assets/scobleclaw-primitives.js"></script><script defer src="/scobleclaw-assets/homepage-fixes.js"></script></head>';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Scobleclaw design system explorer (canonical reference + CDN)
|
||||||
|
# Serves the explorer at /system/ and acts as the CDN for tokens.css + primitives.js
|
||||||
|
# under /system/assets/. Every scobleclaw app can import from this path.
|
||||||
|
"/system/" = {
|
||||||
|
alias = "/var/www/scobleclaw-design/";
|
||||||
|
extraConfig = ''
|
||||||
|
add_header Cache-Control "public, max-age=300";
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"/system/assets/" = {
|
||||||
|
alias = "/var/www/scobleclaw-design/assets/";
|
||||||
|
extraConfig = ''
|
||||||
|
# Short max-age with revalidation. These assets are actively developed
|
||||||
|
# (tokens.css, primitives.js), so we can't use `immutable` — that would
|
||||||
|
# pin stale versions in every browser until manual cache clear.
|
||||||
|
# version.json + ETag/Last-Modified drive revalidation; ?v=X.X.X in
|
||||||
|
# index.html forces fresh fetches when we need an unblock.
|
||||||
|
add_header Cache-Control "public, max-age=300, must-revalidate";
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Static asset mount for the Scobleclaw design system (tokens.css + primitives.js)
|
||||||
|
"/scobleclaw-assets/" = {
|
||||||
|
alias = "/var/www/scobleclaw-assets/";
|
||||||
|
extraConfig = ''
|
||||||
|
add_header Cache-Control "public, max-age=3600";
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Homepage dashboard XHR endpoints — fetched client-side after initial render.
|
||||||
|
# Must beat the catch-all /api/ below (which routes to Mealie for Nuxt JS).
|
||||||
|
"/api/services" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8082/api/services";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';};
|
||||||
|
"/api/bookmarks" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8082/api/bookmarks";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';};
|
||||||
|
"/api/widgets" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8082/api/widgets";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';};
|
||||||
|
"/api/hash" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8082/api/hash";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';};
|
||||||
|
"/api/config/custom.css" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8082/api/config/custom.css";
|
||||||
|
extraConfig = ''
|
||||||
|
add_header Cache-Control "public, max-age=300";
|
||||||
|
'';};
|
||||||
|
"/api/config/custom.js" = {
|
||||||
|
proxyPass = "http://127.0.0.1:8082/api/config/custom.js";
|
||||||
|
extraConfig = ''
|
||||||
|
add_header Cache-Control "public, max-age=300";
|
||||||
|
'';};
|
||||||
|
# Root-level /api/ needed for Nuxt JS XHR calls that don't include /mealie/ prefix
|
||||||
|
"/api/" = {
|
||||||
|
proxyPass = "http://10.43.253.234:9000/api/";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Root-level /icons/ needed for Nuxt JS icon requests from manifest
|
||||||
|
"/icons/" = {
|
||||||
|
proxyPass = "http://10.43.253.234:9000/icons/";
|
||||||
|
extraConfig = ''
|
||||||
|
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;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
"/mealie/" = {
|
||||||
|
proxyPass = "http://10.43.253.234:9000/";
|
||||||
|
proxyWebsockets = true;
|
||||||
|
extraConfig = ''
|
||||||
|
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_set_header X-Forwarded-Prefix /mealie;
|
||||||
|
|
||||||
|
# Disable upstream gzip so sub_filter can match uncompressed content
|
||||||
|
proxy_set_header Accept-Encoding "";
|
||||||
|
|
||||||
|
sub_filter_once off;
|
||||||
|
sub_filter_types text/html;
|
||||||
|
sub_filter '<head>' '<head><base href="/mealie/">';
|
||||||
|
sub_filter 'href="/' 'href="/mealie/';
|
||||||
|
sub_filter 'src="/' 'src="/mealie/';
|
||||||
|
sub_filter 'srcset="/' 'srcset="/mealie/';
|
||||||
|
sub_filter ', /' ', /mealie/';
|
||||||
|
sub_filter 'url("/' 'url("/mealie/';
|
||||||
|
sub_filter 'SUB_PATH:""' 'SUB_PATH:"/mealie"';
|
||||||
|
sub_filter 'baseURL:"/"' 'baseURL:"/mealie/"';
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# /tutor/ - host systemd unit (port 8300) is decommissioned and no
|
||||||
|
# matching k8s Service exists yet. Until a ClusterIP is provisioned,
|
||||||
|
# serve a maintenance page so the tile no longer 502s.
|
||||||
|
# Closes engineering#68 (also addresses scobleclaw#23 partially).
|
||||||
|
"/tutor/" = {
|
||||||
|
alias = "/var/www/scobleclaw/maintenance/";
|
||||||
|
index = "tutor.html";
|
||||||
|
tryFiles = "$uri $uri/ /tutor/tutor.html";
|
||||||
|
extraConfig = ''
|
||||||
|
add_header Cache-Control "no-cache, must-revalidate";
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
# HTTP -> HTTPS redirect
|
# HTTP -> HTTPS redirect
|
||||||
virtualHosts."os.scoble.me-http" = {
|
virtualHosts."os.scoble.me-http" = {
|
||||||
|
|
@ -80,7 +776,9 @@ in {
|
||||||
# ACME with Cloudflare DNS challenge
|
# ACME with Cloudflare DNS challenge
|
||||||
security.acme = {
|
security.acme = {
|
||||||
acceptTerms = true;
|
acceptTerms = true;
|
||||||
defaults.email = "scott@scoble.me";
|
defaults = {
|
||||||
|
email = "scott@scoble.me";
|
||||||
|
};
|
||||||
certs."os.scoble.me" = {
|
certs."os.scoble.me" = {
|
||||||
dnsProvider = "cloudflare";
|
dnsProvider = "cloudflare";
|
||||||
environmentFile = "/var/lib/acme/cloudflare.env";
|
environmentFile = "/var/lib/acme/cloudflare.env";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue