Files
ROMFASTSQL/proxmox/lxc103-dokploy/docs/autopass.md
Marius 4a73d0dd39 docs(lxc103): autopass - env vars noi obligatorii + fix DB readonly (uid non-root)
Documentate doua probleme intalnite la deploy: variabile env noi
(AUTOPASS_RAR_ENV, AUTOPASS_SESSION_SECRET, AUTOPASS_WORKER_SEND_ENABLED)
devenite obligatorii in docker-compose.yml fara actualizare in Dokploy,
si crash-loop api/worker cu "readonly database" dupa ce imaginea a trecut
la user non-root (uid 10001) fara chown pe volumul SQLite existent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-09 14:15:45 +03:00

238 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# autopass — Deploy pe Dokploy LXC 103
## Descriere
Gateway RAR AUTOPASS (Python / FastAPI) care preia prezentări de service-auto și le
declară la RAR AUTOPASS (Legea 142/2023). Deployat pe Dokploy / LXC 103 ca serviciu
Docker Compose.
- **Domenii:** `autopass.romfast.ro` (public, site IIS dedicat) + `autopass.roa.romfast.ro` (prin wildcard)
- **Staging:** branch `staging``autopass-test.roa.romfast.ro` — vezi [autopass-staging.md](autopass-staging.md)
- **Server:** LXC 103 (10.0.20.167)
- **Deployment:** Dokploy → Docker Compose, provider **Custom Git**
- **Repo:** `git@gitea.romfast.ro:romfast/rar-autopass.git`, branch **`main`**
### Arhitectură
```
Browser → VM201 IIS (TLS, site roa-apps) → Traefik LXC103 (HTTP :80) → api (uvicorn :8010)
worker + autoheal
SQLite /data/autopass.db
```
Servicii Docker Compose (`docker-compose.yml`, un singur image):
- `api` — FastAPI/uvicorn pe `:8010`, expus prin Traefik (entrypoint `web`)
- `worker` — procesează coada; partajează volumul `autopass-data` și `AUTOPASS_CREDS_KEY`
- `autoheal` — restartează `worker`-ul când probe-ul îl marchează unhealthy (proces agățat)
---
## Deploy în Dokploy UI
### 1. Creare Service
1. https://dokploy.romfast.ro → **Services****Create Service****Docker Compose**
2. Name: `autopass`, Server: **LXC 103 (local)**
### 2. Provider — Custom Git (NU Gitea nativ)
Integrarea nativă **Gitea** dă "Failed to fetch repositories: Unauthorized" (token OAuth
expirat în Dokploy). Folosește tab-ul **`</> Git`**:
- Repository URL: `git@gitea.romfast.ro:romfast/rar-autopass.git`
- Branch: `main`
- Compose Path: `./docker-compose.yml`
- SSH: adaugă deploy key-ul afișat de Dokploy în Gitea → repo → Settings → Deploy Keys
### 3. Environment (în Dokploy, NU în .env — e gitignored)
| Variabilă | Valoare |
|-----------|---------|
| `AUTOPASS_CREDS_KEY` | cheie Fernet, **partajată api↔worker** (vezi mai jos) |
| `AUTOPASS_REQUIRE_API_KEY` | `true` pentru prod |
| `AUTOPASS_RAR_ENV` | `prod` |
| `AUTOPASS_SESSION_SECRET` | secret random (vezi generare mai jos), doar pt `api` |
| `AUTOPASS_WORKER_SEND_ENABLED` | `true` pentru prod (worker-ul trimite efectiv la RAR) |
```bash
# Generare AUTOPASS_CREDS_KEY:
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# Generare AUTOPASS_SESSION_SECRET:
openssl rand -hex 32
```
Fără cheie, compose pică explicit (`:?seteaza AUTOPASS_CREDS_KEY` / `:?seteaza
AUTOPASS_RAR_ENV` / `:?seteaza AUTOPASS_SESSION_SECRET` / `:?seteaza
AUTOPASS_WORKER_SEND_ENABLED`) — compose-ul din repo a adăugat ulterior aceste variabile
ca obligatorii pentru `api`/`worker`; dacă lipsesc din Environment-ul Dokploy, deploy-ul
pică la interpolare **înainte** de build, câte una pe rând (docker compose se oprește la
prima variabilă lipsă, deci fixul se poate face în mai multe runde), cu eroare de tipul:
```
error while interpolating services.api.environment.AUTOPASS_RAR_ENV: required variable
AUTOPASS_RAR_ENV is missing a value: seteaza AUTOPASS_RAR_ENV (test|prod) in .env
```
containerele deja rulante nu sunt afectate — doar noul deploy eșuează, prod rămâne pe
imaginea veche până se adaugă variabilele lipsă și se redeploy-ează.
### 4. Domain
**Domains** → service **`api`**, port **`8010`**, domain `autopass.roa.romfast.ro`.
TLS-ul public se termină la IIS (VM 201), deci în Dokploy e suficient entrypoint `web`.
### 5. Autodeploy (deploy automat la commit)
Toggle **Autodeploy** = ON nu e suficient — trebuie webhook în Gitea (Custom Git nu-l
creează automat):
1. Dokploy → General → copiază **Webhook URL** (`https://dokploy.romfast.ro/api/deploy/compose/<token>`)
2. Gitea → repo → Settings → Webhooks → Add Webhook → Gitea:
- Target URL: *(URL-ul din Dokploy)*, Method `POST`, Content Type `application/json`
- Branch filter: `main`, trigger Push Events, Active
- **Test Delivery** → trebuie `200`
3. Doar ce e `git push`-uit pe `main` declanșează deploy.
---
## Rutare (de ce NU trebuie regulă IIS per-app)
IIS site `roa-apps` (VM 201) are binding wildcard `*.roa.romfast.ro` (80+443) și o regulă
URL Rewrite catch-all care forwardează tot către Traefik:
```xml
<rule name="Proxy to LXC 103 Traefik" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="http://10.0.20.167/{R:1}" />
...
</rule>
```
ARR are `preserveHostHeader=True`, deci Host-ul original ajunge la Traefik, care rutează
după `Host(...)`. Un app nou sub `*.roa` merge automat — **nu** adaugi nimic în IIS, doar
domeniul în Dokploy.
---
## Probleme întâlnite la primul deploy (și fix-urile)
### 1. Gitea Unauthorized în Dokploy
**Simptom:** "Failed to fetch repositories: Unauthorized", dropdown Repository gol.
**Cauză:** token-ul OAuth al integrării native Gitea în Dokploy e expirat/revocat.
**Fix:** folosește provider **Custom Git** (vezi pasul 2). Alternativ: regenerează OAuth
app / PAT în Gitea și reconectează în Dokploy → Settings → Git.
### 2. 404 la `https://autopass.roa.romfast.ro/` — api în crash-loop
**Simptom:** site-ul răspunde **404** (nu 502). `docker ps``api` în `Restarting (1)`,
worker + autoheal healthy.
**Cauză:** `ModuleNotFoundError: No module named 'itsdangerous'`. `SessionMiddleware`
(`app/main.py`) îl cere, dar lipsea din `requirements.txt` — era doar instalat local în
dev, **nu** tras tranzitiv în imaginea Docker. Worker-ul nu importă SessionMiddleware,
de-aia el era healthy.
**Fix:** adăugat `itsdangerous==2.2.0` în `requirements.txt`, push → autodeploy a
reconstruit imaginea (commit `412102b`).
> 404 vs 502: 404 = niciun backend sănătos / niciun router care să răspundă; 502 = portul
> din Traefik ≠ portul containerului. Aici api nu pornea deloc → 404.
### 3. Deploy eșuat la interpolare — variabile noi obligatorii lipsă din Dokploy
**Simptom:** deploy-ul pică **înainte** de build, cu `error while interpolating
services.<serviciu>.environment.<VAR>: required variable <VAR> is missing a value`.
**Cauză:** un commit ulterior în `docker-compose.yml` a adăugat `AUTOPASS_RAR_ENV`,
`AUTOPASS_SESSION_SECRET` (api) și `AUTOPASS_WORKER_SEND_ENABLED` (worker) ca variabile
`${VAR:?...}` obligatorii, dar Environment-ul din Dokploy pentru serviciul `autopass` nu
fusese actualizat. Docker compose se oprește la **prima** variabilă lipsă, deci fixul
poate ieși în mai multe runde (adaugi una, redeploy, apare următoarea).
**Fix:** adaugă în Dokploy → service `autopass` → Environment toate cele 3 (vezi tabelul
de mai sus), apoi redeploy.
### 4. 404 la `https://autopass.romfast.ro/` — api + worker crash-loop, DB readonly
**Simptom:** după fixul de la #3, deploy-ul trece de build, dar `api` și `worker` intră în
crash-loop (`docker ps``Restarting`), site-ul răspunde **404**. Loguri:
`sqlite3.OperationalError: attempt to write a readonly database`.
**Cauză:** imaginea nouă rulează containerele ca user non-root `app` (uid **10001**,
`docker inspect --format '{{.Config.User}}'``app`), dar volumul Docker
`autopass-data` (cu `autopass.db`) era deținut de `root:root` din deploy-urile vechi
(când containerul rula ca root) → userul `app` nu poate scrie nici fișierul, nici WAL-ul
din directorul volumului.
**Fix:**
```bash
ssh root@10.0.20.201 "pct exec 103 -- docker volume inspect autopass-roaautopass-jqqw1l_autopass-data --format '{{.Mountpoint}}'"
# chown pe mountpoint-ul returnat, la uid/gid-ul userului din imagine:
ssh root@10.0.20.201 "pct exec 103 -- chown -R 10001:10001 /var/lib/docker/volumes/autopass-roaautopass-jqqw1l_autopass-data/_data"
# apoi restart:
ssh root@10.0.20.201 "pct exec 103 -- sh -lc 'cd /etc/dokploy/compose/autopass-roaautopass-jqqw1l/code && docker compose -p autopass-roaautopass-jqqw1l -f ./docker-compose.yml restart api worker'"
```
Fără pierdere de date — doar schimbă proprietarul fișierului SQLite existent. Dacă
imaginea schimbă din nou uid-ul userului non-root, verifică din nou cu `docker run --rm
<image-api> id app` înainte de chown.
### 5. (Nu era o problemă) `/` → 303
După fix, root-ul redirectează `303 → /login` (auth pe sesiune). E comportament normal,
nu eroare.
---
## Verificare post-deploy
```bash
# Stare containere
ssh root@10.0.20.201 "pct exec 103 -- docker ps --format '{{.Names}} -> {{.Status}}' | grep autopass"
# Așteptat: api Up (healthy), worker Up (healthy), autoheal Up (healthy)
# API direct în Traefik (host header), pe LXC 103
ssh root@10.0.20.201 'pct exec 103 -- sh -lc "curl -s -o /dev/null -w \"%{http_code}\n\" -H \"Host: autopass.roa.romfast.ro\" http://localhost/healthz"'
# Așteptat: 200
# Lanț complet de pe VM 201 (loopback, TLS real)
# autopass.roa.romfast.ro -> HTTP 303 (redirect /login) = OK
# Logs api dacă pică
ssh root@10.0.20.201 "pct exec 103 -- docker logs \$(ssh ... ) --tail 40"
```
---
## Atenție: deploy de TEST, nu producție
`docker-compose.yml` are hardcodat:
- `AUTOPASS_RAR_ENV: test`
- `AUTOPASS_WORKER_SEND_ENABLED: "false"` — worker-ul **NU** trimite efectiv la RAR.
Pentru producție (trimiteri reale către RAR, Legea 142/2023) acestea trebuie schimbate
explicit — preferabil prin Environment în Dokploy, nu hardcodat în compose.
---
## DNS
```
autopass.romfast.ro A 188.26.14.103
autopass.roa.romfast.ro A 188.26.14.103
```
`autopass.roa…` e acoperit de wildcard `*.roa.romfast.ro`. `autopass.romfast.ro` e
single-name → are site IIS + cert propriu (vezi mai jos).
---
## Domeniu public `autopass.romfast.ro` (site IIS dedicat)
Spre deosebire de `*.roa`, domeniul public single-name are nevoie de **site IIS propriu**
pe VM 201 (model identic cu `roa-qr` — pattern din `setup-new-iis-sites.ps1`).
**IIS (VM 201, 10.0.20.122)** — Site ID **7**, nume `autopass`:
- HTTP `:80` + HTTPS `:443` hostHeader `autopass.romfast.ro`, SNI (SslFlags 1)
- `C:\inetpub\autopass\web.config` = catch-all rewrite → `http://10.0.20.167/{R:1}`
(Traefik LXC 103) cu `X-Forwarded-Proto/Host` + `X-Real-IP`
**Cert SSL — win-acme HTTP-01, auto-renew** (la fel ca site-urile 15):
```powershell
C:\Tools\win-acme\wacs.exe --source iis --siteid 7 --host autopass.romfast.ro `
--validationmode http-01 --validation selfhosting --installation iis `
--store certificatestore --accepttos --emailaddress admin@romfast.ro
```
- Validation `selfhosting` (c7d5e050) — ascultă pe http.sys, **nu** e blocat de catch-all-ul
din web.config. Install `iis` (ea6a5be3) → re-leagă automat binding-ul SNI la renew.
- Renewal: `[IIS] autopass, autopass.romfast.ro`, Scheduled Task "win-acme renew", next ~2026-08-23.
- Monitorizat în `monitor-ssl-certificates.sh` (Site ID 7) ca safety-net.
**Pas necesar în Dokploy** (Traefik rutează după Host → trebuie domeniul adăugat explicit):
> Dokploy → service `autopass` → **Domains** → **Add Domain**
> - Host: `autopass.romfast.ro`, Service: **`api`**, Container Port: **`8010`**, Path: `/`
> - **HTTPS: OFF** (entrypoint `web`) — TLS-ul public se termină la IIS; Certificate: None
> Save → Traefik hot-reload. Înainte de pas: `https://autopass.romfast.ro/` → **404** (Traefik
> n-are router pe acest Host). După pas: **200**.