# Test-контур Sova на k3s: что сделано и как перенести на удалённый сервер

Статья описывает изолированный test-контур в каталоге `k3s-test/`: архитектуру, порядок локального развёртывания и план переноса на реальный сервер.

---

## Зачем это нужно

Монорепозиторий Sova (`apps/backend`, `apps/cabinet`, `local/` и т.д.) не трогали. Вместо этого собран **отдельный контур** — песочница, где можно:

- поднять backend, admin panel, БД и моки в Kubernetes;
- проверить GitOps (ArgoCD), CI (Gitea Actions), мониторинг (Prometheus/Grafana);
- накатывать **изолированные test-БД** (не `*_local`, а `*_test`) из тех же SQL, что и в `local/`;
- отрабатывать сценарии до выкладки на stage/production.

Домены локально: `*.sova.local`. На удалённом сервере их заменят на реальные, например `*.test.sova.dev`.

---

## Архитектура

```mermaid
flowchart TB
  subgraph dev["Mac (разработчик)"]
    docker["Docker build"]
    helm["Helm / kubectl"]
    scripts["scripts/*.sh"]
  end

  subgraph vm["VM sova-test (Multipass) / удалённый сервер"]
    k3s["k3s single-node"]
    ingress["ingress-nginx"]
  end

  subgraph platform["Platform layer"]
    argocd["ArgoCD"]
    gitea["Gitea + Actions"]
    prom["Prometheus"]
    graf["Grafana"]
  end

  subgraph apps["Application layer"]
    be["backend nginx+php-fpm"]
    ap["adminpanel nginx"]
    mocks["WireMock + Mailpit"]
  end

  subgraph data["Data layer (sova-data-test)"]
    pg["PostgreSQL Bitnami"]
    mysql["MySQL 8.0"]
    redis["Redis"]
    dbinit["Job db-init: schema → seed"]
  end

  scripts --> helm
  docker --> k3s
  helm --> k3s
  k3s --> ingress
  ingress --> be & ap & argocd & gitea & graf & prom
  argocd --> apps & data
  gitea --> argocd
  dbinit --> pg & mysql
  be --> pg & redis & mysql
  be --> mocks
```

### Структура репозитория `k3s-test/`

| Каталог | Назначение |
|---------|------------|
| `sova-backend/` | Копия Symfony API + Dockerfile + workflow Gitea |
| `sova-adminpanel/` | Копия React admin + runtime `env.js` |
| `sova-cabinet/` | Копия Symfony ЛК + Dockerfile + workflow Gitea |
| `sova-docs/` | VitePress-документация (из `docs/` монорепо) + Dockerfile + workflow Gitea |
| `sova-deploy/` | Helm charts, ArgoCD manifests, SQL для test-БД |
| `sova-mocks/` | WireMock (MIS, Calltouch, Captcha) + Mailpit |
| `sova-platform/` | Terraform-модуль установки k3s на VM |
| `scripts/` | Bootstrap, сборка образов, деплой, smoke |

---

## Что сделано в коде

### Backend (`sova-backend`)

- Вынесены URL интеграций в env: `WIDGET_API_URL`, `API_PUBLIC_URL`.
- Stub-режим внешних сервисов: SMS, Calltouch, SmartCaptcha (`INTEGRATIONS_STUB_MODE=true`).
- Production Dockerfile: PHP 8.4-FPM, multistage.
- JWT-ключи монтируются из Helm Secret (`sova-deploy/apps/backend/jwt/`).

### Admin Panel (`sova-adminpanel`)

- Runtime-конфиг `public/env.js` (API URL без пересборки).
- Dockerfile: Node build → nginx.
- ConfigMap + entrypoint для подстановки URL в k8s.

### Базы данных

SQL из монорепо (`local/postgres/init/`, `local/mysql-bitrix/init/`) **разделён на два этапа**:

1. **Schema** — `sova-deploy/data/test/sql/*/schema/`
2. **Seed** — `sova-deploy/data/test/sql/*/seed/`

Скрипт `scripts/prepare-db-init.py`:

- переименовывает `*_local` → `*_test`;
- добавляет `\connect` в seed-файлы;
- заменяет `md5('...')` на литералы (PostgreSQL 18 не поддерживает `md5()` в INSERT).

Job `db-init` (Helm chart `sova-deploy/data/db-init/`) выполняет: schema PostgreSQL → schema MySQL → seed PostgreSQL → seed MySQL.

