154 lines
8.5 KiB
Bash
Executable File
154 lines
8.5 KiB
Bash
Executable File
#!/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 пройди визард настройки домена"
|