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

291 lines
20 KiB
Markdown
Raw Permalink 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.

# 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.pl``77.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.md``doc/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).
### Auth: formularz logowania zamiast popup (session cookie)
- 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).