Files
trade-doc/steps.md
2026-01-06 16:20:37 +01:00

20 KiB
Raw Blame History

steps.md

Log działań wykonywanych w ramach migracji trade → k3s + Gitea + GitOps (pull).
Uwaga: nie zapisuję sekretów (hasła, tokeny, prywatne klucze) jeśli pojawiają się w outputach narzędzi, są pomijane/redagowane.

2026-01-05

Repo: inwentaryzacja i plan

  • Przejrzano strukturę repo (apps/, devops/, services/, doc/) i istniejące Compose stacki:
    • DB: devops/db/docker-compose.yml
    • App: devops/app/docker-compose.yml
    • Bootstrap (one-shot): devops/tools/bootstrap/docker-compose.yml
  • Sprawdzono Dockerfile:
    • API: devops/api/Dockerfile
    • Frontend: devops/app/frontend/Dockerfile
    • Ingestor: devops/ingestor/Dockerfile
  • Sprawdzono skrypty wersjonowania Compose (v1/v2…): scripts/ops/* + env: devops/versions/v1.env.
  • Dodano dokument planu migracji: doc/migration.md.

Gitea: repo GitOps (MCP Gitea)

  • Utworzono repo trade/trade-deploy (public, main, auto-init).
  • Dodano szkielet Kustomize:
    • kustomize/base/ (placeholder ConfigMap)
    • kustomize/overlays/staging/ (namespace: trade-staging)
    • kustomize/overlays/prod/ (namespace: trade-prod)
  • Dodano bootstrap manifesty Argo CD Application:
    • bootstrap/argocd/application-trade-staging.yaml (auto-sync + CreateNamespace)
    • bootstrap/argocd/application-trade-prod.yaml (CreateNamespace, bez auto-sync)
  • Dodano DB stack do trade-deploy (manifests + bootstrap):
    • Postgres/Timescale: kustomize/base/postgres/* + init SQL: kustomize/base/initdb/001_init.sql
    • Hasura: kustomize/base/hasura/* + job hasura-bootstrap (track tables/functions)
    • Staging: kustomize/overlays/staging/pgadmin.yaml + patch Hasury (console/dev mode)
    • Zaktualizowano README.md w trade-deploy o wymagane sekrety i port-forward.

VPS: audyt stanu (MCP SSH)

  • Host: qstack (Debian), k3s działa; dostęp do klastra uzyskany przez KUBECONFIG=/etc/rancher/k3s/k3s.yaml (bez sudo).
  • Ingress: Traefik działa (LB na 77.90.8.171, porty 80/443).
  • TLS: cert-manager działa; ClusterIssuer/letsencrypt-prod jest Ready=True.
  • Gitea jest zainstalowana w k3s (Helm release gitea w namespace gitea), Ingress na rv32i.pl, cert rv32i-pl-tls jest Ready=True.
  • GitOps controller (Argo CD / Flux): nie znaleziono (brak namespace argocd i flux-system).
  • Wdrożenia aplikacji trade-* w k3s: nie znaleziono (brak deploy/podów z nazwą trade).
  • Registry: endpoint https://rv32i.pl/v2/ odpowiada (401 bez autoryzacji).

Gitea: audyt stanu (MCP Gitea)

  • Zalogowany użytkownik: u1 (admin).
  • Organizacja trade istnieje; są utworzone repozytoria (puste, bez commitów/branchy):
    • trade/trade-api
    • trade/trade-frontend
    • trade/trade-ingestor
    • trade/trade-infra
  • Repo trade-deploy (manifesty GitOps): nie znaleziono.

VPS: Argo CD (MCP SSH)

  • Zainstalowano Argo CD przez Helm: release argocd w namespace argocd (argo/argo-cd).
  • Utworzono Application dla staging z trade-deploy:
    • zastosowano bootstrap/argocd/application-trade-staging.yaml
    • Argo utworzyło namespace trade-staging i zsynchronizowało placeholder ConfigMap trade-deploy-info.
  • Uwaga: jeśli port 8080 jest zajęty lokalnie (np. przez Hasurę), do port-forward użyj innego portu (np. 8090:443).

VPS: Gitea Actions runner (MCP SSH)

  • Wykryto wymaganie: sudo wymaga hasła, więc runner postawiony w k3s (bez instalacji binarki na hoście).
  • Utworzono namespace gitea-actions oraz zasoby dla act_runner (DinD sidecar + runner):
    • manifest w trade-deploy: bootstrap/gitea-actions/act-runner.yaml
    • sekret act-runner-registration-token utworzony w klastrze z tokena rejestracyjnego pobranego z Gitei (API admin; token nie trafia do gita)
  • Naprawiono start Dockera w sidecar:
    • początkowo próba po unix-sockecie powodowała konflikt (docker.sock jako katalog)
    • finalnie ustawiono DOCKER_HOST=tcp://127.0.0.1:2375 (runner ↔ dind po localhost), co uruchomiło runnera poprawnie.

Uwaga: seed sekretów (blokada narzędzia)

  • Przy próbie seedowania sekretów do trade-staging przez MCP SSH pojawił się trwały timeout na każde polecenie (ssh-mcp/exec), więc ten krok został opisany w trade-deploy/README.md do wykonania ręcznie na VPS.

VPS: dostęp SSH kluczem (wymagane hasło)

  • Użytkownik poprosił o używanie naszego klucza prywatnego dla VPS: k/qstack/qstack.
  • Klucz jest zaszyfrowany hasłem (passphrase). Bez podania passphrase nie da się go użyć w trybie nieinteraktywnym (ssh/ssh-keygen zwraca błąd o niepoprawnym passphrase / brak ssh-askpass).
  • Żeby kontynuować automatyzację z tej sesji są 2 opcje:
    1. dostarczyć passphrase poza czatem (np. lokalnie w pliku w tokens/, gitignored) i używać SSH_ASKPASS, albo
    2. wygenerować osobny klucz bez passphrase jako “deploy key” tylko do automatyzacji i dodać jego publiczną część do ~user/.ssh/authorized_keys na VPS.

Następne kroki (do zrobienia)

  • Wybrać GitOps controller (Argo CD vs Flux) i zainstalować w k3s.
  • Utworzyć repo trade-deploy i dodać strukturę Helm/Kustomize (staging/prod).
  • Postawić runner CI (Gitea Actions act_runner lub alternatywa) i dodać pipeline build+push obrazów.
  • Dopiero potem: manifesty K8s dla trade-api/trade-frontend/trade-ingestor + DB/Hasura + joby migracji.

2026-01-06

VPS: SSH kluczem projektu (passphrase)

  • Użyto klucza prywatnego k/qstack/qstack do połączenia z VPS przez ssh (z SSH_ASKPASS czytającym passphrase z tokens/vps-ssh.json, plik gitignored).
  • Skrypt tokens/ssh-askpass.sh był tworzony tymczasowo i został usunięty po użyciu (żeby nie ryzykować przypadkowego commita).

Doprecyzowanie dostępu i bezpieczeństwo repo

  • Dopisano w doc/migration.md bieżący status VPS/k3s oraz komendy do Argo CD (port-forward na 8090) i logów runnera.
  • Zaktualizowano .gitignore, żeby ignorować sekrety/klucze lokalne: tokens/*, k/, argo/pass, gitea/token.

k3s: seed sekretów (staging)

  • Utworzono sekrety w namespace trade-staging (wartości wygenerowane losowo, nie logowane):
    • trade-postgres (POSTGRES_*)
    • trade-hasura (HASURA_GRAPHQL_ADMIN_SECRET, HASURA_JWT_KEY)
    • trade-pgadmin (PGADMIN_DEFAULT_*)

Argo CD: deploy DB stack (staging)

  • Wymuszono refresh aplikacji Argo trade-staging (annotation argocd.argoproj.io/refresh=hard).
  • Status: trade-staging = Synced / Healthy.
  • Workloady w trade-staging działają:
    • StatefulSet/postgres = postgres-0 Running
    • Deployment/hasura = Running
    • Deployment/pgadmin = Running
    • Job/hasura-bootstrap = Completed (log: [hasura-bootstrap] ok)

Portainer: dlaczego nie widać k3s (wyjaśnienie)

  • Portainer uruchomiony jako kontener Docker domyślnie widzi tylko środowisko Docker; k3s używa containerd, więc nie zobaczysz „podów” w docker ps ani w Portainerze jako kontenerów Dockera.
  • Żeby Portainer widział „wnętrze” k3s trzeba dodać środowisko typu Kubernetes w Portainer UI albo postawić Portainera bezpośrednio w klastrze (rekomendowane).

Portainer (opcja A): wdrożenie w k3s + Ingress portainer.rv32i.pl

  • Dodano do GitOps repo trade/trade-deploy paczkę Kustomize: kustomize/infra/portainer (Namespace+RBAC+PVC+Deployment+Service+Ingress).
  • Dodano Argo CD Application dla Portainera: bootstrap/argocd/application-portainer.yaml.
  • Zastosowano Application na VPS i potwierdzono status: portainer = Synced / Healthy.
  • Ingress utworzony na host portainer.rv32i.pl (Traefik).
  • cert-manager utworzył Certificate/portainer-rv32i-pl-tls, ale ACME jest pending dopóki DNS portainer.rv32i.pl nie wskazuje na 77.90.8.171 (brak rekordu A w momencie sprawdzania).

Portainer: DNS + TLS + odblokowanie „New Portainer installation timed out”

  • Potwierdzono, że rekord A portainer.rv32i.pl istnieje na autorytatywnych DNS (dns*.home.pl) i rozwiązuje się na publicznych resolverach.
  • Zrestartowano Deployment/portainer w namespace portainer, żeby odblokować ekran inicjalizacji (komunikat “timed out for security purposes”).
  • cert-manager miał „zawieszone” HTTP-01 self-check przez cache NXDOMAIN w klastrze; zrestartowano Deployment/coredns w kube-system, po czym certyfikat stał się Ready=True (Certificate/portainer-rv32i-pl-tls).

Registry: token do Gitea Packages + docker login

  • Na VPS (z poziomu poda Gitei) wygenerowano token read:package,write:package dla użytkownika u1:
    • polecenie: gitea admin user generate-access-token --username u1 --scopes "read:package,write:package" --raw
    • token zapisany lokalnie w tokens/gitea-registry.token (gitignored; wartość nie jest logowana).
  • Wykonano docker login rv32i.pl (bez logowania tokena).
  • Zaktualizowano .dockerignore, żeby nie wysyłać do build context katalogów z sekretami/kluczami (tokens/*, k/, gitea/, argo/).

Obrazy: build + push (trade-api, trade-ingestor)

  • Zbudowano i wypchnięto obrazy do Gitea registry:
    • rv32i.pl/trade/trade-api:k3s-20260106013603
    • rv32i.pl/trade/trade-ingestor:k3s-20260106013603
  • Tag zapisany lokalnie w tokens/last-image-tag.txt (gitignored).

GitOps: manifesty k3s dla API + ingestor (trade-deploy)

  • Dodano do repo trade/trade-deploy:
    • kustomize/base/api/deployment.yaml + kustomize/base/api/service.yaml
    • kustomize/base/ingestor/deployment.yaml
    • aktualizacja kustomize/base/kustomization.yaml (dopięcie nowych zasobów).
  • Konfiguracja:
    • trade-api używa HASURA_GRAPHQL_URL=http://hasura:8080/v1/graphql i sekretów trade-hasura + trade-api (admin).
    • trade-ingestor startuje w trybie INGEST_VIA=hasura (bez tokenów API) i używa trade-hasura + trade-ingestor-tokens (RPC).

k3s: seedy sekretów + weryfikacja (staging)

  • Utworzono w trade-staging:
    • Secret/gitea-registry (imagePullSecret do rv32i.pl)
    • Secret/trade-api (API_ADMIN_SECRET, wygenerowany losowo; nie logowany)
    • Secret/trade-ingestor-tokens (plik heliusN.json; wartość nie logowana)
  • Argo CD Application/trade-staging po refresh: Synced / Healthy.
  • Pody w trade-staging uruchomione: trade-api i trade-ingestor (Running).
  • Logi:
    • trade-api startuje i maskuje sekrety (***).
    • trade-ingestor pobiera ceny i ingestuje ticki; URL RPC jest redagowany (api-key=***).

Ingest przez API + tokeny read/write

  • Zmieniono trade-ingestor na INGEST_VIA=api (pisze do trade-api zamiast bezpośrednio do Hasury); manifest w trade/trade-deploy: kustomize/base/ingestor/deployment.yaml.
  • Utworzono tokeny w trade-api:
    • write (dla ingestora) oraz read (dla klientów UI/odczytu)
    • tokeny zapisane jako K8s Secrets (wartości nie logowane):
      • trade-staging/Secret/trade-ingestor-tokens: alg.json (write) + heliusN.json (RPC)
      • trade-staging/Secret/trade-read-token: read.json (read)
  • Wymuszono rollout Deployment/trade-ingestor i potwierdzono w logach:
    • ingestVia: "api", writeUrl: "http://trade-api:8787/v1/ingest/tick", auth: "bearer".
  • Potwierdzono, że trade-api /v1/ticks działa z tokenem read (bez ujawniania tokena).

Frontend: deploy na k3s (staging) + Ingress trade.rv32i.pl

  • Zbudowano i wypchnięto obraz do Gitea registry:
    • rv32i.pl/trade/trade-frontend:k3s-20260106013603
  • Dodano manifesty do trade/trade-deploy:
    • kustomize/base/frontend/deployment.yaml + kustomize/base/frontend/service.yaml
    • staging: kustomize/overlays/staging/frontend-ingress.yaml (host trade.rv32i.pl)
    • aktualizacje kustomize/base/kustomization.yaml i kustomize/overlays/staging/kustomization.yaml.
  • Utworzono sekret trade-staging/Secret/trade-frontend-tokens (pliki frontend.json + read.json; wartości nie logowane).
  • Argo CD Application/trade-staging: Synced / Healthy; Deployment/trade-frontend = Running, /healthz działa.
  • TLS dla trade.rv32i.pl jest pending dopóki DNS trade.rv32i.pl nie wskazuje na 77.90.8.171 (w razie cache NXDOMAIN: restart kube-system/coredns jak wcześniej).

DNS: trade.rv32i.pl (weryfikacja)

  • Sprawdzono rekord A trade.rv32i.pl na autorytatywnych DNS (dns*.home.pl) oraz na publicznych resolverach: brak odpowiedzi (rekord nie był jeszcze widoczny na NS), więc cert-manager trzyma trade-rv32i-pl-tls w stanie pending (NXDOMAIN).
  • Uwaga: jeśli w panelu DNS dodasz rekord i nadal masz NXDOMAIN, sprawdź czy host nie ma literówki (np. rade zamiast trade) i czy rekord jest zapisany jako A/CNAME dla właściwej nazwy trade.rv32i.pl.

DNS/TLS: trade.rv32i.pl (finalizacja)

  • Użytkownik potwierdził rekord A trade.rv32i.pl77.90.8.171 na autorytatywnych DNS (dns.home.pl).
  • Na VPS potwierdzono:
    • Ingress/trade-frontend ma host trade.rv32i.pl.
    • Certificate/trade-rv32i-pl-tls = Ready=True.
    • https://trade.rv32i.pl odpowiada HTTP 401 (basic auth) czyli Ingress + TLS działają.

Weryfikacja wdrożenia (MCP SSH)

  • argocd/Application: trade-staging i portainer = Synced / Healthy.
  • trade-staging: wszystkie workloady Running (Postgres/Hasura/pgAdmin/trade-api/trade-ingestor/trade-frontend).
  • trade-ingestor ma INGEST_VIA=api oraz INGEST_API_URL=http://trade-api:8787 (ingest przez API z tokenem write).

Portainer: odblokowanie ekranu timeout.html

  • Zrestartowano Deployment/portainer w namespace portainer, żeby ponownie pojawił się kreator inicjalizacji (admin user/password).

Gitea: lista repo w organizacji trade

  • trade/trade-api
  • trade/trade-deploy
  • trade/trade-doc
  • trade/trade-frontend
  • trade/trade-infra
  • trade/trade-ingestor

2026-01-06

Zmiana: log działań w doc/

  • Przeniesiono log działań z steps.mddoc/steps.md (zgodnie z nową zasadą: wszystko projektowe ląduje w doc/).

Superproject: decyzja + plan (do akceptacji)

  • Zaproponowano trade/trade-infra jako repo główne (superproject) spinające subrepo (API/ingestor/frontend/deploy/doc).
  • Plan refaktoru opisany w doc/migration.md (sekcja „Metoda superproject”).
  • Użytkownik zaakceptował trade/trade-infra jako superproject; do decyzji pozostało: submodules vs subtree (rekomendacja: submodules, jeśli chcemy zachować niezależne repo + pinowanie wersji).

Superproject: implementacja (git submodules)

  • Przygotowano i wypchnięto initial commity do subrepo:
    • trade/trade-api
    • trade/trade-ingestor
    • trade/trade-frontend
    • trade/trade-doc (to repo)
  • Utworzono trade/trade-infra jako superproject i dodano submodules:
    • api/trade/trade-api
    • frontend/trade/trade-frontend
    • ingestor/trade/trade-ingestor
    • deploy/trade/trade-deploy
    • doc/trade/trade-doc
  • Do push użyto tokena repo-scope wygenerowanego w podzie Gitei; token zapisany lokalnie w gitea/token (gitignored), wartość nie jest logowana.

Dostęp: admin vs użytkownicy (plan)

  • Wymaganie: admin ma dostęp do wszystkich serwisów; pozostali użytkownicy logują się tylko do trade.rv32i.pl.
  • Decyzja: logowanie wdrażamy tylko dla trade; pozostałe serwisy zostają z własnymi loginami.
  • Wybrano metodę dla trade: Traefik basicAuth na Ingress (Middleware).
  • Plan wdrożenia opisany w migration.md (sekcja „Dostęp i logowanie”).

Logowanie do trade (Traefik basicAuth) wdrożenie

  • Utworzono Secret/trade-basic-auth w trade-staging (format htpasswd w kluczu users) z userami: admin, mpabi, mkost33 (bez logowania haseł).
  • Dodano Middleware/trade-basic-auth (Traefik CRD) i podpięto do Ingress/trade-frontend adnotacją traefik.ingress.kubernetes.io/router.middlewares.
  • Zaktualizowano trade-frontend:
    • dodano BASIC_AUTH_MODE=off (wyłącza wbudowany basic auth w aplikacji),
    • wdrożono nowy obraz rv32i.pl/trade/trade-frontend:sha-8217bae.
  • Weryfikacja: https://trade.rv32i.pl zwraca 401 z basic auth (Traefik), a pod trade-frontend loguje basicAuthMode: "off".

UI: status użytkownika + wylogowanie

  • Dodano endpoint GET /whoami w serwerze trade-frontend, który zwraca username z nagłówka ustawianego przez Traefik (headerField w middleware).
  • Dodano w UI w prawym górnym rogu status „kto jest zalogowany” oraz przycisk Wyloguj.
  • Wyloguj przekierowuje do GET /logout (best-effort dla Basic Auth; w praktyce wymusza ponowny prompt i pozwala przełączyć użytkownika).
  • Zaktualizowano manifesty:
    • Middleware/trade-basic-auth ma headerField: X-Trade-User,
    • trade-frontend wdrożony jako rv32i.pl/trade/trade-frontend:sha-5f8c2ef.

UI: poprawa Wyloguj + rollout

  • Naprawiono /logout w trade-frontend: po wymuszeniu ponownej autoryzacji następuje redirect do / (żeby nie utknąć na 401/prompt loop).
  • Zbudowano i wypchnięto obraz rv32i.pl/trade/trade-frontend:sha-1b0820f.
  • trade/trade-deploy: bump obrazu frontendu (commit add373d) i push do Gitea.
  • trade/trade-infra: bump submodules deploy + frontend (commit dc03bd1) i push do Gitea.
  • Weryfikacja na VPS (k3s):
    • Deployment/trade-frontend używa rv32i.pl/trade/trade-frontend:sha-1b0820f.
    • Ingress/trade-frontend ma middleware trade-staging-trade-basic-auth@kubernetescrd.
    • Middleware/trade-basic-auth ma headerField: X-Trade-User.
    • https://trade.rv32i.pl/whoami zwraca 401 bez auth; z nagłówkiem X-Trade-User zwraca JSON z userem.

UI: przywrócenie statusu portfela + poprawa układu topbara

  • Przywrócono domyślne elementy TopNav (w tym „Main Account”) jako „status portfela”; Wyloguj jest skrajnie po prawej.
  • Zmieniono implementację TopNav: dodano prop rightEndSlot, żeby dokładać elementy po prawej bez zastępowania domyślnego UI.
  • Zmieniono AuthStatus: kompaktowy „badge” z userem + przycisk Wyloguj.
  • Zbudowano i wypchnięto obraz rv32i.pl/trade/trade-frontend:sha-77122e0.
  • trade/trade-deploy: bump obrazu frontendu (commit 0851e52) i push do Gitea.
  • trade/trade-infra: bump submodules deploy + frontend (commit 2e570b2) i push do Gitea.
  • Weryfikacja na VPS (k3s): Deployment/trade-frontend używa rv32i.pl/trade/trade-frontend:sha-77122e0, pod Running.
  • Uwaga: MCP SSH timeoutował; weryfikację wykonano po zwykłym ssh z naszym kluczem (bez logowania sekretów).
  • Cel: zastąpić przeglądarkowy popup HTTP BasicAuth normalną formatką logowania w aplikacji.
  • trade-frontend (commit e20a1f5, obraz rv32i.pl/trade/trade-frontend:sha-e20a1f5):
    • dodano sesję HttpOnly cookie + endpointy POST /auth/login i POST /auth/logout (GET /logout = redirect),
    • POST /auth/login weryfikuje usera po htpasswd (binarka htpasswd w obrazie; plik z K8s secret),
    • /api/* jest blokowane bez zalogowania (401 JSON, bez WWW-Authenticate, więc bez popupu),
    • UI pokazuje ekran logowania, dopóki GET /whoami nie zwróci usera.
  • GitOps (trade/trade-deploy):
    • bump obrazu do sha-e20a1f5 (commit f949a72),
    • usunięto middleware Traefik basicAuth z Ingress/trade-frontend i wdrożono auth na poziomie aplikacji (commit e7d4d40),
    • Deployment/trade-frontend montuje sekrety:
      • trade-staging/Secret/trade-basic-auth/auth/users (htpasswd),
      • trade-staging/Secret/trade-session-secret/auth/session-secret (HMAC do podpisu sesji; wartość nie logowana).
  • VPS (k3s):
    • utworzono trade-staging/Secret/trade-session-secret bez commitowania do gita (wartość wygenerowana losowo, nie logowana),
    • weryfikacja: https://trade.rv32i.pl/ = 200 bez popupu; https://trade.rv32i.pl/api/... = 401 bez zalogowania; POST /auth/login działa (401 dla złych danych).