| База | Имя | Назначение |
|------|-----|------------|
| PostgreSQL | `sova_backend_test` | Backend API, admin auth |
| PostgreSQL | `sova_cabinet_test` | Личный кабинет (если подключается) |
| MySQL | `sova_bitrix_test` | Views/синк Bitrix |
| Redis | — | Кэш, messenger |

MySQL поднят через простой manifest `mysql:8.0` (Bitnami chart давал ImagePullBackOff). Auth plugin: `mysql_native_password` для совместимости с клиентом в init Job.

### GitOps (`sova-deploy`)

- Helm: `apps/backend`, `apps/adminpanel`, `data/db-init`, `data/test`.
- ArgoCD Applications: backend, adminpanel, mocks, data-test.
- Репозитории в Gitea: `sova/backend`, `sova/adminpanel`, `sova/sova-deploy`, `sova/sova-mocks`.

#### Backend: stateless console в ArgoCD

Помимо Deployment (nginx + php-fpm), chart `apps/backend` создаёт **CronJob-ы с контейнером `console`** — Symfony-команды по расписанию. В ArgoCD они видны в дереве приложения `backend-test` как отдельные ресурсы с label `app.kubernetes.io/component: console`:

| CronJob | Расписание (MSK) | Команда |
|---------|------------------|---------|
| `backend-clear-schedule-cache` | `0 */6 * * *` | `app:schedule:clear-cache` |
| `backend-sync-doctors` | `0 3 * * *` | `upload:doctors` |
| `backend-sync-reviews` | `30 3 * * *` | `bitrix-update-reviews` |

Конфигурация — список `cronjobs` в `values.yaml`. Для test-контура Job миграций (`backend-migrate`) **отключён** (`migrate.enabled: false`), т.к. схема накатывается через `db-init`, а не `doctrine:migrations:migrate`. На stage/prod можно включить PreSync-migrate.

Deployment использует initContainer `warmup-cache` и `emptyDir` для `/app/var/cache` и `/app/var/log` — иначе Symfony не может писать кэш в prod (HTTP 500).

### Platform layer

| Компонент | Namespace | Ingress |
|-----------|-----------|---------|
| ingress-nginx | `ingress` | LoadBalancer на IP VM |
| cert-manager | `cert-manager` | — |
| sealed-secrets | `kube-system` | — |
| ArgoCD | `argocd` | `argocd.sova.local` |
| Gitea | `gitea` | `git.sova.local` |
| kube-prometheus-stack | `monitoring` | `grafana.sova.local`, `prometheus.sova.local` |
| Loki + Promtail | `monitoring` | только внутри кластера (логи в Grafana) |

**Метрики vs логи:** Prometheus собирает **метрики** (CPU, память, kube-state). **Логи подов** — через **Loki + Promtail** (Explore → Loki в Grafana). Без Loki в Grafana «логов нет» — это ожидаемо до `./scripts/deploy-monitoring-logs.sh`.

```bash
# После deploy-platform или отдельно:
./scripts/deploy-monitoring-logs.sh

# В Grafana Explore:
#   datasource: Loki
#   query: {namespace="sova-test", app="backend"}
```

**Важно для ArgoCD:** UI работает по HTTP только при:

- `server.insecure=true` в `argocd-cmd-params-cm` (ключ `server.insecure`, не вложенный YAML);
- `url: http://argocd.sova.local` в `argocd-cm`;
- ingress без принудительного HTTPS.

**Терминал в pod (exec):** по умолчанию в Argo CD выключен. В test-контуре включён через `sova-deploy/platform/argocd/values-test.yaml` (`exec.enabled: true` + RBAC). В UI: Application → backend-test → Pod → **⋮** → **Terminal** → контейнер **`php-fpm`**.

Пример миграций из терминала ArgoCD или kubectl:

```bash
php bin/console doctrine:migrations:migrate --no-interaction
php bin/console doctrine:migrations:status
```

Через kubectl (если UI недоступен):

```bash
kubectl exec -n sova-test deploy/backend -c php-fpm -it -- sh
# внутри pod:
php bin/console doctrine:migrations:migrate --no-interaction
```

ArgoCD внутри кластера ходит в Gitea по **внутреннему URL**:  
`http://gitea-http.gitea.svc.cluster.local:3000/sova/...`  
(снаружи браузер использует `http://git.sova.local`).

