Add deployment tooling: Makefile, scripts, docs

- Makefile: единые команды build/up/update/migrate/assets/backup/shell
- scripts/build.sh: сборка образа с apps.json → base64
- scripts/update-apps.sh: проверка последних коммитов приложений через GitHub API
- scripts/new-site.sh: создание нового сайта со всеми приложениями
- .env.example: шаблон конфигурации с комментариями
- DEPLOY.md: документация по развёртыванию и обновлению

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
abounoone 2026-03-18 08:56:08 +00:00
parent 7f5b0a2c47
commit c60ecbbabc
6 changed files with 444 additions and 0 deletions

45
.env.example Normal file
View file

@ -0,0 +1,45 @@
# ============================================================
# Frappe ERP — пример конфигурации (.env.example)
# Скопируй в .env и заполни реальными значениями:
# cp .env.example .env
# ============================================================
# Версия ERPNext (используется только при PULL_POLICY=always)
ERPNEXT_VERSION=v16.9.1
# Кастомный образ, собранный из apps.json
# Собрать: make build
CUSTOM_IMAGE=frappe-custom
CUSTOM_TAG=v16
PULL_POLICY=missing
# Пароль MariaDB (сгенерируй: openssl rand -base64 32)
DB_PASSWORD=CHANGE_ME_use_strong_password
# Внешняя БД (оставь пустым для встроенной MariaDB)
DB_HOST=
DB_PORT=
# Внешний Redis (оставь пустым для встроенного)
REDIS_CACHE=
REDIS_QUEUE=
# Имя сайта (должно совпадать с именем, созданным через bench new-site)
FRAPPE_SITE_NAME_HEADER=erp.local
# HTTP-порт публикации
HTTP_PUBLISH_PORT=8090
# Политика перезапуска контейнеров
RESTART_POLICY=unless-stopped
# Таймаут nginx (секунды)
PROXY_READ_TIMEOUT=300
# Максимальный размер тела запроса
CLIENT_MAX_BODY_SIZE=100m
# Расписание автобэкапа (формат ofelia/cron)
# @every 6h — каждые 6 часов
# 0 2 * * * — каждый день в 02:00
BACKUP_CRONSTRING=@every 6h

148
DEPLOY.md Normal file
View file

@ -0,0 +1,148 @@
# Frappe ERP — развёртывание и обновление
## Структура репозитория
```
frappe_docker/
├── apps.json ← список приложений для образа
├── compose.yaml ← основной docker-compose
├── .env ← конфигурация (не в git, создать из .env.example)
├── .env.example ← шаблон конфигурации
├── Makefile ← все команды управления
├── DEPLOY.md ← эта документация
├── images/
│ ├── layered/Containerfile ← сборка на базе frappe/build (быстро)
│ └── custom/Containerfile ← сборка с нуля (полный контроль)
├── overrides/
│ ├── compose.assets-volume.yaml ← общий том assets (обязателен)
│ ├── compose.mariadb.yaml ← встроенная MariaDB
│ └── ... ← другие оверрайды (proxy, ssl, etc.)
└── scripts/
├── build.sh ← сборка образа
├── update-apps.sh ← проверка обновлений приложений
└── new-site.sh ← создание нового сайта
```
---
## Первый запуск
```bash
# 1. Создать конфигурацию
cp .env.example .env
# Отредактировать .env: задать DB_PASSWORD, FRAPPE_SITE_NAME_HEADER и т.д.
# 2. Собрать образ с приложениями из apps.json
make build
# 3. Запустить стек
make up
# 4. Создать сайт (если ещё не создан)
./scripts/new-site.sh erp.local YourAdminPassword
```
---
## Обновление приложений
### Шаг 1 — проверить доступные обновления
```bash
./scripts/update-apps.sh
```
Скрипт покажет последние коммиты каждого приложения из `apps.json`.
### Шаг 2 — обновить apps.json при необходимости
Если нужна конкретная ветка или тег:
```json
// apps.json
[
{ "url": "https://github.com/frappe/erpnext", "branch": "version-16" },
{ "url": "https://github.com/frappe/crm", "branch": "main" }
]
```
### Шаг 3 — полный цикл обновления
```bash
make update
```
Это выполнит:
1. `make build` — пересборка образа frappe-custom:v16
2. `docker compose up -d --no-deps` — замена контейнеров без даунтайма DB/Redis
3. `bench migrate` — применение миграций БД
4. `bench build` — пересборка JS/CSS assets
5. Перезапуск nginx
---
## Частичные операции
```bash
make migrate # только миграции (после ручного обновления)
make assets # только пересборка JS/CSS
make restart # перезапуск backend/frontend
make backup # резервная копия сайта
make logs # логи backend в реальном времени
make shell # bash в контейнере backend
make ps # статус всех контейнеров
```
---
## Смена версии (например, v16 → v17)
```bash
# 1. Обновить ветки в apps.json
# 2. Пересобрать с новым тегом
make build TAG=v17
# 3. Обновить .env
# CUSTOM_TAG=v17
# 4. Пересоздать стек
make up
make migrate
```
---
## Добавление нового приложения
1. Добавить в `apps.json`:
```json
{ "url": "https://github.com/frappe/hrms", "branch": "version-16" }
```
2. Пересобрать образ и обновить:
```bash
make update
```
3. Установить на сайт:
```bash
make shell
bench --site erp.local install-app hrms
```
---
## Переменные окружения (.env)
| Переменная | Описание | Пример |
|-------------------------|------------------------------------------|---------------------|
| `CUSTOM_IMAGE` | Имя Docker-образа | `frappe-custom` |
| `CUSTOM_TAG` | Тег образа | `v16` |
| `PULL_POLICY` | `missing` — использовать локальный образ | `missing` |
| `DB_PASSWORD` | Пароль MariaDB | (сильный пароль) |
| `FRAPPE_SITE_NAME_HEADER` | Имя сайта | `erp.local` |
| `HTTP_PUBLISH_PORT` | Внешний порт HTTP | `8090` |
| `BACKUP_CRONSTRING` | Расписание бэкапов | `@every 6h` |

