mail/deploy/install.sh
deeily 5024bf9a8d init: full mail stack — phases 0..8 (web client, admin, IMAP/SMTP,
sieve, search, sessions, dramatiq, deploy/install, ELK, monitoring)
2026-04-29 16:30:43 +03:00

154 lines
8.5 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Mail-stack installer.
#
# Idempotent — можно запускать повторно, шаги уже выполненные пропускаются.
# Что делает:
# 1. Проверяет/ставит системные зависимости (docker, python3, build-essential)
# 2. Создаёт venv и ставит requirements.txt
# 3. Готовит .env (копирует из .env.example если нет)
# 4. Поднимает docker-compose стек (postgres, redis, meili, docker-mailserver)
# 5. Дожидается БД и применяет миграции Alembic
# 6. Устанавливает systemd-юниты (mail-flask, mail-dramatiq) и поднимает их
# 7. Прописывает cron на ежедневный backup
# 8. Применяет fail2ban override для DMS (whitelist Docker bridge)
#
# Запуск:
# sudo bash deploy/install.sh
#
# Переменные окружения (опционально):
# PROJECT_DIR=/home/deeily/mail — куда установлено приложение
# APP_USER=deeily — под кем работает Flask
# SKIP_DOCKER=1 — пропустить установку пакетов и compose
# SKIP_SYSTEMD=1 — пропустить установку systemd-юнитов
# SKIP_CRON=1 — пропустить cron-запись для бэкапа
set -euo pipefail
# ── Параметры ────────────────────────────────────────────────────────────
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/.." && pwd)"
APP_USER="${APP_USER:-${SUDO_USER:-$USER}}"
PROJECT_DIR="${PROJECT_DIR:-$ROOT}"
PYTHON_BIN="${PYTHON_BIN:-python3}"
note() { echo -e "\033[1;34m==>\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
err() { echo -e "\033[1;31m[ERR]\033[0m $*" >&2; }
need_root() { [ "$(id -u)" -eq 0 ] || { err "запусти под root: sudo bash $0"; exit 1; }; }
# ── 0. Sanity ───────────────────────────────────────────────────────────
need_root
note "user=$APP_USER dir=$PROJECT_DIR"
[ -d "$PROJECT_DIR" ] || { err "PROJECT_DIR не существует: $PROJECT_DIR"; exit 1; }
# ── 1. Системные пакеты ─────────────────────────────────────────────────
if [ "${SKIP_DOCKER:-0}" != "1" ]; then
if ! command -v docker >/dev/null; then
note "ставлю docker / docker-compose-plugin"
apt-get update -y
apt-get install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update -y
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
usermod -aG docker "$APP_USER" || true
fi
if ! command -v "$PYTHON_BIN" >/dev/null; then
apt-get install -y python3 python3-venv python3-pip build-essential libpq-dev
fi
fi
# ── 2. venv + requirements ──────────────────────────────────────────────
note "venv + pip install"
sudo -u "$APP_USER" -- bash -c "
cd '$PROJECT_DIR'
[ -d .venv ] || $PYTHON_BIN -m venv .venv
./.venv/bin/pip install --quiet --upgrade pip
./.venv/bin/pip install --quiet -r requirements.txt
"
# ── 3. .env ─────────────────────────────────────────────────────────────
if [ ! -f "$PROJECT_DIR/.env" ]; then
note "создаю .env из .env.example"
cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env"
SECRET=$(head -c 32 /dev/urandom | base64 | tr -d '/+=' | head -c 40)
sed -i "s|^FLASK_SECRET_KEY=.*|FLASK_SECRET_KEY=$SECRET|" "$PROJECT_DIR/.env"
chown "$APP_USER:$APP_USER" "$PROJECT_DIR/.env"
chmod 600 "$PROJECT_DIR/.env"
warn ".env создан. Открой и заполни ADMINS, IMAP_PASSWORD, MAIL_SERVER_HOSTNAME."
fi
# ── 4. docker-compose стек ──────────────────────────────────────────────
if [ "${SKIP_DOCKER:-0}" != "1" ]; then
# Копируем безопасные конфиги DMS из репы в docker/dms-config/ (он в .gitignore).
mkdir -p "$PROJECT_DIR/docker/dms-config"
for f in fail2ban-jail.cf postfix-main.cf; do
if [ -f "$PROJECT_DIR/deploy/dms-config/$f" ] && [ ! -f "$PROJECT_DIR/docker/dms-config/$f" ]; then
cp "$PROJECT_DIR/deploy/dms-config/$f" "$PROJECT_DIR/docker/dms-config/$f"
fi
done
chown -R "$APP_USER:$APP_USER" "$PROJECT_DIR/docker/dms-config"
note "поднимаю docker-compose (postgres, redis, meili, mailserver)"
sudo -u "$APP_USER" -- bash -c "cd '$PROJECT_DIR/docker' && docker compose up -d"
# Fail2ban override для DMS — whitelist Docker bridge.
if [ -f "$PROJECT_DIR/docker/dms-config/fail2ban-jail.cf" ]; then
docker exec docker-mailserver-1 cp /tmp/docker-mailserver/fail2ban-jail.cf /etc/fail2ban/jail.d/user-jail.local 2>/dev/null || true
docker exec docker-mailserver-1 supervisorctl restart fail2ban 2>/dev/null || true
fi
fi
# ── 5. Postgres миграции ────────────────────────────────────────────────
note "жду postgres + alembic upgrade head"
for i in {1..30}; do
if docker exec docker-postgres-1 pg_isready -U mail >/dev/null 2>&1; then break; fi
sleep 1
done
sudo -u "$APP_USER" -- bash -c "cd '$PROJECT_DIR' && ./.venv/bin/alembic upgrade head"
# ── 6. systemd ──────────────────────────────────────────────────────────
if [ "${SKIP_SYSTEMD:-0}" != "1" ]; then
note "ставлю systemd-юниты"
bash "$HERE/systemd/install.sh"
fi
# ── 7. cron для бэкапа ──────────────────────────────────────────────────
if [ "${SKIP_CRON:-0}" != "1" ]; then
note "прописываю ежедневный backup в crontab @ 03:30"
touch /var/log/mail-backup.log
chown "$APP_USER:$APP_USER" /var/log/mail-backup.log
CRON_LINE="30 3 * * * $PROJECT_DIR/deploy/backup.sh >>/var/log/mail-backup.log 2>&1"
EXISTING=$(sudo -u "$APP_USER" crontab -l 2>/dev/null | grep -v "$PROJECT_DIR/deploy/backup.sh" || true)
printf '%s\n%s\n' "$EXISTING" "$CRON_LINE" | sudo -u "$APP_USER" crontab -
fi
# ── 8. Sudoers для ELK-apply через UI ────────────────────────────────────
if [ ! -f /etc/sudoers.d/mail-elk ]; then
note "ставлю sudoers entry для UI-кнопки 'Применить ELK'"
cat > /etc/sudoers.d/mail-elk <<EOF
$APP_USER ALL=(root) NOPASSWD: $PROJECT_DIR/deploy/rsyslog/install.sh *
$APP_USER ALL=(root) NOPASSWD: /usr/bin/tee /etc/rsyslog.d/49-mail-stack.conf
$APP_USER ALL=(root) NOPASSWD: /usr/bin/systemctl restart rsyslog
EOF
chmod 440 /etc/sudoers.d/mail-elk
visudo -c -f /etc/sudoers.d/mail-elk
fi
note "готово"
echo
echo " Веб: http://$(hostname -I | awk '{print $1}'):${FLASK_PORT:-5000}/"
echo " Health: curl http://localhost:${FLASK_PORT:-5000}/api/health"
echo " Сервисы: systemctl status mail-flask mail-dramatiq"
echo " Логи: journalctl -u mail-flask -f"
echo " Бэкап вручную: $PROJECT_DIR/deploy/backup.sh"
echo
echo "Дальше:"
echo " 1. Заполни ADMINS / IMAP_PASSWORD в $PROJECT_DIR/.env и: systemctl restart mail-flask"
echo " 2. Создай первый ящик:"
echo " docker exec docker-mailserver-1 setup email add admin@your-domain pass"
echo " 3. На /admin/domains/new пройди визард настройки домена"