---

## Локальное развёртывание (Multipass)

Требования: Docker, Multipass, Helm ≥ 3.14, kubectl, Terraform (опционально).

### Полный цикл

```bash
cd k3s-test

# 1. Синхронизация кода из монорепо (если нужно обновить)
./scripts/sync-from-monorepo.sh

# 2. VM + k3s (4 CPU, 8 GB RAM, 40 GB disk)
./scripts/bootstrap-multipass.sh local-test

# 3. Platform: ingress, ArgoCD, Gitea, Prometheus/Grafana
./scripts/deploy-platform.sh

# 4. Приложения, БД, моки
USE_MULTIPASS=1 ./scripts/deploy-test-stack.sh local-test

# 5. Репозитории в Gitea + CI user
./scripts/bootstrap-gitea.sh

# 6. Gitea Actions runner
./scripts/bootstrap-gitea-runner.sh

# 7. ArgoCD applications + HTTP UI
./scripts/bootstrap-argocd.sh

# 8. Логины и URL
./scripts/print-test-users.sh
./scripts/print-urls.sh
```

### /etc/hosts на Mac

Подставьте IP из `multipass info sova-test`:

```
192.168.x.x api.test.sova.local admin.test.sova.local
192.168.x.x argocd.sova.local git.sova.local grafana.sova.local prometheus.sova.local
```

Kubeconfig: `~/.kube/sova-test-config`.

### Альтернатива без Multipass (k3d)

Для быстрой проверки на Mac без VM:

```bash
./scripts/k3d-bootstrap.sh local-test
echo "127.0.0.1 api.test.sova.local admin.test.sova.local" | sudo tee -a /etc/hosts
./scripts/smoke-test.sh
```

Platform layer (Gitea, ArgoCD) в k3d-варианте поднимается по необходимости через `deploy-platform.sh`.

---

## URL и тестовые пользователи

| Сервис | URL |
|--------|-----|
| Backend API | http://api.test.sova.local |
| Admin Panel | http://admin.test.sova.local |
| Gitea | http://git.sova.local/sova/ |
| ArgoCD | http://argocd.sova.local |
| Grafana | http://grafana.sova.local |
| Prometheus | http://prometheus.sova.local |

### Пользователи приложения (seed в БД)

| Сценарий | Логин | Пароль |
|----------|-------|--------|
| Admin Panel + Backend JWT | `local.backend@example.test` | `local-password` |
| Cabinet (отдельная БД) | `local.cabinet@example.test` | `local-password` |
| Auth по pcode | uid `100001`, birthDate `19900101` | — |

Проверка логина:

```bash
curl -X POST http://api.test.sova.local/user/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"local.backend@example.test","password":"local-password"}'
```

### Platform credentials

После `bootstrap-gitea.sh` — файл `.generated/platform-credentials.env`:

- Gitea admin: `gitea_admin`
- CI user: `sova-ci`
- ArgoCD: `admin` + пароль из `argocd-initial-admin-secret`

Актуальные значения: `./scripts/print-test-users.sh`.

---

## CI/CD (Gitea Actions)

В репозиториях лежат workflow:

- `sova-backend/.gitea/workflows/build.yml`
- `sova-adminpanel/.gitea/workflows/build.yml`

Цепочка по тегу `backend-v1.0.1-test` / `adminpanel-v1.0.1-test`:

1. **test** — composer / npm, lint, build
2. **build-and-push** — Docker-образ в Gitea Container Registry
3. **deploy-gitops** — commit в `sova/sova-deploy` (repository, tag, pullPolicy)

### Registry и секреты CI

| Секрет | Назначение |
|--------|------------|
| `REGISTRY_USER` / `REGISTRY_PASSWORD` | Push образов (`gitea_admin` — у `sova-ci` нет прав на packages) |
| `DEPLOY_TOKEN` | HTTPS push в `sova-deploy` |
| `HOST_IP` | IP VM для `/etc/hosts` (имена с префиксом `GITEA_` Gitea не принимает) |

Push из runner идёт на **внутренний URL** `gitea-http.gitea.svc.cluster.local:3000` (DinD не резолвит `git.sova.local`). В `values-test.yaml` для pull на ноде k3s используется **`git.sova.local/sova/backend`** — после `./scripts/configure-k3s-registry.sh`.

