Skip to main content

Backups

Regular backups are critical for disaster recovery. This guide covers backup strategies for UAPK Gateway.

What to Backup

ComponentPriorityFrequency
PostgreSQL databaseCriticalDaily
Signing keysCriticalOn change
ConfigurationHighOn change
Audit log exportsHighWeekly

Database Backups

Simple Backup Script

#!/bin/bash
# /opt/uapk-gateway/backup.sh

set -e

BACKUP_DIR=/var/backups/uapk-gateway
DATE=$(date +%Y%m%d-%H%M%S)
RETENTION_DAYS=30

mkdir -p $BACKUP_DIR

# Database backup
echo "Backing up database..."
docker compose exec -T db pg_dump -U uapk uapk_gateway \
| gzip > $BACKUP_DIR/db-$DATE.sql.gz

echo "Database backup: $BACKUP_DIR/db-$DATE.sql.gz"

# Cleanup old backups
find $BACKUP_DIR -name "db-*.sql.gz" -mtime +$RETENTION_DAYS -delete

echo "Backup completed: $DATE"

Automated Daily Backups

# Add to crontab
# 0 2 * * * /opt/uapk-gateway/backup.sh >> /var/log/uapk-backup.log 2>&1
crontab -e

Point-in-Time Recovery

Enable PostgreSQL WAL archiving for point-in-time recovery:

# docker-compose.yml
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: uapk_gateway
POSTGRES_USER: uapk
POSTGRES_PASSWORD: ${DB_PASSWORD}
command:
- "postgres"
- "-c"
- "wal_level=replica"
- "-c"
- "archive_mode=on"
- "-c"
- "archive_command=cp %p /var/lib/postgresql/wal_archive/%f"
volumes:
- postgres_data:/var/lib/postgresql/data
- wal_archive:/var/lib/postgresql/wal_archive

Key Backups

Backup Signing Key

#!/bin/bash
# Encrypt and backup signing key

BACKUP_DIR=/var/backups/uapk-gateway/keys
DATE=$(date +%Y%m%d)

mkdir -p $BACKUP_DIR

# Encrypt with GPG
gpg --symmetric --cipher-algo AES256 \
-o $BACKUP_DIR/signing-key-$DATE.pem.gpg \
/etc/uapk-gateway/keys/signing.pem

echo "Key backup: $BACKUP_DIR/signing-key-$DATE.pem.gpg"

Store Passphrase Securely

Passphrase Storage

Store the GPG passphrase separately from the encrypted key. Use a password manager or secrets vault.

Configuration Backups

#!/bin/bash
# Backup configuration

BACKUP_DIR=/var/backups/uapk-gateway/config
DATE=$(date +%Y%m%d)

mkdir -p $BACKUP_DIR

# Backup docker-compose and env
cp /opt/uapk-gateway/docker-compose.yml $BACKUP_DIR/docker-compose-$DATE.yml
cp /opt/uapk-gateway/Caddyfile $BACKUP_DIR/Caddyfile-$DATE

# Backup environment (redact secrets)
grep -v PASSWORD /opt/uapk-gateway/.env > $BACKUP_DIR/env-$DATE.txt

echo "Config backup completed"

Audit Log Exports

Weekly Export Script

#!/bin/bash
# Export audit logs weekly

BACKUP_DIR=/var/backups/uapk-gateway/logs
DATE=$(date +%Y%m%d)
WEEK_AGO=$(date -d "7 days ago" +%Y-%m-%dT00:00:00Z)
NOW=$(date +%Y-%m-%dT23:59:59Z)

mkdir -p $BACKUP_DIR

# Export logs for each agent
for AGENT in $(curl -s http://localhost:8000/api/v1/orgs/$ORG_ID/manifests \
-H "Authorization: Bearer $TOKEN" | jq -r '.items[].uapk_id'); do

curl -X POST http://localhost:8000/api/v1/orgs/$ORG_ID/logs/export/download \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"uapk_id\": \"$AGENT\", \"from\": \"$WEEK_AGO\", \"to\": \"$NOW\"}" \
> $BACKUP_DIR/logs-$AGENT-$DATE.json

