Barazo Docker Compose templates for self-hosting barazo.forum

feat(ops): add backup/restore scripts, smoke test, and documentation (#6)

Scripts:
- backup.sh: PostgreSQL dump with optional age encryption, auto-cleanup
- restore.sh: Interactive restore with confirmation, GDPR compliance note
- smoke-test.sh: Validates local or remote deployment (services, API, SSL)

Documentation:
- installation.md: Step-by-step server setup and first deploy
- configuration.md: All environment variables with types and defaults
- upgrading.md: Standard upgrade, pinned versions, rollback procedure
- administration.md: Monitoring, logs, common tasks, troubleshooting
- backups.md: Automated backups, encryption, restore, disaster recovery

authored by

Guido X Jansen and committed by
GitHub
c87123a4 822ce89d

+963
+163
docs/administration.md
··· 1 + # Administration Guide 2 + 3 + Day-to-day administration of a running Barazo instance. 4 + 5 + ## Monitoring 6 + 7 + ### Service Status 8 + 9 + ```bash 10 + # Check all services 11 + docker compose ps 12 + 13 + # Check specific service 14 + docker compose ps barazo-api 15 + ``` 16 + 17 + All services should show `healthy` status. 18 + 19 + ### Logs 20 + 21 + ```bash 22 + # All services 23 + docker compose logs -f 24 + 25 + # Specific service (last 100 lines, follow) 26 + docker compose logs -f --tail 100 barazo-api 27 + 28 + # Specific service without follow 29 + docker compose logs --tail 50 postgres 30 + ``` 31 + 32 + ### Smoke Test 33 + 34 + Run the smoke test to verify everything is working: 35 + 36 + ```bash 37 + ./scripts/smoke-test.sh https://your-domain.com 38 + ``` 39 + 40 + ### Resource Usage 41 + 42 + ```bash 43 + docker stats --no-stream 44 + ``` 45 + 46 + ## Backups 47 + 48 + ### Automated Backups (Recommended) 49 + 50 + Set up a daily backup via cron: 51 + 52 + ```bash 53 + crontab -e 54 + ``` 55 + 56 + Add this line (runs daily at 2 AM): 57 + 58 + ``` 59 + 0 2 * * * cd /path/to/barazo-deploy && ./scripts/backup.sh --encrypt >> /var/log/barazo-backup.log 2>&1 60 + ``` 61 + 62 + Requires `BACKUP_PUBLIC_KEY` in your `.env` file. 63 + 64 + ### Manual Backup 65 + 66 + ```bash 67 + ./scripts/backup.sh # Unencrypted (local storage only) 68 + ./scripts/backup.sh --encrypt # Encrypted (safe for off-server storage) 69 + ``` 70 + 71 + Backups are saved to `./backups/` with timestamps. Old backups are automatically cleaned up after 7 days (configurable via `BACKUP_RETAIN_DAYS`). 72 + 73 + ### Restore 74 + 75 + ```bash 76 + ./scripts/restore.sh backups/barazo-backup-20260214-020000.sql.gz 77 + ``` 78 + 79 + See [Backup & Restore](backups.md) for full documentation. 80 + 81 + ## Common Tasks 82 + 83 + ### Restart a Service 84 + 85 + ```bash 86 + docker compose restart barazo-api 87 + ``` 88 + 89 + ### Stop Everything 90 + 91 + ```bash 92 + docker compose down # Stops containers (preserves data) 93 + docker compose down -v # Stops containers AND deletes all data 94 + ``` 95 + 96 + ### Connect to Database 97 + 98 + ```bash 99 + docker compose exec postgres psql -U barazo 100 + ``` 101 + 102 + ### View Caddy Access Logs 103 + 104 + ```bash 105 + docker compose logs caddy 106 + ``` 107 + 108 + ### Force SSL Certificate Renewal 109 + 110 + Caddy renews certificates automatically. If you need to force renewal: 111 + 112 + ```bash 113 + docker compose restart caddy 114 + ``` 115 + 116 + ### Update Images Without Restart 117 + 118 + ```bash 119 + docker compose pull # Pull new images 120 + docker compose up -d # Restart only changed services 121 + ``` 122 + 123 + ## Troubleshooting 124 + 125 + ### Service Won't Start 126 + 127 + ```bash 128 + # Check the logs 129 + docker compose logs <service-name> 130 + 131 + # Check if port is already in use 132 + sudo lsof -i :80 133 + sudo lsof -i :443 134 + ``` 135 + 136 + ### Out of Disk Space 137 + 138 + ```bash 139 + # Check disk usage 140 + df -h 141 + 142 + # Clean up Docker resources 143 + docker system prune -f # Remove stopped containers, unused images 144 + docker volume prune -f # Remove unused volumes (careful!) 145 + ``` 146 + 147 + ### High Memory Usage 148 + 149 + Check which service is consuming memory: 150 + 151 + ```bash 152 + docker stats --no-stream 153 + ``` 154 + 155 + Consider enabling resource limits in `docker-compose.yml` (uncomment the `mem_limit` lines). 156 + 157 + ### Database Is Slow 158 + 159 + Connect to PostgreSQL and check for long-running queries: 160 + 161 + ```bash 162 + docker compose exec postgres psql -U barazo -c "SELECT pid, now() - query_start AS duration, query FROM pg_stat_activity WHERE state = 'active' ORDER BY duration DESC LIMIT 5;" 163 + ```
+136
docs/backups.md
··· 1 + # Backup & Restore 2 + 3 + How to back up and restore your Barazo forum data. 4 + 5 + ## What Gets Backed Up 6 + 7 + | Data | Backed up | Priority | 8 + |------|-----------|----------| 9 + | PostgreSQL (topics, replies, users, settings) | Yes | Critical | 10 + | Valkey (cache, sessions) | No | Low -- regenerated automatically | 11 + | Caddy (SSL certificates) | No | Medium -- re-obtained automatically by Let's Encrypt | 12 + | Tap (firehose cursor) | No | Low -- re-syncs from the relay | 13 + | Plugins | No | Medium -- reinstallable from npm | 14 + 15 + The backup script creates a PostgreSQL dump. This contains all forum data (topics, replies, users, categories, settings, moderation logs). 16 + 17 + ## Creating Backups 18 + 19 + ### Manual Backup 20 + 21 + ```bash 22 + # Unencrypted (keep on server only) 23 + ./scripts/backup.sh 24 + 25 + # Encrypted (safe to store off-server) 26 + ./scripts/backup.sh --encrypt 27 + ``` 28 + 29 + Backups are saved to `./backups/` with filenames like `barazo-backup-20260214-020000.sql.gz`. 30 + 31 + ### Automated Backups 32 + 33 + Add a cron job for daily backups: 34 + 35 + ```bash 36 + crontab -e 37 + ``` 38 + 39 + ``` 40 + # Daily at 2 AM, encrypted 41 + 0 2 * * * cd /path/to/barazo-deploy && ./scripts/backup.sh --encrypt >> /var/log/barazo-backup.log 2>&1 42 + ``` 43 + 44 + ### Backup Encryption 45 + 46 + Backups contain user content and potentially PII. Encrypt before storing off-server. 47 + 48 + **Setup age encryption:** 49 + 50 + ```bash 51 + # Generate a keypair (do this once) 52 + age-keygen -o barazo-backup-key.txt 53 + 54 + # The public key is printed to the terminal -- add it to .env 55 + # BACKUP_PUBLIC_KEY="age1..." 56 + 57 + # Store the private key file securely (NOT on the same server) 58 + # You need this to decrypt backups 59 + ``` 60 + 61 + **Install age:** 62 + 63 + ```bash 64 + # Ubuntu/Debian 65 + sudo apt install age 66 + 67 + # macOS 68 + brew install age 69 + ``` 70 + 71 + ### Backup Retention 72 + 73 + By default, backups older than 7 days are automatically deleted. Change this with `BACKUP_RETAIN_DAYS` in `.env`: 74 + 75 + ```bash 76 + BACKUP_RETAIN_DAYS=30 # Keep 30 days 77 + BACKUP_RETAIN_DAYS=0 # Never delete (manage manually) 78 + ``` 79 + 80 + ## Restoring from Backup 81 + 82 + ### Standard Restore 83 + 84 + ```bash 85 + ./scripts/restore.sh backups/barazo-backup-20260214-020000.sql.gz 86 + ``` 87 + 88 + ### Encrypted Backup Restore 89 + 90 + ```bash 91 + BACKUP_PRIVATE_KEY_FILE=/path/to/barazo-backup-key.txt \ 92 + ./scripts/restore.sh backups/barazo-backup-20260214-020000.sql.gz.age 93 + ``` 94 + 95 + ### What the Restore Script Does 96 + 97 + 1. Stops the API and Web services (prevents writes during restore) 98 + 2. Drops and recreates the database 99 + 3. Restores the SQL dump 100 + 4. Restarts the API and Web services 101 + 5. Verifies the database has tables 102 + 103 + ### After Restoring 104 + 105 + **GDPR compliance:** If the backup is older than the most recent data, you must re-apply deletions that occurred after the backup date. Check the `deletion_log` table for events after the backup timestamp. 106 + 107 + ## Disaster Recovery 108 + 109 + If your server is completely lost: 110 + 111 + 1. **Provision a new server** (same OS, Docker installed) 112 + 2. **Clone the deploy repo** and copy your `.env` file 113 + 3. **Start services:** `docker compose up -d` 114 + 4. **Restore the database** from your most recent backup 115 + 5. **Run the smoke test** to verify: `./scripts/smoke-test.sh https://your-domain.com` 116 + 117 + Caddy will automatically obtain a new SSL certificate. The Tap service will re-sync from the relay. Valkey cache will rebuild as users access the forum. 118 + 119 + ## Verifying Backups 120 + 121 + Periodically verify that your backups are valid: 122 + 123 + ```bash 124 + # List backup files 125 + ls -lh backups/ 126 + 127 + # Test a backup by restoring to a separate database 128 + docker compose exec postgres psql -U barazo -d postgres \ 129 + -c "CREATE DATABASE barazo_test;" 130 + gunzip -c backups/barazo-backup-LATEST.sql.gz \ 131 + | docker compose exec -T postgres psql -U barazo -d barazo_test -q 132 + docker compose exec postgres psql -U barazo -d barazo_test \ 133 + -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';" 134 + docker compose exec postgres psql -U barazo -d postgres \ 135 + -c "DROP DATABASE barazo_test;" 136 + ```
+96
docs/configuration.md
··· 1 + # Configuration Reference 2 + 3 + All Barazo environment variables with descriptions, defaults, and examples. 4 + 5 + ## Community Identity 6 + 7 + | Variable | Required | Default | Description | 8 + |----------|----------|---------|-------------| 9 + | `COMMUNITY_NAME` | Yes | `"My Community"` | Display name for your forum | 10 + | `COMMUNITY_DOMAIN` | Yes | -- | Domain name (e.g., `forum.example.com`). Used by Caddy for SSL. | 11 + | `COMMUNITY_DID` | No | -- | AT Protocol DID. Created automatically during first setup. | 12 + | `COMMUNITY_MODE` | No | `single` | `single` for one community, `global` for aggregator mode | 13 + 14 + ## Database (PostgreSQL) 15 + 16 + | Variable | Required | Default | Description | 17 + |----------|----------|---------|-------------| 18 + | `POSTGRES_USER` | Yes | -- | PostgreSQL superuser name | 19 + | `POSTGRES_PASSWORD` | Yes | -- | PostgreSQL superuser password | 20 + | `POSTGRES_DB` | Yes | -- | Database name | 21 + | `POSTGRES_PORT` | No | `5432` | Host port mapping (dev compose only) | 22 + | `DATABASE_URL` | Yes | -- | Connection string for the API. Format: `postgresql://user:pass@postgres:5432/dbname` | 23 + | `MIGRATION_DATABASE_URL` | No | -- | Connection string for migrations (DDL role, if using role separation) | 24 + 25 + ## Cache (Valkey) 26 + 27 + | Variable | Required | Default | Description | 28 + |----------|----------|---------|-------------| 29 + | `VALKEY_PASSWORD` | Prod: Yes | -- | Valkey authentication password. Dangerous commands are disabled. | 30 + | `VALKEY_PORT` | No | `6379` | Host port mapping (dev compose only) | 31 + | `VALKEY_URL` | No | Auto | Connection URL for the API. Auto-constructed in compose. | 32 + 33 + ## AT Protocol 34 + 35 + | Variable | Required | Default | Description | 36 + |----------|----------|---------|-------------| 37 + | `RELAY_URL` | No | `wss://bsky.network` | Bluesky relay URL for firehose | 38 + | `TAP_ADMIN_PASSWORD` | Yes | -- | Tap admin API password | 39 + | `TAP_PORT` | No | `2480` | Host port mapping (dev compose only) | 40 + | `OAUTH_CLIENT_ID` | Yes | -- | Your forum's public URL (e.g., `https://forum.example.com`) | 41 + | `OAUTH_REDIRECT_URI` | Yes | -- | OAuth callback URL (e.g., `https://forum.example.com/api/auth/callback`) | 42 + 43 + ## Frontend 44 + 45 + | Variable | Required | Default | Description | 46 + |----------|----------|---------|-------------| 47 + | `NEXT_PUBLIC_API_URL` | Yes | -- | Public API URL as seen by browsers (e.g., `https://forum.example.com/api`) | 48 + | `NEXT_PUBLIC_SITE_URL` | Yes | -- | Public site URL (e.g., `https://forum.example.com`) | 49 + 50 + ## Image Versions 51 + 52 + | Variable | Required | Default | Description | 53 + |----------|----------|---------|-------------| 54 + | `BARAZO_API_VERSION` | No | `latest` | API Docker image tag. Pin to a specific version in production (e.g., `1.2.3`). | 55 + | `BARAZO_WEB_VERSION` | No | `latest` | Web Docker image tag. Pin to a specific version in production. | 56 + 57 + ## Search 58 + 59 + | Variable | Required | Default | Description | 60 + |----------|----------|---------|-------------| 61 + | `EMBEDDING_URL` | No | -- | Enables hybrid semantic search. Example: `http://ollama:11434/api/embeddings` | 62 + | `AI_EMBEDDING_DIMENSIONS` | No | `768` | Vector dimensions (must match your embedding model) | 63 + 64 + ## Encryption 65 + 66 + | Variable | Required | Default | Description | 67 + |----------|----------|---------|-------------| 68 + | `AI_ENCRYPTION_KEY` | Conditional | -- | AES-256-GCM key for encrypting BYOK API keys at rest. Required if BYOK features are used. Generate: `openssl rand -base64 32` | 69 + 70 + ## Cross-Posting 71 + 72 + | Variable | Required | Default | Description | 73 + |----------|----------|---------|-------------| 74 + | `FEATURE_CROSSPOST_FRONTPAGE` | No | `false` | Enable Frontpage cross-posting (Bluesky cross-posting is always available) | 75 + 76 + ## Plugins 77 + 78 + | Variable | Required | Default | Description | 79 + |----------|----------|---------|-------------| 80 + | `PLUGINS_ENABLED` | No | `true` | Set to `false` to disable all plugins | 81 + | `PLUGIN_REGISTRY_URL` | No | `https://registry.npmjs.org` | npm registry URL for plugin installation | 82 + 83 + ## Monitoring 84 + 85 + | Variable | Required | Default | Description | 86 + |----------|----------|---------|-------------| 87 + | `GLITCHTIP_DSN` | No | -- | GlitchTip/Sentry DSN for error reporting | 88 + | `LOG_LEVEL` | No | `info` | Pino log level: `trace`, `debug`, `info`, `warn`, `error`, `fatal` | 89 + 90 + ## Backups 91 + 92 + | Variable | Required | Default | Description | 93 + |----------|----------|---------|-------------| 94 + | `BACKUP_PUBLIC_KEY` | No | -- | age public key for encrypting backups. Generate: `age-keygen -o key.txt` | 95 + | `BACKUP_DIR` | No | `./backups` | Directory for backup files | 96 + | `BACKUP_RETAIN_DAYS` | No | `7` | Days to keep old backups before cleanup |
+121
docs/installation.md
··· 1 + # Installation Guide 2 + 3 + Step-by-step guide to deploying a Barazo forum on your own server. 4 + 5 + ## Prerequisites 6 + 7 + - **VPS or dedicated server** with a public IP address 8 + - Minimum: 2 vCPU, 4 GB RAM, 20 GB SSD (Hetzner CX22 recommended) 9 + - Linux (Ubuntu 22.04+ or Debian 12+ recommended) 10 + - **Domain name** with a DNS A record pointing to your server's IP 11 + - **Docker** v24+ and **Docker Compose** v2 12 + - **SSH access** to your server 13 + 14 + ## 1. Install Docker 15 + 16 + If Docker is not already installed: 17 + 18 + ```bash 19 + # Install Docker (official script) 20 + curl -fsSL https://get.docker.com | sh 21 + 22 + # Add your user to the docker group (avoids sudo for docker commands) 23 + sudo usermod -aG docker $USER 24 + 25 + # Log out and back in for group change to take effect 26 + exit 27 + # SSH back in 28 + 29 + # Verify 30 + docker --version 31 + docker compose version 32 + ``` 33 + 34 + ## 2. Clone the Repository 35 + 36 + ```bash 37 + git clone https://github.com/barazo-forum/barazo-deploy.git 38 + cd barazo-deploy 39 + ``` 40 + 41 + ## 3. Configure Environment 42 + 43 + ```bash 44 + cp .env.example .env 45 + nano .env # or your preferred editor 46 + ``` 47 + 48 + **Required variables to set:** 49 + 50 + | Variable | What to set | 51 + |----------|-------------| 52 + | `COMMUNITY_NAME` | Your forum's display name | 53 + | `COMMUNITY_DOMAIN` | Your domain (e.g., `forum.example.com`) | 54 + | `POSTGRES_PASSWORD` | Strong random password | 55 + | `VALKEY_PASSWORD` | Strong random password | 56 + | `TAP_ADMIN_PASSWORD` | Strong random password | 57 + | `DATABASE_URL` | Update the password to match `POSTGRES_PASSWORD` | 58 + | `OAUTH_CLIENT_ID` | `https://your-domain.com` | 59 + | `OAUTH_REDIRECT_URI` | `https://your-domain.com/api/auth/callback` | 60 + | `NEXT_PUBLIC_API_URL` | `https://your-domain.com/api` | 61 + | `NEXT_PUBLIC_SITE_URL` | `https://your-domain.com` | 62 + 63 + Generate passwords with: 64 + 65 + ```bash 66 + openssl rand -base64 24 67 + ``` 68 + 69 + ## 4. Start Services 70 + 71 + ```bash 72 + docker compose up -d 73 + ``` 74 + 75 + This starts all 6 services: PostgreSQL, Valkey, Tap, API, Web, and Caddy. 76 + 77 + First startup may pull several Docker images (allow a few minutes on slower connections). 78 + 79 + ## 5. Verify Installation 80 + 81 + ```bash 82 + # Check all services are running 83 + docker compose ps 84 + 85 + # Run smoke test 86 + ./scripts/smoke-test.sh https://your-domain.com 87 + ``` 88 + 89 + All services should show `healthy` status. Caddy automatically obtains an SSL certificate from Let's Encrypt on first request. 90 + 91 + Visit `https://your-domain.com` in your browser. You should see the Barazo forum. 92 + 93 + ## 6. First-Time Setup 94 + 95 + The first user to complete the setup wizard becomes the community administrator. Open your forum URL and follow the on-screen instructions to: 96 + 97 + 1. Sign in with your AT Protocol account (Bluesky) 98 + 2. Set community name and description 99 + 3. Create initial categories 100 + 4. Configure moderation settings 101 + 102 + ## Troubleshooting 103 + 104 + **Caddy fails to obtain SSL certificate:** 105 + 106 + - Verify your DNS A record points to the server's IP: `dig +short your-domain.com` 107 + - Ensure ports 80 and 443 are open in your firewall 108 + - Check Caddy logs: `docker compose logs caddy` 109 + 110 + **Services fail to start:** 111 + 112 + - Check logs: `docker compose logs` 113 + - Verify `.env` has no syntax errors 114 + - Ensure all required variables are set (no `CHANGE_ME` remaining) 115 + 116 + **Database connection errors:** 117 + 118 + - Verify `DATABASE_URL` password matches `POSTGRES_PASSWORD` 119 + - Check PostgreSQL logs: `docker compose logs postgres` 120 + 121 + See also: [Troubleshooting](https://github.com/barazo-forum/barazo-deploy#troubleshooting) in README.
+79
docs/upgrading.md
··· 1 + # Upgrade Guide 2 + 3 + How to upgrade your Barazo installation to a new version. 4 + 5 + ## Standard Upgrade 6 + 7 + ```bash 8 + cd barazo-deploy 9 + 10 + # Pull new images 11 + docker compose pull 12 + 13 + # Restart with new versions 14 + docker compose up -d 15 + 16 + # Verify 17 + docker compose ps 18 + ./scripts/smoke-test.sh https://your-domain.com 19 + ``` 20 + 21 + Database migrations run automatically when the API starts. No manual migration step is needed. 22 + 23 + ## Pinned Version Upgrade 24 + 25 + If you pin image versions in `.env` (recommended for production): 26 + 27 + ```bash 28 + # Edit .env to update versions 29 + nano .env 30 + # Change BARAZO_API_VERSION=1.2.3 to BARAZO_API_VERSION=1.3.0 31 + # Change BARAZO_WEB_VERSION=1.2.3 to BARAZO_WEB_VERSION=1.3.0 32 + 33 + # Pull and restart 34 + docker compose pull 35 + docker compose up -d 36 + ``` 37 + 38 + ## Pre-Upgrade Checklist 39 + 40 + 1. **Read the changelog** for the new version -- check for breaking changes 41 + 2. **Create a backup** before upgrading: 42 + ```bash 43 + ./scripts/backup.sh 44 + ``` 45 + 3. **Test on staging first** if you have a staging environment 46 + 47 + ## Rollback 48 + 49 + If the upgrade causes issues: 50 + 51 + ```bash 52 + # Stop services 53 + docker compose down 54 + 55 + # Edit .env to revert to previous version 56 + nano .env 57 + # Change versions back to previous values 58 + 59 + # Pull previous images 60 + docker compose pull 61 + 62 + # Restore database from pre-upgrade backup 63 + ./scripts/restore.sh backups/barazo-backup-YYYYMMDD-HHMMSS.sql.gz 64 + 65 + # Start services 66 + docker compose up -d 67 + 68 + # Verify 69 + docker compose ps 70 + ``` 71 + 72 + ## Breaking Changes 73 + 74 + Major version bumps (e.g., 1.x to 2.x) may include breaking changes that require manual steps. These are documented in the release notes and CHANGELOG.md. 75 + 76 + Common breaking changes to watch for: 77 + - **Environment variable renames** -- update your `.env` file 78 + - **Database schema changes** -- migrations run automatically, but rollback may require the backup 79 + - **Caddy configuration changes** -- check if Caddyfile needs updates
+99
scripts/backup.sh
··· 1 + #!/usr/bin/env bash 2 + # Barazo Backup Script 3 + # 4 + # Creates a compressed PostgreSQL backup with timestamp. 5 + # Optionally encrypts with age for off-server storage. 6 + # 7 + # Usage: 8 + # ./scripts/backup.sh # Plain backup (local storage only) 9 + # ./scripts/backup.sh --encrypt # Encrypted backup (requires BACKUP_PUBLIC_KEY) 10 + # 11 + # Environment: 12 + # BACKUP_DIR Backup directory (default: ./backups) 13 + # BACKUP_RETAIN_DAYS Days to keep old backups (default: 7) 14 + # BACKUP_PUBLIC_KEY age public key for encryption (required with --encrypt) 15 + # COMPOSE_FILE Docker Compose file (default: docker-compose.yml) 16 + 17 + set -euo pipefail 18 + 19 + BACKUP_DIR="${BACKUP_DIR:-./backups}" 20 + BACKUP_RETAIN_DAYS="${BACKUP_RETAIN_DAYS:-7}" 21 + COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" 22 + ENCRYPT=false 23 + 24 + # Parse arguments 25 + for arg in "$@"; do 26 + case "$arg" in 27 + --encrypt) ENCRYPT=true ;; 28 + --help|-h) 29 + echo "Usage: $0 [--encrypt]" 30 + echo "" 31 + echo "Options:" 32 + echo " --encrypt Encrypt backup with age (requires BACKUP_PUBLIC_KEY)" 33 + echo "" 34 + echo "Environment variables:" 35 + echo " BACKUP_DIR Backup directory (default: ./backups)" 36 + echo " BACKUP_RETAIN_DAYS Days to keep old backups (default: 7)" 37 + echo " BACKUP_PUBLIC_KEY age public key for encryption" 38 + echo " COMPOSE_FILE Docker Compose file (default: docker-compose.yml)" 39 + exit 0 40 + ;; 41 + *) 42 + echo "Unknown argument: $arg" >&2 43 + exit 1 44 + ;; 45 + esac 46 + done 47 + 48 + # Validate encryption prerequisites 49 + if [ "$ENCRYPT" = true ] && [ -z "${BACKUP_PUBLIC_KEY:-}" ]; then 50 + echo "Error: --encrypt requires BACKUP_PUBLIC_KEY environment variable" >&2 51 + exit 1 52 + fi 53 + 54 + if [ "$ENCRYPT" = true ] && ! command -v age &>/dev/null; then 55 + echo "Error: age is required for encryption. Install: https://github.com/FiloSottile/age" >&2 56 + exit 1 57 + fi 58 + 59 + # Create backup directory 60 + mkdir -p "$BACKUP_DIR" 61 + 62 + TIMESTAMP=$(date +%Y%m%d-%H%M%S) 63 + BACKUP_FILE="$BACKUP_DIR/barazo-backup-$TIMESTAMP.sql.gz" 64 + 65 + echo "Starting backup at $(date)" 66 + 67 + # Check PostgreSQL is running 68 + if ! docker compose -f "$COMPOSE_FILE" exec -T postgres pg_isready -U "${POSTGRES_USER:-barazo}" &>/dev/null; then 69 + echo "Error: PostgreSQL is not running" >&2 70 + exit 1 71 + fi 72 + 73 + # Dump PostgreSQL 74 + echo "Dumping PostgreSQL..." 75 + docker compose -f "$COMPOSE_FILE" exec -T postgres \ 76 + pg_dump -U "${POSTGRES_USER:-barazo}" "${POSTGRES_DB:-barazo}" \ 77 + | gzip > "$BACKUP_FILE" 78 + 79 + BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1) 80 + echo "Backup created: $BACKUP_FILE ($BACKUP_SIZE)" 81 + 82 + # Encrypt if requested 83 + if [ "$ENCRYPT" = true ]; then 84 + echo "Encrypting backup..." 85 + age -r "$BACKUP_PUBLIC_KEY" -o "${BACKUP_FILE}.age" "$BACKUP_FILE" 86 + rm "$BACKUP_FILE" 87 + BACKUP_FILE="${BACKUP_FILE}.age" 88 + echo "Encrypted backup: $BACKUP_FILE" 89 + fi 90 + 91 + # Clean up old backups 92 + if [ "$BACKUP_RETAIN_DAYS" -gt 0 ]; then 93 + DELETED=$(find "$BACKUP_DIR" -name "barazo-backup-*" -mtime +"$BACKUP_RETAIN_DAYS" -delete -print | wc -l | tr -d ' ') 94 + if [ "$DELETED" -gt 0 ]; then 95 + echo "Cleaned up $DELETED old backup(s) (older than $BACKUP_RETAIN_DAYS days)" 96 + fi 97 + fi 98 + 99 + echo "Backup complete at $(date)"
+115
scripts/restore.sh
··· 1 + #!/usr/bin/env bash 2 + # Barazo Restore Script 3 + # 4 + # Restores a PostgreSQL backup from a file created by backup.sh. 5 + # 6 + # Usage: 7 + # ./scripts/restore.sh backups/barazo-backup-20260214-020000.sql.gz 8 + # ./scripts/restore.sh backups/barazo-backup-20260214-020000.sql.gz.age 9 + # 10 + # For encrypted backups (.age), set BACKUP_PRIVATE_KEY_FILE to the path 11 + # of your age private key file. 12 + # 13 + # WARNING: This will overwrite the current database contents. 14 + 15 + set -euo pipefail 16 + 17 + COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" 18 + 19 + if [ $# -lt 1 ]; then 20 + echo "Usage: $0 <backup-file>" >&2 21 + echo "" >&2 22 + echo "Examples:" >&2 23 + echo " $0 backups/barazo-backup-20260214-020000.sql.gz" >&2 24 + echo " $0 backups/barazo-backup-20260214-020000.sql.gz.age" >&2 25 + echo "" >&2 26 + echo "Environment variables:" >&2 27 + echo " BACKUP_PRIVATE_KEY_FILE Path to age private key (for .age files)" >&2 28 + echo " COMPOSE_FILE Docker Compose file (default: docker-compose.yml)" >&2 29 + exit 1 30 + fi 31 + 32 + BACKUP_FILE="$1" 33 + 34 + if [ ! -f "$BACKUP_FILE" ]; then 35 + echo "Error: Backup file not found: $BACKUP_FILE" >&2 36 + exit 1 37 + fi 38 + 39 + # Check if encrypted 40 + IS_ENCRYPTED=false 41 + if [[ "$BACKUP_FILE" == *.age ]]; then 42 + IS_ENCRYPTED=true 43 + if [ -z "${BACKUP_PRIVATE_KEY_FILE:-}" ]; then 44 + echo "Error: Encrypted backup requires BACKUP_PRIVATE_KEY_FILE environment variable" >&2 45 + exit 1 46 + fi 47 + if [ ! -f "$BACKUP_PRIVATE_KEY_FILE" ]; then 48 + echo "Error: Private key file not found: $BACKUP_PRIVATE_KEY_FILE" >&2 49 + exit 1 50 + fi 51 + if ! command -v age &>/dev/null; then 52 + echo "Error: age is required for decryption. Install: https://github.com/FiloSottile/age" >&2 53 + exit 1 54 + fi 55 + fi 56 + 57 + # Confirm 58 + echo "WARNING: This will overwrite the current database." 59 + echo "Backup file: $BACKUP_FILE" 60 + echo "" 61 + read -p "Continue? (y/N) " -r 62 + if [[ ! $REPLY =~ ^[Yy]$ ]]; then 63 + echo "Restore cancelled." 64 + exit 0 65 + fi 66 + 67 + # Check PostgreSQL is running 68 + if ! docker compose -f "$COMPOSE_FILE" exec -T postgres pg_isready -U "${POSTGRES_USER:-barazo}" &>/dev/null; then 69 + echo "Error: PostgreSQL is not running. Start it with: docker compose -f $COMPOSE_FILE up -d postgres" >&2 70 + exit 1 71 + fi 72 + 73 + echo "Starting restore at $(date)" 74 + 75 + # Stop API and Web to prevent writes during restore 76 + echo "Stopping API and Web services..." 77 + docker compose -f "$COMPOSE_FILE" stop barazo-api barazo-web 2>/dev/null || true 78 + 79 + # Restore 80 + DB_NAME="${POSTGRES_DB:-barazo}" 81 + DB_USER="${POSTGRES_USER:-barazo}" 82 + 83 + echo "Dropping and recreating database..." 84 + docker compose -f "$COMPOSE_FILE" exec -T postgres \ 85 + psql -U "$DB_USER" -d postgres -c "DROP DATABASE IF EXISTS $DB_NAME;" 86 + docker compose -f "$COMPOSE_FILE" exec -T postgres \ 87 + psql -U "$DB_USER" -d postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" 88 + 89 + echo "Restoring from backup..." 90 + if [ "$IS_ENCRYPTED" = true ]; then 91 + age -d -i "$BACKUP_PRIVATE_KEY_FILE" "$BACKUP_FILE" \ 92 + | gunzip \ 93 + | docker compose -f "$COMPOSE_FILE" exec -T postgres psql -U "$DB_USER" -d "$DB_NAME" -q 94 + else 95 + gunzip -c "$BACKUP_FILE" \ 96 + | docker compose -f "$COMPOSE_FILE" exec -T postgres psql -U "$DB_USER" -d "$DB_NAME" -q 97 + fi 98 + 99 + # Restart services 100 + echo "Restarting API and Web services..." 101 + docker compose -f "$COMPOSE_FILE" up -d barazo-api barazo-web 102 + 103 + # Verify 104 + echo "Verifying restore..." 105 + sleep 5 106 + TABLE_COUNT=$(docker compose -f "$COMPOSE_FILE" exec -T postgres \ 107 + psql -U "$DB_USER" -d "$DB_NAME" -t -c "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public';") 108 + echo "Tables in database: $(echo "$TABLE_COUNT" | tr -d ' ')" 109 + 110 + echo "" 111 + echo "Restore complete at $(date)" 112 + echo "" 113 + echo "IMPORTANT: If this backup is older than the latest data, check the" 114 + echo "deletion_log table and re-apply any deletions that occurred after" 115 + echo "the backup timestamp to maintain GDPR compliance."
+154
scripts/smoke-test.sh
··· 1 + #!/usr/bin/env bash 2 + # Barazo Smoke Test 3 + # 4 + # Validates that a running Barazo instance is healthy. 5 + # Run after deployment or upgrade to verify all services are working. 6 + # 7 + # Usage: 8 + # ./scripts/smoke-test.sh # Test local (docker compose ps) 9 + # ./scripts/smoke-test.sh https://forum.example.com # Test remote URL 10 + # 11 + # Exit codes: 12 + # 0 All checks passed 13 + # 1 One or more checks failed 14 + 15 + set -euo pipefail 16 + 17 + COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.yml}" 18 + REMOTE_URL="${1:-}" 19 + PASSED=0 20 + FAILED=0 21 + 22 + pass() { 23 + echo " PASS: $1" 24 + PASSED=$((PASSED + 1)) 25 + } 26 + 27 + fail() { 28 + echo " FAIL: $1" 29 + FAILED=$((FAILED + 1)) 30 + } 31 + 32 + echo "Barazo Smoke Test" 33 + echo "=================" 34 + echo "" 35 + 36 + # --- Docker Compose checks (local only) --- 37 + if [ -z "$REMOTE_URL" ]; then 38 + echo "Local deployment checks:" 39 + 40 + # Check all services are running 41 + SERVICES=$(docker compose -f "$COMPOSE_FILE" ps --format json 2>/dev/null || echo "") 42 + if [ -z "$SERVICES" ]; then 43 + fail "Docker Compose services not running" 44 + else 45 + for SERVICE in postgres valkey barazo-api barazo-web caddy; do 46 + STATUS=$(docker compose -f "$COMPOSE_FILE" ps --format json "$SERVICE" 2>/dev/null | grep -o '"Health":"[^"]*"' | head -1 || echo "") 47 + if echo "$STATUS" | grep -q "healthy"; then 48 + pass "$SERVICE is healthy" 49 + elif docker compose -f "$COMPOSE_FILE" ps --format json "$SERVICE" 2>/dev/null | grep -q "running"; then 50 + pass "$SERVICE is running (no healthcheck)" 51 + else 52 + fail "$SERVICE is not running or unhealthy" 53 + fi 54 + done 55 + fi 56 + 57 + echo "" 58 + 59 + # Check PostgreSQL connection 60 + echo "Database checks:" 61 + if docker compose -f "$COMPOSE_FILE" exec -T postgres pg_isready -U "${POSTGRES_USER:-barazo}" &>/dev/null; then 62 + pass "PostgreSQL is accepting connections" 63 + else 64 + fail "PostgreSQL is not accepting connections" 65 + fi 66 + 67 + # Check Valkey connection 68 + if docker compose -f "$COMPOSE_FILE" exec -T valkey valkey-cli ping 2>/dev/null | grep -q "PONG"; then 69 + pass "Valkey is responding" 70 + else 71 + fail "Valkey is not responding" 72 + fi 73 + 74 + echo "" 75 + fi 76 + 77 + # --- HTTP checks --- 78 + if [ -n "$REMOTE_URL" ]; then 79 + BASE_URL="$REMOTE_URL" 80 + echo "Remote deployment checks ($BASE_URL):" 81 + else 82 + BASE_URL="http://localhost:3000" 83 + echo "HTTP checks (via localhost):" 84 + fi 85 + 86 + # API health 87 + echo "" 88 + echo "API checks:" 89 + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/api/health" 2>/dev/null || echo "000") 90 + if [ "$HTTP_CODE" = "200" ]; then 91 + pass "API health endpoint returns 200" 92 + else 93 + fail "API health endpoint returned $HTTP_CODE (expected 200)" 94 + fi 95 + 96 + # API health/ready should be blocked externally (403 from Caddy) or 200 internally 97 + if [ -n "$REMOTE_URL" ]; then 98 + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/api/health/ready" 2>/dev/null || echo "000") 99 + if [ "$HTTP_CODE" = "403" ]; then 100 + pass "/api/health/ready blocked externally (403)" 101 + else 102 + fail "/api/health/ready returned $HTTP_CODE (expected 403)" 103 + fi 104 + fi 105 + 106 + # Frontend 107 + echo "" 108 + echo "Frontend checks:" 109 + if [ -n "$REMOTE_URL" ]; then 110 + HOMEPAGE=$(curl -s "$BASE_URL" 2>/dev/null || echo "") 111 + else 112 + HOMEPAGE=$(curl -s "http://localhost:3001" 2>/dev/null || echo "") 113 + fi 114 + 115 + if echo "$HOMEPAGE" | grep -qi "barazo\|html"; then 116 + pass "Frontend returns HTML content" 117 + else 118 + fail "Frontend did not return expected HTML" 119 + fi 120 + 121 + # SSL check (remote only) 122 + if [ -n "$REMOTE_URL" ] && [[ "$REMOTE_URL" == https://* ]]; then 123 + echo "" 124 + echo "SSL checks:" 125 + DOMAIN=$(echo "$REMOTE_URL" | sed 's|https://||' | sed 's|/.*||') 126 + SSL_EXPIRY=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:443" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2 || echo "") 127 + if [ -n "$SSL_EXPIRY" ]; then 128 + pass "SSL certificate valid (expires: $SSL_EXPIRY)" 129 + else 130 + fail "Could not verify SSL certificate" 131 + fi 132 + 133 + # HTTPS redirect 134 + HTTP_REDIRECT=$(curl -s -o /dev/null -w "%{redirect_url}" "http://$DOMAIN" 2>/dev/null || echo "") 135 + if [[ "$HTTP_REDIRECT" == https://* ]]; then 136 + pass "HTTP redirects to HTTPS" 137 + else 138 + fail "HTTP does not redirect to HTTPS" 139 + fi 140 + fi 141 + 142 + # --- Summary --- 143 + echo "" 144 + echo "=================" 145 + TOTAL=$((PASSED + FAILED)) 146 + echo "Results: $PASSED/$TOTAL passed" 147 + 148 + if [ "$FAILED" -gt 0 ]; then 149 + echo "" 150 + echo "$FAILED check(s) failed. Review the output above." 151 + exit 1 152 + fi 153 + 154 + echo "All checks passed."