```bash
./scripts/bootstrap-gitea-ci-secrets.sh   # секреты в Gitea Actions
./scripts/configure-k3s-registry.sh       # k3s registries.yaml + pull secret
./scripts/release-test-tag.sh backend backend-v1.0.1-test
```

### Runner (Gitea Actions)

Развёрнут через официальный chart `gitea-charts/actions` + DinD (Docker-in-Docker):

```bash
./scripts/bootstrap-gitea-runner.sh
```

Конфиг: `sova-deploy/platform/gitea-actions/values-test.yaml`

| Параметр | Значение |
|----------|----------|
| Release | `gitea-actions` |
| Namespace | `gitea` |
| Имя runner | `sova-test-k8s` |
| Labels | `ubuntu-latest`, `ubuntu-22.04` |
| Gitea URL (внутри кластера) | `http://gitea-http.gitea.svc.cluster.local:3000` |

Проверка: http://git.sova.local/admin/actions/runners — должен быть **Online**.

Логи: `kubectl logs -n gitea -l app.kubernetes.io/name=actions-runner -c runner -f`

Workflow запускается по push тега. Job `test` не требует registry; `build-and-push` — нужен Container Registry (`packages.ENABLED=true` в Gitea).

---

## Проверка штатного режима (чеклист)

После полного bootstrap или CI-релиза:

```bash
export KUBECONFIG=~/.kube/sova-test-config

# 1. Поды
kubectl get pods -n sova-test
kubectl get cronjobs -n sova-test   # backend-sync-doctors, backend-sync-reviews, ...

# 2. ArgoCD
kubectl get applications -n argocd
# UI: http://argocd.sova.local → backend-test → дерево с CronJob

# 3. API
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: api.test.sova.local" \
  "http://$(multipass info sova-test | awk '/IPv4/{print $2; exit}')/news/list?page=1"
# Ожидается: 200

curl -X POST http://api.test.sova.local/user/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"local.backend@example.test","password":"local-password"}'

# 4. CI (если настроен runner)
# http://git.sova.local/sova/backend/actions — последний run: success

# 5. Логи в Grafana (не Prometheus!)
# http://grafana.sova.local → Explore → Loki → {namespace="sova-test"}

# 6. Admin panel
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: admin.test.sova.local" \
  "http://$(multipass info sova-test | awk '/IPv4/{print $2; exit}')/"
```

---

## Перенос на удалённый сервер

Ниже — пошаговый план миграции с Multipass на bare metal / VPS / облако.

### 1. Требования к серверу

| Ресурс | Минимум (test) | Рекомендуется |
|--------|----------------|---------------|
| CPU | 4 vCPU | 8 vCPU |
| RAM | 8 GB | 16 GB |
| Disk | 40 GB SSD | 80+ GB SSD |
| ОС | Ubuntu 22.04 LTS | Ubuntu 22.04 / 24.04 |
| Сеть | Публичный IP или VPN | + DNS на ваш домен |

Открытые порты: **80**, **443** (ingress), опционально **22** (SSH).

### 2. Подготовка сервера

```bash
# На сервере (Ubuntu)
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git

# Установка k3s (аналог install-k3s-multipass.sh, но на bare metal)
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik" sh -

# Kubeconfig на вашу машину
scp user@SERVER:/etc/rancher/k3s/k3s.yaml ~/.kube/sova-remote-config
# Заменить 127.0.0.1 на IP сервера в server:
export KUBECONFIG=~/.kube/sova-remote-config
```

Terraform-модуль `sova-platform/terraform/modules/k3s-single-node` можно использовать и для удалённого сервера: задать `server_ip`, `ssh_user`, `ssh_private_key_path` в `terraform.tfvars`.

### 3. DNS вместо /etc/hosts

Замените `*.sova.local` на реальный домен, например:

| Было (local) | Станет (remote test) |
|--------------|----------------------|
| `api.test.sova.local` | `api.test.sova.dev` |
| `admin.test.sova.local` | `admin.test.sova.dev` |
| `git.sova.local` | `git.test.sova.dev` |
| `argocd.sova.local` | `argocd.test.sova.dev` |
| `grafana.sova.local` | `grafana.test.sova.dev` |

A-записи всех имён → **публичный IP сервера**.

Файлы для правки:

