Dev ops prep

This commit is contained in:
IGNY8 VPS (Salman)
2026-01-21 17:53:42 +00:00
parent 3398130bff
commit 4a200822bb
14 changed files with 2027 additions and 0 deletions

83
.env.staging.example Normal file
View File

@@ -0,0 +1,83 @@
# =============================================================================
# IGNY8 STAGING ENVIRONMENT CONFIGURATION
# =============================================================================
# Copy this file to .env.staging and configure values
# This environment runs alongside production with separate database/redis
# =============================================================================
# Environment Identifier
DJANGO_ENV=staging
DEBUG=False
# Database (Uses separate staging database)
DB_HOST=postgres
DB_NAME=igny8_staging_db
DB_USER=igny8
DB_PASSWORD=igny8pass
DB_PORT=5432
# Redis (Uses DB index 1 instead of 0)
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_DB=1
# Security (Generate unique key for staging)
SECRET_KEY=staging-secret-key-CHANGE-THIS-TO-UNIQUE-VALUE
ALLOWED_HOSTS=staging-api.igny8.com,staging.igny8.com,localhost,127.0.0.1
CORS_ALLOWED_ORIGINS=https://staging.igny8.com,https://staging-api.igny8.com
# =============================================================================
# API Keys - USE TEST/SANDBOX KEYS FOR STAGING
# =============================================================================
# AI Services (can use same keys or separate test keys)
OPENAI_API_KEY=sk-your-openai-key
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key
# Image Generation
RUNWARE_API_KEY=your-runware-key
BRIA_API_KEY=your-bria-key
# =============================================================================
# Payment Gateways - MUST USE SANDBOX/TEST MODE
# =============================================================================
# Stripe (TEST MODE)
STRIPE_SECRET_KEY=sk_test_your_stripe_test_key
STRIPE_PUBLISHABLE_KEY=pk_test_your_stripe_test_key
STRIPE_WEBHOOK_SECRET=whsec_test_your_webhook_secret
# PayPal (SANDBOX MODE)
PAYPAL_CLIENT_ID=sandbox_client_id
PAYPAL_CLIENT_SECRET=sandbox_client_secret
PAYPAL_MODE=sandbox
# =============================================================================
# Email - Use test email service or same service
# =============================================================================
RESEND_API_KEY=re_your_resend_key
BREVO_API_KEY=your_brevo_key
DEFAULT_FROM_EMAIL=staging@igny8.com
# =============================================================================
# URLs
# =============================================================================
FRONTEND_URL=https://staging.igny8.com
BACKEND_URL=https://staging-api.igny8.com
MARKETING_URL=https://staging-marketing.igny8.com
# =============================================================================
# Feature Flags (can enable/disable features for testing)
# =============================================================================
ENABLE_LINKER=True
ENABLE_OPTIMIZER=True
ENABLE_SOCIALIZER=False
# =============================================================================
# Logging
# =============================================================================
LOG_LEVEL=DEBUG

145
GO-LIVE-CHECKLIST.md Normal file
View File

@@ -0,0 +1,145 @@
# 🚀 IGNY8 Go-Live Checklist
**Date:** January 20, 2026
**Purpose:** Quick reference for launching IGNY8 to production
---
## ✅ Pre-Launch Checklist
### Infrastructure Ready
- [ ] PostgreSQL running and accessible
- [ ] Redis running and accessible
- [ ] Caddy configured with SSL for all domains
- [ ] DNS records pointing to server
- [ ] Firewall configured (ports 80, 443 open)
### Application Ready
- [ ] Production `.env` configured with real secrets
- [ ] All API keys set (OpenAI, Stripe, etc.)
- [ ] Django `SECRET_KEY` is unique and secure
- [ ] `DEBUG=False` in production
- [ ] CORS and ALLOWED_HOSTS configured
### Operational Scripts Ready
- [x] `/data/app/igny8/scripts/ops/backup-db.sh`
- [x] `/data/app/igny8/scripts/ops/backup-full.sh`
- [x] `/data/app/igny8/scripts/ops/restore-db.sh`
- [x] `/data/app/igny8/scripts/ops/deploy-production.sh`
- [x] `/data/app/igny8/scripts/ops/deploy-staging.sh`
- [x] `/data/app/igny8/scripts/ops/rollback.sh`
- [x] `/data/app/igny8/scripts/ops/health-check.sh`
- [x] `/data/app/igny8/scripts/ops/sync-prod-to-staging.sh`
- [x] `/data/app/igny8/scripts/ops/log-rotate.sh`
### Staging Environment (Optional but Recommended)
- [x] `docker-compose.staging.yml` created
- [x] `.env.staging.example` created
- [ ] Copy to `.env.staging` and configure
- [ ] Create staging database
- [ ] Configure staging DNS records
---
## 🏁 Go-Live Steps
### Step 1: Create Initial Backup
```bash
/data/app/igny8/scripts/ops/backup-db.sh pre-deploy
```
### Step 2: Verify Health
```bash
/data/app/igny8/scripts/ops/health-check.sh
```
### Step 3: Set Up Automated Backups
```bash
# Install cron job
sudo cp /data/app/igny8/scripts/ops/igny8-cron /etc/cron.d/igny8
sudo chmod 644 /etc/cron.d/igny8
# Verify cron
sudo crontab -l -u root
```
### Step 4: Test Backup & Restore (Optional)
```bash
# Create test backup
/data/app/igny8/scripts/ops/backup-db.sh daily
# Verify backup exists
ls -la /data/backups/daily/
```
---
## 📋 Daily Operations
### Check System Health
```bash
/data/app/igny8/scripts/ops/health-check.sh
```
### View Logs
```bash
# Backend logs
docker logs -f igny8_backend
# All app logs
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app logs -f
```
### Container Status
```bash
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app ps
```
---
## 🚨 Emergency Procedures
### Immediate Rollback
```bash
/data/app/igny8/scripts/ops/rollback.sh
```
### Restore Database
```bash
# List available backups
ls -la /data/backups/
# Restore from latest
/data/app/igny8/scripts/ops/restore-db.sh /data/backups/latest_db.sql.gz
```
### Restart All Services
```bash
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app restart
```
---
## 📁 Key File Locations
| Item | Location |
|------|----------|
| Production Compose | `/data/app/igny8/docker-compose.app.yml` |
| Staging Compose | `/data/app/igny8/docker-compose.staging.yml` |
| Production Env | `/data/app/igny8/.env` |
| Staging Env | `/data/app/igny8/.env.staging` |
| Ops Scripts | `/data/app/igny8/scripts/ops/` |
| Backups | `/data/backups/` |
| Logs | `/data/logs/` |
---
## 📞 Support Contacts
- **Documentation:** `/data/app/igny8/docs/`
- **Deployment Guide:** `/data/app/igny8/docs/50-DEPLOYMENT/`
- **Operations Guide:** `/data/app/igny8/docs/50-DEPLOYMENT/DEVOPS-OPERATIONS-GUIDE.md`
---
**You're ready to go live! 🎉**

150
docker-compose.staging.yml Normal file
View File

