diff --git a/README.md b/README.md index 83380a0..94de9aa 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,283 @@ -# launch +# Как запустить свой проект на платформе -Как запустить свой проект на платформе — гайд, стартер и платформенный action \ No newline at end of file +Один файл `.env` + один пуш → сайт на твоём домене с автоматическим SSL. +Это единственный репозиторий, который нужно знать: тут и гайд, и файлы стартера, +и сам платформенный action. + +--- + +## TL;DR — 4 шага + +1. Скопируй в **свой** репозиторий 2 файла из папки [`starter/`](starter) этого репо: + - `.forgejo/workflows/deploy.yml` — **не трогать** (тянет платформенный action по ссылке) + - `.env` — из `starter/.env.example` + + Форкать/клонировать этот репо **не надо** — деплой цепляется к твоей репе по + самому факту наличия `.forgejo/workflows/deploy.yml`. +2. Заполни `.env`, минимум: + ```env + DOMAIN=mysite.com + ``` +3. Пропиши **A-запись** домена → IP платформы **`51.161.128.94`**. +4. `git push` в дефолтную ветку. Через ~30–90 сек сайт открыт на `https://mysite.com`. + +Дальше любой push = редеплой. `deploy.yml` трогать не нужно никогда. + +--- + +## Как это работает (флоу) + +``` +git push + │ + ▼ +Forgejo Actions запускает .forgejo/workflows/deploy.yml + │ (uses: https://git.extract.team/platform/launch@main) + ▼ +platform/launch action: + ├─ клонирует твою репу + ├─ читает .env → DOMAIN, тип деплоя (static / docker / docker-db) + ├─ билд: static → bun build | docker → docker build + ├─ публикует статику в /srv/sites │ или поднимает контейнер + └─ пишет фрагмент Caddy на твой домен + ▼ +Caddy автоматически выпускает SSL (Let's Encrypt) → https://mysite.com живой +``` + +Один репозиторий = один сайт (один `.env`, один `DOMAIN`). + +--- + +## Тип проекта: static / docker / docker-db + +Тип определяется полем `DEPLOY` в `.env` или авто-детектом (по наличию `Dockerfile`). + +### 1. `static` — фронт (90% проектов) + +Vite / CRA / Next static export. `bun build` → статика раздаётся напрямую, **без +контейнера**. Деплой за секунды; редеплой = только заливка новых файлов. + +```env +DOMAIN=mysite.com +# DEPLOY=static # дефолт, если в корне НЕТ Dockerfile +# BUILD_DIR=dist # Vite=dist (дефолт), CRA=build, Next export=out +``` + +- **Vite + React** — ничего настраивать, дефолт. +- **CRA** — `BUILD_DIR=build`. +- **Next.js static export** — в `next.config`: `output: 'export'`, + `images: { unoptimized: true }`, `trailingSlash: true`; в `.env` — `BUILD_DIR=out`. + SSR / API routes / Server Actions в static-режиме не работают. + +### 2. `docker` — фронт + бэкенд в контейнере + +Положи `Dockerfile` в корень. Платформа собирает образ, поднимает контейнер и +проксирует домен на него. Контейнер должен слушать порт из `APP_PORT`. + +```env +DOMAIN=mysite.com +DEPLOY=docker # или просто наличие Dockerfile +APP_PORT=3000 # порт контейнера (default 80) +``` + +### 3. `docker-db` — docker + персистентная база (SQLite) + +Как `docker`, плюс платформа монтирует named-volume в `/data` и прокидывает +`DATABASE_URL=file:/data/app.db`. Том переживает редеплои. + +```env +DOMAIN=mysite.com +DEPLOY=docker-db +APP_PORT=3000 +``` + +В коде читаешь `process.env.DATABASE_URL`. Миграции (drizzle/prisma) запускай в +своём entrypoint — платформа их не делает. **Не** указывай `DATABASE_URL` в +`RUNTIME_KEYS` (ставится автоматически). + +--- + +## Домен и SSL + +- **`DOMAIN`** — обязательное поле. Заранее пропиши **A-запись** домена на IP + платформы `51.161.128.94` (у своего регистратора). +- **`www.`** подключается автоматически, если у `www.` тоже есть A-запись на + наш IP. Не нужен — поставь `WWW=false`. +- **SSL выпускается сам** (Let's Encrypt), обычно за 30–60 сек после первого деплоя, + когда DNS уже указывает на наш IP. Отдельный серт на каждый домен. Ничего + настраивать не надо. +- **Не открывай сайт в браузере, пока workflow не зелёный** — пока идёт деплой, + валидного серта ещё может не быть, и браузер закеширует «сломанный SSL». Дождись + зелёного run в **Actions** (или открой в инкогнито). + +### Доп. домены — один сайт на нескольких доменах + +```env +DOMAIN=mysite.com +EXTRA_DOMAINS=mysite.io,app.mysite.com,promo.xyz +``` + +Тот же сайт на всех доменах, на **каждый** — свой авто-SSL. От тебя: A-запись +каждого домена на наш IP. `www.` для доп-доменов сам не добавляется — если нужен, +впиши `www.mysite.io` явным пунктом. + +--- + +## Twitter / X проекта + +Хочешь, чтобы в **админ-панели** рядом с сайтом показывался его твиттер — добавь в +`.env`: + +```env +TWITTER_URL=https://x.com/mysite +``` + +Принимается полный URL, `@handle` или голый `handle` (`TWITTER=` тоже работает). +Платформа нормализует и покажет как `𝕏 @mysite`. Ни на деплой, ни на сайт не влияет — +только метаданные для админки. + +--- + +## Монорепозиторий + +Платформа деплоит **один сайт на репозиторий** (один `.env`, один `DOMAIN`). Отдельного +поля «папка приложения» нет — сборка идёт от **корня репы**. Поэтому для монорепо: + +- `.env` и `package.json` (или `Dockerfile`) — **в корне** репозитория. +- **static:** корневой скрипт `build` должен собирать нужное приложение (через + workspaces / turbo / nx), а `BUILD_DIR` — указывать на его вывод: + ```env + DOMAIN=mysite.com + BUILD_DIR=apps/web/dist # куда собирается целевое приложение + ``` + ```jsonc + // package.json в корне + { "scripts": { "build": "npm run build -w apps/web" } } + ``` +- **docker:** `Dockerfile` в корне, контекст сборки = весь репозиторий, так что он + спокойно `COPY apps/web …` и собирает нужный пакет. Задай `APP_PORT`. +- Нужно задеплоить **несколько** приложений из одного монорепо на **разные** домены — + сделай под каждое отдельный репозиторий (один репо = один сайт). + +--- + +## Установочный сабдомен `curl get. | sh` (self-service) + +Красивый one-liner установки — делается **сам, без админа**, для `static`-сайтов: + +1. Положи скрипт в репу → `public/install.sh` (папка `public/` у Vite попадает в + `dist/` как есть → окажется в корне сайта). +2. **DNS:** A-запись `get` → IP платформы (host = `get`). +3. В `.env`: `INSTALL_SH=install.sh` → `git push`. + +CI сам поднимет `get.`, выпустит SSL и будет отдавать `install.sh` как +`text/plain`. Убери `INSTALL_SH` и запушь — сабдомен снимется. В админке этот +сабдомен виден в строке «сабдомены». + +--- + +## Секреты (DATABASE_URL, API-ключи) + +`.env` в runtime контейнера **не попадает** (парсится только при деплое). Секреты: + +1. **Forgejo → репа → Settings → Actions → Secrets → Add Secret.** Имя секрета = имя + переменной окружения (`DATABASE_URL`, `JWT_SECRET`). +2. В `.env` перечисли, какие прокинуть: + ```env + RUNTIME_KEYS=DATABASE_URL,JWT_SECRET + ``` +3. В коде: `process.env.DATABASE_URL`. + +Платформа берёт **только** ключи из `RUNTIME_KEYS`. (Только для `docker`/`docker-db`.) + +--- + +## Скорость билда + +Фронт собирается через **bun** (install в 10–30× быстрее npm). Закоммить lock: + +```bash +bun install # создаст bun.lock +git add bun.lock && git commit -m "chore: bun lock" && git push +``` + +### docker-сайты — кеш слоёв и лёгкие образы + +Сборка идёт с **BuildKit** и `--cache-from` предыдущего образа. Чтобы это работало: + +- ставь зависимости ДО копирования кода (слой install кешируется): + ```dockerfile + COPY package.json package-lock.json ./ + RUN npm ci + COPY . . + RUN npm run build + ``` +- cache-mount'ы: + ```dockerfile + # syntax=docker/dockerfile:1.7 + RUN --mount=type=cache,target=/root/.npm npm ci + RUN --mount=type=cache,target=/app/.next/cache npm run build + ``` +- **Next.js** → `output: 'standalone'` в `next.config` роняет образ с ~1–3 ГБ до + ~150–250 МБ → деплой заметно быстрее. + +--- + +## Выключить сайт (kill-switch) + +```env +ENABLED=false +``` + +Push → контейнер/файлы удаляются, домен отдаёт 404. Убери строку (или `true`) + +push → поднимается обратно. Том и секреты сохраняются. (Админ может выключить и без +коммита — переменной `ENABLED=false` в Settings → Actions → Variables.) + +--- + +## Все переменные `.env` + +| Key | Default | Зачем | +|----------------|---------|-------| +| `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 проекта для админки (`@handle` / URL) | +| `INSTALL_SH` | — | static: отдать файл-скрипт на `get.` (`curl \| sh`) | +| `RUNTIME_KEYS` | — | docker: список секретов в контейнер | +| `ENABLED` | `true` | `false` — выключить сайт | + +--- + +## Доступ к гиту (один раз) + +- Форж: **https://git.extract.team**, логин — твой username. +- SSH: `git@git.extract.team:2223`. Добавь ключ: **Settings → SSH/GPG Keys**, затем в + `~/.ssh/config`: + ``` + Host git.extract.team + Port 2223 + User git + ``` + Ремоут: `git remote set-url origin git@git.extract.team:<логин>/<репа>.git` + (или HTTPS — логин + пароль). + +--- + +## Частые проблемы + +- **502 / сайт не грузится сразу после пуша** — деплой ещё идёт или серт выпускается. + Дождись зелёного run в Actions. +- **`build output 'dist' not found`** — не та `BUILD_DIR` (CRA → `build`, Next export + → `out`, монорепо → путь до вывода приложения). +- **`no .env` / `no package.json`** — оба файла должны быть в **корне** репозитория. +- **SSL не выпускается** — нет A-записи домена на `51.161.128.94` (в логе деплоя + будет `::warning::`). Пропиши DNS — серт выпустится сам. +- Застрял — пиши админу. + +--- + +_Файлы для копирования — в [`starter/`](starter). Платформенный action — [`action.yml`](action.yml)._