Портал батальона для Arma 3: вход по никнейму из публичной Google-таблицы, просмотр профилей, вкладки с Google Forms и скрытая админка по адресу /#/ghost-admin.
backend/- FastAPI API, кэш состава из Google Sheets, учётки в SQLite, хранение форм в JSON.frontend/- React + Vite SPA с hash-router.
В production Docker собирает frontend в статические файлы и отдаёт их через Nginx. В финальный frontend-образ не попадают исходники, ESLint, Vite-конфигурация и документация. Backend-образ содержит только app/, зависимости и скрипт запуска.
Docker:
cp backend/.env.example backend/.env
docker compose up --buildОткрой http://localhost:5173. Backend также доступен на http://localhost:8000.
Админка: http://localhost:5173/#/ghost-admin.
Backend:
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
uvicorn app.main:app --reload --port 8000Frontend:
cd frontend
npm install
npm run devОткрой http://localhost:5173.
В backend/.env:
GOOGLE_SHEET_ID- ID таблицы.GOOGLE_SHEET_GID- gid листа.GOOGLE_ONLINE_SHEET_GID- gid листа с онлайном и недельной статистикой.GOOGLE_MEDALS_SHEET_GID- gid листа с медалями.GOOGLE_SHEET_CSV_URL- необязательная прямая CSV-ссылка из “Опубликовать в интернете”.ADMIN_NICKNAME- ник админа по умолчанию.ADMIN_PASSWORD- пароль админа по умолчанию.TOKEN_SECRET- секрет подписи токенов.TOKEN_TTL_SECONDS- срок жизни токена входа в секундах.REFRESH_TOKEN_TTL_SECONDS- срок жизни refresh-токена в секундах.FORMS_STORE_PATH- путь к JSON-хранилищу вкладок/форм.DATABASE_URL- база данных. По умолчанию SQLite:sqlite:///data/app.db.CORS_ORIGINS- адреса frontend, которым разрешено обращаться к API.COOKIE_SECURE-trueдля HTTPS-прода,falseтолько для локального HTTP.COOKIE_SAMESITE- обычноlax.TRUSTED_PROXY_IPS- IP reverse proxy, от которого backend принимаетX-Forwarded-For; локально можно оставить пустым.
Таблица читается через Google Sheets API от service account и кэшируется локально. Service account должен быть добавлен в таблицу с правами Viewer.
Пример минимального backend/.env:
GOOGLE_SHEET_ID=1bfBjMkB9p8Wpnfi4NGWG6suege0tbUkylUtt6qKRkRA
GOOGLE_SHEET_GID=1363288683
GOOGLE_SHEET_CSV_URL=
ADMIN_NICKNAME=Fixx
ADMIN_PASSWORD=change-this-password
TOKEN_SECRET=change-this-token-secret
TOKEN_TTL_SECONDS=43200
REFRESH_TOKEN_TTL_SECONDS=2592000
FORMS_STORE_PATH=data/forms.json
DATABASE_URL=sqlite:///data/app.db
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
COOKIE_SECURE=false
COOKIE_SAMESITE=lax
TRUSTED_PROXY_IPS=Самый простой вариант - через Docker Desktop.
-
Установи:
- Docker Desktop: https://www.docker.com/products/docker-desktop/
- Git for Windows: https://git-scm.com/download/win
-
Открой PowerShell в папке проекта.
-
Создай env-файл:
copy backend\.env.example backend\.env
notepad backend\.env-
Заполни в
backend\.env:ADMIN_NICKNAMEADMIN_PASSWORDTOKEN_SECRETGOOGLE_SHEET_IDGOOGLE_SHEET_GID
-
Запусти:
docker compose up -d --build- Открой:
http://localhost:5173
Админка:
http://localhost:5173/#/ghost-admin
Остановка:
docker compose downПросмотр логов:
docker compose logs -fДля production используй отдельный файл docker-compose.prod.yml: он запускает frontend, backend и Caddy. Backend подключается к уже существующему PostgreSQL через DATABASE_URL; снаружи открыты только порты 80 и 443 Caddy.
Нужен Linux-сервер с публичным IPv4, домен и права root/sudo. Создай A-запись домена, например portal.example.com, на IP сервера. До запуска убедись, что порты 80/tcp и 443/tcp открыты в firewall/панели хостинга.
Установи Docker Engine и Docker Compose Plugin по официальной инструкции Docker. Затем скопируй проект на сервер и перейди в его каталог:
git clone <repo-url> site327
cd site327cp deploy/.env.production.example deploy/.env.production
cp backend/.env.example backend/.env
nano deploy/.env.production
nano backend/.envВ deploy/.env.production укажи домен без https://:
DOMAIN=portal.example.comВ backend/.env укажи строку подключения к уже существующему PostgreSQL и свой домен:
DATABASE_URL=postgresql+psycopg://postgres:POSTGRES_PASSWORD@172.30.0.1:5432/site327_db
CORS_ORIGINS=https://portal.example.com
COOKIE_SECURE=true
COOKIE_SAMESITE=lax
TRUSTED_PROXY_IPS=172.30.0.0/24
TOKEN_SECRET=long-random-secretВ примере используется существующий PostgreSQL-пользователь postgres и база site327_db; замени только POSTGRES_PASSWORD на его пароль. 172.30.0.1 — gateway хоста в сети приложения 172.30.0.0/24, а backend получает адрес 172.30.0.2. Чтобы PostgreSQL оставался закрытым от интернета, в postgresql.conf укажи только локальный адрес и этот Docker gateway:
listen_addresses = 'localhost,172.30.0.1'В pg_hba.conf разреши только backend-сеть:
host site327_db postgres 172.30.0.0/24 scram-sha-256Если PostgreSQL находится на другом сервере, используй его DNS-имя или IP вместо 172.30.0.1 и разреши подключение с IP приложения в настройках PostgreSQL/firewall.
Также заполни Google Sheets, администратора, Discord и остальные обязательные настройки из .env.example. Помести service account JSON в backend/.creditials.json — именно этот путь монтируется в production-контейнер.
Пароль в
DATABASE_URLдолжен быть URL-кодирован. Если в нём есть, например,@,:,/или#, замени их соответственно на%40,%3A,%2F,%23. Проще всего использовать пароль из букв, цифр,-и_.
docker compose -f docker-compose.prod.yml up -d --build
docker compose -f docker-compose.prod.yml ps
docker compose -f docker-compose.prod.yml logs -f caddy backendCaddy автоматически запрашивает и продлевает HTTPS-сертификат, когда DNS домена указывает на сервер, а порты 80 и 443 доступны извне. После успешного запуска открой https://portal.example.com. Automatic HTTPS Caddy требует доступного публичного домена и этих портов. Документация Caddy
git pull
docker compose -f docker-compose.prod.yml up -d --buildРезервная копия JSON-данных и загрузок backend:
docker run --rm -v site327_backend-data:/data -v "$PWD":/backup alpine tar czf /backup/site327-backend-data.tar.gz -C /data .PostgreSQL резервируй и восстанавливай средствами уже существующего сервера БД, например через pg_dump и psql; перед восстановлением останови backend командой docker compose -f docker-compose.prod.yml stop backend.
Compose допускает отдельный production-файл с изменениями для production-среды; этот проект использует самостоятельный docker-compose.prod.yml, чтобы локальный запуск на SQLite не менялся. Документация Docker Compose
Ниже оставлен пример локального HTTP-запуска для Ubuntu/Debian. Для реального сервера используй раздел production выше.
- Установи Docker:
sudo apt update
sudo apt install -y ca-certificates curl git
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.asc >/dev/null
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin- Скопируй проект на сервер, например:
git clone <repo-url> site327
cd site327Если проект переносится архивом, просто распакуй его и перейди в папку проекта.
- Создай env-файл:
cp backend/.env.example backend/.env
nano backend/.env- Для сервера поменяй
CORS_ORIGINSна домен или IP, с которого будет открываться сайт:
CORS_ORIGINS=http://your-server-ip:5173Если будет домен и HTTPS:
CORS_ORIGINS=https://example.com- Запусти:
docker compose up -d --build- Проверь:
docker compose ps
curl http://127.0.0.1:8000/api/healthСайт будет доступен на:
http://SERVER_IP:5173
В docker-compose.yml:
- frontend открыт наружу:
5173:8080 - backend доступен только локально на сервере:
127.0.0.1:8000:8000
Это сделано специально: пользователи должны ходить через frontend/Nginx, а не напрямую в API.
Скрипт выполняется непосредственно перед командой запуска FastAPI:
chown -R app:app /app/data
exec su app -s /bin/sh -c "$*"Он решает две production-задачи:
/app/data— Docker volume с SQLite, JSON-файлами и загрузками. Docker может создать или подключить его владельцемroot, поэтому приложение от непривилегированного пользователя не сможет записывать данные.chownисправляет владельца при каждом старте контейнера.- После этого
uvicornзапускается от отдельного непривилегированного пользователяapp, а не отroot. Если в приложении или зависимости окажется уязвимость, у процесса внутри контейнера будут существенно меньшие права.
Удалять этот скрипт не нужно. Его можно упростить только при отказе от named volume или при отдельной настройке прав на volume — оба варианта ухудшат надёжность либо безопасность запуска.
Если код обновился:
git pull
docker compose up -d --buildЕсли менялся только backend/.env, обычно достаточно:
docker compose up -d --force-recreate backendВажные runtime-данные лежат в Docker volume site327_backend-data:
- SQLite база пользователей и кэша состава:
/app/data/app.db - JSON с вкладками и формами:
/app/data/forms.json - JSON с разделами и Markdown-документами:
/app/data/docs.json - JSON главной страницы:
/app/data/home-page.json
Сделать бэкап:
docker run --rm -v site327_backend-data:/data -v "$PWD":/backup alpine tar czf /backup/site327-backup.tar.gz -C /data .Восстановить бэкап:
docker compose down
docker run --rm -v site327_backend-data:/data -v "$PWD":/backup alpine sh -c "rm -rf /data/* && tar xzf /backup/site327-backup.tar.gz -C /data"
docker compose up -dBackend читает лист таблицы через Google Sheets API от service account.
- Создай service account в Google Cloud.
- Скачай JSON-ключ и положи его как
backend/.creditials.json. - Добавь email service account в доступы таблицы с ролью
Viewer. - В
backend/.envукажиGOOGLE_SERVICE_ACCOUNT_FILE=.creditials.json.
После этого таблицу можно закрыть от публичного доступа.
Состав кэшируется локально и обновляется каждый час в :01 и :06.
Ручная синхронизация доступна в API только админу:
POST /api/admin/soldiers-sync
В админке /#/ghost-admin есть отдельные блоки “Доступы форм” и “Доступы документации”. В каждом блоке можно создавать свои группы доступа, например “Инструкторы” или “Офицерский состав”, и указывать списки званий и специализаций через поп-ап настройки.
Вкладки/формы и разделы/документы используют разные наборы доступов. При удалении группы связанные элементы переводятся в доступ “Для всех”.
Раздел “Документация” работает отдельно от форм. Админ создаёт разделы документации, привязывает их к группам доступа документации и добавляет документы в формате Markdown. Каждый документ открывается отдельной страницей /#/docs/{id}. Поддерживаются заголовки, списки, таблицы, ссылки и картинки через обычный Markdown-синтаксис.
- Не коммить
backend/.env. - Меняй
ADMIN_PASSWORDиTOKEN_SECRETперед запуском на сервере. - Смена
TOKEN_SECRETпринудительно инвалидирует все старые access и refresh токены. - Для публичного сервера лучше поставить reverse proxy с HTTPS.
- Backend-порт
8000в compose привязан к127.0.0.1, не открывай его наружу без необходимости. - Пароли пользователей хранятся хешированными.
- Пароль админа по умолчанию хранится только в
.env.
Логи всех сервисов:
docker compose logs -fЛоги backend:
docker compose logs -f backendПерезапуск:
docker compose restartПолная остановка:
docker compose down