# Публичная точка SQS на ВМ 213 — как устроено (справочник, 2026-10-10) > Текущее состояние и конфигурация. Хронология и ошибки — в `HISTORY/2026-10-10-session-log.md`. > Проверено снаружи: `https://sqs.sinefo.ru/health` → 200. ## 1. Схема ``` клиент (AWS SDK, SigV4) │ https://sqs.sinefo.ru (443, TLS Let's Encrypt) ▼ [ ВМ 213: главный nginx ] conf.d/sqs-sinefo.conf │ proxy_pass http://127.0.0.1:4100 ; Host $http_host ▼ [ shared-sqs ] ~/sqs-test/shared-sqs (Go, memory-only) ``` ВМ: публичный `5.172.178.213` (NAT), внутренний `10.10.102.21`, hostname `naeel-vm`. ## 2. Файлы | Что | Где | |---|---| | Vhost nginx (80 ACME+редирект, 443 TLS) | `/etc/nginx/conf.d/sqs-sinefo.conf` (ВМ) | | Сертификат | `/etc/letsencrypt/live/sqs.sinefo.ru/{fullchain,privkey}.pem` | | ACME-webroot | `/var/www/acme/.well-known/acme-challenge/` | | Бинарник/лог/креды | `~/sqs-test/shared-sqs`, `~/sqs-test/sqs.log` | | Бэкапы nginx | `/etc/nginx/conf.d.bak-*` | ## 3. Конфиг vhost (эталон) ```nginx server { listen 80; server_name sqs.sinefo.ru; location /.well-known/acme-challenge/ { root /var/www/acme; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name sqs.sinefo.ru; ssl_certificate /etc/letsencrypt/live/sqs.sinefo.ru/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/sqs.sinefo.ru/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; client_max_body_size 10m; proxy_request_buffering off; location / { proxy_pass http://127.0.0.1:4100; proxy_http_version 1.1; proxy_set_header Host $http_host; # ВАЖНО: не $host — иначе SigV4 не сойдётся proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 120s; proxy_send_timeout 120s; } } ``` ## 4. Критичные условия (иначе не работает) 1. **`Host $http_host`** — SigV4 подписывает заголовок `Host` целиком (с портом). `$host` порт отбрасывает → `SignatureDoesNotMatch`. Сервис берёт хост из запроса: `app/auth/auth_middleware.go:315-317`. 2. **DNS:** `sqs.sinefo.ru A 5.172.178.213`; делегирование `sinefo.ru` → `ns3/ns4.ngcloud.ru`. Сертификат выдаётся только когда домен резолвится (HTTP-01 через главный nginx, порт 80). 3. **Порт 8443 на 213 занят** DNAT-ом транзита (`→ 95.179.252.111:443`) — не использовать. 4. Проксирование тел — `proxy_request_buffering off`; лимит SQS 256 КБ. ## 5. Замеры (2026-10-10) — болезнь managed-шлюза НЕ воспроизводится - HTTP 30× (1КБ–256КБ): 30/30 200, max 0.99 с. - HTTPS 10× (26КБ–260КБ): 10/10 200, 0.7–3.2 с. - Long-poll `WaitTimeSeconds=20` → 20.3 с, 200. Гипотеза мануала (Шаг 1) подтверждена: 0 зависаний ~51 с, 0 таймаутов 31–33 с. ## 6. Управление ```bash # запуск SQS на ВМ cd ~/sqs-test && setsid nohup env SHARED_SQS_SEED_DEMO=true \ ./shared-sqs --admin-token=dev-token-123 --port=4100 --loglevel=info > sqs.log 2>&1 /` (плейсхолдеры — на ВМ нет `goaws.yaml`). **Стало:** база URL задаётся через `ExternalURLBase` (config `goaws.yaml` **или** env `SHARED_SQS_EXTERNAL_URL_BASE`). На ВМ запускается с `SHARED_SQS_EXTERNAL_URL_BASE=https://sqs.sinefo.ru` → `QueueUrl` = `https://sqs.sinefo.ru//` (проверено снаружи). Изменения (коммиты `469132b`, `ca0372b`): - `app/models/configuration.go` — поле `Environment.ExternalURLBase`; - `app/gosqs/tenant_helpers.go` — `tenantQueueURL` учитывает `ExternalURLBase`; добавлена экспортированная `TenantQueueURL`; - `app/conf/config.go` — env-override `SHARED_SQS_EXTERNAL_URL_BASE` в `applyEnvironmentDefaults` (срабатывает и при отсутствии `goaws.yaml` — важная тонкость: `LoadYamlConfig` делает ранний `return` без конфига, поэтому override там не срабатывал); - `app/cmd/seed.go` — демо-очереди формируют URL через `gosqs.TenantQueueURL` (убрано дублирование логики — оно и было причиной, что фикс «не применялся»); - тест `app/gosqs/tenant_helpers_test.go`. ## 8. Устойчивость: systemd + локальный Redis (2026-10-10) - **Redis** установлен на 213 (`apt install redis-server` 7.0.15), слушает `127.0.0.1:6379`, включён **AOF** (`/etc/redis/redis.conf`: `appendonly yes`) — состояние переживает рестарт. - **systemd-юнит** `/etc/systemd/system/shared-sqs.service`: `Restart=always`, `RestartSec=2`, `WantedBy=multi-user.target` (автостарт при ребуте), `Requires=redis-server.service`, `User=naeel`, `WorkingDirectory=/home/naeel/sqs-test`, `StandardOutput=append:.../sqs.log`. - **env** `/etc/shared-sqs.env` (600, root): `SHARED_SQS_ADMIN_TOKEN`, `SHARED_SQS_SEED_DEMO`, `SHARED_SQS_EXTERNAL_URL_BASE=https://sqs.sinefo.ru`, `REDIS_ADDR=127.0.0.1:6379`. - Управление: `sudo systemctl {status,restart,stop,start} shared-sqs`. **Закрыто:** крах процесса (авторестарт) и ребут ВМ (автостарт + Redis с диска из AOF; проверено — сообщение пережило рестарт). **НЕ закрыто:** падение самой ВМ — SQS и Redis лежат на одной машине (single point of failure). Настоящая отказоустойчивость — после Шага 3 (Redis на отдельном хосте + несколько инстансов). ### Дефекты seed, обнаруженные при включении Redis (исправлены, коммит `c0aa618`) 1. **Демо-очереди не персистились** — после рестарта у демо-тенанта не было очередей. Фикс: `persistence.SaveQueue` + `SaveMessages` в `app/cmd/seed.go`. 2. **Дубли тенантов** — каждый старт создавал нового демо-тенанта (новые креды). При `Restart=always` это утечка. Фикс: идемпотентность в `seedDemoData` (проверка по имени через `store.List()` → «сид пропущен»). ### Для прода `SHARED_SQS_SEED_DEMO=false` (демо-данные не нужны), тенанты/очереди создаются через API (они персистятся штатно). ## 9. Прод-хардненинг (2026-10-10, выполнено) - `SHARED_SQS_SEED_DEMO=false` — демо-данные отключены (существующий тенант подхватывается из Redis). - **admin-token сменён** на случайный 64-hex (хранится в `/etc/shared-sqs.env`, 600). Прежний `dev-token-123` был слабым, а сервис теперь публичный. Прочитать: `sudo sed -n '1p' /etc/shared-sqs.env`. - **logrotate** `/etc/logrotate.d/shared-sqs`: daily, rotate 7, compress, delaycompress, copytruncate, `su naeel naeel` (copytruncate — процесс держит файл открытым). - Billing (PostgreSQL) и JWT-валидация — **не настроены** (сервис работает без них). ## 10. Разбиение data-plane / control-plane (Шаг 2, 2026-10-10, коммит `c1520c0`) - `router.NewDataPlane(store)` — публичный SQS API: `/health`, `/static`, `/examples`, SQS actions. - `router.NewControlPlane(store, adminToken)` — админка: `/admin` (UI+API), `/metrics`, `/ui`, `/static`. - `main`: два HTTP-сервера — data-plane на `0.0.0.0:<--port>` (4100, публично), control-plane на `127.0.0.1:<--control-port>` (4101, только localhost). - Проверено: публично `/health` → 200; SQS API работает; `/admin` и `/metrics` публично → **403** (падают в SQS-auth — админка наружу **НЕ доступна**). Локально: `127.0.0.1:4101/admin` → 200, `4101/metrics` без токена → 401. **Почему пока два листенера в одном процессе, а не два процесса:** состояние (очереди/тенанты) — in-memory; два процесса разъедутся (админка создаст тенанта в своей памяти — data-plane его не увидит). Полноценный процесс-split — после Шага 3 (Redis как источник истины).