Some checks failed
CI / Lint (push) Has been cancelled
CI / TypeCheck (push) Has been cancelled
CI / Test (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/api/Dockerfile name:api]) (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/cms/Dockerfile name:cms]) (push) Has been cancelled
Deploy / Docker Build & Push (map[context:. dockerfile:apps/web/Dockerfile name:web]) (push) Has been cancelled
CI / Build (push) Has been cancelled
Deploy / Deploy (push) Has been cancelled
- Monorepo: Turborepo + pnpm workspaces with 7 apps/packages - Backend: NestJS scaffold with 9 modules (auth, tenant, curriculum, simulation, portfolio, ai-coach, analytics, classroom, gamification) - Frontend: React 18 + Vite + TailwindCSS + shadcn/ui with 10 pages, 14 UI primitives, 3 Zustand stores - Database: Prisma schema with 19 models, 13 enums, seed script, multi-tenant ready - Docker: Dev, production, and Portainer Compose files with Traefik reverse proxy - Configuration: .env.example (47 vars), Zod validation, frontend-safe env exposure - i18n: English + Greek locale files (10 files), ICU MessageFormat, react-i18next - Shared packages: @investplay/types, @investplay/ui, @investplay/i18n, @investplay/utils - CI/CD: GitHub Actions (lint, typecheck, test, build, deploy, PR checks) - Documentation: CONTEXT.md, ARCHITECTURE.md (10 Mermaid diagrams), API.md, LOCALIZATION.md, DEVELOPMENT-ROADMAP.md - Infrastructure: Dockerfiles (dev + prod), nginx configs, backup/healthcheck scripts - Security: JWT auth guards, role-based access, rate limiting, Helmet, CORS, PII sanitization
155 lines
6.4 KiB
Bash
155 lines
6.4 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ─── Configuration ──────────────────────────────────────────────
|
|
BACKUP_DIR="${BACKUP_DIR:-./backups}"
|
|
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
|
|
BACKUP_PATH="${BACKUP_DIR}/${TIMESTAMP}"
|
|
RETENTION_DAILY=7
|
|
RETENTION_WEEKLY=4
|
|
|
|
# S3-compatible backup bucket (optional)
|
|
S3_ENDPOINT="${S3_BACKUP_ENDPOINT:-}"
|
|
S3_BUCKET="${S3_BACKUP_BUCKET:-}"
|
|
S3_ACCESS_KEY="${S3_BACKUP_ACCESS_KEY:-}"
|
|
S3_SECRET_KEY="${S3_BACKUP_SECRET_KEY:-}"
|
|
|
|
# ─── Color output ──────────────────────────────────────────────
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
|
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $*"; }
|
|
|
|
cleanup() {
|
|
local exit_code=$?
|
|
if [ $exit_code -ne 0 ]; then
|
|
error "Backup script failed with exit code ${exit_code}"
|
|
fi
|
|
exit $exit_code
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# ─── Ensure backup directory ───────────────────────────────────
|
|
mkdir -p "${BACKUP_PATH}"
|
|
|
|
# ─── 1. PostgreSQL backup ───────────────────────────────────────
|
|
info "Backing up PostgreSQL..."
|
|
POSTGRES_CONTAINER="${POSTGRES_CONTAINER:-investplay-postgres}"
|
|
POSTGRES_USER="${POSTGRES_USER:-investplay}"
|
|
POSTGRES_DB="${POSTGRES_DB:-investplay}"
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${POSTGRES_CONTAINER}$"; then
|
|
docker exec "${POSTGRES_CONTAINER}" pg_dump -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" \
|
|
--no-owner --no-acl --compress=9 \
|
|
> "${BACKUP_PATH}/postgres_${TIMESTAMP}.sql.gz" 2>/dev/null || {
|
|
warn "pg_dump failed — database may be temporarily unavailable"
|
|
}
|
|
ok "PostgreSQL backup saved"
|
|
else
|
|
warn "PostgreSQL container '${POSTGRES_CONTAINER}' not running — skipping"
|
|
fi
|
|
|
|
# ─── 2. Redis backup ────────────────────────────────────────────
|
|
info "Backing up Redis..."
|
|
REDIS_CONTAINER="${REDIS_CONTAINER:-investplay-redis}"
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${REDIS_CONTAINER}$"; then
|
|
docker exec "${REDIS_CONTAINER}" redis-cli SAVE >/dev/null 2>&1 || {
|
|
warn "Redis SAVE command failed"
|
|
}
|
|
# Copy the dump file from the container
|
|
docker cp "${REDIS_CONTAINER}:/data/dump.rdb" "${BACKUP_PATH}/redis_${TIMESTAMP}.rdb" 2>/dev/null || {
|
|
warn "Failed to copy Redis dump"
|
|
}
|
|
ok "Redis backup saved"
|
|
else
|
|
warn "Redis container '${REDIS_CONTAINER}' not running — skipping"
|
|
fi
|
|
|
|
# ─── 3. MinIO backup ────────────────────────────────────────────
|
|
info "Backing up MinIO..."
|
|
MINIO_CONTAINER="${MINIO_CONTAINER:-investplay-minio}"
|
|
MINIO_ALIAS="${MINIO_ALIAS:-investplay}"
|
|
MINIO_USER="${MINIO_ROOT_USER:-minioadmin}"
|
|
MINIO_PASS="${MINIO_ROOT_PASSWORD:-}"
|
|
|
|
if docker ps --format '{{.Names}}' | grep -q "^${MINIO_CONTAINER}$"; then
|
|
# Configure mc alias inside the container
|
|
docker exec "${MINIO_CONTAINER}" mc alias set "${MINIO_ALIAS}" \
|
|
http://localhost:9000 "${MINIO_USER}" "${MINIO_PASS}" >/dev/null 2>&1
|
|
|
|
# Mirror all buckets to local backup directory
|
|
mkdir -p "${BACKUP_PATH}/minio"
|
|
docker exec "${MINIO_CONTAINER}" mc mirror --overwrite \
|
|
"${MINIO_ALIAS}/" "/tmp/minio-backup" >/dev/null 2>&1 || {
|
|
warn "MinIO mc mirror failed"
|
|
}
|
|
docker cp "${MINIO_CONTAINER}:/tmp/minio-backup/." "${BACKUP_PATH}/minio/" 2>/dev/null || true
|
|
docker exec "${MINIO_CONTAINER}" rm -rf /tmp/minio-backup 2>/dev/null || true
|
|
ok "MinIO backup saved"
|
|
else
|
|
warn "MinIO container '${MINIO_CONTAINER}' not running — skipping"
|
|
fi
|
|
|
|
# ─── 4. Upload to S3-compatible backup bucket (optional) ────────
|
|
if [ -n "${S3_ENDPOINT}" ] && [ -n "${S3_BUCKET}" ]; then
|
|
info "Uploading backup to S3-compatible storage..."
|
|
|
|
if command -v mc &>/dev/null; then
|
|
mc alias set backup-target "${S3_ENDPOINT}" "${S3_ACCESS_KEY}" "${S3_SECRET_KEY}" >/dev/null 2>&1
|
|
mc mirror "${BACKUP_PATH}" "backup-target/${S3_BUCKET}/investplay/${TIMESTAMP}" >/dev/null 2>&1 && {
|
|
ok "Backup uploaded to S3"
|
|
} || {
|
|
warn "S3 upload failed"
|
|
}
|
|
elif command -v aws &>/dev/null; then
|
|
AWS_ACCESS_KEY_ID="${S3_ACCESS_KEY}" AWS_SECRET_ACCESS_KEY="${S3_SECRET_KEY}" \
|
|
aws s3 cp --endpoint-url="${S3_ENDPOINT}" --recursive \
|
|
"${BACKUP_PATH}" "s3://${S3_BUCKET}/investplay/${TIMESTAMP}/" >/dev/null 2>&1 && {
|
|
ok "Backup uploaded to S3"
|
|
} || {
|
|
warn "S3 upload failed"
|
|
}
|
|
else
|
|
warn "Neither 'mc' nor 'aws' CLI found — skipping S3 upload"
|
|
fi
|
|
fi
|
|
|
|
# ─── 5. Cleanup old backups ─────────────────────────────────────
|
|
info "Cleaning up old backups..."
|
|
total_backups=$(ls -1d "${BACKUP_DIR}/"20*/ 2>/dev/null | wc -l)
|
|
|
|
# Keep last N daily backups
|
|
ls -1d "${BACKUP_DIR}/"20*/ 2>/dev/null | sort -r | tail -n +$((RETENTION_DAILY + 1)) | while read -r dir; do
|
|
rm -rf "${dir}"
|
|
info "Removed old backup: $(basename "${dir}")"
|
|
done
|
|
|
|
# Keep last N weekly backups (every Sunday)
|
|
if [ "$(date +%u)" -eq 7 ]; then
|
|
ls -1d "${BACKUP_DIR}/"20*/ 2>/dev/null | sort -r | tail -n +$((RETENTION_WEEKLY + 1)) | while read -r dir; do
|
|
rm -rf "${dir}"
|
|
info "Removed weekly backup: $(basename "${dir}")"
|
|
done
|
|
fi
|
|
|
|
ok "Cleanup complete"
|
|
|
|
# ─── Summary ────────────────────────────────────────────────────
|
|
backup_size=$(du -sh "${BACKUP_PATH}" | cut -f1)
|
|
echo ""
|
|
echo -e "${GREEN}══════════════════════════════════════════════════════════${NC}"
|
|
echo -e "${GREEN} Backup completed successfully${NC}"
|
|
echo -e "${GREEN}══════════════════════════════════════════════════════════${NC}"
|
|
echo ""
|
|
echo -e " ${CYAN}Location:${NC} ${BACKUP_PATH}"
|
|
echo -e " ${CYAN}Size:${NC} ${backup_size}"
|
|
echo -e " ${CYAN}Date:${NC} $(date)"
|
|
echo ""
|