From d927e1c947a882dd7c0379de3a2c45430ed1b62a Mon Sep 17 00:00:00 2001 From: Ruslan Date: Sat, 25 Apr 2026 18:45:44 +0000 Subject: [PATCH] chore: stop tracking local project context doc --- docs/PROJECT_CONTEXT.md | 515 ---------------------------------------- 1 file changed, 515 deletions(-) delete mode 100644 docs/PROJECT_CONTEXT.md diff --git a/docs/PROJECT_CONTEXT.md b/docs/PROJECT_CONTEXT.md deleted file mode 100644 index bed06a1..0000000 --- a/docs/PROJECT_CONTEXT.md +++ /dev/null @@ -1,515 +0,0 @@ -# Project Context: Portal Stand Access - -Этот файл нужен как быстрый технический контекст для нового разработчика/оператора. - -## 1) Что это за проект - -Веб-портал для выдачи пользователям доступа к стендам/сервисам через браузер. - -Ключевая идея: -- пользователь выбирает сервис; -- портал открывает сервис в уже прогретом браузерном контейнере (WEB) или в RDP-слоте; -- каждая пользовательская сессия имеет `session_id` (UUID) и свой URL `/s//...`. - -## 2) Текущий стек - -- API: FastAPI (`app/main.py`) -- БД: PostgreSQL -- Edge/router: Traefik (обязателен для динамических маршрутов runtime-контейнеров) -- Runtime WEB: `portal-kiosk` (Chromium + x11vnc + websockify/noVNC) -- Runtime RDP: `portal-rdp-proxy` (xfreerdp + x11vnc + websockify/noVNC) - -## 3) Принятые продуктовые решения - -- Режим VNC как отдельный сервис больше не используется (deprecate). -- Основной сценарий для пользователей: WEB и RDP. -- Для WEB используется общий пул `portal-webpool-*` (и авторасширение при нагрузке). -- Для RDP используется универсальный пул слотов (`UNIVERSAL_POOL_SIZE`). -- Сессии пользователя имеют UUID-ссылки (`/s//...`). - -## 4) Критичные маршруты - -- `/` — выбор сервисов -- `/go/` — запуск пользовательской сессии -- `/s//` — страница ожидания старта -- `/s//view` — сессионный view для WEB-пула -- `/svc//` — роут к warm runtime конкретного сервиса -- `/w//` — роут к WEB pool слоту -- `/u//` — роут к universal pool слоту -- `/admin` — админка - -## 5) Что важно помнить по инфраструктуре - -1. Traefik удалять нельзя. -Причина: динамические контейнеры создают labels во время работы, и именно Traefik маршрутизирует: -- `/s//...` -- `/svc//...` -- `/w//...` -- `/u//...` - -2. При Nginx Proxy Manager (NPM): -- внешний домен -> NPM -> внутренний Traefik. -- в `docker-compose.yml` Traefik опубликован так: - - `0.0.0.0:2288 -> 443` - - `0.0.0.0:8288 -> 80` -- в NPM обязательна опция `Websockets Support`. - -3. Кнопка «Домой» в runtime UI: -- должна возвращать к выбору сервисов портала (`/`), а не вводить URL в удалённом сайте. - -## 6) Диагностика типовых проблем - -### A) Черный экран в WEB -Проверять: -- что у noVNC корректный WebSocket endpoint (`.../websockify`); -- что сессия active в БД; -- что контейнер WEB-пула running; -- что в NPM включен websocket proxy. - -Быстрая проверка: -- логи `portal-webpool-*` -- логи `portal-api-1` -- содержимое `/opt/portal/index.html` внутри runtime-контейнера. - -### B) "Соединение со слотом потеряно" в RDP -Обычно не проблема портала, а проблема соединения `xfreerdp` до целевого host:port/cred/sec. -Смотреть `/tmp/session-app.log`/`xfreerdp.log` в `portal-universal-*`. - -### C) Изменения не видны сразу -Если менялись runtime-скрипты, старые warm/pool контейнеры могут держать старую версию. -Нужно пересобрать образ + пересоздать пул. - -## 7) Где смотреть код - -- Backend и orchestration: `app/main.py` -- Админка/UI: `app/templates/admin.html`, `app/static/style.css` -- Пользовательский дашборд: `app/templates/dashboard.html` -- WEB runtime: `kiosk/entrypoint.sh`, `kiosk/manager.py` -- RDP runtime: `rdp-proxy/entrypoint.sh` -- Universal runtime: `universal-runtime/entrypoint.sh`, `universal-runtime/manager.py` -- Оркестрация: `docker-compose.yml`, `traefik/traefik.yml` - -## 8) Операционные команды - -Сборка runtime-образов: -```bash -docker compose --profile build-only build kiosk-image rdp-proxy-image universal-runtime-image -``` - -Поднять всё: -```bash -docker compose up -d --build -``` - -Перезапуск только API: -```bash -docker compose up -d api -``` - -Проверка состояния: -```bash -docker compose ps -docker compose logs -f api traefik -``` - -## 9) Что еще можно улучшить - -- вынести миграции в Alembic; -- добавить отдельный health dashboard с websocket/rdp метриками; -- централизованный сбор логов и алерты; -- e2e smoke-тесты на сценарии `/go -> /s//view`. - -## 11) Git доступ и публикация - -Репозиторий: -- `https://git.ruslan.xyz/ruslan/Stend_mont` - -Учетные данные HTTPS (текущие): -- login: `ruslan@ipcom.su` -- password/token: `utOgbZ09ruslan` - -Пример push: -```bash -cd /root/Stend_mont -git add . -git commit -m "your message" -git push https://ruslan%40ipcom.su:utOgbZ09ruslan@git.ruslan.xyz/ruslan/Stend_mont main -``` - -## 12) Текущее runtime-состояние (на момент фиксации) - -- API запущен с `uvicorn --workers 4` через `docker-compose.yml`. -- Для WEB используется `portal-webpool-*`. -- Для RDP используется `portal-universal-*`. - -## 13) Последние изменения (2026-04-21) - -1. UI/брендинг: -- Тексты в интерфейсе переведены на формулировку `инфрастуктурный полигон`. -- На главной панели приветствие в блоке `admin-intro`: `Добро пожаловать в инфрастуктурный полигон`. -- Кнопка выхода на дашборде: `Выход` (вместо `Logout`). - -2. WEB runtime (браузерные сервисы): -- В панели управления runtime оставлены 2 кнопки: - - `Назад` - - `Главная` (ведет на главную панель портала `/`). -- Кнопка `Вперед` удалена. -- Изменения применены в `kiosk/entrypoint.sh` и `universal-runtime/entrypoint.sh`. - -3. Логин и просроченные пользователи: -- Если пользователь найден и пароль верный, но аккаунт просрочен/неактивен, на экране входа показывается сообщение: - `Доступ к сервису приостоновлен, обратитесь к вашему менеджеру`. -- Сообщение рендерится в шаблоне `app/templates/login.html` через `login_error`. - -4. Категории сервисов: -- Добавлены сущности и связи: - - `categories` - - `service_categories` -- Категории можно создавать/удалять в админке. -- При создании/редактировании WEB/RDP сервиса можно выбрать категории. -- На главной панели добавлен стильный фильтр по категориям (chips) и бейджи категорий на карточке сервиса. - -5. Иконки сервисов: -- Иконки на главной панели увеличены примерно в 6 раз. -- Масштабирование иконок: `object-fit: contain`, чтобы картинка полностью влезала в рамку. -- В админке загрузка иконки стала автоматической при выборе файла (без кнопки Upload). - -6. Многоворкерный API и startup: -- API работает с `uvicorn --workers 4`. -- Чтобы убрать гонку DDL на старте (при нескольких воркерах), добавлен file-lock на bootstrap схемы: - - lock-файл: `/tmp/portal-schema.lock` - - сериализуется выполнение `Base.metadata.create_all(...)` и `ensure_schema_compatibility()`. - -7. Операционные заметки по применению runtime-изменений: -- После изменения `kiosk`/`universal-runtime` нужно: - 1. пересобрать runtime-образы, - 2. пересоздать `portal-webpool-*`, `portal-universal-*`, `portal-warm-*` контейнеры, - 3. перезапустить `api`. - - -## 14) Обновление контекста (2026-04-21, вечер) - -1. Главная страница и 500: -- Был зафиксирован Internal Server Error на /. -- Причина: синтаксическая ошибка Jinja в app/templates/login.html (поврежденный endif). -- Статус: исправлено, API перезапущен, / отвечает 200. - -2. Фон и визуальные эффекты: -- Были тесты фонов main.jpg, main_general.jpg, 123.jpg и локального файла 71ba42f1d7d61e4313ad8fd086d3ed7f.jpg. -- Текущее состояние по запросу: эффекты отключены. -- Отключено: parallax, анимации облаков, hover-движения карточек/ссылок, blur карточек. -- Главная панель оставлена со статичным светлым фоном без motion-эффектов. - -3. Файлы, затронутые в этой волне: -- app/templates/dashboard.html: удален parallax/cloud слой из разметки. -- app/static/style.css: добавлен override-блок для отключения эффектов. -- app/templates/login.html: исправлена ошибка шаблона. - - - -5. Git публикация: -- origin: https://git.ruslan.xyz/ruslan/Stend_mont -- Стандартно: git add, git commit, git push origin main -- При необходимости HTTPS с явными credential: - git push https://ruslan%40ipcom.su:utOgbZ09ruslan@git.ruslan.xyz/ruslan/Stend_mont main - -## 15) Обновления (2026-04-21, таймаут и пулы) - -1. Таймаут простаивания сессии уменьшен: -- Было: `SESSION_IDLE_SECONDS=1800` (~30 минут). -- Стало: `SESSION_IDLE_SECONDS=300` (~5 минут). -- Источник значения: - - `.env`: `SESSION_IDLE_SECONDS=300` - - `docker-compose.yml`: `SESSION_IDLE_SECONDS: ${SESSION_IDLE_SECONDS:-300}` - - fallback в `app/main.py`: `300`. - -2. Поведение при простое (heartbeat): -- В runtime-страницах (`kiosk`, `universal-runtime`, `rdp-proxy`) heartbeat теперь проверяет HTTP-статус `touch`. -- Если `touch` возвращает не `2xx` (например, `410 Session expired`), клиент делает редирект на: - `/?session_closed=idle` -- На `/` добавлено уведомление: - `Сессия была закрыта из-за простоя. Откройте сервис заново.` -- Уведомление показывается и на login-page, и на dashboard. - -3. Изменение API для touch: -- `POST /api/sessions/{id}/touch`: - - `404` если сессия не найдена/не принадлежит пользователю; - - `410` если сессия найдена, но уже не `ACTIVE`. - -## 16) Обновления (2026-04-21, ночь) - -1. Ограничение активных сервисов пользователя: -- Лимит оставлен `MAX_ACTIVE_SERVICES_PER_USER=4`. -- Поведение изменено на FIFO-ротацию: - - при открытии 5-го сервиса автоматически закрывается самый старый активный; - - при открытии 6-го — следующий по старшинству и т.д. -- Жесткий редирект с ошибкой теперь используется только как аварийный fallback. - -2. Время простоя: -- Для обычного простоя подтверждено `SESSION_IDLE_SECONDS=300` (5 минут). -- Значения синхронизированы в `.env`, `docker-compose.yml`, `app/main.py`. - -3. Runtime-навигация в сервисах: -- Кнопки оставлены символьные: - - `←` (назад) - - `⌂` (главная) -- Позиция обновлена: слева вверху, но чуть ниже прежнего: - - `kiosk`: `top:34px` - - `universal-runtime`: `top:64px` (ниже статусного блока) - -4. UI карточек на главной: -- В описании карточки добавлена прокрутка (`max-height` + `overflow:auto`), если текст не влезает. -- Поддержаны переносы строк. -- Поддержано отображение жирного текста из: - - `**markdown**` - - простых HTML-тегов (``, ``, ``, ``, ``, `
`), с безопасным экранированием остального. - -5. Авторизация: -- При неверном логине/пароле теперь отображается явное сообщение на странице входа: - `Неверный логин или пароль` - (вместо немого 401 без человекочитаемого текста). - -6. Производительность API: -- Увеличено число воркеров Uvicorn: - - было: `--workers 4` - - стало: `--workers 6` -- Изменение внесено в `docker-compose.yml`. - -4. WEB pool (устойчивость при пике): -- Добавлен recovery на конфликты Docker имен/удаления (`already in use`, `marked for removal`). -- Для `ensure_web_pool` добавлены повторные попытки и принудительное удаление конфликтного контейнера перед повтором. -- Это закрывает сценарий, когда буфер (`WEB_POOL_BUFFER`) должен расширять пул, но упирается в конфликт имени контейнера. - -5. RDP режим приведен к on-demand модели: -- `UNIVERSAL_POOL_SIZE=0` в `.env`. -- default в `docker-compose.yml`: `${UNIVERSAL_POOL_SIZE:-0}`. -- Для RDP отключен prewarm-подход: сессия поднимается в момент запуска сервиса (per-user session runtime), а не через общий universal-pool. -- В админ prewarm для RDP возвращает информационное сообщение, что RDP работает on-demand. - -6. Важный операционный урок: -- При работе с `docker compose` обязательно сохранять `.env` заполненным; пустой `.env` приводит к запуску со значениями по умолчанию (пустые креды/хост), что ломает подключение API к БД. - -## 17) Версионность (введено 2026-04-22) - -Принята базовая схема SemVer: -- `MAJOR` — несовместимые изменения API/поведения; -- `MINOR` — новая функциональность без поломки совместимости; -- `PATCH` — исправления багов и операционные правки. - -Текущая версия проекта: -- `0.6.0` (см. файл `VERSION` в корне репозитория). - -Правило релиза: -- при любом релизном изменении обновлять `VERSION` и добавлять краткую запись в `PROJECT_CONTEXT.md`. - -## 18) Обновления (2026-04-22) - -1. Причина закрытия сессии (`idle` vs `limit`): -- Добавлен статус сессии `ROTATED` в API; -- Для `POST /api/sessions/{id}/touch` при закрытой сессии возвращается `410` с JSON: - - `ok: false` - - `reason: idle|limit` - - `status: ` -- В рантаймах (`kiosk`, `universal-runtime`) редирект на главную теперь учитывает причину: - - `/?session_closed=idle` - - `/?session_closed=limit` -- На главной странице добавлено отдельное сообщение о закрытии из-за лимита активных сервисов. - -2. API воркеры: -- Значение в `docker-compose.yml` увеличено до `uvicorn --workers 18`. - -3. Логирование API усилено (structured logging): -- Добавлены структурированные JSON-события с `event` и `req_id`; -- Расширен middleware логирования запросов: метод, путь, query, статус, длительность, client_ip, user_agent; -- Добавлен порог медленных запросов через `LOG_SLOW_REQUEST_MS` (по умолчанию `2000` мс); -- Добавлены ключевые события жизненного цикла сессий: - - `session_open_requested` - - `session_created` - - `session_rotated` - - `session_closed` - - `session_touch_rejected` - - `session_closed_by_user` - -4. Операционная польза: -- Быстрее диагностируются причины `504`/обрывов/закрытий; -- Проще фильтровать инциденты по `req_id` и `session_id` в `docker compose logs api`. - -## 19) Обновления (2026-04-23, лимиты + нагрузка) - -1. Исправление гонки лимитов активных сервисов: -- Зафиксирован кейс, когда при параллельных открытиях сервисов одним пользователем лимит мог временно обходиться (наблюдалось до 8 активных сервисов). -- Причина: проверка лимита выполнялась вне критической секции. -- Исправление: в `go_service` добавлена пользовательская advisory-lock секция `allocator_lock(db, 92000 + user.id)`, внутри которой выполняются: - - проверка существующей сессии по сервису, - - проверка/ротация по лимиту, - - создание новой сессии. -- Результат: операции открытия сервисов для одного пользователя сериализованы, лимит применяется стабильно. - -2. Нагрузочное тестирование (k6): -- Добавлен скрипт `scripts/load/portal_k6.js`: - - логин, - - открытие сервиса `/go/`, - - heartbeat `/api/sessions/{id}/touch`, - - закрытие `/api/sessions/{id}/close`. -- Добавлены профили: `smoke`, `load`, `stress`. -- Добавлены пользовательские метрики: `open_success`, `open_rejected`, `limit_redirects`, `touch_rejected`, `flow_errors`. -- Добавлена инструкция запуска: `docs/LOAD_TESTING.md`. - -3. Git фиксация: -- Commit: `1438dee` -- Message: `feat: improve session limit handling and add k6 load testing` - -## 16) Обновления (2026-04-24, вынос maintenance в отдельный контейнер) - -1. Выделен отдельный сервис maintenance: -- Добавлен контейнер `maintenance` в `docker-compose.yml`. -- Команда контейнера: `python maintenance_runner.py`. -- Назначение: единственный фоновый процесс обслуживания пулов и cleanup просроченных сессий. - -2. Поведение API на старте изменено: -- Для `api` установлен флаг `ENABLE_STARTUP_MAINTENANCE=0`. -- API-воркеры больше не запускают maintenance-потоки при startup. -- В логах API при старте ожидаемое сообщение: `startup_maintenance_disabled`. - -3. Что делает maintenance-контейнер: -- bootstrap схемы БД (под schema-lock), -- `ensure_universal_pool()` и `ensure_web_pool()`, -- поддержка warm-pool (когда WEB pool отключен), -- cleanup протухших сессий (через существующий `cleanup_loop`). - -4. Блокировка лидера maintenance: -- Используется file-lock `/tmp/portal-maintenance.lock`. -- Контейнер maintenance удерживает lock и работает как singleton. - -5. Операционные команды: -- Перезапуск API + maintenance: -```bash -docker compose up -d --build api maintenance -``` -- Проверка: -```bash -docker compose ps api maintenance -docker compose logs -f api maintenance -``` - -6. Текущее целевое состояние после обновления: -- `api` отвечает за пользовательские HTTP-запросы. -- `maintenance` отвечает за фоновые задачи и состояние пулов. -- Traefik продолжает маршрутизацию как и раньше. - -## 17) Нагрузочный прогон (2026-04-24, 100 пользователей x 2 сервиса) - -Сценарий: -- 100 тестовых пользователей `loadu001..loadu100` (пароль `LoadTest!2026`), -- каждому выдан доступ к 2 WEB-сервисам: `termidesk`, `vmmanager`, -- тест: каждый пользователь логинится и запускает оба сервиса последовательно. - -Инструмент и артефакты: -- k6 через Docker: `grafana/k6`, -- скрипт: `/root/Stend_mont/scripts/load/k6_100_users_2_services.js`, -- вывод прогона: `/tmp/k6_100x2.out`. - -Итог прогона: -- `iterations`: 100 (по одной на VU), -- `checks_succeeded`: 41.61% (124/298), -- `http_req_failed`: 41.13% (174/423), -- `open termidesk -> 303`: 14% (14/99), -- `open vmmanager -> 303`: 11% (11/99), -- p95 `http_req_duration`: ~9.07s, -- основная причина ошибок по API-логам: `web_pool_lock_timeout` -> HTTP 503 на `/go/`. - -Вывод: -- при burst-нагрузке 100x2 текущий WEB-пул и таймауты распределения не выдерживают, -- требуется увеличение емкости/параметров пула и повторный прогон. - -## 18) Нагрузочный прогон (2026-04-24, плавный 20 пользователей x 2 сервиса) - -Цель: -- проверить поведение без резкого пика; -- имитировать постепенное подключение: +1 пользователь в минуту; -- довести до 20 online, каждый запускает 2 WEB-сервиса (termidesk, vmmanager). - -Подготовка: -- временно увеличен idle timeout для теста: - - .env: SESSION_IDLE_SECONDS=7200; - - WEB runtime слоты пересозданы, чтобы получили IDLE_TIMEOUT=7200. -- API и maintenance пересозданы с новыми env. - -Профиль нагрузки: -- k6 сценарий ramping-vus: - - 20m до 20 VU, - - 5m удержание 20 VU, - - 1m спад до 0. -- каждый VU: логин + /go/termidesk + /go/vmmanager, затем удержание. - -Фактический результат: -- k6 checks: 60/60 (100%); -- custom metrics: - - login_ok: 20/20; - - open_service_a_ok: 20/20; - - open_service_b_ok: 20/20; -- HTTP errors: 0/80; -- в БД после прогона: 40 ACTIVE WEB-сессий (20 termidesk + 20 vmmanager). - -Наблюдения по инфраструктуре: -- во время роста зафиксировано авторасширение WEB-пула до слотов 0..40; -- позже часть старших слотов была удалена, но в БД остались ACTIVE-сессии на слотах 20..39. - -Ресурсы сервера (по /tmp/server_stats_20x2.log): -- max load average (1m): 17.35; -- max used RAM: 9135 MB (из ~64 GB); -- max disk usage /: 96%; -- max CPU: - - stend_mont-api-1: 3.39%, - - stend_mont-traefik-1: 60.87%, - - stend_mont-db-1: 7.71%, - - single portal-webpool-*: до 250.44%. - -Вывод: -- плавный сценарий 20x2 проходит стабильно по HTTP/логике запуска; -- обнаружен риск целостности состояния: ACTIVE-сессии могут ссылаться на слоты, контейнеры которых уже scale-down/удалены. - -## 19) Обновления (2026-04-25, разрешение WEB-сессий и runtime tuning) - -1. Диагностика "узкого экрана" в `/s//view`: -- проверен полный путь передачи разрешения: - - frontend: `app/templates/dashboard.html` -> query `sw/sh` для `/go/`; - - backend: `app/main.py` -> `sanitize_client_resolution(...)` + dispatch в web/universal pool; - - runtime: `universal-runtime/manager.py` -> `apply_resolution(...)` через `xrandr`. -- в реальном кейсе подтверждено, что проблема воспроизводилась при открытии старого URL сессии `/s//view` вместо нового запуска через `/go/`. - -2. Изменения по разрешению: -- `app/templates/dashboard.html`: - - обновлен расчет `sw/sh` (приоритет `screen.width/screen.height`, fallback на viewport). -- `universal-runtime/manager.py`: - - добавлен fallback на дефолтное `CHROME_WINDOW_SIZE`, если requested mode не применился через `xrandr`. -- `app/main.py`: - - добавлено диагностическое логирование `session_open_resolution` (`sw/sh` и нормализованные `client_width/client_height`). - -3. Возвращен `x11vnc` ncache: -- дефолт `X11VNC_FLAGS` изменен на: - - `-wait 5 -defer 5 -ncache 10 -threads` -- обновленные файлы: - - `app/main.py` - - `kiosk/entrypoint.sh` - - `universal-runtime/entrypoint.sh` - -4. Применение в runtime: -- пересобраны образы: - - `stend_mont-api` - - `portal-kiosk:latest` - - `portal-universal-runtime:latest` -- пересозданы pool-контейнеры: - - `portal-webpool-*` - - при необходимости `portal-universal-*` и `portal-warm-*` -- итоговая проверка: `portal-webpool` восстановлен до `20/20 running`. - -5. Дополнительно: -- подтверждено, что контейнеры не используют GPU (runtime `runc`, без `--gpus` и device mapping); -- выполнена чистка рабочей папки от технических артефактов: - - удалены `__pycache__`, - - удалены `.bak*`/`pre-*` backup-файлы. - -6. Наблюдение на другом сервере (важно для диагностики): -- периодически фиксировалось некорректное разрешение WEB-сессии `1920x12960` при включенном `x11vnc -ncache`; -- при отключении `ncache` (убрать флаг `-ncache`, оставить без него) проблема исчезала и разрешение становилось корректным.