diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..1281d1f3 --- /dev/null +++ b/.env.example @@ -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 diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 00000000..ebc178b8 --- /dev/null +++ b/DEPLOY.md @@ -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` | diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e32a229b --- /dev/null +++ b/Makefile @@ -0,0 +1,109 @@ +# ============================================================ +# Frappe ERP — управление стеком +# Использование: make +# ============================================================ + +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 diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 00000000..88d57d5f --- /dev/null +++ b/scripts/build.sh @@ -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 # просто запустить (если контейнеры не существуют)" diff --git a/scripts/new-site.sh b/scripts/new-site.sh new file mode 100755 index 00000000..c216f11d --- /dev/null +++ b/scripts/new-site.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# ============================================================ +# Создание нового Frappe-сайта +# Использование: ./scripts/new-site.sh +# Пример: ./scripts/new-site.sh mycompany.local Admin123 +# ============================================================ +set -euo pipefail + +SITE="${1:-}" +ADMIN_PASS="${2:-}" + +if [ -z "$SITE" ] || [ -z "$ADMIN_PASS" ]; then + echo "Использование: $0 " + 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" diff --git a/scripts/update-apps.sh b/scripts/update-apps.sh new file mode 100755 index 00000000..cafd3aa4 --- /dev/null +++ b/scripts/update-apps.sh @@ -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)"