@@ -0,0 +1,150 @@
# =============================================================================
# IGNY8 STAGING ENVIRONMENT COMPOSE FILE
# =============================================================================
# Runs alongside production on the same server using different:
# - Ports, database, Redis DB, domains, and container names
# =============================================================================
#
# Usage:
# docker compose -f docker-compose.staging.yml -p igny8-staging up -d
# docker compose -f docker-compose.staging.yml -p igny8-staging down
# docker compose -f docker-compose.staging.yml -p igny8-staging logs -f
# =============================================================================
name: igny8-staging
services:
igny8_staging_backend:
image: igny8-backend:staging
container_name: igny8_staging_backend
restart: always
working_dir: /app
ports:
- "0.0.0.0:8012:8010"
environment:
DJANGO_ENV: staging
DB_HOST: postgres
DB_NAME: igny8_staging_db
DB_USER: igny8
DB_PASSWORD: igny8pass
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_DB: "1"
USE_SECURE_COOKIES: "True"
USE_SECURE_PROXY_HEADER: "True"
DEBUG: "False"
volumes:
- /data/app/igny8/backend:/app:rw
- /data/app/igny8:/data/app/igny8:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
- /data/logs/staging:/app/logs:rw
env_file:
- .env.staging
healthcheck:
test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8010/api/v1/system/status/').read()\" || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
command: ["gunicorn", "igny8_core.wsgi:application", "--bind", "0.0.0.0:8010", "--workers", "2", "--timeout", "120"]
networks: [igny8_net]
labels:
- "com.docker.compose.project=igny8-staging"
- "com.docker.compose.service=igny8_staging_backend"
igny8_staging_frontend:
image: igny8-frontend-dev:staging
container_name: igny8_staging_frontend
restart: always
ports:
- "0.0.0.0:8024:5173"
environment:
VITE_BACKEND_URL: "https://staging-api.igny8.com/api"
VITE_ENV: "staging"
volumes:
- /data/app/igny8/frontend:/app:rw
depends_on:
igny8_staging_backend:
condition: service_healthy
networks: [igny8_net]
labels:
- "com.docker.compose.project=igny8-staging"
- "com.docker.compose.service=igny8_staging_frontend"
igny8_staging_marketing_dev:
image: igny8-marketing-dev:staging
container_name: igny8_staging_marketing_dev
restart: always
ports:
- "0.0.0.0:8026:5174"
environment:
VITE_BACKEND_URL: "https://staging-api.igny8.com/api"
VITE_ENV: "staging"
volumes:
- /data/app/igny8/frontend:/app:rw
networks: [igny8_net]
labels:
- "com.docker.compose.project=igny8-staging"
- "com.docker.compose.service=igny8_staging_marketing_dev"
igny8_staging_celery_worker:
image: igny8-backend:staging
container_name: igny8_staging_celery_worker
restart: always
working_dir: /app
environment:
DJANGO_ENV: staging
DB_HOST: postgres
DB_NAME: igny8_staging_db
DB_USER: igny8
DB_PASSWORD: igny8pass
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_DB: "1"
C_FORCE_ROOT: "true"
volumes:
- /data/app/igny8/backend:/app:rw
- /data/logs/staging:/app/logs:rw
env_file:
- .env.staging
command: ["celery", "-A", "igny8_core", "worker", "--loglevel=info", "--concurrency=2"]
depends_on:
igny8_staging_backend:
condition: service_healthy
networks: [igny8_net]
labels:
- "com.docker.compose.project=igny8-staging"
- "com.docker.compose.service=igny8_staging_celery_worker"
igny8_staging_celery_beat:
image: igny8-backend:staging
container_name: igny8_staging_celery_beat
restart: always
working_dir: /app
environment:
DJANGO_ENV: staging
DB_HOST: postgres
DB_NAME: igny8_staging_db
DB_USER: igny8
DB_PASSWORD: igny8pass
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_DB: "1"
C_FORCE_ROOT: "true"
volumes:
- /data/app/igny8/backend:/app:rw
- /data/logs/staging:/app/logs:rw
env_file:
- .env.staging
command: ["celery", "-A", "igny8_core", "beat", "--loglevel=info", "--scheduler", "django_celery_beat.schedulers:DatabaseScheduler"]
depends_on:
igny8_staging_backend:
condition: service_healthy
networks: [igny8_net]
labels:
- "com.docker.compose.project=igny8-staging"
- "com.docker.compose.service=igny8_staging_celery_beat"
networks:
igny8_net:
external: true

View File

