From 7e5577c58f35109697c6effad10ccb2e9e1319da Mon Sep 17 00:00:00 2001 From: Oskar Kapala Date: Thu, 9 Jul 2026 16:15:35 +0200 Subject: [PATCH] =?UTF-8?q?feat(kb):=20configi=20Paperless/Nextcloud=20wg?= =?UTF-8?q?=209=20decyzji=20=E2=80=94=20NC=20na=20PIHA,=20domeny=20kapala,?= =?UTF-8?q?=20Redis=20requirepass,=20backup=20SOLARIA,=20NC=20pin=2034?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co zrobione: - Nextcloud host = PIHA (always-on dla aktywnego uzycia), owner_node + LAN_BIND_IP/TRUSTED_PROXIES w .env, README zaktualizowane - Redis brokera Paperlessa: requirepass, PAPERLESS_REDIS_PASSWORD w .env po obu stronach (PIHA + worker@SOLARIA), healthchecki z auth - Domeny potwierdzone: paper.kapala.org, cloud.kapala.org (Cloudflare DNS-only -> Tailscale PIHA, wildcard cert juz pokrywa) — udokumentowane, nic nie utworzone - Backup Paperlessa zatwierdzony: document_exporter + rsync/borg -> SOLARIA, retencja 7/4/6, offsite jako future-note - Nextcloud pin: 34-apache (zweryfikowany aktualny stable, endoflife.date) - Whoosh fallback-worker: zaakceptowane bez zmian - Porty/wylaczenie local login/sizing OCR-workera: przeniesione z "decyzji" na "TODO przy deployu" - DECYZJE-do-podjecia.md zaktualizowane: wszystko poza portami/loginem/ sizingiem przeniesione do "Rozstrzygniete" Tylko edycja configow w repo — nic nie zdeployowane, zadne kontenery nie byly ruszane, DNS/vhosty nie utworzone. Co-Authored-By: Claude Sonnet 5 --- docs/kb/modules/DECYZJE-do-podjecia.md | 123 +++++++++---------- services/nextcloud/README.md | 48 +++++--- services/nextcloud/docker-compose.yml | 39 +++--- services/nextcloud/env.example | 20 +-- services/nextcloud/service.yaml | 12 +- services/paperless-worker/docker-compose.yml | 11 +- services/paperless-worker/env.example | 7 +- services/paperless-worker/service.yaml | 1 + services/paperless/README.md | 29 +++-- services/paperless/docker-compose.yml | 32 +++-- services/paperless/env.example | 9 +- services/paperless/healthcheck.sh | 5 +- services/paperless/service.yaml | 1 + 13 files changed, 195 insertions(+), 142 deletions(-) diff --git a/docs/kb/modules/DECYZJE-do-podjecia.md b/docs/kb/modules/DECYZJE-do-podjecia.md index f5eeec5..0e44445 100644 --- a/docs/kb/modules/DECYZJE-do-podjecia.md +++ b/docs/kb/modules/DECYZJE-do-podjecia.md @@ -1,32 +1,13 @@ # Decyzje do podjęcia — filar dokumentów (moduły 2/3/4) -> Zbiorcza lista otwartych decyzji z przygotowania configów (2026-07-06, -> branch `task/paperless-nextcloud-config`). Każda pozycja ma odpowiadający -> komentarz `# TODO DECYZJA OSKARA:` w plikach serwisów. Configi są gotowe do -> review — NIC nie zostało zdeployowane. +> Zbiorcza lista decyzji z przygotowania configów (2026-07-06, branch +> `task/paperless-nextcloud-config`; decyzje podjęte 2026-07-09, branch +> `task/paperless-decyzje`). Configi są zaktualizowane wg decyzji poniżej — +> NIC nie zostało zdeployowane, ten etap to tylko edycja plików w repo. -## 1. Host Nextclouda: PIHA czy SOLARIA +## Otwarte — do zrobienia PRZY DEPLOYU (nie są to już decyzje, tylko kroki wykonawcze) -Jedyna duża decyzja architektoniczna. Moduł 4 i kb-02 skłaniają się ku -**SOLARIA** (KB czyta własną kopię z archiwum na PIHA, więc dostępność -Nextclouda nie warunkuje zapytań KB; koszt = sync dogania się po wybudzeniu). -Tabela za/przeciw: `services/nextcloud/README.md`. Compose jest przenośne — -decyzja ustawia tylko `owner_node` w `service.yaml` + `LAN_BIND_IP` -i `TRUSTED_PROXIES` w `.env`. Wstępnie wpisane: SOLARIA. - -## 2. Backup Paperlessa (warunek brzegowy hybrydy — musi być przed produkcyjnym użyciem) - -Propozycja w `services/paperless/README.md`: - -- nocny `document_exporter` (oryginały + archiwa + manifest metadanych, - odtwarzalny bez dumpa SQL) do `/opt/homelab/data/paperless/export`, -- kopia eksportu poza hosta: **rsync/borg → SOLARIA** (2 TB, ta sama LAN); - otwarte: czy dokładać offsite (restic → chmura?), -- retencja (propozycja): 7 dziennych + 4 tygodniowe + 6 miesięcznych. - -Do zatwierdzenia: cel kopii (SOLARIA wystarczy? offsite?), retencja, narzędzie. - -## 3. Porty (potwierdzić na żywym PIHA przed deployem) +### 3. Porty (potwierdzić na żywym PIHA przed deployem) Dobrane wg inwentaryzacji 2026-06-30 (snapshot! zweryfikować `ss -tlnp`): @@ -35,51 +16,20 @@ Dobrane wg inwentaryzacji 2026-06-30 (snapshot! zweryfikować `ss -tlnp`): | 8210 | paperless web | za npm@PIHA | | 5434 | paperless postgres | 5433 zajęte przez kb-postgres | | 6380 | paperless redis (broker dla workera) | 6379 zajęte przez agent-system-redis | -| 8220 | nextcloud web | wolny i na PIHA, i na SOLARII | +| 8220 | nextcloud web | teraz na PIHA (decyzja #1) — potwierdzić że nadal wolny | Wszystkie bindowane na LAN IP (nie 0.0.0.0); ruch worker↔broker/DB/NFS po LAN 192.168.31.x, nie Tailscale. -## 4. Redis brokera bez auth na bindzie LAN - -Broker (6380) i Postgres (5434) są wystawione na LAN IP PIHA dla workera na -SOLARII. Postgres ma auth (scram); **Redis defaultowo nie ma**. Zaufany LAN -domowy — akceptować, czy dołożyć `requirepass` (wtedy `PAPERLESS_REDIS` -z hasłem wędruje do `.env` po obu stronach)? - -## 5. Fallback-worker na PIHA a indeks Whoosh po NFS - -Stockowy obraz na PIHA zawsze ma wbudowany worker (concurrency 1) — to -naturalny fallback „domiela wolno". Ryzyko: gdy SOLARIA pracuje, **dwa hosty -piszą do plikowego indeksu Whoosh** (PIHA lokalnie, SOLARIA po NFS) — znane -upstreamowe objawy wyścigu („This writer is closed", korupcja indeksu). -Indeks jest odtwarzalny (`document_index reindex`), oryginałom nic nie grozi. -Decyzja: zaakceptować i obserwować (rekomendacja), czy od razu wymusić -tryb „kolejka-czeka" (batch-OCR tylko w oknach pracy SOLARII)? -Szczegóły: `services/paperless-worker/README.md`. - -## 6. Domeny + wpisy DNS + vhosty npm - -Założone w configach: `paper.kapala.org` (Paperless), `cloud.kapala.org` -(Nextcloud) — moduły dopuszczały też `*.okit.pl`. Potwierdzić, potem: -A-recordy (DNS Only) → PIHA + vhosty w npm@PIHA + rejestracja OAuth2 apps -w Forgejo (redirect URIs w README serwisów zależą od domeny). - -## 7. Wyłączenie lokalnego loginu w Paperless po weryfikacji OIDC +### 7. Wyłączenie lokalnego loginu w Paperless po weryfikacji OIDC Bootstrap idzie przez lokalnego admina (`PAPERLESS_ADMIN_USER`). Po potwierdzeniu działania Forgejo-OIDC: ustawić -`PAPERLESS_DISABLE_REGULAR_LOGIN=true` + `PAPERLESS_REDIRECT_LOGIN_TO_SSO=true`? -(Vikunja ma dziś oba tryby równolegle.) +`PAPERLESS_DISABLE_REGULAR_LOGIN=true` + `PAPERLESS_REDIRECT_LOGIN_TO_SSO=true` +(decyzja podjęta — wykonać dopiero po potwierdzonym logowaniu OIDC; Vikunja +ma dziś oba tryby równolegle, ten sam wzorzec przejściowy). -## 8. Pin wersji Nextclouda - -Compose ma ruchomy `nextcloud:stable-apache`; przy deployu przypiąć aktualny -major (np. `:31-apache` — sprawdzić bieżący stable w dniu deployu). Nextcloud -nie wspiera skoków o >1 wersję major, więc ruchomy tag na produkcji = ryzyko -niekontrolowanego skoku. - -## 9. Sizing workera OCR pod batch 70k załączników +### 9. Sizing workera OCR pod batch 70k załączników `WORKER_CONCURRENCY=4` × `PAPERLESS_THREADS_PER_WORKER=4` = ~16 wątków na 24 rdzeniach SOLARII. Przed batchem 70k (moduł 5): zmierzyć na próbce i @@ -87,7 +37,54 @@ zdecydować, czy podbić concurrency, czy zostawić zapas na ollama/AI. --- -## Rozstrzygnięte w tym przygotowaniu (dla porządku) +## Rozstrzygnięte + +- **1. Host Nextclouda: PIHA.** Oskar używa Nextclouda aktywnie (telefon, + sync, rodzina) → musi być always-on; sesyjna dostępność SOLARII (sync + dogania się dopiero po wybudzeniu) nie jest akceptowalna dla tego + workloadu — mimo że moduł 4/kb-02 skłaniały się ku SOLARII (KB i tak + czyta własną kopię z archiwum na PIHA, więc host Nextclouda nie warunkuje + zapytań KB). `service.yaml`: `owner_node: piha`. `.env`: `LAN_BIND_IP` + = 192.168.31.5, `TRUSTED_PROXIES` = docker bridge subnet (npm i nextcloud + na tym samym hoście teraz). Uzasadnienie pełne: `services/nextcloud/README.md`. + +- **2. Backup Paperlessa: SOLARIA (LAN), na start.** Nocny + `document_exporter` (oryginały + archiwa + manifest, odtwarzalny bez + dumpa SQL) → `/opt/homelab/data/paperless/export`, kopia poza hosta przez + rsync/borg → SOLARIA (2 TB, ta sama LAN). Retencja: 7 dziennych + + 4 tygodniowe + 6 miesięcznych. Offsite (np. restic → chmura) zostaje jako + future-note, poza zakresem tego etapu. Cron/skrypt deployowy powstaje przy + deployu modułu 2, nie teraz. Szczegóły: `services/paperless/README.md`. + +- **4. Redis brokera: `requirepass`.** Broker (6380) dostaje hasło — + `PAPERLESS_REDIS_PASSWORD` w `.env` po obu stronach (paperless@PIHA, + paperless-worker@SOLARIA), placeholder `CHANGEME` w `env.example`, realne + hasło tylko w `.env` (gitignored). Healthchecki obu stron zaktualizowane + o auth. + +- **5. Fallback-worker na PIHA a indeks Whoosh po NFS: zaakceptować + i obserwować.** Ryzyko wyścigu na plikowym indeksie Whoosh (PIHA lokalnie + + SOLARIA po NFS) świadomie zaakceptowane — indeks jest odtwarzalny + (`document_index reindex`), oryginałom nic nie grozi. Bez zmian w + configu; fallback-worker na PIHA zostaje. Szczegóły: + `services/paperless-worker/README.md`. + +- **6. Domeny: `kapala.org` (mesh, prywatne).** `paper.kapala.org` + (Paperless), `cloud.kapala.org` (Nextcloud) — potwierdzone, `*.okit.pl` + odrzucone. DNS i vhosty NIE utworzone w tym etapie — tylko + udokumentowane w READMY serwisów (wzorzec: rekord A w Cloudflare → + `100.108.208.3`, Tailscale PIHA, DNS Only, ten sam co + `ha.kapala.org`/`immich.kapala.org` — `docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`; + wildcard `*.kapala.org` już pokrywa obie subdomeny, nowe certy + niepotrzebne). OAuth redirect URIs w Forgejo apps zapisane w READMY obu + serwisów. + +- **8. Pin wersji Nextclouda: `34-apache`.** Zweryfikowany aktualny stable + na 2026-07-09 (wydany 2026-06-09, endoflife.date/nextcloud). Zamiast + ruchomego `stable-apache`. Nextcloud wydaje nowy major co ~4 miesiące + i nie wspiera przeskakiwania wersji przy upgrade — TODO przy deployu: + potwierdzić bieżący stable tuż przed `docker compose up`, podbić tag + jeśli wyszła nowsza wersja. - **Split-host OCR-worker przez NFS: WYKONALNY** — wzorzec potwierdzony przez maintainerów paperless-ngx (nieoficjalnie wspierany): ten sam obraz, diff --git a/services/nextcloud/README.md b/services/nextcloud/README.md index 2687b88..2e0f5ae 100644 --- a/services/nextcloud/README.md +++ b/services/nextcloud/README.md @@ -8,36 +8,43 @@ ingestu KB (moduł 5) przez WebDAV. do archiwum KB; Nextcloud NIE jest źródłem prawdy (inaczej niż Paperless). Backup „warto" (dane użytkownika), ale nie jest warunkiem brzegowym KB. -## TODO DECYZJA OSKARA: host (PIHA vs SOLARIA) +## Decyzja: host = PIHA -Otwarte w kb-02 i module 4. Compose jest przenośne (ścieżki po konwencji -`/opt/homelab/data`, bind IP i proxy w `.env`) — decyzja wybiera node -i dwie wartości w `.env`. +Moduł 4 skłaniał się ku SOLARIA (mocniejszy host, bez wpływu na KB — patrz +niżej), ale Oskar zdecydował inaczej: **Nextcloud jest używany aktywnie** +(telefon, sync, rodzina) — sesyjna dostępność SOLARII (sync dogania się +dopiero po wybudzeniu hosta) nie jest akceptowalna dla tego workloadu. +Nextcloud musi być **always-on**, więc ląduje na PIHA mimo ciaśniejszego +RAM/CPU. -| | PIHA | SOLARIA | +| | PIHA (wybrane) | SOLARIA | |---|---|---| | Dostępność | 24/7 (sync zawsze działa) | sesyjna — sync dogania się po wybudzeniu | | RAM/CPU | ciasno nawet po module 0 (Nextcloud+PHP ≈ 0.5–1 Gi+) | 62 Gi RAM, 24 rdzenie — bez znaczenia | | Storage | NVMe 477 G (dzielone z resztą) | NVMe 2 T | -| Ingress | npm lokalnie | npm@PIHA proxuje po LAN do 192.168.31.70:8220 (npm proxuje na dowolny IP — bez przeszkód) | +| Ingress | npm lokalnie (ten sam host) | npm@PIHA proxuje po LAN do 192.168.31.70:8220 | | Wpływ na KB | żaden — KB czyta własną kopię z archiwum na PIHA w obu wariantach | jw. | -Skłonność modułu 4: **SOLARIA** (dlatego `service.yaml` ma wstępnie -`owner_node: solaria`, a `env.example` IP SOLARII). Koszt: przerwy w sync, -gdy host śpi — do zaakceptowania, sync się dogoni. +`service.yaml` ma `owner_node: piha`; `env.example` ma `LAN_BIND_IP` PIHA +(192.168.31.5) i `TRUSTED_PROXIES` pod docker bridge (npm i nextcloud na +tym samym hoście — patrz komentarz w `env.example`). Compose zostaje +przenośne (ścieżki po konwencji `/opt/homelab/data`), gdyby host kiedyś +się zmienił. ## Stack | Kontener | Obraz | Rola | |------------------|-------------------------|---------------------------------------| -| `nextcloud` | `nextcloud:stable-apache` | app + WebDAV (port 80 → host 8220 na LAN_BIND_IP) | -| `nextcloud-cron` | `nextcloud:stable-apache` | joby w tle (`/cron.sh`, ten sam wolumen) | +| `nextcloud` | `nextcloud:34-apache` | app + WebDAV (port 80 → host 8220 na LAN_BIND_IP) | +| `nextcloud-cron` | `nextcloud:34-apache` | joby w tle (`/cron.sh`, ten sam wolumen) | | `nextcloud-db` | `postgres:16-alpine` | baza (bez portu na hoście) | | `nextcloud-redis`| `redis:7-alpine` | cache + file locking (bez portu, bez persystencji) | -TODO DECYZJA OSKARA: przy deployu przypiąć konkretną wersję major -(np. `nextcloud:31-apache`) zamiast ruchomego `stable` — Nextcloud nie -wspiera skoków o więcej niż jedną wersję major przy upgrade. +Wersja przypięta na `34-apache` (aktualny stable na 2026-07-09, wydany +2026-06-09 — zweryfikowane przez endoflife.date/nextcloud). Nextcloud +wydaje nowy major co ~4 miesiące i nie wspiera przeskakiwania wersji przy +upgrade — **TODO PRZY DEPLOYU**: potwierdzić bieżący stable tuż przed +`docker compose up` i podbić tag, jeśli wyszła nowsza wersja. ## OIDC przez Forgejo (krok po-deployowy, occ) @@ -87,12 +94,17 @@ retencja krótsza (np. 7 dziennych + 4 tygodniowe). ## Cutover checklist (przy deployu — NIE teraz) -1. Decyzja hosta ↑ podjęta; `LAN_BIND_IP`/`TRUSTED_PROXIES` w `.env` pod nią. +1. Host = PIHA (decyzja podjęta); `LAN_BIND_IP`/`TRUSTED_PROXIES` w `.env` + już pod nią — `TRUSTED_PROXIES` doprecyzować przez `docker network + inspect` na żywym hoście (patrz komentarz w `env.example`). 2. Port wolny na żywym hoście: `ss -tlnp | grep 8220`. -3. `mkdir -p /opt/homelab/data/nextcloud/{html,db}` na wybranym node. +3. `mkdir -p /opt/homelab/data/nextcloud/{html,db}` na PIHA. 4. `.env` z `env.example`. -5. DNS `cloud.kapala.org` → PIHA (DNS Only) + vhost w npm@PIHA (HTTPS → - `:8220`; jeśli host=SOLARIA, target = 192.168.31.70). +5. DNS: rekord A `cloud.kapala.org` w Cloudflare → `100.108.208.3` + (Tailscale PIHA, DNS Only) — ten sam wzorzec co `ha.kapala.org` / + `immich.kapala.org` (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`); + wildcard `*.kapala.org` już pokrywa tę subdomenę, nowy cert niepotrzebny. + Plus vhost w npm@PIHA (HTTPS → `192.168.31.5:8220`, Advanced puste). 6. `docker compose up -d`; pierwszy start instaluje NC (2–3 min), potem `./healthcheck.sh`. 7. Kroki `occ` dla `user_oidc` (wyżej) + rejestracja appki w Forgejo + diff --git a/services/nextcloud/docker-compose.yml b/services/nextcloud/docker-compose.yml index da91669..2417d7f 100644 --- a/services/nextcloud/docker-compose.yml +++ b/services/nextcloud/docker-compose.yml @@ -5,21 +5,23 @@ # KOPIA in the kb-02 hybrid) — Nextcloud is NOT a source of truth, which # relaxes its backup and availability requirements vs paperless. # -# TODO DECYZJA OSKARA: host — PIHA (always-on, ale ciasno) czy SOLARIA -# (mocna, sesyjna; sync dogania sie po wybudzeniu)? Modul 4 sklania sie ku -# SOLARIA. Compose jest przenosne: wszystkie sciezki po konwencji -# /opt/homelab/data, bind IP i proxy z .env — decyzja wybiera tylko node -# i wartosci w .env. +# Host decided: PIHA (not SOLARIA) — Oskar uses Nextcloud actively (phone +# sync, family), needs always-on availability; SOLARIA's session-only uptime +# was not acceptable for this workload. See +# docs/kb/modules/DECYZJE-do-podjecia.md #1 and README.md. Compose stays +# portable: paths follow the /opt/homelab/data convention, bind IP and proxy +# come from .env. # # EXPOSURE: LAN/Tailscale only przez npm@PIHA (cloud.kapala.org), zero public. -# Jesli host=SOLARIA, npm@PIHA proxuje po LAN do 192.168.31.70:8220. +# npm and nextcloud are both on PIHA — npm reaches it as a local container. services: nextcloud: - # Multi-arch (arm64 + amd64) — runs on either candidate host. - # TODO DECYZJA OSKARA: przy deployu przypiac konkretna wersje major - # (np. nextcloud:31-apache) zamiast ruchomego "stable" — Nextcloud NIE - # wspiera przeskakiwania wersji major przy upgrade. - image: nextcloud:stable-apache + # Pinned major: 34-apache was current stable as of 2026-07-09 (verified + # via endoflife.date/nextcloud — released 2026-06-09). TODO AT DEPLOY: + # Nextcloud ships a new major every ~4 months and does NOT support + # skipping majors on upgrade — reconfirm the current stable major right + # before `docker compose up` and bump the tag if one shipped since. + image: nextcloud:34-apache container_name: nextcloud restart: unless-stopped depends_on: @@ -38,16 +40,19 @@ services: - POSTGRES_USER=nextcloud # Redis: PHP session locking + file locking cache. - REDIS_HOST=redis - # TODO DECYZJA OSKARA: potwierdzic domene cloud.kapala.org + DNS + vhost npm. + # Domain confirmed: cloud.kapala.org (mesh-only, *.kapala.org wildcard + # cert already covers it). DNS + npm vhost are deploy-time steps, see + # README Cutover checklist. - NEXTCLOUD_TRUSTED_DOMAINS=cloud.kapala.org # Behind npm@PIHA (TLS terminated there); without these Nextcloud # generates http:// links and login loops. - OVERWRITEPROTOCOL=https - OVERWRITEHOST=cloud.kapala.org - OVERWRITECLIURL=https://cloud.kapala.org - # npm@PIHA as seen by this container: LAN IP of PIHA when Nextcloud - # runs on SOLARIA; docker bridge subnet when it runs on PIHA itself — - # hence configurable via .env. + # npm@PIHA as seen by this container: since Nextcloud also runs on + # PIHA, npm reaches it as a local container over the docker bridge + # (not PIHA's LAN IP) — see TRUSTED_PROXIES in env.example. Kept + # configurable via .env in case the host ever changes. - TRUSTED_PROXIES=${TRUSTED_PROXIES} - PHP_MEMORY_LIMIT=512M - PHP_UPLOAD_LIMIT=4G @@ -59,8 +64,8 @@ services: ports: # Bind to the node's LAN IP only, never 0.0.0.0 — npm@PIHA is the sole # entry point (LAN/Tailscale). Port 8220 free on PIHA per the 2026-06-30 - # inventory; free on SOLARIA (nearly empty host). - # TODO DECYZJA OSKARA: potwierdzic port na zywym hoscie: ss -tlnp | grep 8220 + # inventory. + # TODO AT DEPLOY: reconfirm on the live host: ss -tlnp | grep 8220 - "${LAN_BIND_IP}:8220:80" healthcheck: test: ["CMD", "curl", "-fs", "--max-time", "5", "http://localhost:80/status.php"] diff --git a/services/nextcloud/env.example b/services/nextcloud/env.example index a7152fc..8ae969b 100644 --- a/services/nextcloud/env.example +++ b/services/nextcloud/env.example @@ -1,16 +1,18 @@ # Nextcloud secrets + host-local binds — copy to .env (gitignored) next to # docker-compose.yml and fill in real values. Never commit .env. -# LAN IP of the node Nextcloud lands on. The web port (8220) binds ONLY to -# this interface — never 0.0.0.0. -# TODO DECYZJA OSKARA: host! SOLARIA -> 192.168.31.70, PIHA -> 192.168.31.5. -LAN_BIND_IP=192.168.31.70 +# LAN IP of the node Nextcloud lands on. Host decided: PIHA (see +# docs/kb/modules/DECYZJE-do-podjecia.md #1). The web port (8220) binds ONLY +# to this interface — never 0.0.0.0. +LAN_BIND_IP=192.168.31.5 -# Reverse proxy (npm@PIHA) as seen from THIS node's containers: -# - Nextcloud on SOLARIA: npm connects over the LAN -> PIHA's LAN IP. -# - Nextcloud on PIHA: npm is a local container -> use the docker bridge -# subnet instead (e.g. 172.16.0.0/12), verify with docker network inspect. -TRUSTED_PROXIES=192.168.31.5 +# Reverse proxy (npm@PIHA) as seen from THIS node's containers. Nextcloud +# runs on PIHA (same host as npm) -> npm reaches it as a local container, so +# the source IP is the docker bridge gateway, not PIHA's LAN IP. 172.16.0.0/12 +# covers Docker's default bridge allocation range; narrow it to the exact +# /24 (docker network inspect _default) once the stack is up on the +# live host. +TRUSTED_PROXIES=172.16.0.0/12 # Postgres password for the nextcloud DB (app reads the same var). POSTGRES_PASSWORD=change-me-very-strong diff --git a/services/nextcloud/service.yaml b/services/nextcloud/service.yaml index 7a6c544..0bc88e5 100644 --- a/services/nextcloud/service.yaml +++ b/services/nextcloud/service.yaml @@ -1,10 +1,12 @@ service: name: nextcloud - # TODO DECYZJA OSKARA: owner_node — piha (always-on, ciasno) vs solaria - # (mocna, sesyjna). Modul 4 / kb-02 sklaniaja sie ku SOLARIA: KB-kopia - # dokumentow i tak lezy w archiwum na PIHA, wiec zapytania KB dzialaja bez - # Nextclouda; sync z telefonu dogania sie po wybudzeniu hosta. - owner_node: solaria + # Decyzja Oskara: PIHA (nie SOLARIA). Uzasadnienie: Nextcloud jest uzywany + # aktywnie (telefon, sync, rodzina) -> musi byc always-on, sesyjna + # dostepnosc SOLARII nie jest akceptowalna dla tego workloadu (w + # przeciwienstwie do KB-zapytan, ktore i tak czytaja wlasna kopie z + # archiwum na PIHA niezaleznie od hosta Nextclouda). Patrz + # docs/kb/modules/DECYZJE-do-podjecia.md #1 i README.md. + owner_node: piha role: file-sync-drive # Drive replacement + WebDAV source for KB ingest (archiwum = KOPIA) exposure: private # LAN/Tailscale only via npm@PIHA (cloud.kapala.org); NO public ingress dependencies: diff --git a/services/paperless-worker/docker-compose.yml b/services/paperless-worker/docker-compose.yml index 0c73fb9..fb30763 100644 --- a/services/paperless-worker/docker-compose.yml +++ b/services/paperless-worker/docker-compose.yml @@ -26,8 +26,10 @@ services: - .env environment: # Broker + DB on PIHA over the fast LAN (NOT Tailscale) — same L2 as - # the NFS mounts, transfer is not the bottleneck. - - PAPERLESS_REDIS=redis://192.168.31.5:6380 + # the NFS mounts, transfer is not the bottleneck. Broker has + # requirepass set — PAPERLESS_REDIS_PASSWORD in .env MUST equal the + # value in services/paperless/.env on PIHA. + - PAPERLESS_REDIS=redis://:${PAPERLESS_REDIS_PASSWORD}@192.168.31.5:6380 - PAPERLESS_DBHOST=192.168.31.5 - PAPERLESS_DBPORT=5434 - PAPERLESS_DBNAME=paperless @@ -41,8 +43,9 @@ services: - PAPERLESS_OCR_LANGUAGE=pol+eng # Threads used INSIDE a single OCR task; total load ~ concurrency × # threads. 4×4=16 threads leaves headroom on 24 cores/32 threads. - # TODO DECYZJA OSKARA: sizing pod batch 70k zalacznikow (moduł 5) — - # podbic po probce (np. concurrency 8) czy zostawic zapas na ollama/AI? + # TODO AT DEPLOY (module 5): measure on a sample before the 70k-attachment + # batch — decide whether to raise concurrency (e.g. 8) or keep headroom + # for ollama/AI workloads. - PAPERLESS_THREADS_PER_WORKER=4 # MUST match paperless@PIHA — files on the NFS export carry numeric # UID/GID; a mismatch means this worker cannot read/write documents. diff --git a/services/paperless-worker/env.example b/services/paperless-worker/env.example index 2e51161..15a9065 100644 --- a/services/paperless-worker/env.example +++ b/services/paperless-worker/env.example @@ -9,8 +9,13 @@ PAPERLESS_SECRET_KEY=change-me-same-as-piha # MUST be identical to PAPERLESS_DBPASS in services/paperless/.env @ PIHA. PAPERLESS_DBPASS=change-me-same-as-piha +# MUST be identical to PAPERLESS_REDIS_PASSWORD in services/paperless/.env @ +# PIHA (broker requirepass) — a mismatch means this worker cannot reach the +# queue. +PAPERLESS_REDIS_PASSWORD=change-me-same-as-piha + # Parallel OCR tasks on SOLARIA (celery --concurrency). Total CPU load is # roughly WORKER_CONCURRENCY x PAPERLESS_THREADS_PER_WORKER (4x4=16 threads # default, on 24 cores / 32 threads). -# TODO DECYZJA OSKARA: sizing pod batch 70k zalacznikow — zmierzyc na probce. +# TODO AT DEPLOY (module 5): measure on a sample before the 70k-attachment batch. WORKER_CONCURRENCY=4 diff --git a/services/paperless-worker/service.yaml b/services/paperless-worker/service.yaml index 6adbdf0..eb641e8 100644 --- a/services/paperless-worker/service.yaml +++ b/services/paperless-worker/service.yaml @@ -29,4 +29,5 @@ service: env_vars: - PAPERLESS_SECRET_KEY # MUST equal the value on PIHA - PAPERLESS_DBPASS # MUST equal the value on PIHA + - PAPERLESS_REDIS_PASSWORD # MUST equal the value on PIHA (broker requirepass) - WORKER_CONCURRENCY # optional, default 4 — parallel OCR tasks diff --git a/services/paperless/README.md b/services/paperless/README.md index 5216cc8..6b97672 100644 --- a/services/paperless/README.md +++ b/services/paperless/README.md @@ -19,8 +19,9 @@ prawdy dla KB. Backup jest OBOWIĄZKOWY (patrz niżej), nie opcjonalny. Wszystkie porty bindowane na `LAN_BIND_IP` (192.168.31.5), nigdy 0.0.0.0: - `8210` → web UI (za npm@PIHA, vhost `paper.kapala.org`, LAN/Tailscale only) -- `5434` → Postgres dla workera@SOLARIA (5433 zajęte przez kb-postgres) -- `6380` → Redis dla workera@SOLARIA (6379 zajęte przez agent-system-redis) +- `5434` → Postgres dla workera@SOLARIA (5433 zajęte przez kb-postgres); auth scram +- `6380` → Redis dla workera@SOLARIA (6379 zajęte przez agent-system-redis); + `requirepass` ustawione — `PAPERLESS_REDIS_PASSWORD` w `.env` po obu stronach Obraz jest multi-arch (arm64 natywnie na Pi 5). Tag `2.14` MUSI być identyczny z `services/paperless-worker/docker-compose.yml` — podbijać oba naraz. @@ -61,8 +62,9 @@ Rejestracja w Forgejo (Settings → Applications, wzorzec jak Vikunja): (npm na PIHA), żeby OIDC discovery szło po LAN — ta sama lekcja co w Vikunji. Lokalny login zostaje na czas bootstrapu (konto `PAPERLESS_ADMIN_USER`); -po zweryfikowaniu OIDC rozważyć `PAPERLESS_DISABLE_REGULAR_LOGIN=true` -(TODO DECYZJA OSKARA w compose). +po zweryfikowaniu OIDC ustawić `PAPERLESS_DISABLE_REGULAR_LOGIN=true` + +`PAPERLESS_REDIRECT_LOGIN_TO_SSO=true` (decyzja podjęta — zrobić PRZY DEPLOYU, +po potwierdzeniu logowania OIDC; TODO w compose). ## Storage i backup (WARUNEK BRZEGOWY hybrydy) @@ -76,7 +78,7 @@ Dane na PIHA NVMe: - `paperless_pgdata` (named volume) — metadane (tagi, korespondenci, daty) - `paperless_redisdata` (named volume) — AOF kolejki zadań -### Propozycja backupu (TODO DECYZJA OSKARA — zatwierdzić przed deployem) +### Backup (zatwierdzone — cel: SOLARIA, LAN; offsite później) 1. **Warstwa 1 — eksport spójny**: nocny cron na PIHA: `docker exec paperless document_exporter ../export --delete -z` — @@ -85,11 +87,16 @@ Dane na PIHA NVMe: Ląduje w `/opt/homelab/data/paperless/export`. 2. **Warstwa 2 — kopia poza hosta**: rsync/borg eksportu na SOLARIA (2 TB NVMe, ta sama LAN) — SOLARIA sesyjna, więc pull przy starcie - lub push z retry. Alternatywa/uzupełnienie: offsite (restic → chmura?). - VPS odpada (brak miejsca). -3. **Retencja (propozycja)**: 7 dziennych + 4 tygodniowe + 6 miesięcznych. + lub push z retry. Wystarcza na start; VPS odpada (brak miejsca). +3. **Retencja (zatwierdzona)**: 7 dziennych + 4 tygodniowe + 6 miesięcznych. 4. Dump Postgresa nie jest konieczny do odtworzenia (manifest wystarcza), ale tani — można dołożyć `pg_dump` do tej samej nocnej pętli. +5. **Future-note (poza zakresem tego etapu)**: offsite backup (np. restic → + chmura) poza SOLARIA-LAN, gdy pojawi się potrzeba/budżet. + +Ten etap to tylko config + dokumentacja decyzji — cron/skrypt deployowy +(cron entry na PIHA, rsync/borg do SOLARII) powstaje przy deployu modułu 2, +nie tutaj. ## RAM na PIHA @@ -104,7 +111,11 @@ Oczekiwany spoczynek: paperless ~400–600 Mi (gunicorn + consumer + beat + 2. Porty wolne na żywym hoście: `ss -tlnp | grep -E '8210|5434|6380'`. 3. `mkdir -p /opt/homelab/data/paperless/{data,media,consume,export}` na PIHA. 4. `.env` z `env.example` (sekrety + rejestracja OAuth2 w Forgejo). -5. DNS `paper.kapala.org` → PIHA (DNS Only) + vhost w npm@PIHA (HTTPS → 192.168.31.5:8210). +5. DNS: rekord A `paper.kapala.org` w Cloudflare → `100.108.208.3` + (Tailscale PIHA, DNS Only) — ten sam wzorzec co `ha.kapala.org` / + `immich.kapala.org` (`docs/sessions/2026-06-30-kapala-cloudflare-wildcard-mesh.md`); + wildcard `*.kapala.org` już pokrywa tę subdomenę, nowy cert niepotrzebny. + Plus vhost w npm@PIHA (HTTPS → `192.168.31.5:8210`, Advanced puste). 6. `docker compose up -d` + `./healthcheck.sh` + testowy login OIDC. 7. Export NFS dla workera (patrz `services/paperless-worker/README.md`) — dopiero przy module 3. diff --git a/services/paperless/docker-compose.yml b/services/paperless/docker-compose.yml index eb2cfc8..7483c69 100644 --- a/services/paperless/docker-compose.yml +++ b/services/paperless/docker-compose.yml @@ -33,11 +33,14 @@ services: env_file: - .env environment: - # TODO DECYZJA OSKARA: potwierdzic domene paper.kapala.org (vs paper.okit.pl) - # + wpis DNS (A -> LAN/Tailscale, DNS Only) + vhost w npm@PIHA. + # Domain confirmed: paper.kapala.org (mesh-only, *.kapala.org wildcard + # cert already covers it — no new cert needed). DNS + npm vhost are + # deploy-time steps, see README Cutover checklist. - PAPERLESS_URL=https://paper.kapala.org - PAPERLESS_TIME_ZONE=Europe/Warsaw - - PAPERLESS_REDIS=redis://broker:6379 + # Password-protected broker (requirepass) — see PAPERLESS_REDIS_PASSWORD + # in .env. MUST match the broker's --requirepass value below. + - PAPERLESS_REDIS=redis://:${PAPERLESS_REDIS_PASSWORD}@broker:6379 - PAPERLESS_DBHOST=db - PAPERLESS_DBNAME=paperless - PAPERLESS_DBUSER=paperless @@ -60,8 +63,9 @@ services: # First Forgejo login auto-creates the matching Paperless account. - PAPERLESS_SOCIALACCOUNT_ALLOW_SIGNUPS=true # No self-service local signups; local admin login stays for bootstrap. - # TODO DECYZJA OSKARA: po zweryfikowaniu logowania OIDC ustawic - # PAPERLESS_DISABLE_REGULAR_LOGIN=true + PAPERLESS_REDIRECT_LOGIN_TO_SSO=true? + # TODO AT DEPLOY: after OIDC login is verified working, set + # PAPERLESS_DISABLE_REGULAR_LOGIN=true + PAPERLESS_REDIRECT_LOGIN_TO_SSO=true + # (decision already made — Vikunja runs both modes in parallel today too). - PAPERLESS_ACCOUNT_ALLOW_SIGNUPS=false volumes: # Bind mounts under /opt/homelab/data (runtime path convention). These @@ -74,8 +78,8 @@ services: - /opt/homelab/data/paperless/export:/usr/src/paperless/export ports: # LAN-only bind; npm@PIHA proxies paper.kapala.org -> 192.168.31.5:8210. - # TODO DECYZJA OSKARA: port 8210 wolny wg inwentaryzacji 2026-06-30 — - # potwierdzic na zywym hoscie przed deployem: ss -tlnp | grep 8210 + # TODO AT DEPLOY: port 8210 free per the 2026-06-30 inventory — + # reconfirm on the live host: ss -tlnp | grep 8210 - "${LAN_BIND_IP}:8210:8000" healthcheck: test: ["CMD", "curl", "-fs", "-S", "--max-time", "2", "http://localhost:8000"] @@ -115,18 +119,22 @@ services: # (up to the 70k-attachment backlog) live only in this Redis. Without AOF a # broker restart would silently drop the queue — violating the "zero lost # tasks" criterion of module 3. Do NOT set maxmemory/eviction here. - command: redis-server --appendonly yes + # requirepass: broker is bound to the LAN interface (trusted home network, + # but still reachable by anything on that LAN) — password auth decided + # over relying on the bind alone. Same value MUST be set as + # PAPERLESS_REDIS_PASSWORD in .env on both PIHA (this file) and + # paperless-worker@SOLARIA. + command: redis-server --appendonly yes --requirepass ${PAPERLESS_REDIS_PASSWORD} volumes: - paperless_redisdata:/data ports: # LAN-only bind for paperless-worker@SOLARIA. 6379 is taken by # agent-system-redis. - # TODO DECYZJA OSKARA: Redis bez auth na bindzie LAN (zaufana siec - # domowa) — akceptowalne, czy dolozyc requirepass? (wtedy PAPERLESS_REDIS - # z haslem przenosi sie do .env po obu stronach) + # TODO AT DEPLOY: port 6380 free per the 2026-06-30 inventory — + # reconfirm on the live host: ss -tlnp | grep 6380 - "${LAN_BIND_IP}:6380:6379" healthcheck: - test: ["CMD", "redis-cli", "ping"] + test: ["CMD", "redis-cli", "-a", "${PAPERLESS_REDIS_PASSWORD}", "--no-auth-warning", "ping"] interval: 10s timeout: 5s retries: 5 diff --git a/services/paperless/env.example b/services/paperless/env.example index 745b541..2354e67 100644 --- a/services/paperless/env.example +++ b/services/paperless/env.example @@ -16,12 +16,17 @@ PAPERLESS_SECRET_KEY=change-me-long-random-string POSTGRES_PASSWORD=change-me-very-strong PAPERLESS_DBPASS=change-me-very-strong +# Redis broker password (requirepass). MUST be identical in +# services/paperless-worker/.env on SOLARIA — a mismatch means the worker +# cannot reach the queue. +PAPERLESS_REDIS_PASSWORD=CHANGEME + # OIDC provider config (django-allauth openid_connect) — single-line JSON. # Lives in .env because it embeds the Forgejo OAuth2 client secret. -# Register the app in Forgejo: Settings > Applications > +# Domain confirmed: paper.kapala.org. Register the app in Forgejo: +# Settings > Applications > # Redirect URI: https://paper.kapala.org/accounts/oidc/forgejo/login/callback/ # then paste client_id + secret below. -# TODO DECYZJA OSKARA: potwierdzic domene paper.kapala.org przed rejestracja w Forgejo. PAPERLESS_SOCIALACCOUNT_PROVIDERS={"openid_connect":{"SCOPE":["openid","profile","email"],"OAUTH_PKCE_ENABLED":true,"APPS":[{"provider_id":"forgejo","name":"Forgejo","client_id":"CHANGE-ME-CLIENT-ID","secret":"CHANGE-ME-CLIENT-SECRET","settings":{"server_url":"https://forgejo.kapala.org/.well-known/openid-configuration"}}]}} # Bootstrap admin (local login) — used until OIDC is verified, then consider diff --git a/services/paperless/healthcheck.sh b/services/paperless/healthcheck.sh index 909f465..6a9d04b 100755 --- a/services/paperless/healthcheck.sh +++ b/services/paperless/healthcheck.sh @@ -24,8 +24,9 @@ if ! curl -sfL --max-time 10 "http://${BIND_IP}:8210/" > /dev/null; then exit 1 fi -# Redis broker must answer on the LAN bind (the SOLARIA worker depends on it) -if ! docker exec paperless-broker redis-cli -h "${BIND_IP}" -p 6380 ping | grep -q PONG; then +# Redis broker must answer on the LAN bind (the SOLARIA worker depends on it). +# requirepass is set — auth with PAPERLESS_REDIS_PASSWORD from .env. +if ! docker exec paperless-broker redis-cli -h "${BIND_IP}" -p 6380 -a "${PAPERLESS_REDIS_PASSWORD}" --no-auth-warning ping | grep -q PONG; then echo "[FAIL] redis broker is not answering on ${BIND_IP}:6380 (worker@SOLARIA cannot connect)" exit 1 fi diff --git a/services/paperless/service.yaml b/services/paperless/service.yaml index 738ce46..ab652d5 100644 --- a/services/paperless/service.yaml +++ b/services/paperless/service.yaml @@ -45,4 +45,5 @@ service: - PAPERLESS_SECRET_KEY - PAPERLESS_DBPASS - POSTGRES_PASSWORD # must equal PAPERLESS_DBPASS + - PAPERLESS_REDIS_PASSWORD # broker requirepass; must equal paperless-worker@SOLARIA - PAPERLESS_SOCIALACCOUNT_PROVIDERS # OIDC provider JSON incl. Forgejo client secret