# Развёртывание на виртуальной машине (Flatlogic executor) Так работает прод/превью Flatlogic: без Docker — PM2 + локальный PostgreSQL + Cloudflare tunnel. В конце — справочная **структура файлов VM** (executor, pm2, проект). > Запуск **через Docker на хосте** (compose / single-image / staging) вынесен в > отдельный документ: [`deployment-docker.md`](./deployment-docker.md). Проект состоит из двух приложений: - `frontend/` — Vite + React + TypeScript (SPA). Сборка → `frontend/dist/`. - `backend/` — Express + Sequelize на TypeScript/ESM. Сборка → `backend/dist/`. Проект живёт в `~/executor/workspace`, процессы — под PM2, БД — локальный PostgreSQL, наружу — Cloudflare tunnel (`cloudflared`). Docker здесь не участвует. ## 1.1. Топология ``` Браузер │ https://.dev.flatlogic.app ▼ cloudflared (tunnel) │ ▼ nginx :8080 ├── / → frontend :3001 (Vite) ├── /api → backend :3000 (Express) └── /api-docs → backend :3000 │ ▼ PostgreSQL :5432 (локальный) ``` - В `NODE_ENV=dev_stage` бэкенд слушает **3000** (`config.serverPort`), фронт — **3001**. - Браузер открывает домен туннеля; фронт зовёт API относительным путём `/api` (см. `frontend/src/shared/constants/api.ts`), nginx проксирует его на бэкенд — тот же origin, поэтому CORS/CSRF не мешают. ## 1.2. Окружение (env) **Инжектит платформа** в pm2-окружение процесса бэкенда (значения — секреты, в репозиторий не коммитятся): | Переменная | Назначение | |---|---| | `NODE_ENV=dev_stage` | режим (прод-подобный, но без жёстких проверок прода) | | `SECRET_KEY` | подпись JWT (обязательна — иначе бэкенд не стартует) | | `DB_HOST`, `DB_PORT`, `DB_NAME`, `DB_USER`, `DB_PASS` | подключение к локальному Postgres (`DB_PASS` = UUID проекта) | | `GOOGLE_CLIENT_ID/SECRET`, `MS_CLIENT_ID/SECRET` | OAuth (опционально) | | `SMTP_*`, `EMAIL_*`, `MAIL_*` | почта (опционально) | | `CF_TUNNEL_*` | Cloudflare tunnel (для `cloudflared`, не для приложения) | **Из закоммиченного `backend/.env`** (не секреты уровня прода): - `PORT`, `SEED_ADMIN_EMAIL`, `SEED_ADMIN_PASSWORD`, `SEED_USER_PASSWORD`. - `backend/src/config/load-env.ts` находит `backend/.env` одинаково и для `tsx`, и для компилированного `dist`. > `ALLOWED_ORIGINS` платформа НЕ задаёт. В `dev_stage` это допустимо: бэкенд не > падает и рефлектит origin запроса (`config.auth.allowAllOrigins`). Жёсткая > проверка `ALLOWED_ORIGINS` действует только при строгом `NODE_ENV=production`. ## 1.3. PM2-процессы `pm2 status` на VM показывает (имена закреплены при провижининге): | Процесс | Команда (cwd) | Порт | |---|---|---| | `frontend-dev` | `npm run dev -- --hostname 0.0.0.0 --port 3001` (`workspace/frontend`) | 3001 | | `backend-dev` | `NODE_ENV=dev_stage npm run start` (`workspace/backend`) | 3000 | | `fl-executor` | `executor.js` — агент Flatlogic (cable, git, AI-команды) | — | | `fl-telemetry` | `telemetry-daemon.js` | — | > ⚠️ `--hostname` — это флаг Next.js; Vite-CLI его отвергает (`Unknown option`). > Поэтому `dev`/`start` фронта запускаются через обёртку `frontend/scripts/serve.mjs`, > которая транслирует `--hostname`→`--host` и стартует Vite. Без неё фронт на VM > не поднимется. ## 1.4. Деплой после `git pull` PM2-команды зависимости **не ставят**. После подтягивания нового кода: ```bash cd ~/executor/workspace/backend && npm ci cd ~/executor/workspace/frontend && npm ci ``` Схема БД создаётся инициальной миграцией. На уже существующей БД `npm run start` сам прогонит `db:migrate` (идемпотентно, `CREATE TABLE IF NOT EXISTS`) + `db:seed`. Для гарантированно чистого состояния (рекомендуется после крупной миграции — **сотрёт данные**): ```bash cd ~/executor/workspace/backend && npm run db:reset # drop all tables → migrate → seed ``` Перезапуск процессов (или это делает executor после pull): ```bash pm2 restart backend-dev frontend-dev --update-env ``` ### Что делают команды запуска - `backend-dev` → `npm run start` = `db:migrate` (инициальная миграция → схема) + `db:seed` + `watch` (сервер через `tsx` + nodemon, порт 3000). - `frontend-dev` → `npm run dev` (через `serve.mjs`) — Vite dev-сервер на 3001, `allowedHosts: true` пускает домен туннеля. ## 1.5. nginx Если nginx — системный сервис, его конфиг должен соответствовать `nginx.conf` из репозитория (`/` → 3001, `/api` и `/api-docs` → 3000): ```bash sudo cp ~/executor/workspace/nginx.conf /etc/nginx/nginx.conf sudo nginx -t && sudo nginx -s reload ``` ## 1.6. Проверка ```bash curl -s -o /dev/null -w "front %{http_code}\n" http://127.0.0.1:3001/ curl -s -o /dev/null -w "api %{http_code}\n" http://127.0.0.1:3000/api-docs/ pm2 status pm2 logs backend-dev --lines 50 ``` ## 1.7. Прод-режим на VM (компилированные сборки) «Прод-режим» = **компилированные сборки при сохранённом `NODE_ENV=dev_stage`** (минификация фронта, `node dist` бэка, source maps). Строгий `NODE_ENV=production` использовать нельзя — он требует `ALLOWED_ORIGINS`, которого платформа не задаёт, и бэкенд упадёт на старте. Для перевода в прод нужно сменить pm2-команды (на стороне executor): ```bash # Frontend cd ~/executor/workspace/frontend && npm ci && npm run build pm2 delete frontend-dev 2>/dev/null || true FRONT_PORT=3001 pm2 start npm --name frontend --update-env -- run start # Backend (NODE_ENV=dev_stage сохраняем) cd ~/executor/workspace/backend && npm ci && npm run build pm2 delete backend-dev 2>/dev/null || true NODE_ENV=dev_stage pm2 start npm --name backend --update-env -- run start:production pm2 save ``` - `frontend npm run start` = `vite preview` на `FRONT_PORT` (3001), отдаёт `dist/`. - `backend npm run start:production` = `db:migrate:prod` + `db:seed:prod` + `node --enable-source-maps dist/index.js` (порт 3000). Чтобы executor сам пересобирал прод при каждом обновлении кода — добавить `npm ci && npm run build` в его шаг рестарта сервисов (см. `vcs.js`, ~строка 412, массив `const services = ['backend-dev', 'frontend-dev']`). ## 1.8. Траблшутинг | Симптом | Причина / решение | |---|---| | Фронт не стартует, `Unknown option --hostname` | старый код без `serve.mjs`; обнови workspace (`git pull` + `npm ci`) | | Бэкенд падает: `ALLOWED_ORIGINS must be configured` | запущен с `NODE_ENV=production`; на VM должно быть `dev_stage` | | Бэкенд падает: `SECRET_KEY` required | платформа не прокинула `SECRET_KEY` в pm2-env | | `tsx: not found` / `vite: not found` | не выполнен `npm ci` после pull | | Сид падает: `Seeding requires SEED_*` | нет `backend/.env` (или переменных в нём) | | 502 на домене | бэк/фронт не слушают 3000/3001, либо nginx-роутинг не совпадает | --- # Часть 2. Структура файлов на VM (справочно) Снимок боевой VM (`pool-saas-*`). Полезно для понимания, где что лежит. ## 2.1. Домашняя директория `~` ``` ~/ ├── executor/ # агент Flatlogic + сам проект (см. ниже) ├── .pm2/ # PM2: процессы, логи, pids │ ├── logs/ # *-out.log, *-error.log по процессам │ ├── pids/ │ └── dump.pm2 # сохранённый список процессов (pm2 save) ├── .bun/ .yarn/ .npm/ .cache/ # тулчейны/кэши ├── .codex/ .gemini/ .config/ # конфиги AI-CLI ├── .ssh/ .pki/ ├── recipes.md └── google-gemini-cli-0.17.1.tgz ``` ## 2.2. `~/executor/` — агент Flatlogic ``` ~/executor/ ├── .env # конфиг агента и проекта (PROJECT_UUID, PROJECT_ID, │ # CABLE_URL, DB_NAME=app_, SUBDOMAIN, FRONT_PORT, ...) ├── executor.js # главный процесс агента (pm2: fl-executor): │ # WebSocket-cable к flatlogic.com, приём команд, │ # запуск AI-раннеров (gemini/codex), fs/git-операции ├── gemini.js / gemini-proc.js / opencode.js / opencode-proc.js # AI-раннеры ├── vcs.js и vcs/vcs.js # git: init/pull/push/commit, Gitea-зеркало, │ # рестарт сервисов (массив ['backend-dev','frontend-dev']) ├── vm-tools.js # инструменты VM (команды от платформы) ├── activity-tracker.js # трекинг активности раннера ├── telemetry-daemon.js / telemetry-server.js / telemetry-file-watcher.js # телеметрия (pm2: fl-telemetry) ├── sentry.js # Sentry ├── config.js # WORKSPACE_ROOT и пр. ├── index.php # статичная страница-прелоадер («Analyzing your requirements…») ├── setup_postgres_project.sh # создание роли/БД проекта в локальном Postgres ├── setup_mariadb_project.sh # то же для MariaDB ├── setup_workspace_permissions.sh ├── cleanup_vm.sh ├── AGENTS.md / README.md # инструкции для AI-агента (описывают шаблон проекта) ├── otel-local.yaml / schema.json / proto/ ├── node_modules/ package.json package-lock.json ├── workspace/ # ◀── САМ ПРОЕКТ (git-репозиторий = этот репозиторий) ├── workspace_baseline.tar.gz # базовый снимок workspace ├── workspace_codegen/ # рабочая область кодогенерации └── templates/ # шаблоны для новых проектов ├── app-templates/ └── frontend-tailwind-backend-nodejs/ # Next.js+Node шаблон (НЕ наш стек) ``` > Файлы `executor/` (агент) и `templates/` к запуску **нашего** проекта отношения > не имеют — их менять не нужно. `index.php` — только прелоадер на время генерации. ## 2.3. `~/executor/workspace/` — проект Это и есть данный git-репозиторий (`40227-vm`): ``` workspace/ ├── frontend/ # Vite + React + TS → собирается в frontend/dist, сервится на :3001 ├── backend/ # Express + Sequelize TS/ESM → backend/dist, сервится на :3000 │ ├── .env # PORT, SEED_* (закоммичен) │ └── src/db/migrations/ # инициальная миграция (схема) ├── nginx.conf # роутинг / → 3001, /api → 3000 (для системного nginx) ├── Dockerfile / Dockerfile.dev / docker/ # альтернативный Docker-путь ├── 502.html └── docs/ # в т.ч. этот файл ``` ## 2.4. `~/executor/.env` — ключи (значения секретны/индивидуальны) ``` PROJECT_UUID, PROJECT_ID, CABLE_URL, GEMINI_MODEL, TELEMETRY_*, OTEL_*, MAIL_*, SMTP_*, SUBDOMAIN, BASE_DOMAIN, FULL_DOMAIN, HOST_FQDN, DB_NAME=app_, DB_USER=app_, DB_HOST=127.0.0.1, DB_PORT=5432, FRONT_PORT=3001, SENTRY_DSN ``` Секреты приложения (`SECRET_KEY`, `DB_PASS`, OAuth/SMTP, токены git, `CF_TUNNEL_*`) платформа кладёт прямо в **pm2-окружение** процессов `backend-dev`/`fl-executor`, а не в `backend/.env`.