knuths.dev

Ett självhostat privat moln på en enda Ubuntu-VM — byggt öppet, härdat medvetet.

Docker Swarm + Dokploy Cloudflare Tunnel · inga öppna portar GPU-LLM + RAG non-root · CSP A+ · Trivy nattligt

01Apparna — live

Flera småappar under en domän, en app per subdomän. Alla länkar går till skarpa tjänster.

Kollar driftstatus…

02Arkitektur

All publik ingress går via en utgående Cloudflare Tunnel — det finns inga öppna inkommande portar på VM:en. Admin (SSH + Dokploy) sker bara över Tailscale.

Publika besökare når apparna via Cloudflares edge — ingen direkt väg in till servern.Internet · publika besökareAdmin (Dokploy/SSH) nås bara över Tailscale-VPN:t — aldrig publikt.Admin · endast TailscaleCLOUDFLARE · EDGECloudflare sköter DNS + TLS-cert vid edgen (DNSSEC på). Origin är oåtkomligt utan tunneln.DNS + TLS-termineringCloudflare Access gatar security/monitor/logs/logsök vid edgen (SSO). Två-lagers: origin verifierar dessutom Access-JWT:n via authgate (ForwardAuth) → stänger H1 'Access är edge-only' (#296).Access · Zero Trustcloudflared öppnar en UTGÅENDE tunnel → inga inkommande portar behöver öppnas i brandväggen.Cloudflare TunnelUBUNTU-VM · bakom NAT · inga öppna inkommande portarKör som systemd-tjänst; token i EnvironmentFile (chmod 600), inte i argv.cloudflared (systemd)Reverse-proxy: routar på Host-header till rätt app-container på dokploy-network.Traefik :80 · Host-headerDeploy-panel + Docker Swarm-orkestrering. Nås bara över Tailscale.Dokploy :3000 · SSHAppar · publikaHubben — startsida + realtidsstatus (Node)navCV — statiskt (nginx)cvBolånekalkyl — vanilla JS (nginx)bolanDen här sidan — arkitektur & stack (nginx)infoMulti-bot-chat mot lokal Ollama + RAG (Node)chatRegelsök — semantisk sök i regelkorpusen: embed (CPU) + pgvector, utan LLM (Node)regelsokCLV-mätlabb — paper-only signaler över tid (Node)betRequest-inspektör — vad ser servern om dig? (Node)echoNettolönekalkylator — brutto→netto per kommun, 2025 års skatt (nginx)lonFöräldrapenning- & ledighetsplanerare — FK 2026-belopp (nginx)foraldraRecept-roulette — slumpa middagar ur Mealie → inköpslista (Node)veckomatChelsea FC-dashboard — nyheter/resultat/tabell ur öppna källor (Node)chelseaSäkerhets-posture — bakom Cloudflare Access (Node)securityInternt · dokploy-network (bara på overlay-nätet)Monitoring: beszel (metrics) + uptime-kuma (publik statussida) + dozzle (enhetlig live-loggvy, Access-gatad).beszel · uptime-kuma · dozzleDokploys interna Postgres + delad redis (rate-limit/samtidighet).Dokploy · postgres · redisLokala LLM:er (llama3.1:8b m.fl.) via native API. Får GPU:n via CDI.ollama · GPU-LLMGPU:n som kör Ollama-modellerna. Query-embed körs på CPU så bge-m3 inte slåss om VRAM:et.RTX A2000 · 6 GBPostgres + pgvector: RAG-korpusens embeddings (databas rag, host-alias ragdb).ragdb · pgvectorDelad rate-limit + samtidighetsspärr (skyddar GPU:n) — överlever redeploy.redis · limiternative APISQLhost-cron: trivy→rapport · regelwatch→issue · gpu-watch→ollama · backup→ragdb-dump · bet-cron→signalerpublik ingressutgående QUIC · inga öppna portarförbi CFpublik trafikväginternt / adminAccess-gatad

Ingress-väg: Internet → Cloudflare (DNS/TLS/Access) → Tunnel → cloudflared → traefik :80 → app:80 (Host-header). Traefik serverar ren HTTP; TLS termineras vid edgen.

Solid linje = publik trafikväg, streckad = internt/admin. Tjänsterna i detalj: se Stacken nedan.

bet.knuths.dev — subsystem

Bet-appen är komplex nog att förtjäna en egen genomlysning. Odds från flera böcker de-viggas till ett kontolöst konsensus-ankare (bok-namn hashas bort), en daglig generate → snapshot → settle mäter varje signals Closing Line Value — som pendlar ≈0, för marknaden är effektiv (ingen mätbar kant, bevisat på 24k+ matcher). Endast papper. Full doc: bet/ARCHITECTURE.md.

ODDS-KÄLLORPrimär odds-källa (1X2 + Över/Under 2.5, #336 — totals bara för BET_TOTALS_SPORTS, +1 kredit/marknad). Kredit-mätt; vid gult kreditläge viker totals FÖRST (h2h svälts aldrig); kreditvakten pausar vid lågt saldo.The Odds API · h2h+O/UBetald plan — extra marknader (h2h + Over/Under). Läggs till via composite-källan.API-Football · h2h+O/UDeterministisk demo-källa — inga API-krediter, för test/utveckling.mocknormalize.mjs — bok-namn → opaka hashade ids. Inga operatörsnamn läcker vidare.normalize · opaka bok-idsengine/ · ren matematik · N-dimensionell · 0 I/Omarkets.mjs — marknadsdeskriptorer: h2h, draw-no-bet, totals (O/U), asian handicap.markets · deskriptorer h2h/O-U/AHeval.mjs — de-vig, EV, bråkdels-Kelly, fairCLV. Ren matte, 0 I/O, N-dimensionell.eval · de-vig · EV · Kelly · fairCLVpick-engine.mjs — de-vig-konsensus + leave-one-out + z-grind → signaler.pick-engine · konsensus + z-grindlifecycle/ · daglig cron — CLV mäts, pendlar ≈ 0 (ingen kant)generate — öppningslinje → PENDING (en runda per BET_MARKETS). Skriver även dagens skann: alla examinerade matcher + varför de valdes/ej vald.generate · öppningsnapshot — stängningslinjen nära avspark → mät CLV → SNAPSHOTTED. Oåterkalleligt.snapshot · stängningsettle — matchresultat → SETTLED / VOID.settle · resultatfairCLVbudget.mjs — kreditvakt (grön→gul→orange→röd). Pausar/degraderar generate + snapshot.budget · kreditvaktFria resultatkällor (0 krediter) för settle: football-data.org, TheSportsDB.fria resultat: football-data.org · TheSportsDB0 krstore/ · JSON på /app/data-volym (överlever redeploy)Auktoritativa dagfiler — varje dags frysta signaler (JSON på volym).picks/<datum>.jsonMaterialiserad, ombyggbar CLV-liggare — det /api/clv läser (bunden, bucketiserad).clv.json · CLV-liggareApp-state: senaste generate/snapshot/settle + hälsonät.state.jsonapi.mjs — bygger read-API:ernas payloads ur storen (rena läsningar).api.mjsserver.mjs — router, strikt CSP (script-src self), token-gatad admin, rate-limit.server.mjs · CSP · adminPublik dashboard — CLV över tid, per-liga, kalibrering, drilldown + Dagens skann (/scan: alla examinerade matcher + skäl). Klicka → bet.knuths.devdashboard · CLV · kalibrering · backfill-panelinfra/bet/ · offline research (0 krediter) · delar bet/engineFri historik: 24 000+ matcher — underlaget för backtesten.football-data.co.uk CSV · 24kbackfill.mjs → statisk backfill.json som matar backfill-panelen i dashboarden.backfill.mjs → backfill.jsonOffline-forskning: backtest, Poisson-predict, de-vig-jämförelse. Klicka → metod-sidanbacktest · predict · devigstatisk fil → panelsignalerfrusna picks + CLVtvärgående: compliance-gate (inga operatörsnamn · inga vinstlöften · 18+/Spelpaus) · golden-CLV-test (byte-identisk regressionsvakt)

CLV är hjälten — det enda robusta kant-testet — och det pendlar ≈0. Fria resultat-feeds settlar utan API-krediter; en offline-backfill på 24k matcher matar samma vyer. Böcker visas aldrig med namn, bara som ett antal.

03Stacken

Allt körs i containrar och återskapas ur repot. Publikt = via Cloudflare · Access = e-postgatad · Internal = bara på dokploy-network · Tailscale = enbart över tailnet.

KomponentImageVadExponering
navnode:24.19.0-alpineNode-hub + realtidsstatus per app-kortPublik
cv / bolan / infonginx⁠-unprivilegedStatiska appar (non-root)Publik
chat · Byggbotennode:24.19.0-alpineMulti-bot AI-chat + RAGPublik
regelsoknode:24.19.0-alpineSemantisk sök i regelkorpusen (utan LLM)Publik
chelseanode:24.19.0-alpineChelsea FC-dashboard — nyheter/resultat/tabell, server-side-hämtatPublik
securitynode:24.19.0-alpineSäkerhets-posture + Trivy-rapportAccess
betnode:24.19.0-alpineCLV-mätlabb (paper-only, CLV över tid)Publik
echonode:24.19.0-alpineRequest-inspektör (stateless)Publik
lonnginx-unprivilegedNettolönekalkylator brutto→netto per kommun (2025)Publik
foraldranginx-unprivilegedFöräldrapenning- & ledighetsplanerare (FK 2026, återanvänder lons skattemodell)Publik
veckomatnode:24.19.0-alpineRecept-roulette: slumpa middagar ur Mealie → inköpslista (pratar med mealie server-side)Publik
ollamaollama/ollamaLokal LLM + embeddings på GPUInternal
ragdbpgvector/pgvector:pg17Vektor-DB för RAGInternal
redisredis:8.10.1-alpineDelad rate-limit/samtidighet (chat)Internal
beszel (+agent)henrygd/beszelMetrics-hub · host/container/GPUAccess
uptime-kumalouislam/uptime-kumaPublik statussidaPublik
dozzleamir20/dozzleEnhetlig live-loggvy (read-only via socket-proxy)Access
dokploy (+pg/redis)dokploy/dokployPaaS-kontrollpanelTailscale
dokploy-traefiktraefik:v3Reverse proxy / ingressvia tunnel
cloudflaredhost · systemdCloudflare Tunnel (utgående)
trivyaquasec/trivyNattlig sårbarhetsscan (cron)

Hårdvara: NVIDIA RTX A2000 6 GB. Modeller i Ollama: llama3.1:8b, qwen2.5:7b, gemma2:9b, bge-m3 (embeddings).

04Så här är det byggt

Designprinciperna bakom bygget — och de medvetna säkerhetsvalen.

Ingen publik attackytaVM:en sitter bakom NAT utan publik IP. En utgående Cloudflare Tunnel bär all ingress — noll öppna inkommande portar. SSH och Dokploy-dashboarden nås bara över Tailscale.
Härdat i lagerAlla appar kör non-root och binder ändå port 80 (setcap cap_net_bind_service). Enhetliga säkerhets-headers: HSTS, nosniff, X-Frame-Options, strikt CSP (extern JS, inga inline-skript). Betyg gick F → A/A+.
Kontinuerlig kontrollEn egen posture-sida live-betygsätter varje apps headers, TLS och exponering. En nattlig Trivy-scan går igenom alla deployade images; en regelwatch bevakar nya byggföreskrifter. Access-gatat och synligt bara för mig.
Egen GPU-LLM med RAGByggboten svarar från en lokal LLM på en RTX A2000. Byggregler chunkas → embeddas (bge-m3) → pgvector → topp-k med citat. Fria myndighetsföreskrifter citeras ordagrant; upphovsrättsskyddade källor pekas bara ut — aldrig återgivna.
Allt ur repotEn mapp per app med egen Dockerfile; infra som Compose-tjänster. Dokploy deployar direkt från Git. Ingen manuell serverpyssel — flytt till egen server = klona repot och deploya om.
Denna sida är dogfoodinginfo.knuths.dev är byggd med exakt samma mönster som den beskriver: en statisk non-root nginx-container med samma header-set, deployad via samma pipeline. Portföljen kör på plattformen den visar upp.