109
Makefile Normal file
View file

@ -0,0 +1,109 @@
# ============================================================
# Frappe ERP — управление стеком
# Использование: make <target>
# ============================================================
SHELL := /bin/bash
SITE ?= erp.local
TAG ?= v16
COMPOSE_OVERRIDES := \
-f compose.yaml \
-f overrides/compose.assets-volume.yaml
APPS_JSON_B64 := $(shell base64 -w 0 apps.json)
.PHONY: help build up down restart update migrate assets backup logs ps shell
# ── Справка ─────────────────────────────────────────────────
help:
@echo ""
@echo " Frappe ERP — доступные команды:"
@echo ""
@echo " Образ:"
@echo " make build — собрать frappe-custom:$(TAG) из apps.json"
@echo " make build TAG=v17 — собрать с другим тегом"
@echo ""
@echo " Стек:"
@echo " make up — запустить все контейнеры"
@echo " make down — остановить и удалить контейнеры"
@echo " make restart — перезапустить backend/frontend"
@echo " make ps — статус контейнеров"
@echo ""
@echo " Обновление:"
@echo " make update — rebuild образа + up + migrate + assets"
@echo " make migrate — bench migrate на сайте $(SITE)"
@echo " make assets — пересобрать JS/CSS бандлы"
@echo ""
@echo " Обслуживание:"
@echo " make backup — создать резервную копию сайта"
@echo " make logs — логи backend (live)"
@echo " make shell — bash в backend контейнере"
@echo ""
# ── Сборка образа ────────────────────────────────────────────
build:
@echo "→ Сборка frappe-custom:$(TAG) из apps.json..."
docker build \
--build-arg APPS_JSON_BASE64=$(APPS_JSON_B64) \
--build-arg FRAPPE_BRANCH=version-16 \
-t frappe-custom:$(TAG) \
-f images/layered/Containerfile \
.
@echo "✓ Образ frappe-custom:$(TAG) готов"
# ── Запуск стека ─────────────────────────────────────────────
up:
docker compose $(COMPOSE_OVERRIDES) up -d
@echo "✓ Стек запущен. Сайт: http://localhost:$${HTTP_PUBLISH_PORT:-8090}"
down:
docker compose $(COMPOSE_OVERRIDES) down
restart:
docker compose $(COMPOSE_OVERRIDES) restart backend frontend websocket
ps:
docker compose $(COMPOSE_OVERRIDES) ps
# ── Обновление (полный цикл) ─────────────────────────────────
update: build
@echo "→ Пересоздаём контейнеры с новым образом..."
docker compose $(COMPOSE_OVERRIDES) up -d --no-deps backend websocket queue-short queue-long scheduler
@echo "→ Ждём готовности backend..."
sleep 10
$(MAKE) migrate
$(MAKE) assets
docker compose $(COMPOSE_OVERRIDES) restart frontend
@echo "✓ Обновление завершено"
# ── Миграции БД ──────────────────────────────────────────────
migrate:
@echo "→ bench migrate --site $(SITE)..."
docker compose $(COMPOSE_OVERRIDES) exec backend \
bench --site $(SITE) migrate
@echo "✓ Миграция завершена"
# ── Пересборка JS/CSS ────────────────────────────────────────
assets:
@echo "→ Пересборка assets..."
docker compose $(COMPOSE_OVERRIDES) exec backend \
bash -c "cd apps/frappe && node esbuild --production"
docker compose $(COMPOSE_OVERRIDES) exec backend \
bench build
docker compose $(COMPOSE_OVERRIDES) restart frontend
@echo "✓ Assets пересобраны"
# ── Резервная копия ──────────────────────────────────────────
backup:
@echo "→ Создание резервной копии сайта $(SITE)..."
docker compose $(COMPOSE_OVERRIDES) exec backend \
bench --site $(SITE) backup --with-files
@echo "✓ Бэкап создан (см. sites/$(SITE)/private/backups/)"
# ── Логи и отладка ───────────────────────────────────────────
logs:
docker compose $(COMPOSE_OVERRIDES) logs -f backend
shell:
docker compose $(COMPOSE_OVERRIDES) exec backend bash