# Verify export
python verify_log_chain.py $BACKUP_DIR/logs-$AGENT-$DATE.json

done

echo "Log export completed"

Remote Storage

AWS S3

#!/bin/bash
# Upload backups to S3

BACKUP_DIR=/var/backups/uapk-gateway
S3_BUCKET=s3://my-backup-bucket/uapk-gateway

# Sync backups
aws s3 sync $BACKUP_DIR $S3_BUCKET --sse AES256

# Lifecycle policy handles retention in S3

S3 Lifecycle Policy

{
"Rules": [
{
"ID": "ExpireOldBackups",
"Status": "Enabled",
"Prefix": "uapk-gateway/",
"Transitions": [
{
"Days": 30,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 365
}
}
]
}

Google Cloud Storage

#!/bin/bash
# Upload backups to GCS

BACKUP_DIR=/var/backups/uapk-gateway
GCS_BUCKET=gs://my-backup-bucket/uapk-gateway

gsutil -m rsync -r $BACKUP_DIR $GCS_BUCKET

Restore Procedures

Database Restore

#!/bin/bash
# Restore database from backup

BACKUP_FILE=$1

if [ -z "$BACKUP_FILE" ]; then
echo "Usage: $0 <backup-file.sql.gz>"
exit 1
fi

# Stop gateway
docker compose stop gateway

# Restore database
zcat $BACKUP_FILE | docker compose exec -T db psql -U uapk -d uapk_gateway

# Start gateway
docker compose start gateway

# Verify
curl http://localhost:8000/api/v1/gateway/health

Key Restore

#!/bin/bash
# Restore signing key

BACKUP_FILE=$1

gpg --decrypt $BACKUP_FILE > /etc/uapk-gateway/keys/signing.pem
chmod 600 /etc/uapk-gateway/keys/signing.pem

# Restart gateway to load new key
docker compose restart gateway

Full Disaster Recovery

#!/bin/bash
# Full recovery procedure

# 1. Provision new server
# 2. Install prerequisites (Docker, etc.)

# 3. Restore configuration
cp config-backup/docker-compose.yml /opt/uapk-gateway/
cp config-backup/Caddyfile /opt/uapk-gateway/

# 4. Restore signing key
gpg --decrypt keys-backup/signing-key.pem.gpg > /etc/uapk-gateway/keys/signing.pem
chmod 600 /etc/uapk-gateway/keys/signing.pem

# 5. Start database
docker compose up -d db
sleep 10

# 6. Restore database
zcat db-backup/db-latest.sql.gz | docker compose exec -T db psql -U uapk -d uapk_gateway

# 7. Start gateway
docker compose up -d gateway

# 8. Verify
curl http://localhost:8000/api/v1/gateway/health

# 9. Verify log chain integrity
curl http://localhost:8000/api/v1/orgs/$ORG_ID/logs/verify/all \
-H "Authorization: Bearer $TOKEN"

Backup Verification

Monthly Verification

#!/bin/bash
# Verify backup integrity

BACKUP_DIR=/var/backups/uapk-gateway

echo "Checking database backups..."
for f in $BACKUP_DIR/db-*.sql.gz; do
if gzip -t "$f" 2>/dev/null; then
echo "✓ $f"
else
echo "✗ $f - CORRUPTED"
fi
done

echo "Checking log exports..."
for f in $BACKUP_DIR/logs/*.json; do
if python verify_log_chain.py "$f" >/dev/null 2>&1; then
echo "✓ $f"
else
echo "✗ $f - VERIFICATION FAILED"
fi
done

Test Restore

Periodically restore backups to a test environment:

# Create test environment
docker compose -f docker-compose.test.yml up -d

# Restore and verify
./restore-db.sh /var/backups/uapk-gateway/db-latest.sql.gz

# Run verification
curl http://localhost:8001/api/v1/gateway/health

Retention Policy

Backup TypeRetentionStorage Class
Daily DB30 daysStandard
Weekly logs1 yearStandard → Glacier
Monthly archive7 yearsGlacier
Signing keysForeverCold storage