- `sova-deploy/apps/backend/values-test.yaml` — ingress host, `API_PUBLIC_URL`, CORS
- `sova-deploy/apps/adminpanel/values-test.yaml` — ingress, env.js API URL
- `scripts/deploy-platform.sh` — Gitea `DOMAIN`, `ROOT_URL`
- `scripts/deploy-platform-ingress.sh` — host rules
- `sova-adminpanel/public/env.js` (или ConfigMap) — `API_BASE_URL`

### 4. TLS (рекомендуется на сервере)

1. cert-manager уже ставится через `deploy-platform.sh`.
2. Создайте ClusterIssuer Let's Encrypt (HTTP-01 или DNS-01).
3. В Ingress добавьте `tls:` секции и annotation `cert-manager.io/cluster-issuer`.
4. Для ArgoCD при TLS на ingress: оставьте `server.insecure=true` **или** настройте TLS passthrough — см. [документацию ArgoCD](https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/).

### 5. Образы: с локального import на registry

**Сейчас (Multipass):** образы собираются на Mac, импортируются через `multipass transfer` + `k3s ctr images import`, `image.pullPolicy: Never`.

**На сервере:**

```bash
# Вариант A: registry в Gitea
# После bootstrap-gitea — включить Container Registry в Gitea,
# push: git.test.sova.dev/sova/backend:backend-v1.0.0-test

# Вариант B: внешний registry (GitHub CR, GitLab, Yandex CR)
docker build -t registry.example.com/sova/backend:1.0.0-test ./sova-backend
docker push registry.example.com/sova/backend:1.0.0-test
```

В `values-test.yaml`:

```yaml
image:
  repository: git.test.sova.dev/sova/backend   # или ваш registry
  tag: backend-v1.0.0-test
  pullPolicy: IfNotPresent
```

Добавьте `imagePullSecrets` если registry приватный.

### 6. Секреты

**Сейчас:** plain Secrets в Helm values (`sova-deploy/apps/backend/values.yaml`).

**На сервере:**

1. [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets) — уже ставится в platform layer.
2. Зашифруйте секреты локально, commit sealed-манifests в `sova-deploy`.
3. Уберите пароли из plain `values.yaml`.

Обязательно смените:

- `APP_SECRET`, пароли БД, `JWT` keys (новая пара для test/stage/prod)
- Gitea admin password
- Grafana admin password

### 7. Базы данных

Test-контур использует in-cluster PostgreSQL/Redis/MySQL. Для удалённого test это допустимо; для stage/prod рассмотрите:

- managed PostgreSQL (Yandex Cloud, RDS, etc.);
- отдельный MySQL для Bitrix views;
- backup PVC или external storage class.

Порядок инициализации **тот же**:

```bash
./scripts/prepare-db-init.sh
helm upgrade --install db-init ./sova-deploy/data/db-init -n sova-data-test
```

При смене PostgreSQL major version перегенерируйте SQL (`prepare-db-init.py` уже учитывает отличия PG 18).

### 8. Деплой на сервер — чеклист

```bash
# С вашей машины (KUBECONFIG → удалённый кластер)
export KUBECONFIG=~/.kube/sova-remote-config

# 1. Platform
BOOTSTRAP_PLATFORM=1 ./scripts/deploy-platform.sh

# 2. Обновить values-test.yaml под новый домен (см. п.3)
# 3. Apps + data
./scripts/deploy-test-stack.sh test   # или ваш tag

# 4. Gitea repos (push с CI-машины или с сервера)
GITEA_HOST=git.test.sova.dev ./scripts/bootstrap-gitea.sh

# 5. ArgoCD
GITEA_REPO_URL=http://gitea-http.gitea.svc.cluster.local:3000 \
  ./scripts/bootstrap-argocd.sh

# 6. Gitea Actions runner
./scripts/bootstrap-gitea-runner.sh

# 7. Smoke
curl https://api.test.sova.dev/news/list?page=1
./scripts/print-test-users.sh
```

### 9. GitOps как единственный источник правды

После первичного bootstrap:

1. Все изменения infra/apps — через commit в `sova/sova-deploy`.
2. ArgoCD sync автоматически (у Applications включён `automated.selfHeal`).
3. CI по тегу обновляет `values-test.yaml` → ArgoCD выкатывает новый образ.

### 10. Мониторинг, логи и алерты