@@ -0,0 +1,303 @@
# DevOps Operations Guide
**Purpose:** Complete operational procedures for managing IGNY8 in production
**Version:** 1.0
**Last Updated:** January 20, 2026
---
## 📋 Executive Summary
This document provides a complete structure for:
1. **Automated Backups** - Regular database + config backups
2. **Environment Management** - Dev vs Staging vs Production
3. **Health Monitoring** - Automated health checks & alerts
4. **Disaster Recovery** - Quick recovery procedures
5. **Change Management** - Safe deployment workflow
---
## 🗂️ Directory Structure (To Be Implemented)
```
/data/
├── app/
│ └── igny8/ # Application code
│ ├── docker-compose.app.yml # Production compose ✅
│ ├── docker-compose.staging.yml # Staging compose ⚠️ TO CREATE
│ ├── .env # Production env
│ ├── .env.staging # Staging env ⚠️ TO CREATE
│ └── scripts/
│ └── ops/ # ⚠️ TO CREATE
│ ├── backup-db.sh # Database backup
│ ├── backup-full.sh # Full backup (db + code + config)
│ ├── restore-db.sh # Database restore
│ ├── deploy-staging.sh # Deploy to staging
│ ├── deploy-production.sh# Deploy to production
│ ├── rollback.sh # Rollback deployment
│ ├── health-check.sh # System health check
│ ├── sync-prod-to-staging.sh # Sync data
│ └── log-rotate.sh # Log rotation
├── backups/ # Backup storage
│ ├── daily/ # Daily automated backups
│ │ └── YYYYMMDD/
│ │ ├── db_igny8_YYYYMMDD_HHMMSS.sql.gz
│ │ └── config_YYYYMMDD.tar.gz
│ ├── weekly/ # Weekly backups (kept 4 weeks)
│ ├── monthly/ # Monthly backups (kept 12 months)
│ └── pre-deploy/ # Pre-deployment snapshots
│ └── YYYYMMDD_HHMMSS/
├── logs/ # Centralized logs
│ ├── production/
│ │ ├── backend.log
│ │ ├── celery-worker.log
│ │ ├── celery-beat.log
│ │ └── access.log
│ ├── staging/
│ └── caddy/
└── stack/ # Infrastructure stack
└── igny8-stack/ # (Future - not yet separated)
```
---
## 🔄 Automated Backup System
### Backup Strategy
| Type | Frequency | Retention | Content |
|------|-----------|-----------|---------|
| **Daily** | 1:00 AM | 7 days | Database + configs |
| **Weekly** | Sunday 2:00 AM | 4 weeks | Full backup |
| **Monthly** | 1st of month | 12 months | Full backup |
| **Pre-Deploy** | Before each deploy | 5 most recent | Database snapshot |
### Cron Schedule
```bash
# /etc/cron.d/igny8-backup
# Daily database backup at 1:00 AM
0 1 * * * root /data/app/igny8/scripts/ops/backup-db.sh daily >> /data/logs/backup.log 2>&1
# Weekly full backup on Sunday at 2:00 AM
0 2 * * 0 root /data/app/igny8/scripts/ops/backup-full.sh weekly >> /data/logs/backup.log 2>&1
# Monthly full backup on 1st at 3:00 AM
0 3 1 * * root /data/app/igny8/scripts/ops/backup-full.sh monthly >> /data/logs/backup.log 2>&1
# Health check every 5 minutes
*/5 * * * * root /data/app/igny8/scripts/ops/health-check.sh >> /data/logs/health.log 2>&1
# Log rotation daily at midnight
0 0 * * * root /data/app/igny8/scripts/ops/log-rotate.sh >> /data/logs/maintenance.log 2>&1
```
---
## 🌍 Environment Management
### Environment Comparison
| Aspect | Development | Staging | Production |
|--------|-------------|---------|------------|
| **Domain** | localhost:5173 | staging.igny8.com | app.igny8.com |
| **API** | localhost:8010 | staging-api.igny8.com | api.igny8.com |
| **Database** | igny8_dev_db | igny8_staging_db | igny8_db |
| **Redis DB** | 2 | 1 | 0 |
| **Debug** | True | False | False |
| **AI Keys** | Test/Limited | Test/Limited | Production |
| **Payments** | Sandbox | Sandbox | Live |
| **Compose File** | docker-compose.dev.yml | docker-compose.staging.yml | docker-compose.app.yml |
| **Project Name** | igny8-dev | igny8-staging | igny8-app |
### Port Allocation
| Service | Dev | Staging | Production |
|---------|-----|---------|------------|
| Backend | 8010 | 8012 | 8011 |
| Frontend | 5173 | 8024 | 8021 |
| Marketing | 5174 | 8026 | 8023 |
| Flower | - | 5556 | 5555 |
---
## 🚀 Deployment Workflow
### Safe Deployment Checklist
```
┌─────────────────────────────────────────────────────────────┐
│ DEPLOYMENT CHECKLIST │
├─────────────────────────────────────────────────────────────┤
│ PRE-DEPLOYMENT │
│ □ All tests passing on staging? │
│ □ Database migrations reviewed? │
│ □ Backup created? │
│ □ Rollback plan ready? │
│ □ Team notified? │
├─────────────────────────────────────────────────────────────┤
│ DEPLOYMENT │
│ □ Create pre-deploy backup │
│ □ Tag current images for rollback │
│ □ Pull latest code │
│ □ Build new images │
│ □ Apply migrations │
│ □ Restart containers │
│ □ Verify health check │
├─────────────────────────────────────────────────────────────┤
│ POST-DEPLOYMENT │
│ □ Monitor logs for 10 minutes │
│ □ Test critical paths (login, API, AI functions) │
│ □ Check error rates │
│ □ If issues → ROLLBACK │
│ □ Update changelog │
└─────────────────────────────────────────────────────────────┘
```
### Git Branch Strategy
```
┌──────────┐
│ main │ ← Production deployments
└────▲─────┘
│ merge (after staging approval)
┌────┴─────┐
│ staging │ ← Staging deployments
└────▲─────┘
│ merge
┌────────────────┼────────────────┐
│ │ │
┌───────┴───────┐ ┌──────┴──────┐ ┌───────┴───────┐
│feature/xyz │ │feature/abc │ │hotfix/urgent │
└───────────────┘ └─────────────┘ └───────────────┘
```
---
## 🏥 Health Monitoring
### Health Check Endpoints
| Endpoint | Purpose | Expected Response |
|----------|---------|-------------------|
| `/api/v1/system/status/` | Overall system status | `{"status": "healthy"}` |
| `/api/v1/system/health/` | Detailed component health | JSON with all components |
### Monitoring Targets
1. **Backend API** - Response time < 500ms
2. **Database** - Connection pool healthy
3. **Redis** - Connection alive
4. **Celery Workers** - Queue length < 100
5. **Celery Beat** - Scheduler running
6. **Disk Space** - > 20% free
7. **Memory** - < 80% used
### Alert Thresholds
| Metric | Warning | Critical |
|--------|---------|----------|
| API Response Time | > 1s | > 5s |
| Error Rate | > 1% | > 5% |
| CPU Usage | > 70% | > 90% |
| Memory Usage | > 70% | > 90% |
| Disk Usage | > 70% | > 90% |
| Celery Queue | > 50 | > 200 |
---
## 🔧 Common Operations
### Daily Operations
```bash
# Check system health
/data/app/igny8/scripts/ops/health-check.sh
# View logs
tail -f /data/logs/production/backend.log
# Check container status
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app ps
```
### Weekly Operations
```bash
# Review backup status
ls -la /data/backups/daily/
du -sh /data/backups/*
# Check disk space
df -h
# Review error logs
grep -i error /data/logs/production/backend.log | tail -50
```
### Emergency Procedures
```bash
# Immediate rollback
/data/app/igny8/scripts/ops/rollback.sh
# Emergency restart
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app restart
# Emergency database restore
/data/app/igny8/scripts/ops/restore-db.sh /data/backups/latest.sql.gz
```
---
## 📊 What's Missing (Action Items)
### Priority 1 - Critical (Before Go-Live)
| Item | Status | Action |
|------|--------|--------|
| `docker-compose.staging.yml` | ❌ Missing | Create from documentation |
| `.env.staging` | ❌ Missing | Create from example |
| Deployment scripts | ❌ Missing | Create all ops scripts |
| Automated backup cron | ❌ Missing | Set up cron jobs |
| Pre-deploy backup | ❌ Missing | Add to deploy script |
### Priority 2 - Important (First Week)
| Item | Status | Action |
|------|--------|--------|
| Health check automation | ❌ Missing | Create monitoring |
| Log rotation | ❌ Missing | Set up logrotate |
| Staging DNS | ❌ Unknown | Configure if needed |
| Caddyfile staging routes | ❌ Unknown | Add staging domains |
### Priority 3 - Nice to Have (First Month)
| Item | Status | Action |
|------|--------|--------|
| CI/CD pipeline | ❌ Not set | Optional automation |
| External monitoring | ❌ Not set | UptimeRobot/Datadog |
| Alerting system | ❌ Not set | Email/Slack alerts |
---
## Next Steps
1. **Create ops scripts directory**: `/data/app/igny8/scripts/ops/`
2. **Create all deployment scripts** (see STAGING-SETUP-GUIDE.md)
3. **Create staging compose file** (copy from documentation)
4. **Set up automated backups**
5. **Test complete deployment cycle** on staging
6. **Go live with confidence**
---
## Related Documentation
- [STAGING-SETUP-GUIDE.md](final-clean-best-deployment-plan/STAGING-SETUP-GUIDE.md) - Detailed staging setup
- [TWO-REPO-ARCHITECTURE.md](final-clean-best-deployment-plan/TWO-REPO-ARCHITECTURE.md) - Architecture overview
- [INFRASTRUCTURE-STACK.md](final-clean-best-deployment-plan/INFRASTRUCTURE-STACK.md) - Stack details

193
scripts/ops/backup-db.sh Normal file
View File

