# Запуск проекта на платформе Кладёшь в репу `.env` с доменом, пушишь — платформа собирает проект и поднимает сайт с SSL. Форкать этот репо не нужно: он тут как справка и как источник двух файлов, которые копируешь к себе. ## Быстрый старт 1. Скопируй в свой репозиторий два файла из [`starter/`](starter): - `.forgejo/workflows/deploy.yml` — как есть, не редактируй - `.env` — из `starter/.env.example` 2. В `.env` укажи домен: `DOMAIN=mysite.com` 3. Пропиши A-запись домена на `51.161.128.94` 4. Запушь в основную ветку. Через минуту-полторы сайт открыт по HTTPS. Дальше каждый push пересобирает и передеплоивает. `deploy.yml` больше не трогаешь. ## Что происходит при пуше Workflow дёргает платформенный action (`action.yml` из этого репо): он клонирует твою репу, читает `.env`, определяет тип сборки, собирает и публикует, прописывает домен в Caddy. Caddy сам выпускает сертификат Let's Encrypt. ## Типы проектов Тип берётся из `DEPLOY` в `.env`, либо определяется сам: есть `Dockerfile` — docker, нет — static. ### static Фронт на Vite / CRA / Next export. Собирается через bun, раздаётся как статика, без контейнера — самый быстрый вариант. ```env DOMAIN=mysite.com # BUILD_DIR=dist # Vite=dist, CRA=build, Next export=out ``` Для Next.js в static-режиме нужно в `next.config`: `output: 'export'`, `images.unoptimized: true`, `trailingSlash: true`, и `BUILD_DIR=out`. SSR и API routes в этом режиме не работают. ### docker Свой `Dockerfile` в корне. Платформа собирает образ, поднимает контейнер и проксирует на него домен. Контейнер должен слушать `APP_PORT`. ```env DOMAIN=mysite.com DEPLOY=docker APP_PORT=3000 ``` ### docker-db То же, что docker, плюс SQLite: том монтируется в `/data`, в контейнер прокидывается `DATABASE_URL=file:/data/app.db`. Том переживает редеплои. Миграции гоняешь сам в entrypoint. `DATABASE_URL` в `RUNTIME_KEYS` добавлять не надо — она ставится сама. ```env DOMAIN=mysite.com DEPLOY=docker-db APP_PORT=3000 ``` ## Домен и SSL - `DOMAIN` обязателен. Заранее пропиши A-запись домена на `51.161.128.94`. - `www` подключается сам, если у него тоже есть A-запись на наш IP. Не нужен — поставь `WWW=false`. - Сертификат выпускается автоматически, обычно за полминуты после первого деплоя. На каждый домен — отдельный. - Пока workflow не позеленел, не открывай сайт в обычном браузере: сертификата ещё может не быть, и браузер закеширует ошибку. Дождись зелёного run или открой в инкогнито. Один сайт на нескольких доменах: ```env EXTRA_DOMAINS=mysite.io,app.mysite.com ``` На каждый домен — свой сертификат, для каждого пропиши A-запись. `www` для доп-доменов сам не добавляется (нужен — впиши `www.mysite.io` отдельным пунктом). ## Twitter Чтобы в админке рядом с сайтом показывался твиттер проекта: ```env TWITTER_URL=https://x.com/mysite ``` Принимается URL, `@handle` или просто `handle`. На сам сайт не влияет — только метка в панели. ## Монорепозиторий Один репозиторий = один сайт. Отдельного поля под папку приложения нет — сборка идёт от корня репы. Поэтому: - `.env` и `package.json` (или `Dockerfile`) держи в корне. - static: корневой скрипт `build` должен собирать нужное приложение, а `BUILD_DIR` — указывать на его вывод: ```env BUILD_DIR=apps/web/dist ``` ```json { "scripts": { "build": "npm run build -w apps/web" } } ``` - docker: `Dockerfile` в корне, контекст сборки — весь репозиторий, так что `COPY` нужного пакета работает. - Нужно несколько приложений на разные домены — заведи под каждое отдельный репозиторий. ## Установочный сабдомен (`curl get.домен | sh`) Только для static, настраивается без админа: 1. Положи скрипт в `public/install.sh` (папка `public/` у Vite попадает в корень сборки). 2. Пропиши A-запись `get` → наш IP. 3. Добавь `INSTALL_SH=install.sh` в `.env` и запушь. Платформа поднимет `get.домен`, выпустит SSL и будет отдавать этот файл как `text/plain`. Убрал `INSTALL_SH` — сабдомен снимется на следующем деплое. ## Секреты `.env` в контейнер не попадает — он читается только при деплое. Секреты заводишь в Forgejo: репа → Settings → Actions → Secrets, имя секрета = имя переменной. Потом перечисляешь нужные: ```env RUNTIME_KEYS=DATABASE_URL,JWT_SECRET ``` В контейнер попадут только они. В коде читаешь как обычно — `process.env.DATABASE_URL`. ## Скорость сборки Фронт собирается через bun — install в разы быстрее npm, закоммить `bun.lock`. docker-образы собираются с BuildKit и кешем от предыдущего образа. Ставь зависимости до копирования кода, чтобы слой install кешировался: ```dockerfile COPY package.json package-lock.json ./ RUN npm ci COPY . . RUN npm run build ``` Для Next.js `output: 'standalone'` в `next.config` роняет образ с гигабайтов до ~200 МБ — деплой заметно быстрее. ## Выключить сайт ```env ENABLED=false ``` Push — контейнер и файлы удаляются, домен отдаёт 404. Убрал строку (или `true`) — вернулся. Том и секреты остаются. Админ может выключить и без коммита — переменной `ENABLED=false` в Settings → Actions → Variables. ## Переменные `.env` | Ключ | Дефолт | Зачем | |-----------------|---------|-------| | `DOMAIN` | — | Обязателен (кроме `ENABLED=false`) | | `DEPLOY` | auto | `static` / `docker` / `docker-db` | | `BUILD_DIR` | `dist` | static: папка сборки (в монорепо — путь до вывода приложения) | | `APP_PORT` | `80` | docker: порт контейнера | | `WWW` | `auto` | `false` — не подключать www | | `EXTRA_DOMAINS` | — | доп. домены через запятую, свой SSL на каждый | | `TWITTER_URL` | — | твиттер/X для админки | | `INSTALL_SH` | — | static: отдать скрипт на `get.` | | `RUNTIME_KEYS` | — | docker: какие секреты прокинуть в контейнер | | `ENABLED` | `true` | `false` — выключить сайт | ## Доступ к гиту - Форж: https://git.extract.team, логин прежний. - SSH: `git@git.extract.team:2223`, ключ добавляешь в Settings → SSH/GPG Keys. ``` Host git.extract.team Port 2223 User git ``` Ремоут: `git remote set-url origin git@git.extract.team:<логин>/<репа>.git` ## Если не работает - **502 сразу после пуша** — деплой ещё идёт или выпускается сертификат, дождись зелёного run. - **`build output 'dist' not found`** — не та `BUILD_DIR` (CRA → `build`, Next export → `out`, монорепо → путь до вывода приложения). - **`no .env` / `no package.json`** — оба файла должны лежать в корне репы. - **Сертификат не выпускается** — нет A-записи домена на `51.161.128.94`. - Не разобрался — пиши админу.