From d3e6b92d9fc7ac5c951408f20c8264d2d6fcf848 Mon Sep 17 00:00:00 2001 From: carsten Date: Tue, 29 Sep 2026 10:31:41 +0200 Subject: [PATCH] =?UTF-8?q?Opdat=C3=A9r=20INSTALL.md=20til=20Proxmox=20LXC?= =?UTF-8?q?-container-topologi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tilføjer trin 1 (opret containeren: unprivileged LXC, Debian 12, statisk IP) og retter netværks-antagelsen: da reverse proxy'en (CloudPanel) kører i en anden container/VM på samme Proxmox-host end appen, skal backend'en bindes til 0.0.0.0 i stedet for 127.0.0.1 og være tilgængelig over det interne Proxmox-netværk — låst ned med Proxmox' egen container-firewall (kun CloudPanel-containerens IP får adgang) i stedet for 127.0.0.1-only. Reverse proxy-trinnet peger nu på containerens IP, og PostgreSQL installeres nativt i samme container som appen (bekræftet valg). Co-Authored-By: Claude Sonnet 5 --- INSTALL.md | 91 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 29 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index efd3372..6ac828a 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -10,15 +10,39 @@ Postal-instans (selvhostet mailserver — se CLAUDE.md's "Tech stack") med DKIM sat op for det domæne der sendes mails fra. Selve Postal-opsætningen er uden for denne vejlednings omfang. -## 1. Forudsætninger på serveren +**Antaget topologi** (Proxmox): appen kører i sin egen LXC-container, +sammen med sin PostgreSQL-database. Reverse proxy'en (CloudPanel) +kører i en **anden** container/VM på samme Proxmox-host — appens +container skal derfor være tilgængelig over det interne +Proxmox-netværk, ikke kun lokalt i sig selv. Kører reverse proxy'en i +stedet i **samme** container som appen, kan trin 9's `--host` i +stedet sættes til `127.0.0.1`, og firewall-reglen i samme trin +udelades. + +## 1. Opret Proxmox-containeren + +- **Unprivileged LXC-container** — intet i denne app kræver + privilegeret adgang. +- **OS-template:** Debian 12 anbefales (stabil, letvægt). `uv` styrer + selv Python 3.13 uafhængigt af systemets Python-version, så + distro-valget er ikke kritisk. +- **Størrelse:** appen er lille (én organisation i drift i dag) og + kører sin egen Postgres i samme container — 1-2 vCPU, 1-2 GB RAM og + 8-10 GB disk er rigeligt til at starte med. +- **Netværk:** giv containeren en **statisk IP** på Proxmox' interne + bridge (fx `vmbr0`), så CloudPanel-containeren kan nå den + konsistent. + +## 2. Forudsætninger i containeren - Python 3.13 (se `.python-version`) - [`uv`](https://docs.astral.sh/uv/) (Python-pakkehåndtering) - Node.js + npm (til at bygge admin-UI'et, `admin-ui/`) -- PostgreSQL +- PostgreSQL (`apt install postgresql`, da databasen kører i samme + container som appen) - git -## 2. Hent koden +## 3. Hent koden ```bash git clone /sti/til/vinindkoeb @@ -26,7 +50,7 @@ cd /sti/til/vinindkoeb uv sync ``` -## 3. Database +## 4. Database Opret en Postgres-bruger og -database der matcher formatet i `DATABASE_URL` (`postgresql+psycopg://:@:5432/`): @@ -36,7 +60,7 @@ CREATE USER vinindkoeb WITH PASSWORD '...'; CREATE DATABASE vinindkoeb OWNER vinindkoeb; ``` -## 4. Miljøvariabler +## 5. Miljøvariabler ```bash cp .env.example .env @@ -46,7 +70,7 @@ Udfyld hvert felt i `.env`: | Variabel | Hvor den kommer fra | |---|---| -| `DATABASE_URL` | Fra trin 3 | +| `DATABASE_URL` | Fra trin 4 (`@localhost`, da Postgres kører i samme container) | | `ENVIRONMENT` | `production` | | `SECRET_KEY` | Generér med `openssl rand -hex 32` | | `ACCESS_TOKEN_EXPIRE_MINUTES` / `ELEVATION_EXPIRE_MINUTES` | Standardværdierne er fine, med mindre andet ønskes | @@ -54,7 +78,7 @@ Udfyld hvert felt i `.env`: | `POSTAL_WEBHOOK_PUBLIC_KEY_B64` | DKIM-nøglen for afsenderdomænet — find med `dig +short TXT ._domainkey.`, brug værdien af `p=` | | `ADMIN_DOMAIN` | Domænet admin-UI'et skal serveres på, fx `admin.vinindkoeb.dk` | -## 5. Migrations +## 6. Migrations ```bash uv run alembic upgrade head @@ -63,7 +87,7 @@ uv run alembic upgrade head Dette opretter alle tabeller og seeder `WineCategory` automatisk (vinkategorierne er globale, delt af alle organisationer/ruter). -## 6. Første organisation, rute og admin-bruger +## 7. Første organisation, rute og admin-bruger Der findes ingen selvbetjenings-registrering — uden en bruger i databasen kan ingen logge ind. Brug bootstrap-scriptet: @@ -81,7 +105,7 @@ Scriptet genbruger en organisation/rute med samme navn, hvis den allerede findes — sikkert at køre igen for at oprette endnu en admin-bruger på en eksisterende organisation. -## 7. Byg admin-UI'et +## 8. Byg admin-UI'et ```bash cd admin-ui @@ -94,10 +118,9 @@ Uden dette trin kører API'et og den offentlige side fint, men `ADMIN_DOMAIN` viser ingenting (se `app/admin_site.py`s sikkerhedsnet). -## 8. Kør backend'en som en vedvarende service +## 9. Kør backend'en som en vedvarende service -Eksempel på en `systemd`-unit (`/etc/systemd/system/vinindkoeb.service`), -uafhængig af hvilken reverse proxy der bruges foran den: +Eksempel på en `systemd`-unit (`/etc/systemd/system/vinindkoeb.service`): ```ini [Unit] @@ -108,7 +131,7 @@ After=network.target postgresql.service Type=simple User=vinindkoeb WorkingDirectory=/sti/til/vinindkoeb -ExecStart=/sti/til/vinindkoeb/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 +ExecStart=/sti/til/vinindkoeb/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000 Restart=always EnvironmentFile=/sti/til/vinindkoeb/.env @@ -121,33 +144,43 @@ sudo systemctl daemon-reload sudo systemctl enable --now vinindkoeb ``` -Backend'en lytter kun på `127.0.0.1` — den skal ikke være direkte -tilgængelig udefra, kun via reverse proxy'en i næste trin. +Da reverse proxy'en (CloudPanel) kører i en anden container, skal +backend'en kunne nås over Proxmox' interne netværk — deraf +`--host 0.0.0.0` i stedet for `127.0.0.1`. Det skal dog **ikke** stå +åbent for alt andet: brug Proxmox' egen indbyggede firewall på +containeren (fanen "Firewall" på containeren, eller Datacenter → +Firewall) til én regel: tillad TCP på port `8000` kun fra +CloudPanel-containerens IP, afvis ellers. Det er mere idiomatisk og +nemmere at vedligeholde end `iptables`/`nftables` inde i selve +gæste-OS'et. -## 9. Domæner + reverse proxy +## 10. Domæner + reverse proxy Løsningen afgør selv hvilken rute/hvilket indhold der skal vises ud fra requestens `Host`-header (se CLAUDE.md's "Route" og "Admin-UI-arkitektur") — det betyder at **alle** domæner nedenfor -bare skal pege på samme kørende backend (samme port, fra trin 8), -uanset hvilken reverse proxy-løsning der bruges: +bare skal pege på samme kørende backend (samme container-IP og port, +fra trin 9), uanset hvilken reverse proxy-løsning der bruges: - Organisationens/rutens `public_domain`-felt(er) i databasen, fx `vinindkoeb.dk` og `test.vinindkoeb.dk` - `ADMIN_DOMAIN` fra `.env`, fx `admin.vinindkoeb.dk` **Med CloudPanel:** opret ét "Reverse Proxy"-site pr. domæne, der -peger på `127.0.0.1:8000` (porten fra trin 8), og aktivér -Let's Encrypt-certifikat for hvert site. Bruges en anden løsning -(nginx, Caddy, Traefik, ...), er princippet det samme: én -vhost/site pr. domæne, alle proxyet til samme port. +peger på `:8000` (IP'en fra trin 1, porten +fra trin 9), og aktivér Let's Encrypt-certifikat for hvert site. +Bruges en anden løsning (nginx, Caddy, Traefik, ...), er princippet +det samme: én vhost/site pr. domæne, alle proxyet til samme +container-IP og port. -## 10. DNS +## 11. DNS A-records (eller CNAME, alt efter opsætning) for hvert domæne fra -trin 9, der peger på serverens IP-adresse. +trin 10, der peger på **CloudPanel-serverens** offentlige IP-adresse +(ikke Vinindkøb-containerens — det er CloudPanel der tager imod +trafik udefra og sender den videre internt). -## 11. Postal-webhook +## 12. Postal-webhook I Postal-instansens konfiguration: sæt webhook-URL'en til `https:///webhooks/postal` (fx @@ -155,7 +188,7 @@ I Postal-instansens konfiguration: sæt webhook-URL'en til leveringsstatus (sendt/leveret/afvist) opdateres asynkront på `MailLog` (se opgave 7c). -## 12. Sæt rutens felter +## 13. Sæt rutens felter For hver rute (den rigtige, og evt. en test-rute) skal disse felter sættes direkte i databasen — der findes ingen rute-indstillings-skærm @@ -169,16 +202,16 @@ UPDATE route SET WHERE id = ; ``` -## 13. Verifikation +## 14. Verifikation 1. `curl https:///health` → `{"status":"ok","database":"ok"}`. 2. Besøg det rigtige domæne i en browser → bestillings-/tilmeldingssiden. -3. Besøg `ADMIN_DOMAIN` → login-siden, log ind med brugeren fra trin 6. +3. Besøg `ADMIN_DOMAIN` → login-siden, log ind med brugeren fra trin 7. 4. Afprøv en tilmelding/bestilling på test-ruten (hvis en sådan er sat op) og bekræft at en rigtig mail bliver sendt og logget i `MailLog`. -## 14. Fremtidige opdateringer +## 15. Fremtidige opdateringer ```bash git pull