- **Prometheus** — метрики k8s и приложений: http://prometheus.sova.local
- **Grafana** — дашборды + **логи через Loki**: http://grafana.sova.local
- Установка логов: `./scripts/deploy-monitoring-logs.sh` (Loki + Promtail + datasource)
- На сервере: смените пароль Grafana, TLS на Ingress

### 11. Registry на сервере

```bash
./scripts/configure-k3s-registry.sh   # HTTP mirror git.sova.local + imagePullSecret
./scripts/bootstrap-gitea-ci-secrets.sh
```

В `values-test.yaml` после CI:

```yaml
image:
  repository: git.test.sova.dev/sova/backend
  tag: backend-v1.0.1-test
  pullPolicy: IfNotPresent
imagePullSecrets:
  - name: gitea-registry
```

### 12. Отличия local vs remote (сводка)

| Аспект | Multipass (сейчас) | Удалённый сервер |
|--------|-------------------|------------------|
| Домен | `*.sova.local` + /etc/hosts | Реальный DNS |
| TLS | HTTP | Let's Encrypt / свой сертификат |
| Образы | local import, `pullPolicy: Never` | Gitea/registry, `IfNotPresent` |
| Секреты | plain values | SealedSecrets / External Secrets |
| k3s | VM Multipass | bare metal / VPS / Terraform |
| Gitea URL для ArgoCD | internal service URL | тот же паттерн (internal) |
| CI runner | `gitea-actions` StatefulSet (DinD) | масштабировать replicas / отдельная VM |
| State Terraform | local | S3 + DynamoDB / GitLab state |

---

## Troubleshooting

| Симптом | Причина | Решение |
|---------|---------|---------|
| ArgoCD редирект на HTTPS | неверный `server.insecure` | `bootstrap-argocd.sh`, patch `argocd-cmd-params-cm` |
| Gitea 503 | неверный service в Ingress | `gitea-http:3000` в `deploy-platform-ingress.sh` |
| `/user/login` 500 | нет JWT keys | Helm chart `backend-jwt` secret, remount |
| `/news/list` 500 | Symfony cache not writable | initContainer `warmup-cache`, emptyDir `/app/var/cache` |
| ImagePullBackOff после CI | k3s не резолвит `.svc.cluster.local` | `repository: git.sova.local/...` + `configure-k3s-registry.sh` |
| Grafana без логов | Loki не установлен | `./scripts/deploy-monitoring-logs.sh`, Explore → Loki |
| ArgoCD нет кнопки Terminal в pod | `exec.enabled: false` | `./scripts/bootstrap-argocd.sh` или values `platform/argocd/values-test.yaml` |
| db-init Failed | MySQL TLS / auth plugin | `--skip-ssl`, `mysql_native_password` |
| db-init Failed на seed | нет `\connect` / md5() | перезапустить `prepare-db-init.sh` |
| ArgoCD Unknown sync | Gitea по внешнему DNS из pod | internal URL `gitea-http.gitea.svc.cluster.local:3000` |
| Bitnami chart 403 | закрытый HTTP repo | OCI: `oci://registry-1.docker.io/bitnamicharts/...` |

---

## Полезные команды

```bash
# Статус
kubectl get pods -A
kubectl get applications -n argocd

# Логи backend
kubectl logs -n sova-test deploy/backend -c php-fpm --tail=50

# Пересоздать БД
kubectl delete job db-init -n sova-data-test
USE_MULTIPASS=1 ./scripts/deploy-test-stack.sh local-test

# Helm dry-run
helm template backend-test ./sova-deploy/apps/backend \
  -f ./sova-deploy/apps/backend/values-test.yaml
```

---

## Итог

В `k3s-test/` собран **полноценный test-контур**: приложения, изолированные БД (schema → seed), моки внешних сервисов, GitOps и заготовка CI. Локально он крутится на Multipass + k3s и имитирует production-подобный стек без изменений монорепо.

Перенос на удалённый сервер — это в основном замена **домена**, **TLS**, **registry образов** и **управления секретами**; скрипты и Helm charts из `k3s-test/` переиспользуются с минимальными правками `values-test.yaml`.

Дальнейшие шаги для production-ready test-сервера:

1. DNS + TLS
2. Gitea Container Registry (для `docker push` в CI)
3. SealedSecrets
4. Backup БД
5. Отдельный stage-контур с `values-stage.yaml`
