Initial open-source release: Aura CRM platform (core + production)
This commit is contained in:
Executable
+245
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-command installer for the Aura CRM platform (core + production).
|
||||
#
|
||||
# What it does:
|
||||
# 1. Checks Docker/Docker Compose are installed.
|
||||
# 2. Asks for your admin account and business name (only on first run —
|
||||
# re-running this script after a successful install just leaves your
|
||||
# existing .env files alone and skips straight to `docker compose up`).
|
||||
# 3. Generates every secret (JWT signing key, DB passwords, MinIO keys,
|
||||
# the module token core and production authenticate each other with)
|
||||
# — you never have to invent or copy-paste a secret by hand.
|
||||
# 4. Starts core (auth/staff/roles) first, waits for it to come up, logs
|
||||
# in as the admin account you just described (bootstrapped
|
||||
# automatically by core itself on first boot), and registers
|
||||
# "production" as a module against core's API to get the token
|
||||
# production needs to talk to it.
|
||||
# 5. Starts production (the CRM itself) with that token already wired in.
|
||||
#
|
||||
# Usage:
|
||||
# ./install.sh
|
||||
#
|
||||
# Everything runs locally on http://localhost by default — see the "Going
|
||||
# public" note this script prints at the end for what to change if you want
|
||||
# a real domain in front of it.
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
CORE_DIR="$SCRIPT_DIR/core"
|
||||
PROD_DIR="$SCRIPT_DIR/production"
|
||||
CORE_ENV="$CORE_DIR/.env"
|
||||
PROD_ENV="$PROD_DIR/.env"
|
||||
|
||||
CORE_URL="http://127.0.0.1:18090"
|
||||
PROD_URL="http://127.0.0.1:18091"
|
||||
WEB_URL="http://127.0.0.1:18092"
|
||||
|
||||
bold() { printf '\033[1m%s\033[0m\n' "$*"; }
|
||||
info() { printf ' %s\n' "$*"; }
|
||||
err() { printf '\033[31m✗ %s\033[0m\n' "$*" >&2; }
|
||||
ok() { printf '\033[32m✓ %s\033[0m\n' "$*"; }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. Prerequisite checks
|
||||
# ---------------------------------------------------------------------------
|
||||
bold "Aura CRM — установка"
|
||||
echo
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
err "Docker не найден. Установите Docker: https://docs.docker.com/engine/install/"
|
||||
exit 1
|
||||
fi
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
err "Docker Compose v2 не найден (нужен плагин 'docker compose', не старый docker-compose)."
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v openssl >/dev/null 2>&1; then
|
||||
err "openssl не найден (нужен для генерации секретов)."
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
err "curl не найден (нужен для настройки связи между core и production)."
|
||||
exit 1
|
||||
fi
|
||||
ok "Docker, Docker Compose, openssl, curl — на месте."
|
||||
echo
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 2. Interactive prompts — only if this is a fresh install
|
||||
# ---------------------------------------------------------------------------
|
||||
FRESH_INSTALL=true
|
||||
if [[ -f "$CORE_ENV" && -f "$PROD_ENV" ]]; then
|
||||
FRESH_INSTALL=false
|
||||
bold "Найдены существующие .env — пропускаю вопросы, перехожу сразу к запуску."
|
||||
echo
|
||||
fi
|
||||
|
||||
if $FRESH_INSTALL; then
|
||||
bold "Учётная запись владельца (первый вход в CRM)"
|
||||
read -rp " Имя: " OWNER_NAME
|
||||
while [[ -z "$OWNER_NAME" ]]; do read -rp " Имя (обязательно): " OWNER_NAME; done
|
||||
|
||||
read -rp " Email: " OWNER_EMAIL
|
||||
while [[ -z "$OWNER_EMAIL" ]]; do read -rp " Email (обязательно): " OWNER_EMAIL; done
|
||||
|
||||
while true; do
|
||||
read -rsp " Пароль (минимум 8 символов): " OWNER_PASSWORD; echo
|
||||
if [[ ${#OWNER_PASSWORD} -lt 8 ]]; then
|
||||
err "Слишком короткий пароль, минимум 8 символов."
|
||||
continue
|
||||
fi
|
||||
read -rsp " Повторите пароль: " OWNER_PASSWORD_CONFIRM; echo
|
||||
if [[ "$OWNER_PASSWORD" != "$OWNER_PASSWORD_CONFIRM" ]]; then
|
||||
err "Пароли не совпадают, попробуйте снова."
|
||||
continue
|
||||
fi
|
||||
break
|
||||
done
|
||||
echo
|
||||
|
||||
bold "Реквизиты бизнеса (необязательно — можно заполнить позже в Настройках)"
|
||||
read -rp " Название организации: " BUSINESS_NAME
|
||||
echo
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Generate secrets + write .env files (fresh install only)
|
||||
# ---------------------------------------------------------------------------
|
||||
rand_hex() { openssl rand -hex "$1"; }
|
||||
|
||||
if $FRESH_INSTALL; then
|
||||
bold "Генерирую секреты..."
|
||||
JWT_SECRET="$(rand_hex 32)"
|
||||
CORE_PG_PASSWORD="$(rand_hex 20)"
|
||||
CORE_MINIO_ACCESS_KEY="$(rand_hex 10)"
|
||||
CORE_MINIO_SECRET_KEY="$(rand_hex 20)"
|
||||
PROD_PG_PASSWORD="$(rand_hex 20)"
|
||||
PROD_MINIO_ACCESS_KEY="$(rand_hex 10)"
|
||||
PROD_MINIO_SECRET_KEY="$(rand_hex 20)"
|
||||
ok "Секреты сгенерированы."
|
||||
echo
|
||||
|
||||
cp "$CORE_DIR/.env.example" "$CORE_ENV"
|
||||
sed -i \
|
||||
-e "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=${CORE_PG_PASSWORD}|" \
|
||||
-e "s|^DATABASE_URL=.*|DATABASE_URL=postgres://service_center:${CORE_PG_PASSWORD}@postgres:5432/service_center?sslmode=disable|" \
|
||||
-e "s|^JWT_SECRET=.*|JWT_SECRET=${JWT_SECRET}|" \
|
||||
-e "s|^MINIO_ACCESS_KEY=.*|MINIO_ACCESS_KEY=${CORE_MINIO_ACCESS_KEY}|" \
|
||||
-e "s|^MINIO_SECRET_KEY=.*|MINIO_SECRET_KEY=${CORE_MINIO_SECRET_KEY}|" \
|
||||
-e "s|^CORS_ORIGINS=.*|CORS_ORIGINS=${WEB_URL}|" \
|
||||
-e "s|^OWNER_NAME=.*|OWNER_NAME=${OWNER_NAME}|" \
|
||||
-e "s|^OWNER_EMAIL=.*|OWNER_EMAIL=${OWNER_EMAIL}|" \
|
||||
-e "s|^OWNER_PASSWORD=.*|OWNER_PASSWORD=${OWNER_PASSWORD}|" \
|
||||
"$CORE_ENV"
|
||||
|
||||
cp "$PROD_DIR/.env.example" "$PROD_ENV"
|
||||
sed -i \
|
||||
-e "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=${PROD_PG_PASSWORD}|" \
|
||||
-e "s|^DATABASE_URL=.*|DATABASE_URL=postgres://production:${PROD_PG_PASSWORD}@postgres:5432/production?sslmode=disable|" \
|
||||
-e "s|^JWT_SECRET=.*|JWT_SECRET=${JWT_SECRET}|" \
|
||||
-e "s|^MINIO_ACCESS_KEY=.*|MINIO_ACCESS_KEY=${PROD_MINIO_ACCESS_KEY}|" \
|
||||
-e "s|^MINIO_SECRET_KEY=.*|MINIO_SECRET_KEY=${PROD_MINIO_SECRET_KEY}|" \
|
||||
-e "s|^CORS_ORIGINS=.*|CORS_ORIGINS=${WEB_URL}|" \
|
||||
-e "s|^CORE_URL=.*|CORE_URL=http://core:3000|" \
|
||||
-e "s|^WEB_CORE_URL=.*|WEB_CORE_URL=${CORE_URL}|" \
|
||||
-e "s|^WEB_PRODUCTION_URL=.*|WEB_PRODUCTION_URL=${PROD_URL}|" \
|
||||
-e "s|^BUSINESS_NAME=.*|BUSINESS_NAME=${BUSINESS_NAME}|" \
|
||||
"$PROD_ENV"
|
||||
# MODULE_TOKEN / CORE_CONTROL_TOKEN get filled in below, once core is up
|
||||
# and can issue them — they don't exist yet at this point.
|
||||
ok ".env файлы записаны (core/.env, production/.env)."
|
||||
echo
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Shared network + core
|
||||
# ---------------------------------------------------------------------------
|
||||
docker network create platform_net >/dev/null 2>&1 || true
|
||||
|
||||
bold "Запускаю core (авторизация/сотрудники/роли)..."
|
||||
(cd "$CORE_DIR" && docker compose up -d --build)
|
||||
|
||||
printf " Жду, пока core поднимется"
|
||||
CORE_UP=false
|
||||
for _ in $(seq 1 60); do
|
||||
if curl -sf "$CORE_URL/api/health" >/dev/null 2>&1; then
|
||||
CORE_UP=true
|
||||
break
|
||||
fi
|
||||
printf '.'
|
||||
sleep 2
|
||||
done
|
||||
echo
|
||||
if ! $CORE_UP; then
|
||||
err "core не отвечает на $CORE_URL/api/health после ожидания. Проверьте: cd core && docker compose logs backend"
|
||||
exit 1
|
||||
fi
|
||||
ok "core запущен."
|
||||
echo
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 5. Register production as a module against core (only on fresh install —
|
||||
# already done and stored in production/.env on a re-run)
|
||||
# ---------------------------------------------------------------------------
|
||||
if $FRESH_INSTALL; then
|
||||
bold "Регистрирую production в core..."
|
||||
|
||||
LOGIN_RESP="$(curl -sf -X POST "$CORE_URL/api/auth/login" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"email\":\"${OWNER_EMAIL}\",\"password\":\"${OWNER_PASSWORD}\"}")" \
|
||||
|| { err "Не удалось войти в core как владелец — проверьте OWNER_EMAIL/OWNER_PASSWORD в core/.env и логи: cd core && docker compose logs backend"; exit 1; }
|
||||
|
||||
OWNER_JWT="$(printf '%s' "$LOGIN_RESP" | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])' 2>/dev/null)"
|
||||
if [[ -z "$OWNER_JWT" ]]; then
|
||||
err "Не удалось извлечь токен из ответа core. Ответ: $LOGIN_RESP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MODULE_RESP="$(curl -sf -X POST "$CORE_URL/api/modules" \
|
||||
-H "Authorization: Bearer ${OWNER_JWT}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name":"production","base_url":"http://production:3000"}')" \
|
||||
|| { err "Не удалось зарегистрировать модуль production в core."; exit 1; }
|
||||
|
||||
MODULE_TOKEN="$(printf '%s' "$MODULE_RESP" | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])' 2>/dev/null)"
|
||||
CONTROL_TOKEN="$(printf '%s' "$MODULE_RESP" | python3 -c 'import json,sys; print(json.load(sys.stdin)["control_token"])' 2>/dev/null)"
|
||||
if [[ -z "$MODULE_TOKEN" || -z "$CONTROL_TOKEN" ]]; then
|
||||
err "core вернул неожиданный ответ при регистрации модуля: $MODULE_RESP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sed -i \
|
||||
-e "s|^MODULE_TOKEN=.*|MODULE_TOKEN=${MODULE_TOKEN}|" \
|
||||
-e "s|^CORE_CONTROL_TOKEN=.*|CORE_CONTROL_TOKEN=${CONTROL_TOKEN}|" \
|
||||
"$PROD_ENV"
|
||||
ok "production зарегистрирован в core."
|
||||
echo
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6. Start production
|
||||
# ---------------------------------------------------------------------------
|
||||
bold "Запускаю production (сама CRM)..."
|
||||
(cd "$PROD_DIR" && docker compose up -d --build)
|
||||
echo
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Done
|
||||
# ---------------------------------------------------------------------------
|
||||
bold "Готово!"
|
||||
echo
|
||||
info "CRM: ${WEB_URL}"
|
||||
info "Вход: ${OWNER_EMAIL:-<email из первого запуска>} / <пароль, который вы задали>"
|
||||
echo
|
||||
info "core API: ${CORE_URL} (журнал: cd core && docker compose logs -f backend)"
|
||||
info "production API: ${PROD_URL} (журнал: cd production && docker compose logs -f backend)"
|
||||
echo
|
||||
bold "Выход в интернет (свой домен)"
|
||||
info "Сейчас всё живёт на localhost. Чтобы выставить наружу под своим доменом:"
|
||||
info " 1. Поставьте обратный прокси (Caddy/nginx) со своим доменом и TLS перед всеми тремя портами:"
|
||||
info " ${WEB_URL} (фронтенд), ${CORE_URL} (core API), ${PROD_URL} (production API)."
|
||||
info " 2. В production/.env поменяйте WEB_CORE_URL и WEB_PRODUCTION_URL на публичные https-адреса."
|
||||
info " 3. В core/.env и production/.env поменяйте CORS_ORIGINS на публичный адрес фронтенда."
|
||||
info " 4. Пересоберите фронтенд: cd production && docker compose up -d --build web"
|
||||
Reference in New Issue
Block a user