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