diff --git a/astrololo/README-astroklient-demo.md b/astrololo/README-astroklient-demo.md new file mode 100644 index 0000000..b40e095 --- /dev/null +++ b/astrololo/README-astroklient-demo.md @@ -0,0 +1,188 @@ +# astroklient-demo — wdrożenie wersji demo (PRE-28/29) + +Osobna usługa o dwóch funkcjach: **dodanie pliku bazy** i **zapytanie o +interpretację urodzeniową**. Opis aplikacji: `services/astroklient-demo/README.md` +w repo aplikacji. + +--- + +## Jak to jest odizolowane + +``` +astroklient-demo → logic-demo → data-demo → /mnt/Tank1/astrololo-demo + └── klientA/ ← pula konta + └── klientB/ ← pula konta +``` + +**Oryginalne bazy są dla demo nieosiągalne.** Nie chodzi o uprawnienia: demo ma +własną warstwę danych, pracującą na osobnym udziale, pustym na starcie. + +Dlaczego osobna jest też **warstwa logiczna**: zna ona jeden adres warstwy danych, +więc astroklient-demo korzystający z produkcyjnej logiki i tak trafiłby na produkcyjne +bazy. Izolacja musi sięgnąć obu warstw naraz, inaczej nie ma jej wcale. + +**Każde konto ma własną pulę** — swój podkatalog na tym udziale. Konta nie widzą +swoich plików nawzajem ani w liście, ani w wynikach wyszukiwania. Pula bierze się +z **loginu zalogowanej osoby**, nigdy z pola formularza. + +--- + +## Krok 1 — udział `astrololo-demo` na TrueNAS + +Ta sama procedura co przy `astrololo-state`, więc jeśli tamten działa, ten też +zadziała. Po SSH na NAS: + +```bash +sudo zfs create Tank1/astrololo-demo # albo: sudo mkdir -p /mnt/Tank1/astrololo-demo +``` + +```bash +midclt call sharing.nfs.query '[["path","=","/mnt/Tank1/astrololo-demo"]]' \ + | python3 -m json.tool +``` + +**Pusta lista `[]`** — twórz: + +```bash +midclt call sharing.nfs.create '{ + "path": "/mnt/Tank1/astrololo-demo", + "comment": "astrololo — pule kont wersji demo (PRE-29)", + "hosts": ["192.168.1.73", "192.168.1.80", "192.168.1.81"], + "enabled": true, + "ro": false, + "mapall_user": "root", + "mapall_group": "root" +}' +``` + +**Coś zwróciło** — weź `id` i użyj `sharing.nfs.update ` z tą samą treścią. + +```bash +midclt call service.restart nfs +sudo exportfs -v | grep astrololo-demo +``` + +Musi tam być `rw`. Sprawdź jeszcze zapis z węzła — to jest ten test, którego +zabrakło przy poprzednim udziale: + +```bash +ssh 'sudo mkdir -p /mnt/t && sudo mount -t nfs 192.168.1.34:/mnt/Tank1/astrololo-demo /mnt/t \ + && sudo touch /mnt/t/proba && echo ZAPIS-OK || echo ZAPIS-NIE; sudo rm -f /mnt/t/proba; sudo umount /mnt/t' +``` + +--- + +## Krok 2 — konta demo + +Każde konto to osobna pula, więc **rozdajesz konta, nie jedno hasło**. + +Hasła najlepiej jako hash — wtedy nie leżą nigdzie jawnie (skrypt jest w repo +aplikacji, `services/presentation/scripts/make_user.py`): + +```bash +python scripts/make_user.py klientA # wypisze: scrypt$… +python scripts/make_user.py klientB +``` + +```bash +kubectl -n astrololo create secret generic astrololo-demo \ + --from-literal=DEMO_USERS='klientA:scrypt$…,klientB:scrypt$…' \ + --from-literal=SESSION_SECRET="$(openssl rand -hex 32)" +``` + +`SESSION_SECRET` podpisuje ciasteczka sesji (LOG-34). **Pod bez niego celowo nie +wstanie.** Jest WŁASNY, nie ten z pełnej aplikacji: demo i produkcja nie mają +powodu uznawać nawzajem swoich sesji. + +> **Zdalne unieważnienie w demo działa inaczej niż w pełnej wersji.** Demo nie ma +> własnego wolumenu, więc nie ma gdzie trzymać licznika sesji. Zamiast tego: +> usunięcie konta z `DEMO_USERS` albo zmiana jego hasła **natychmiast ubija jego +> otwarte sesje** — bo odcisk poświadczenia wpisany w ciasteczko przestaje pasować. +> Rotacja `SESSION_SECRET` wylogowuje wszystkich naraz. + +### Dodanie konta później + +```bash +STARE=$(kubectl -n astrololo get secret astrololo-demo -o jsonpath='{.data.DEMO_USERS}' | base64 -d) + +kubectl -n astrololo create secret generic astrololo-demo \ + --from-literal=DEMO_USERS="${STARE},klientC:scrypt\$…" \ + --dry-run=client -o yaml | kubectl apply -f - + +kubectl -n astrololo rollout restart deploy/astroklient-demo +``` + +### Odebranie dostępu + +Usuń wpis z `DEMO_USERS` tą samą drogą. **Pula zostaje na udziale** — pliki +klienta nie znikają, tylko przestaje być komu je pokazywać. + +--- + +## Krok 3 — wdrożenie + +```bash +kubectl apply -k astrololo +kubectl -n astrololo rollout status deploy/data-demo deploy/logic-demo deploy/astroklient-demo +``` + +Sprawdzenie na czas jednej sesji, bez wystawiania na świat: + +```bash +kubectl -n astrololo port-forward deploy/astroklient-demo 8005:8005 +``` + +Wejście z zewnątrz wymaga reguły w `ingress.yaml` — osobny host albo ścieżka. +**Celowo nie zakładam tego za Ciebie**: to decyzja, pod jakim adresem świat +zobaczy demo. + +--- + +## Import puli klienta do pełnej aplikacji + +Sedno całego układu: klient, który przechodzi na pełną wersję, **nie traci +dotychczasowych wgrań**. Jego pula to jeden katalog. + +**1. Zobacz, co tam jest** (na NAS): + +```bash +LOGIN=klientA +sudo ls -la /mnt/Tank1/astrololo-demo/$LOGIN/ +``` + +**2. Sprawdź kolizje nazw** z bazami produkcyjnymi: + +```bash +comm -12 \ + <(cd /mnt/Tank1/astrololo-demo/$LOGIN && ls *.xlsx 2>/dev/null | sort) \ + <(cd /mnt/Tank1/astrololo && ls *.xlsx 2>/dev/null | sort) +``` + +Pusto = brak kolizji. Cokolwiek się wypisze, przenieś ręcznie pod inną nazwą — +**nie nadpisuj bazy produkcyjnej**. + +**3. Skopiuj** (`-n` = nie nadpisuj niczego, co już jest): + +```bash +sudo cp -n /mnt/Tank1/astrololo-demo/$LOGIN/*.xlsx /mnt/Tank1/astrololo/ +``` + +> Kopiujemy **tylko pliki `.xlsx`**. Plik `.files-state.json` zostaje — opisuje +> stan wewnątrz puli demo i w produkcji nie ma sensu; produkcja ma własny. + +**4. Włącz je w pełnej aplikacji**: zakładka **Pliki**, nowe bazy pojawią się jako +„gotowa, odstawiona". Klikasz przełącznik przy tych, które mają wejść do użytku. + +To, że nie włączają się same, jest zamierzone: przeniesienie plików jest czynnością +techniczną, a decyzja, które bazy biorą udział w interpretacji, należy do Ciebie. + +--- + +## Odcięcie całego demo + +```bash +kubectl -n astrololo scale deploy/astroklient-demo --replicas=0 +``` + +Pule i dane zostają. Skasowanie sekretu `astrololo-demo` zatrzyma pod (nie wstanie +bez niego) i to też jest zachowanie zamierzone. diff --git a/astrololo/README.md b/astrololo/README.md index d887e7f..40dbb62 100644 --- a/astrololo/README.md +++ b/astrololo/README.md @@ -29,6 +29,15 @@ Runbook: **[README-pliki.md](README-pliki.md)**. Ekran „Pliki" wymaga zapisu d udziału z bazami, więc znosi gwarancję z DAN-25, że baz nie da się zmienić przez NFS. Co zostaje z zabezpieczeń i jak to wdrożyć — w runbooku. +## astroklient-demo — wersja demo (PRE-28) + +Osobna usługa o dwóch funkcjach. Runbook: **[README-astroklient-demo.md](README-astroklient-demo.md)**. + +Ma **własną warstwę logiczną i własną warstwę danych** na osobnym udziale +`astrololo-demo` — oryginalne bazy są dla demo nieosiągalne. Każde konto demo ma +własną, niewidoczną dla pozostałych pulę plików, a jego pulę da się później +przenieść do pełnej aplikacji jako jeden katalog. + ## ⚠️ Sekret `astrololo-auth` — utwórz PRZED wdrożeniem Aplikacja wystawia treść **oryginalnych baz interpretacyjnych**, dlatego wymaga diff --git a/astrololo/astroklient-demo-stack.yaml b/astrololo/astroklient-demo-stack.yaml new file mode 100644 index 0000000..baad6a6 --- /dev/null +++ b/astrololo/astroklient-demo-stack.yaml @@ -0,0 +1,122 @@ +# Warstwa danych i logiki WYŁĄCZNIE dla wersji demo (PRE-29). +# +# DLACZEGO OSOBNY KOMPLET, A NIE WSPÓŁDZIELONY. Warstwa logiczna zna JEDEN adres +# warstwy danych, więc astroklient-demo korzystający z produkcyjnej logiki i tak trafiłby +# na produkcyjne bazy. Izolacja demo musi więc sięgnąć obu warstw naraz, inaczej +# nie ma jej wcale. +# +# Udział `astrololo-demo` jest PUSTY na starcie i nigdy nie zawiera oryginalnych +# baz. Każde konto demo dostaje w nim własny podkatalog (pulę) — patrz PRE-29. +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: data-demo + namespace: astrololo +spec: + replicas: 1 + selector: + matchLabels: { app: data-demo } + template: + metadata: + labels: { app: data-demo } + spec: + containers: + - name: data + # TEN SAM obraz co produkcja — różni się wyłącznie udziałem, na którym + # pracuje. Osobny obraz oznaczałby drugi kod do utrzymania i pewność, + # że kiedyś się rozjadą. + image: gitea.czernobog.pl/gitea/astrololo-data:latest + ports: [{ containerPort: 8002 }] + env: + - name: DATA_PROVIDER + value: "excel" + - name: EXCEL_DIR + value: "/app/data_files" + - name: CACHE_DIR + value: "/app/.cache" + - name: INTERNAL_TOKEN + valueFrom: + secretKeyRef: { name: astrololo-auth, key: INTERNAL_TOKEN } + - name: LINK_KEY_LOGIC_DATA + valueFrom: + secretKeyRef: { name: astrololo-link, key: LINK_KEY_LOGIC_DATA } + - name: LINK_ENCRYPTION_REQUIRED + value: "true" + volumeMounts: + - name: cache + mountPath: /app/.cache + - name: demo + mountPath: /app/data_files + resources: + requests: { cpu: "100m", memory: "256Mi" } + limits: { cpu: "500m", memory: "512Mi" } + volumes: + - name: cache + emptyDir: {} + - name: demo + nfs: + server: 192.168.1.34 + # OSOBNY udział, pusty na starcie. NIE /mnt/Tank1/astrololo — + # to jest cała istota izolacji demo. + path: /mnt/Tank1/astrololo-demo +--- +apiVersion: v1 +kind: Service +metadata: + name: data-demo + namespace: astrololo +spec: + type: ClusterIP + selector: { app: data-demo } + ports: [{ port: 8002, targetPort: 8002 }] +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: logic-demo + namespace: astrololo +spec: + replicas: 1 + selector: + matchLabels: { app: logic-demo } + template: + metadata: + labels: { app: logic-demo } + spec: + containers: + - name: logic + image: gitea.czernobog.pl/gitea/astrololo-logic:latest + ports: [{ containerPort: 8001 }] + env: + # JEDYNA różnica wobec produkcyjnej logiki — i zarazem cała izolacja. + - name: DATA_URL + value: "http://data-demo:8002" + - name: EPHEMERIS_ENGINE + value: "own" + - name: INTERNAL_TOKEN + valueFrom: + secretKeyRef: { name: astrololo-auth, key: INTERNAL_TOKEN } + - name: LINK_KEY_PRESENTATION_LOGIC + valueFrom: + secretKeyRef: { name: astrololo-link, key: LINK_KEY_PRESENTATION_LOGIC } + - name: LINK_KEY_LOGIC_DATA + valueFrom: + secretKeyRef: { name: astrololo-link, key: LINK_KEY_LOGIC_DATA } + - name: LINK_ENCRYPTION_REQUIRED + value: "true" + # Bez kluczy do modeli językowych. Astroklient nie umie o nie prosić, + # więc ich tu nie ma — czego nie ma w podzie, tego nie wyniesie. + resources: + requests: { cpu: "100m", memory: "128Mi" } + limits: { cpu: "500m", memory: "512Mi" } +--- +apiVersion: v1 +kind: Service +metadata: + name: logic-demo + namespace: astrololo +spec: + type: ClusterIP + selector: { app: logic-demo } + ports: [{ port: 8001, targetPort: 8001 }] diff --git a/astrololo/astroklient-demo.yaml b/astrololo/astroklient-demo.yaml new file mode 100644 index 0000000..419e1d9 --- /dev/null +++ b/astrololo/astroklient-demo.yaml @@ -0,0 +1,86 @@ +# astroklient-demo — wersja demonstracyjna (PRE-28). +# +# Osobna usługa o DWÓCH funkcjach: dodanie pliku bazy i zapytanie o interpretację +# urodzeniową. Pozostałe funkcje nie są tu ukryte — nie ma ich w obrazie. +# +# IZOLACJA (PRE-29): astroklient-demo ma WŁASNĄ warstwę logiczną i WŁASNĄ warstwę +# danych (astroklient-demo-stack.yaml), pracujące na osobnym, pustym na starcie +# udziale. Oryginalne bazy interpretacyjne są dla demo NIEOSIĄGALNE. +# +# Wewnątrz demo każde konto ma własną pulę plików — swój podkatalog na tym +# udziale — niewidoczną dla pozostałych kont. Dzięki temu demo można rozdawać +# szeroko, a pliki jednego klienta da się później przenieść do pełnej aplikacji +# jako jeden katalog (patrz README-astroklient-demo.md). +apiVersion: apps/v1 +kind: Deployment +metadata: + name: astroklient-demo + namespace: astrololo +spec: + replicas: 1 + selector: + matchLabels: { app: astroklient-demo } + template: + metadata: + labels: { app: astroklient-demo } + spec: + containers: + - name: astroklient-demo + image: gitea.czernobog.pl/gitea/astrololo-astroklient-demo:latest + ports: [{ containerPort: 8005 }] + env: + # WŁASNA warstwa logiczna, wskazująca na WŁASNĄ warstwę danych. + # Wpisanie tu produkcyjnego `logic` cofnęłoby całą izolację demo + # jednym słowem — dlatego stoi to tuż obok siebie w jednym pliku. + - name: LOGIC_URL + value: "http://logic-demo:8001" + # Konto demo — WŁASNE, nie astrololo-auth. Sekret tworzony POZA repo + # (jak pozostałe), patrz README-astroklient-demo.md. + # KONTA DEMO — lista `login:sekret` po przecinku. Każde konto ma + # WŁASNĄ, niewidoczną dla pozostałych pulę baz (PRE-29), więc jedno + # wspólne konto oznaczałoby klientów oglądających swoje wgrania + # nawzajem. Sekrety najlepiej jako hash scrypt. + - name: DEMO_USERS + valueFrom: + secretKeyRef: { name: astrololo-demo, key: DEMO_USERS } + # Token międzywarstwowy i klucz łącza — TE SAME co prezentacja: + # demo nie jest furtką omijającą ochronę warstwy logicznej. + - name: INTERNAL_TOKEN + valueFrom: + secretKeyRef: { name: astrololo-auth, key: INTERNAL_TOKEN } + - name: LINK_KEY_PRESENTATION_LOGIC + valueFrom: + secretKeyRef: { name: astrololo-link, key: LINK_KEY_PRESENTATION_LOGIC } + - name: LINK_ENCRYPTION_REQUIRED + value: "true" + # Klucz podpisu ciasteczek sesji (LOG-34). WYMAGANY — pod bez niego + # celowo nie wstaje. WŁASNY, nie ten z pełnej aplikacji: demo i produkcja + # nie mają powodu uznawać nawzajem swoich sesji, a wspólny klucz + # znaczyłby, że sesja z demo bywa ważna tam, gdzie nie powinna. + - name: SESSION_SECRET + valueFrom: + secretKeyRef: { name: astrololo-demo, key: SESSION_SECRET } + # Za Ingressem bezpośrednim rozmówcą jest pod Traefika. Bez tego limit + # liczyłby WSZYSTKICH do jednego wiadra i pierwsza osoba, która go + # wyklika, odcięłaby pozostałe. + - name: TRUST_PROXY + value: "true" + # Niższy niż w pełnej aplikacji: demo bywa udostępniane szerzej, + # a każde zapytanie sięga do treści baz. + - name: RATE_LIMIT_PER_MIN + value: "60" + resources: + requests: { cpu: "50m", memory: "96Mi" } + limits: { cpu: "300m", memory: "256Mi" } +--- +apiVersion: v1 +kind: Service +metadata: + name: astroklient-demo + namespace: astrololo +spec: + # ClusterIP — wejście z zewnątrz wyłącznie przez Ingress po https, tak samo + # jak prezentacja. Druga, nieszyfrowana droga byłaby obejściem PRE-16. + type: ClusterIP + selector: { app: astroklient-demo } + ports: [{ port: 8005, targetPort: 8005 }] diff --git a/astrololo/image-updater.yaml b/astrololo/image-updater.yaml index 5eb4ce5..87f2f46 100644 --- a/astrololo/image-updater.yaml +++ b/astrololo/image-updater.yaml @@ -34,6 +34,10 @@ spec: imageName: gitea.czernobog.pl/gitea/astrololo-presentation - alias: render imageName: gitea.czernobog.pl/gitea/astrololo-render + # Wersja demo (PRE-28). Obraz, którego nie ma na tej liście, NIGDY się nie + # podbije, choćby CI go budowało — tak przez chwilę wisiał render na :latest. + - alias: astroklient-demo + imageName: gitea.czernobog.pl/gitea/astrololo-astroklient-demo commonUpdateSettings: updateStrategy: newest-build writeBackConfig: diff --git a/astrololo/kustomization.yaml b/astrololo/kustomization.yaml index f376d8d..2c27b3a 100644 --- a/astrololo/kustomization.yaml +++ b/astrololo/kustomization.yaml @@ -6,6 +6,8 @@ resources: - data.yaml - logic.yaml - presentation.yaml + - astroklient-demo-stack.yaml # OSOBNA warstwa danych i logiki dla demo (PRE-29) + - astroklient-demo.yaml # wersja demo (PRE-28) — sekret astrololo-demo POZA repo - render.yaml # składanie raportu PDF (PRE-24), osobny obraz z TeX Live - tls.yaml # certyfikat z własnego CA (wymaga cert-managera) - ingress.yaml # wejście po https + przekierowanie z http @@ -16,5 +18,7 @@ images: newTag: ee3c515d - name: gitea.czernobog.pl/gitea/astrololo-render newTag: latest + - name: gitea.czernobog.pl/gitea/astrololo-astroklient-demo + newTag: latest - name: gitea.czernobog.pl/gitea/astrololo-presentation newTag: ee3c515d