# 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).