@@ -0,0 +1,193 @@
#!/bin/bash
# =============================================================================
# IGNY8 Database Backup Script
# =============================================================================
# Usage: ./backup-db.sh [daily|weekly|monthly|pre-deploy]
# Creates compressed database backup with automatic retention
# =============================================================================
set -e
# Configuration
BACKUP_TYPE="${1:-daily}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DATE_FOLDER=$(date +%Y%m%d)
# Directories
BACKUP_ROOT="/data/backups"
DAILY_DIR="${BACKUP_ROOT}/daily"
WEEKLY_DIR="${BACKUP_ROOT}/weekly"
MONTHLY_DIR="${BACKUP_ROOT}/monthly"
PREDEPLOY_DIR="${BACKUP_ROOT}/pre-deploy"
# Database settings (from docker environment)
DB_CONTAINER="postgres"
DB_NAME="igny8_db"
DB_USER="igny8"
# Retention settings
DAILY_RETENTION_DAYS=7
WEEKLY_RETENTION_WEEKS=4
MONTHLY_RETENTION_MONTHS=12
PREDEPLOY_RETENTION_COUNT=5
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log() {
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
log_success() {
log "${GREEN}$1${NC}"
}
log_warn() {
log "${YELLOW}⚠️ $1${NC}"
}
log_error() {
log "${RED}$1${NC}"
}
# Create directories if they don't exist
create_dirs() {
mkdir -p "${DAILY_DIR}"
mkdir -p "${WEEKLY_DIR}"
mkdir -p "${MONTHLY_DIR}"
mkdir -p "${PREDEPLOY_DIR}"
}
# Determine backup destination based on type
get_backup_path() {
case $BACKUP_TYPE in
daily)
echo "${DAILY_DIR}/${DATE_FOLDER}"
;;
weekly)
echo "${WEEKLY_DIR}/week_$(date +%Y_%W)"
;;
monthly)
echo "${MONTHLY_DIR}/$(date +%Y_%m)"
;;
pre-deploy)
echo "${PREDEPLOY_DIR}/${TIMESTAMP}"
;;
*)
log_error "Invalid backup type: $BACKUP_TYPE"
exit 1
;;
esac
}
# Perform database backup
backup_database() {
local backup_dir="$1"
local backup_file="${backup_dir}/db_${DB_NAME}_${TIMESTAMP}.sql.gz"
mkdir -p "${backup_dir}"
log "Starting database backup: ${DB_NAME}"
log "Destination: ${backup_file}"
# Check if postgres container is running
if ! docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
log_error "PostgreSQL container '${DB_CONTAINER}' is not running"
exit 1
fi
# Perform backup with compression
if docker exec "${DB_CONTAINER}" pg_dump -U "${DB_USER}" "${DB_NAME}" | gzip > "${backup_file}"; then
local size=$(du -h "${backup_file}" | cut -f1)
log_success "Database backup complete: ${backup_file} (${size})"
# Create latest symlink
ln -sf "${backup_file}" "${BACKUP_ROOT}/latest_db.sql.gz"
return 0
else
log_error "Database backup failed"
rm -f "${backup_file}"
return 1
fi
}
# Clean up old backups based on retention policy
cleanup_old_backups() {
log "Cleaning up old backups..."
# Daily backups - keep last N days
if [[ -d "${DAILY_DIR}" ]]; then
find "${DAILY_DIR}" -type d -mtime +${DAILY_RETENTION_DAYS} -exec rm -rf {} \; 2>/dev/null || true
local daily_count=$(find "${DAILY_DIR}" -type f -name "*.sql.gz" | wc -l)
log "Daily backups: ${daily_count} files"
fi
# Weekly backups - keep last N weeks
if [[ -d "${WEEKLY_DIR}" ]]; then
find "${WEEKLY_DIR}" -type d -mtime +$((WEEKLY_RETENTION_WEEKS * 7)) -exec rm -rf {} \; 2>/dev/null || true
local weekly_count=$(find "${WEEKLY_DIR}" -type f -name "*.sql.gz" | wc -l)
log "Weekly backups: ${weekly_count} files"
fi
# Monthly backups - keep last N months
if [[ -d "${MONTHLY_DIR}" ]]; then
find "${MONTHLY_DIR}" -type d -mtime +$((MONTHLY_RETENTION_MONTHS * 30)) -exec rm -rf {} \; 2>/dev/null || true
local monthly_count=$(find "${MONTHLY_DIR}" -type f -name "*.sql.gz" | wc -l)
log "Monthly backups: ${monthly_count} files"
fi
# Pre-deploy backups - keep last N
if [[ -d "${PREDEPLOY_DIR}" ]]; then
local predeploy_dirs=$(ls -dt "${PREDEPLOY_DIR}"/*/ 2>/dev/null | tail -n +$((PREDEPLOY_RETENTION_COUNT + 1)))
if [[ -n "$predeploy_dirs" ]]; then
echo "$predeploy_dirs" | xargs rm -rf 2>/dev/null || true
fi
local predeploy_count=$(find "${PREDEPLOY_DIR}" -maxdepth 1 -type d | wc -l)
log "Pre-deploy backups: $((predeploy_count - 1)) snapshots"
fi
log_success "Cleanup complete"
}
# Display backup summary
show_summary() {
echo ""
echo "=========================================="
echo "BACKUP SUMMARY"
echo "=========================================="
echo "Type: ${BACKUP_TYPE}"
echo "Database: ${DB_NAME}"
echo "Timestamp: ${TIMESTAMP}"
echo ""
echo "Disk Usage:"
du -sh "${BACKUP_ROOT}"/* 2>/dev/null || echo "No backups yet"
echo ""
echo "Latest backup: $(readlink -f ${BACKUP_ROOT}/latest_db.sql.gz 2>/dev/null || echo 'None')"
echo "=========================================="
}
# Main execution
main() {
echo "=========================================="
echo "IGNY8 Database Backup"
echo "=========================================="
echo ""
create_dirs
local backup_path=$(get_backup_path)
if backup_database "${backup_path}"; then
cleanup_old_backups
show_summary
exit 0
else
exit 1
fi
}
main "$@"

150
scripts/ops/backup-full.sh Normal file
View File

@@ -0,0 +1,150 @@
#!/bin/bash
# =============================================================================
# IGNY8 Full Backup Script
# =============================================================================
# Creates complete backup: database + configuration + code snapshot
# Usage: ./backup-full.sh [weekly|monthly]
# =============================================================================
set -e
# Configuration
BACKUP_TYPE="${1:-weekly}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
APP_DIR="/data/app/igny8"
BACKUP_ROOT="/data/backups"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() {
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
log_success() {
log "${GREEN}$1${NC}"
}
log_error() {
log "${RED}$1${NC}"
}
# Determine backup directory
case $BACKUP_TYPE in
weekly)
BACKUP_DIR="${BACKUP_ROOT}/weekly/week_$(date +%Y_%W)"
;;
monthly)
BACKUP_DIR="${BACKUP_ROOT}/monthly/$(date +%Y_%m)"
;;
*)
log_error "Invalid backup type. Use: weekly or monthly"
exit 1
;;
esac
mkdir -p "${BACKUP_DIR}"
echo "=========================================="
echo "IGNY8 Full Backup"
echo "=========================================="
echo "Type: ${BACKUP_TYPE}"
echo "Destination: ${BACKUP_DIR}"
echo ""
# Step 1: Database backup
log "Step 1/4: Backing up database..."
/data/app/igny8/scripts/ops/backup-db.sh "${BACKUP_TYPE}" || {
log_error "Database backup failed"
exit 1
}
# Step 2: Configuration backup
log "Step 2/4: Backing up configuration files..."
CONFIG_BACKUP="${BACKUP_DIR}/config_${TIMESTAMP}.tar.gz"
tar -czf "${CONFIG_BACKUP}" \
-C "${APP_DIR}" \
.env \
.env.staging 2>/dev/null \
docker-compose.app.yml \
docker-compose.staging.yml 2>/dev/null \
--ignore-failed-read || true
if [[ -f "${CONFIG_BACKUP}" ]]; then
log_success "Config backup: ${CONFIG_BACKUP}"
else
log_error "Config backup failed"
fi
# Step 3: Code snapshot (without node_modules, __pycache__, etc.)
log "Step 3/4: Creating code snapshot..."
CODE_BACKUP="${BACKUP_DIR}/code_${TIMESTAMP}.tar.gz"
tar -czf "${CODE_BACKUP}" \
-C "/data/app" \
--exclude='igny8/node_modules' \
--exclude='igny8/frontend/node_modules' \
--exclude='igny8/__pycache__' \
--exclude='igny8/**/__pycache__' \
--exclude='igny8/*.pyc' \
--exclude='igny8/**/*.pyc' \
--exclude='igny8/.git' \
--exclude='igny8/backend/staticfiles' \
--exclude='igny8/frontend/dist' \
--exclude='igny8/backend/media' \
--exclude='igny8/logs' \
igny8/ 2>/dev/null || true
if [[ -f "${CODE_BACKUP}" ]]; then
local size=$(du -h "${CODE_BACKUP}" | cut -f1)
log_success "Code backup: ${CODE_BACKUP} (${size})"
else
log_error "Code backup failed"
fi
# Step 4: Media files (if they exist)
log "Step 4/4: Backing up media files..."
MEDIA_DIR="${APP_DIR}/backend/media"
if [[ -d "${MEDIA_DIR}" ]] && [[ "$(ls -A ${MEDIA_DIR})" ]]; then
MEDIA_BACKUP="${BACKUP_DIR}/media_${TIMESTAMP}.tar.gz"
tar -czf "${MEDIA_BACKUP}" -C "${APP_DIR}/backend" media/ 2>/dev/null || true
if [[ -f "${MEDIA_BACKUP}" ]]; then
local media_size=$(du -h "${MEDIA_BACKUP}" | cut -f1)
log_success "Media backup: ${MEDIA_BACKUP} (${media_size})"
fi
else
log "No media files to backup"
fi
# Create manifest
MANIFEST="${BACKUP_DIR}/manifest.txt"
cat > "${MANIFEST}" << EOF
IGNY8 Full Backup Manifest
===========================
Timestamp: ${TIMESTAMP}
Type: ${BACKUP_TYPE}
Server: $(hostname)
Files:
$(ls -la "${BACKUP_DIR}")
Sizes:
$(du -sh "${BACKUP_DIR}"/*)
Git Info:
$(cd "${APP_DIR}" && git log -1 --format='Commit: %H%nDate: %ci%nMessage: %s' 2>/dev/null || echo 'Not a git repo')
EOF
echo ""
echo "=========================================="
echo "BACKUP COMPLETE"
echo "=========================================="
echo "Location: ${BACKUP_DIR}"
echo ""
echo "Contents:"
ls -lh "${BACKUP_DIR}"
echo ""
echo "Total size: $(du -sh ${BACKUP_DIR} | cut -f1)"
echo "=========================================="

View File

@@ -0,0 +1,145 @@
#!/bin/bash
# =============================================================================
# IGNY8 Deploy to Production
# =============================================================================
# Safely deploys code to production with backup and rollback capability
# Usage: ./deploy-production.sh
# =============================================================================
set -e
APP_DIR="/data/app/igny8"
COMPOSE_FILE="docker-compose.app.yml"
PROJECT_NAME="igny8-app"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
echo "=========================================="
echo "IGNY8 PRODUCTION DEPLOYMENT"
echo "=========================================="
echo ""
# Safety confirmation
log_warn "This will deploy to PRODUCTION!"
log_warn "Make sure you have tested on staging first."
echo ""
read -p "Type 'deploy' to continue: " confirm
if [[ "${confirm}" != "deploy" ]]; then
log "Deployment cancelled"
exit 0
fi
cd "${APP_DIR}"
# Verify on main branch
CURRENT_BRANCH=$(git branch --show-current)
if [[ "${CURRENT_BRANCH}" != "main" ]]; then
log_error "Not on main branch (currently on: ${CURRENT_BRANCH})"
log "Switch to main: git checkout main && git pull"
exit 1
fi
# Step 1: Create pre-deploy backup
log "Step 1/8: Creating pre-deploy backup..."
/data/app/igny8/scripts/ops/backup-db.sh pre-deploy
log_success "Backup created"
# Step 2: Tag current images for rollback
log "Step 2/8: Tagging current images for rollback..."
docker tag igny8-backend:latest igny8-backend:rollback 2>/dev/null || true
docker tag igny8-frontend-dev:latest igny8-frontend-dev:rollback 2>/dev/null || true
docker tag igny8-marketing-dev:latest igny8-marketing-dev:rollback 2>/dev/null || true
log_success "Rollback images tagged"
# Step 3: Pull latest code
log "Step 3/8: Pulling latest code..."
git pull origin main
log_success "Code updated"
# Step 4: Build new images
log "Step 4/8: Building new images..."
docker build -t igny8-backend:latest -f backend/Dockerfile backend/
docker build -t igny8-frontend-dev:latest -f frontend/Dockerfile.dev frontend/
docker build -t igny8-marketing-dev:latest -f frontend/Dockerfile.marketing.dev frontend/
log_success "Images built"
# Step 5: Check for pending migrations
log "Step 5/8: Checking for pending migrations..."
PENDING=$(docker exec igny8_backend python manage.py showmigrations --plan 2>/dev/null | grep "\[ \]" || true)
if [[ -n "${PENDING}" ]]; then
log_warn "Pending migrations found:"
echo "${PENDING}"
read -p "Apply migrations? (yes/no): " apply_migrations
if [[ "${apply_migrations}" != "yes" ]]; then
log "Skipping migrations"
fi
fi
# Step 6: Restart containers
log "Step 6/8: Restarting containers..."
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" down
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" up -d
# Step 7: Wait for backend to be healthy
log "Step 7/8: Waiting for backend to be healthy..."
for i in {1..30}; do
if docker exec igny8_backend python -c "import urllib.request; urllib.request.urlopen('http://localhost:8010/api/v1/system/status/').read()" 2>/dev/null; then
log_success "Backend is healthy"
break
fi
if [[ $i -eq 30 ]]; then
log_error "Backend failed to become healthy"
log_error "ROLLING BACK..."
/data/app/igny8/scripts/ops/rollback.sh --auto
exit 1
fi
echo -n "."
sleep 2
done
# Step 8: Apply migrations and collect static
log "Step 8/8: Applying migrations and collecting static..."
if [[ "${apply_migrations}" == "yes" ]] || [[ -z "${PENDING}" ]]; then
docker exec igny8_backend python manage.py migrate --noinput
fi
docker exec igny8_backend python manage.py collectstatic --noinput
echo ""
echo "=========================================="
log_success "PRODUCTION DEPLOYMENT COMPLETE"
echo "=========================================="
echo ""
echo "Production is live!"
echo " App: https://app.igny8.com"
echo " API: https://api.igny8.com"
echo ""
echo "Monitor logs for 10 minutes:"
echo " docker compose -f ${COMPOSE_FILE} -p ${PROJECT_NAME} logs -f"
echo ""
echo "If issues occur, rollback:"
echo " /data/app/igny8/scripts/ops/rollback.sh"
echo ""
echo "Container status:"
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" ps
echo "=========================================="

View File

@@ -0,0 +1,125 @@
#!/bin/bash
# =============================================================================
# IGNY8 Deploy to Staging
# =============================================================================
# Safely deploys code to staging environment
# Usage: ./deploy-staging.sh
# =============================================================================
set -e
APP_DIR="/data/app/igny8"
COMPOSE_FILE="docker-compose.staging.yml"
PROJECT_NAME="igny8-staging"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
echo "=========================================="
echo "IGNY8 STAGING DEPLOYMENT"
echo "=========================================="
echo ""
cd "${APP_DIR}"
# Check if staging compose file exists
if [[ ! -f "${COMPOSE_FILE}" ]]; then
log_error "Staging compose file not found: ${COMPOSE_FILE}"
log "Please create it from the documentation template"
exit 1
fi
# Check if .env.staging exists
if [[ ! -f ".env.staging" ]]; then
log_error "Staging environment file not found: .env.staging"
log "Please create it from .env.example"
exit 1
fi
# Step 1: Pull latest code
log "Step 1/6: Pulling latest code..."
git fetch origin
CURRENT_BRANCH=$(git branch --show-current)
log "Current branch: ${CURRENT_BRANCH}"
if [[ "${CURRENT_BRANCH}" == "staging" ]]; then
git pull origin staging
elif [[ "${CURRENT_BRANCH}" == "main" ]]; then
log_warn "On main branch, switching to staging..."
git checkout staging
git pull origin staging
else
log_warn "On feature branch '${CURRENT_BRANCH}', not pulling..."
fi
# Step 2: Build staging images
log "Step 2/6: Building staging images..."
docker build -t igny8-backend:staging -f backend/Dockerfile backend/
docker build -t igny8-frontend-dev:staging -f frontend/Dockerfile.dev frontend/
docker build -t igny8-marketing-dev:staging -f frontend/Dockerfile.marketing.dev frontend/
log_success "Images built"
# Step 3: Stop existing staging containers
log "Step 3/6: Stopping existing staging containers..."
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" down 2>/dev/null || true
# Step 4: Start staging containers
log "Step 4/6: Starting staging containers..."
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" up -d
# Step 5: Wait for backend to be healthy
log "Step 5/6: Waiting for backend to be healthy..."
for i in {1..30}; do
if docker exec igny8_staging_backend python -c "import urllib.request; urllib.request.urlopen('http://localhost:8010/api/v1/system/status/').read()" 2>/dev/null; then
log_success "Backend is healthy"
break
fi
if [[ $i -eq 30 ]]; then
log_error "Backend failed to become healthy"
docker logs igny8_staging_backend --tail 50
exit 1
fi
echo -n "."
sleep 2
done
# Step 6: Run migrations
log "Step 6/6: Running database migrations..."
docker exec igny8_staging_backend python manage.py migrate --noinput
docker exec igny8_staging_backend python manage.py collectstatic --noinput
echo ""
echo "=========================================="
log_success "STAGING DEPLOYMENT COMPLETE"
echo "=========================================="
echo ""
echo "Staging environment is ready:"
echo " App: https://staging.igny8.com"
echo " API: https://staging-api.igny8.com"
echo ""
echo "View logs:"
echo " docker compose -f ${COMPOSE_FILE} -p ${PROJECT_NAME} logs -f"
echo ""
echo "Container status:"
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" ps
echo "=========================================="

199
scripts/ops/health-check.sh Normal file
View File

@@ -0,0 +1,199 @@
#!/bin/bash
# =============================================================================
# IGNY8 Health Check Script
# =============================================================================
# Checks health of all services and reports status
# Usage: ./health-check.sh [--quiet]
# =============================================================================
set -e
QUIET="${1}"
EXIT_CODE=0
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Output functions
output() {
if [[ "${QUIET}" != "--quiet" ]]; then
echo -e "$1"
fi
}
check_passed() {
output "${GREEN}$1${NC}"
}
check_failed() {
output "${RED}$1${NC}"
EXIT_CODE=1
}
check_warn() {
output "${YELLOW}⚠️ $1${NC}"
}
# Check HTTP endpoint
check_endpoint() {
local name="$1"
local url="$2"
local timeout="${3:-10}"
local response
local http_code
local time_total
response=$(curl -s -o /dev/null -w "%{http_code}|%{time_total}" --max-time "${timeout}" "${url}" 2>/dev/null || echo "000|0")
http_code=$(echo "${response}" | cut -d'|' -f1)
time_total=$(echo "${response}" | cut -d'|' -f2)
if [[ "${http_code}" == "200" ]]; then
check_passed "${name}: OK (${time_total}s)"
return 0
else
check_failed "${name}: FAILED (HTTP ${http_code})"
return 1
fi
}
# Check container status
check_container() {
local name="$1"
if docker ps --format '{{.Names}}' | grep -q "^${name}$"; then
local status=$(docker inspect --format='{{.State.Health.Status}}' "${name}" 2>/dev/null || echo "none")
if [[ "${status}" == "healthy" ]]; then
check_passed "Container ${name}: Running (healthy)"
elif [[ "${status}" == "none" ]]; then
check_passed "Container ${name}: Running (no healthcheck)"
else
check_warn "Container ${name}: Running (${status})"
fi
return 0
else
check_failed "Container ${name}: NOT RUNNING"
return 1
fi
}
# Check disk space
check_disk() {
local path="$1"
local threshold="${2:-80}"
local usage=$(df "${path}" | tail -1 | awk '{print $5}' | sed 's/%//')
if [[ ${usage} -lt ${threshold} ]]; then
check_passed "Disk ${path}: ${usage}% used"
elif [[ ${usage} -lt 90 ]]; then
check_warn "Disk ${path}: ${usage}% used (warning)"
else
check_failed "Disk ${path}: ${usage}% used (critical)"
fi
}
# Check Redis connection
check_redis() {
if docker exec redis redis-cli ping 2>/dev/null | grep -q "PONG"; then
check_passed "Redis: Connected"
return 0
else
check_failed "Redis: NOT RESPONDING"
return 1
fi
}
# Check PostgreSQL connection
check_postgres() {
if docker exec postgres pg_isready -U igny8 2>/dev/null | grep -q "accepting"; then
check_passed "PostgreSQL: Accepting connections"
return 0
else
check_failed "PostgreSQL: NOT READY"
return 1
fi
}
# Check Celery queue
check_celery_queue() {
local queue_length=$(docker exec redis redis-cli llen celery 2>/dev/null || echo "error")
if [[ "${queue_length}" == "error" ]]; then
check_warn "Celery queue: Cannot check"
elif [[ ${queue_length} -lt 50 ]]; then
check_passed "Celery queue: ${queue_length} tasks"
elif [[ ${queue_length} -lt 200 ]]; then
check_warn "Celery queue: ${queue_length} tasks (high)"
else
check_failed "Celery queue: ${queue_length} tasks (critical)"
fi
}
# Main health check
main() {
if [[ "${QUIET}" != "--quiet" ]]; then
echo "=========================================="
echo "IGNY8 Health Check"
echo "$(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
echo ""
fi
# Infrastructure
output "INFRASTRUCTURE:"
check_postgres
check_redis
check_disk "/data"
echo ""
# Production containers
output "PRODUCTION CONTAINERS:"
check_container "igny8_backend"
check_container "igny8_frontend"
check_container "igny8_celery_worker"
check_container "igny8_celery_beat"
echo ""
# Production endpoints
output "PRODUCTION ENDPOINTS:"
check_endpoint "API Status" "http://localhost:8011/api/v1/system/status/" 5
check_endpoint "Frontend" "http://localhost:8021" 5
echo ""
# Celery
output "BACKGROUND TASKS:"
check_celery_queue
echo ""
# Staging (if running)
if docker ps --format '{{.Names}}' | grep -q "igny8_staging_backend"; then
output "STAGING CONTAINERS:"
check_container "igny8_staging_backend"
check_container "igny8_staging_frontend"
echo ""
output "STAGING ENDPOINTS:"
check_endpoint "Staging API" "http://localhost:8012/api/v1/system/status/" 5
check_endpoint "Staging Frontend" "http://localhost:8024" 5
echo ""
fi
# Summary
if [[ "${QUIET}" != "--quiet" ]]; then
echo "=========================================="
if [[ ${EXIT_CODE} -eq 0 ]]; then
echo -e "${GREEN}All checks passed${NC}"
else
echo -e "${RED}Some checks failed${NC}"
fi
echo "=========================================="
fi
exit ${EXIT_CODE}
}
main "$@"

49
scripts/ops/igny8-cron Normal file
View File

@@ -0,0 +1,49 @@
# =============================================================================
# IGNY8 Automated Tasks (Cron Configuration)
# =============================================================================
# Install: sudo cp igny8-cron /etc/cron.d/igny8
# Verify: sudo crontab -l -u root
# =============================================================================
# Environment
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# =============================================================================
# BACKUP JOBS
# =============================================================================
# Daily database backup at 1:00 AM
0 1 * * * root /data/app/igny8/scripts/ops/backup-db.sh daily >> /data/logs/backup.log 2>&1
# Weekly full backup on Sunday at 2:00 AM
0 2 * * 0 root /data/app/igny8/scripts/ops/backup-full.sh weekly >> /data/logs/backup.log 2>&1
# Monthly full backup on 1st of month at 3:00 AM
0 3 1 * * root /data/app/igny8/scripts/ops/backup-full.sh monthly >> /data/logs/backup.log 2>&1
# =============================================================================
# HEALTH & MONITORING
# =============================================================================
# Health check every 5 minutes (logs only failures)
*/5 * * * * root /data/app/igny8/scripts/ops/health-check.sh --quiet || echo "[$(date)] Health check failed" >> /data/logs/health.log 2>&1
# =============================================================================
# MAINTENANCE
# =============================================================================
# Log rotation daily at midnight
0 0 * * * root /data/app/igny8/scripts/ops/log-rotate.sh >> /data/logs/maintenance.log 2>&1
# Docker cleanup weekly on Saturday at 4:00 AM
0 4 * * 6 root docker system prune -f >> /data/logs/maintenance.log 2>&1
# =============================================================================
# OPTIONAL: External backup sync (uncomment if using remote backup)
# =============================================================================
# Sync backups to remote storage daily at 5:00 AM
# 0 5 * * * root rsync -avz /data/backups/ user@backup-server:/backups/igny8/ >> /data/logs/backup.log 2>&1
# =============================================================================

108
scripts/ops/log-rotate.sh Normal file
View File

@@ -0,0 +1,108 @@
#!/bin/bash
# =============================================================================
# IGNY8 Log Rotation Script
# =============================================================================
# Rotates and compresses old log files
# Usage: ./log-rotate.sh
# =============================================================================
set -e
LOG_DIR="/data/logs"
ARCHIVE_DIR="${LOG_DIR}/archive"
RETENTION_DAYS=30
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
echo "=========================================="
echo "IGNY8 Log Rotation"
echo "$(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="
echo ""
# Create archive directory
mkdir -p "${ARCHIVE_DIR}"
# Rotate function
rotate_log() {
local log_file="$1"
local max_size="${2:-50M}"
if [[ ! -f "${log_file}" ]]; then
return
fi
local size=$(stat -f%z "${log_file}" 2>/dev/null || stat -c%s "${log_file}" 2>/dev/null || echo 0)
local max_bytes=$((50 * 1024 * 1024)) # 50MB
if [[ ${size} -gt ${max_bytes} ]]; then
local basename=$(basename "${log_file}")
local timestamp=$(date +%Y%m%d_%H%M%S)
local archive_file="${ARCHIVE_DIR}/${basename}.${timestamp}.gz"
log "Rotating ${log_file} ($(numfmt --to=iec ${size}))"
gzip -c "${log_file}" > "${archive_file}"
> "${log_file}" # Truncate original
log_success "Archived to ${archive_file}"
fi
}
# Rotate production logs
log "Checking production logs..."
for log_file in "${LOG_DIR}/production"/*.log; do
[[ -f "${log_file}" ]] && rotate_log "${log_file}"
done
# Rotate staging logs
log "Checking staging logs..."
for log_file in "${LOG_DIR}/staging"/*.log; do
[[ -f "${log_file}" ]] && rotate_log "${log_file}"
done
# Rotate Caddy logs
log "Checking Caddy logs..."
for log_file in "${LOG_DIR}/caddy"/*.log; do
[[ -f "${log_file}" ]] && rotate_log "${log_file}"
done
# Clean up old archives
log "Cleaning old archives (> ${RETENTION_DAYS} days)..."
DELETED=$(find "${ARCHIVE_DIR}" -name "*.gz" -mtime +${RETENTION_DAYS} -delete -print | wc -l)
if [[ ${DELETED} -gt 0 ]]; then
log "Deleted ${DELETED} old archive files"
fi
# Docker container logs
log "Pruning Docker container logs..."
for container in igny8_backend igny8_frontend igny8_celery_worker igny8_celery_beat; do
if docker ps -a --format '{{.Names}}' | grep -q "^${container}$"; then
LOG_FILE=$(docker inspect --format='{{.LogPath}}' "${container}" 2>/dev/null || true)
if [[ -n "${LOG_FILE}" ]] && [[ -f "${LOG_FILE}" ]]; then
local size=$(stat -c%s "${LOG_FILE}" 2>/dev/null || echo 0)
if [[ ${size} -gt $((100 * 1024 * 1024)) ]]; then # 100MB
log "Truncating Docker log for ${container}"
truncate -s 0 "${LOG_FILE}"
fi
fi
fi
done
# Report disk usage
echo ""
echo "Log disk usage:"
du -sh "${LOG_DIR}"/* 2>/dev/null || echo "No logs found"
echo ""
log_success "Log rotation complete"
echo "=========================================="

142
scripts/ops/restore-db.sh Normal file
View File

@@ -0,0 +1,142 @@
#!/bin/bash
# =============================================================================
# IGNY8 Database Restore Script
# =============================================================================
# Usage: ./restore-db.sh <backup_file> [target_db]
# Restores database from a backup file (.sql or .sql.gz)
# =============================================================================
set -e
# Configuration
BACKUP_FILE="${1}"
TARGET_DB="${2:-igny8_db}"
DB_CONTAINER="postgres"
DB_USER="igny8"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log() {
echo -e "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
log_success() {
log "${GREEN}$1${NC}"
}
log_warn() {
log "${YELLOW}⚠️ $1${NC}"
}
log_error() {
log "${RED}$1${NC}"
}
# Validate input
if [[ -z "${BACKUP_FILE}" ]]; then
echo "Usage: ./restore-db.sh <backup_file> [target_db]"
echo ""
echo "Arguments:"
echo " backup_file Path to backup file (.sql or .sql.gz)"
echo " target_db Target database (default: igny8_db)"
echo ""
echo "Available backups:"
ls -la /data/backups/latest_db.sql.gz 2>/dev/null || echo " No latest backup found"
echo ""
find /data/backups -name "*.sql.gz" -type f 2>/dev/null | head -10
exit 1
fi
if [[ ! -f "${BACKUP_FILE}" ]]; then
log_error "Backup file not found: ${BACKUP_FILE}"
exit 1
fi
echo "=========================================="
echo "IGNY8 Database Restore"
echo "=========================================="
echo ""
echo "Backup file: ${BACKUP_FILE}"
echo "Target DB: ${TARGET_DB}"
echo ""
# Safety confirmation
log_warn "This will REPLACE the database '${TARGET_DB}' with backup data!"
read -p "Are you sure? Type 'yes' to continue: " confirm
if [[ "${confirm}" != "yes" ]]; then
log "Restore cancelled"
exit 0
fi
# Check if postgres container is running
if ! docker ps --format '{{.Names}}' | grep -q "^${DB_CONTAINER}$"; then
log_error "PostgreSQL container '${DB_CONTAINER}' is not running"
exit 1
fi
# Create pre-restore backup
log "Creating pre-restore backup of current database..."
PRE_RESTORE_BACKUP="/data/backups/pre-restore_${TARGET_DB}_$(date +%Y%m%d_%H%M%S).sql.gz"
if docker exec "${DB_CONTAINER}" pg_dump -U "${DB_USER}" "${TARGET_DB}" 2>/dev/null | gzip > "${PRE_RESTORE_BACKUP}"; then
log_success "Pre-restore backup: ${PRE_RESTORE_BACKUP}"
else
log_warn "Could not create pre-restore backup (database might not exist)"
fi
# Stop application containers to prevent connections
log "Stopping application containers..."
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app stop igny8_backend igny8_celery_worker igny8_celery_beat 2>/dev/null || true
# Drop and recreate database
log "Dropping existing database..."
docker exec "${DB_CONTAINER}" psql -U postgres -c "DROP DATABASE IF EXISTS ${TARGET_DB};" || true
log "Creating fresh database..."
docker exec "${DB_CONTAINER}" psql -U postgres -c "CREATE DATABASE ${TARGET_DB} OWNER ${DB_USER};"
# Restore from backup
log "Restoring database from backup..."
if [[ "${BACKUP_FILE}" == *.gz ]]; then
# Compressed backup
gunzip -c "${BACKUP_FILE}" | docker exec -i "${DB_CONTAINER}" psql -U "${DB_USER}" -d "${TARGET_DB}"
else
# Uncompressed backup
docker exec -i "${DB_CONTAINER}" psql -U "${DB_USER}" -d "${TARGET_DB}" < "${BACKUP_FILE}"
fi
if [[ $? -eq 0 ]]; then
log_success "Database restore complete"
else
log_error "Database restore failed"
exit 1
fi
# Restart application containers
log "Restarting application containers..."
docker compose -f /data/app/igny8/docker-compose.app.yml -p igny8-app start igny8_backend igny8_celery_worker igny8_celery_beat 2>/dev/null || true
# Wait for backend to be healthy
log "Waiting for backend to be healthy..."
sleep 10
# Verify restoration
log "Verifying database..."
TABLE_COUNT=$(docker exec "${DB_CONTAINER}" psql -U "${DB_USER}" -d "${TARGET_DB}" -t -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public';")
log_success "Database restored with ${TABLE_COUNT} tables"
echo ""
echo "=========================================="
echo "RESTORE COMPLETE"
echo "=========================================="
echo "Database '${TARGET_DB}' has been restored"
echo ""
echo "Pre-restore backup saved to:"
echo " ${PRE_RESTORE_BACKUP}"
echo ""
echo "To rollback this restore, run:"
echo " ./restore-db.sh ${PRE_RESTORE_BACKUP} ${TARGET_DB}"
echo "=========================================="

108
scripts/ops/rollback.sh Normal file
View File

@@ -0,0 +1,108 @@
#!/bin/bash
# =============================================================================
# IGNY8 Rollback Script
# =============================================================================
# Rolls back production to previous version using tagged images
# Usage: ./rollback.sh [--auto]
# =============================================================================
set -e
APP_DIR="/data/app/igny8"
COMPOSE_FILE="docker-compose.app.yml"
PROJECT_NAME="igny8-app"
AUTO_MODE="${1}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
echo "=========================================="
echo "IGNY8 PRODUCTION ROLLBACK"
echo "=========================================="
echo ""
# Check for rollback images
if ! docker image inspect igny8-backend:rollback &>/dev/null; then
log_error "No rollback image found!"
log "Rollback images are created during deployment."
log "To restore from backup, use:"
log " /data/app/igny8/scripts/ops/restore-db.sh /data/backups/latest_db.sql.gz"
exit 1
fi
# Confirmation (unless auto mode)
if [[ "${AUTO_MODE}" != "--auto" ]]; then
log_warn "This will rollback production to the previous version!"
read -p "Type 'rollback' to continue: " confirm
if [[ "${confirm}" != "rollback" ]]; then
log "Rollback cancelled"
exit 0
fi
fi
cd "${APP_DIR}"
# Step 1: Stop current containers
log "Step 1/4: Stopping current containers..."
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" down
# Step 2: Restore rollback images
log "Step 2/4: Restoring previous images..."
docker tag igny8-backend:rollback igny8-backend:latest
docker tag igny8-frontend-dev:rollback igny8-frontend-dev:latest
docker tag igny8-marketing-dev:rollback igny8-marketing-dev:latest 2>/dev/null || true
log_success "Images restored"
# Step 3: Start containers
log "Step 3/4: Starting containers with previous version..."
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" up -d
# Step 4: Wait for backend
log "Step 4/4: Waiting for backend to be healthy..."
for i in {1..30}; do
if docker exec igny8_backend python -c "import urllib.request; urllib.request.urlopen('http://localhost:8010/api/v1/system/status/').read()" 2>/dev/null; then
log_success "Backend is healthy"
break
fi
if [[ $i -eq 30 ]]; then
log_error "Backend failed to start after rollback"
log "Manual intervention required!"
exit 1
fi
echo -n "."
sleep 2
done
echo ""
echo "=========================================="
log_success "ROLLBACK COMPLETE"
echo "=========================================="
echo ""
echo "Production is running on previous version."
echo ""
echo "If database rollback is also needed:"
echo " /data/app/igny8/scripts/ops/restore-db.sh /data/backups/pre-deploy/LATEST/db_igny8_*.sql.gz"
echo ""
echo "Container status:"
docker compose -f "${COMPOSE_FILE}" -p "${PROJECT_NAME}" ps
echo "=========================================="

View File

@@ -0,0 +1,127 @@
#!/bin/bash
# =============================================================================
# IGNY8 Sync Production Data to Staging
# =============================================================================
# Syncs production database to staging with data sanitization
# Usage: ./sync-prod-to-staging.sh
# =============================================================================
set -e
DB_CONTAINER="postgres"
PROD_DB="igny8_db"
STAGING_DB="igny8_staging_db"
DB_USER="igny8"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
echo -e "${BLUE}[$(date '+%H:%M:%S')]${NC} $1"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warn() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
echo "=========================================="
echo "SYNC PRODUCTION → STAGING"
echo "=========================================="
echo ""
log_warn "This will REPLACE staging database with production data!"
log_warn "Data will be sanitized (emails, passwords anonymized)"
echo ""
read -p "Type 'sync' to continue: " confirm
if [[ "${confirm}" != "sync" ]]; then
log "Sync cancelled"
exit 0
fi
# Check if staging containers are running
STAGING_RUNNING=$(docker ps --format '{{.Names}}' | grep -c "igny8_staging" || true)
if [[ ${STAGING_RUNNING} -gt 0 ]]; then
log "Stopping staging containers..."
docker compose -f /data/app/igny8/docker-compose.staging.yml -p igny8-staging stop igny8_staging_backend igny8_staging_celery_worker igny8_staging_celery_beat 2>/dev/null || true
fi
# Step 1: Dump production
log "Step 1/5: Dumping production database..."
TEMP_DUMP="/tmp/prod_sync_$(date +%Y%m%d_%H%M%S).sql"
docker exec "${DB_CONTAINER}" pg_dump -U "${DB_USER}" "${PROD_DB}" > "${TEMP_DUMP}"
log_success "Production dump created: ${TEMP_DUMP}"
# Step 2: Drop staging database
log "Step 2/5: Dropping staging database..."
docker exec "${DB_CONTAINER}" psql -U postgres -c "DROP DATABASE IF EXISTS ${STAGING_DB};"
docker exec "${DB_CONTAINER}" psql -U postgres -c "CREATE DATABASE ${STAGING_DB} OWNER ${DB_USER};"
log_success "Staging database recreated"
# Step 3: Restore to staging
log "Step 3/5: Restoring to staging database..."
docker exec -i "${DB_CONTAINER}" psql -U "${DB_USER}" -d "${STAGING_DB}" < "${TEMP_DUMP}"
log_success "Data restored to staging"
# Step 4: Sanitize data
log "Step 4/5: Sanitizing staging data..."
docker exec "${DB_CONTAINER}" psql -U "${DB_USER}" -d "${STAGING_DB}" << 'EOF'
-- Sanitize user emails (keep superusers intact)
UPDATE auth_user
SET email = CONCAT('staging_', id, '@igny8.test')
WHERE is_superuser = FALSE;
-- Invalidate all sessions
DELETE FROM django_session;
-- Update notification endpoints to staging
UPDATE notification_settings
SET webhook_url = REPLACE(webhook_url, 'app.igny8.com', 'staging.igny8.com')
WHERE webhook_url IS NOT NULL;
-- Log sanitization
SELECT 'Sanitized ' || COUNT(*) || ' user emails' FROM auth_user WHERE email LIKE '%@igny8.test';
EOF
log_success "Data sanitized"
# Step 5: Run migrations (in case staging has newer schema)
log "Step 5/5: Checking migrations..."
if docker ps --format '{{.Names}}' | grep -q "igny8_staging_backend"; then
docker exec igny8_staging_backend python manage.py migrate --noinput 2>/dev/null || true
fi
# Cleanup
rm -f "${TEMP_DUMP}"
# Restart staging
if [[ ${STAGING_RUNNING} -gt 0 ]]; then
log "Restarting staging containers..."
docker compose -f /data/app/igny8/docker-compose.staging.yml -p igny8-staging start igny8_staging_backend igny8_staging_celery_worker igny8_staging_celery_beat 2>/dev/null || true
fi
echo ""
echo "=========================================="
log_success "SYNC COMPLETE"
echo "=========================================="
echo ""
echo "Staging database now contains sanitized production data."
echo ""
echo "Changes made:"
echo " - User emails changed to staging_*@igny8.test"
echo " - All sessions invalidated"
echo " - Webhook URLs updated to staging domain"
echo ""
echo "To test with a specific user:"
echo " docker exec -it igny8_staging_backend python manage.py changepassword <username>"
echo "=========================================="