48
scripts/build.sh Executable file
View file

@ -0,0 +1,48 @@
#!/usr/bin/env bash
# ============================================================
# Сборка образа frappe-custom
# Использование: ./scripts/build.sh [tag]
# По умолчанию: tag = v16
# ============================================================
set -euo pipefail
TAG="${1:-v16}"
FRAPPE_BRANCH="${FRAPPE_BRANCH:-version-16}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
echo "╔══════════════════════════════════════════════╗"
echo " Сборка frappe-custom:${TAG}"
echo " Ветка Frappe : ${FRAPPE_BRANCH}"
echo " Репозиторий : ${REPO_DIR}"
echo "╚══════════════════════════════════════════════╝"
cd "$REPO_DIR"
# Проверяем apps.json
if [ ! -f apps.json ]; then
echo "✗ Файл apps.json не найден"
exit 1
fi
echo "→ Приложения из apps.json:"
python3 -c "import json; [print(' -', a['url'].split('/')[-1], '@', a['branch']) for a in json.load(open('apps.json'))]"
# Кодируем apps.json в base64
APPS_JSON_B64=$(base64 -w 0 apps.json)
echo ""
echo "→ Запуск docker build..."
docker build \
--build-arg APPS_JSON_BASE64="$APPS_JSON_B64" \
--build-arg FRAPPE_BRANCH="$FRAPPE_BRANCH" \
-t "frappe-custom:${TAG}" \
-f images/layered/Containerfile \
.
echo ""
echo "✓ Образ frappe-custom:${TAG} собран"
echo ""
echo "Следующий шаг:"
echo " make update # пересоздать контейнеры, мигрировать БД, пересобрать assets"
echo " make up # просто запустить (если контейнеры не существуют)"

48
scripts/new-site.sh Executable file
View file

@ -0,0 +1,48 @@
#!/usr/bin/env bash
# ============================================================
# Создание нового Frappe-сайта
# Использование: ./scripts/new-site.sh <site-name> <admin-password>
# Пример: ./scripts/new-site.sh mycompany.local Admin123
# ============================================================
set -euo pipefail
SITE="${1:-}"
ADMIN_PASS="${2:-}"
if [ -z "$SITE" ] || [ -z "$ADMIN_PASS" ]; then
echo "Использование: $0 <site-name> <admin-password>"
echo "Пример: $0 mycompany.local Admin123"
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
cd "$REPO_DIR"
COMPOSE_OVERRIDES="-f compose.yaml -f overrides/compose.assets-volume.yaml"
echo "→ Создание сайта: $SITE"
docker compose $COMPOSE_OVERRIDES exec backend \
bench new-site "$SITE" \
--mariadb-root-password "$(grep DB_PASSWORD .env | cut -d= -f2)" \
--admin-password "$ADMIN_PASS" \
--install-app erpnext
echo "→ Устанавливаем дополнительные приложения..."
for APP in crm helpdesk payments insights lms; do
echo " + $APP"
docker compose $COMPOSE_OVERRIDES exec backend \
bench --site "$SITE" install-app "$APP" || echo " ! $APP пропущен (возможно не нужен)"
done
echo "→ Запуск миграций..."
docker compose $COMPOSE_OVERRIDES exec backend \
bench --site "$SITE" migrate
echo ""
echo "✓ Сайт $SITE создан"
echo " URL: http://localhost:${HTTP_PUBLISH_PORT:-8090}"
echo " Логин: Administrator"
echo " Пароль: $ADMIN_PASS"

46
scripts/update-apps.sh Executable file
View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# ============================================================
# Обновление версий приложений в apps.json
# Проверяет последние коммиты каждого приложения
# Использование: ./scripts/update-apps.sh
# ============================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
APPS_FILE="$REPO_DIR/apps.json"
echo "╔══════════════════════════════════════════════╗"
echo " Проверка обновлений приложений"
echo "╚══════════════════════════════════════════════╝"
echo ""
python3 -c "
import json, urllib.request, sys
apps = json.load(open('$APPS_FILE'))
for app in apps:
url = app['url']
branch = app['branch']
name = url.rstrip('/').split('/')[-1]
# GitHub API: последний коммит ветки
api_url = url.replace('https://github.com/', 'https://api.github.com/repos/') + '/commits/' + branch
try:
req = urllib.request.Request(api_url, headers={'User-Agent': 'frappe-update-check'})
data = json.loads(urllib.request.urlopen(req, timeout=5).read())
sha = data['sha'][:8]
date = data['commit']['committer']['date'][:10]
msg = data['commit']['message'].splitlines()[0][:60]
print(f' {name:20s} [{branch}] последний коммит: {sha} ({date})')
print(f' {msg}')
except Exception as e:
print(f' {name:20s} [{branch}] ошибка проверки: {e}')
print()
"
echo ""
echo "Чтобы обновить образ после изменения apps.json:"
echo " make build — только пересобрать образ"
echo " make update — полный цикл (build + migrate + assets)"