feat: CI/CD, per-domain HTTPS, backup, config generator

- sites.yml — единый источник истины для всех сайтов
- generate-configs.sh — генератор nginx конфигов, certbot domains.txt, .env
- nginx: per-domain HTTPS (вместо all-or-nothing switch-config)
- certbot: единый renew-all.sh, динамический init (без 5 дублирующих скриптов)
- backup: контейнер с pg_dump + rclone (Яндекс.Диск), ежедневно в 3AM
- Gitea + Gitea Runner в docker-compose (self-hosted Git + CI/CD)
- .gitea/workflows/deploy.yml — CI/CD pipeline: push → авто-деплой
- Makefile: generate-configs, reconfig, deploy, backup, restore, gitea, help
This commit is contained in:
valitovgaziz
2026-06-12 12:22:19 +05:00
parent abcb327278
commit 8e766b540e
31 changed files with 1535 additions and 343 deletions
+47
View File
@@ -0,0 +1,47 @@
#!/bin/bash
# backup.sh — ежедневный бэкап: pg_dump + файлы → локально + Яндекс.Диск
set -euo pipefail
BACKUP_DIR="/backups/$(date +%Y-%m-%d)"
RETENTION_DAYS="${BACKUP_RETENTION_DAYS:-7}"
DB_NAMES="${DB_NAMES:-mydb}"
TIMESTAMP=$(date +%H%M%S)
mkdir -p "$BACKUP_DIR/db" "$BACKUP_DIR/files"
echo "=== Backup $TIMESTAMP ==="
# 1. Дампы всех БД
IFS=',' read -ra databases <<< "$DB_NAMES"
for db in "${databases[@]}"; do
db=$(echo "$db" | xargs) # trim
echo "→ Dumping database: $db"
PGPASSWORD="$DB_PASSWORD" pg_dump -h "$DB_HOST" -p "${DB_PORT:-5432}" \
-U "$DB_USER" -d "$db" --format=custom \
-f "$BACKUP_DIR/db/${db}-${TIMESTAMP}.dump"
done
# 2. Архив файлов
echo "→ Archiving files..."
tar -czf "$BACKUP_DIR/files/certbot-${TIMESTAMP}.tar.gz" -C /data/certbot . 2>/dev/null || true
tar -czf "$BACKUP_DIR/files/uploads-${TIMESTAMP}.tar.gz" -C /data/uploads . 2>/dev/null || true
tar -czf "$BACKUP_DIR/files/analytics-${TIMESTAMP}.tar.gz" -C /data/analytics . 2>/dev/null || true
# 3. Создаём symlink latest
rm -f /backups/latest
ln -sf "$BACKUP_DIR" /backups/latest
# 4. Ротация — удаляем старше RETENTION_DAYS
find /backups -maxdepth 1 -type d -name '2*' -mtime "+$RETENTION_DAYS" -exec rm -rf {} \; 2>/dev/null || true
echo "✓ Local backup saved to $BACKUP_DIR"
# 5. Синхронизация с Яндекс.Диск
if command -v rclone > /dev/null 2>&1 && [ -n "${RCLONE_REMOTE:-}" ]; then
echo "→ Syncing to cloud: $RCLONE_REMOTE"
rclone sync /backups "$RCLONE_REMOTE" --progress 2>&1 || \
echo " ⚠ Cloud sync failed (check rclone config)"
echo "✓ Cloud sync complete"
fi
echo "=== Backup finished ==="
+43
View File
@@ -0,0 +1,43 @@
#!/bin/bash
# restore.sh — восстановление из бэкапа
# Использование: docker compose run --rm backup /opt/restore.sh [дата]
set -euo pipefail
BACKUP_DATE="${1:-latest}"
BACKUP_DIR="/backups/$BACKUP_DATE"
if [ ! -d "$BACKUP_DIR" ]; then
echo "Ошибка: бэкап $BACKUP_DIR не найден"
echo "Доступные бэкапы:"
ls -d /backups/2* 2>/dev/null || echo " (нет бэкапов)"
exit 1
fi
echo "=== Restore from $BACKUP_DIR ==="
# Восстановить БД
if [ -d "$BACKUP_DIR/db" ]; then
for dump in "$BACKUP_DIR/db"/*.dump; do
[ -f "$dump" ] || continue
db=$(basename "$dump" | sed 's/-.*//')
echo "→ Restoring database: $db"
PGPASSWORD="$DB_PASSWORD" pg_restore -h "$DB_HOST" -p "${DB_PORT:-5432}" \
-U "$DB_USER" -d "$db" --clean --if-exists "$dump" || \
echo " ⚠ Restore of $db had warnings (non-fatal)"
done
fi
# Распаковать файлы
if [ -d "$BACKUP_DIR/files" ]; then
for archive in "$BACKUP_DIR/files"/*.tar.gz; do
[ -f "$archive" ] || continue
name=$(basename "$archive" | sed 's/-.*//')
target="/data/$name"
echo "→ Extracting $name to $target"
mkdir -p "$target"
tar -xzf "$archive" -C "$target" || true
done
fi
echo "=== Restore completed ==="
echo "При необходимости перезапусти сервисы: docker compose restart"