Azure Blob Offsite Backup Setup
Azure Blob Offsite Backup Setup
Overview
Purpose:Status: OffsiteLIVE — verified against running processes, LaunchAgent plists and config on 2026-07-28.
Owner: FlowForge (infra) / John (orchestration)
Scope note: This page documents specifically the Azure Blob offsite paths. For the full 4-layer ALAI backup forpicture ALAI(including system databasesGit and gitBackblaze bundles
B2), Region:see NorthALAI EuropeBackup (Dublin)Strategy.
1. There are two independent Azure Blob backup paths — geographicone separationactive, fromone primary Sweden Central VM
Retention: 365 days with lifecycle policies (Hot → Cool → Archive → Delete)
Recovery Time Objective: 4 hours (manual restore)
Azure Resourcesscaffolded
| Storage account | |||
|---|---|---|---|
plockfrontstaging (swedencentral) |
ACTIVE — weekly | ||
| SQLite databases (mission-control, hivemind, costs, events, etc.) | alaibackups0ebb (RG alai-backups-rg) |
2. ACTIVE: LightRAG/Neo4j → plockfrontstaging
Script: ~/system/tools/lightrag-backup.sh
LaunchAgent: com.alai.lightrag-backup.plist — weekly, Sunday 04:00 CEST
Credentials: ~/system/config/azure-lightrag-backup.env (mode 0600) — AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_KEY, AZURE_STORAGE_CONTAINER
What happens:
- SSH to Azure VM
vm-alai-lightrag(20.240.61.67) docker compose stop lightrag neo4j(graceful, ~30-90s downtime)- Tar each of 4 Docker volumes (
lightrag-data,lightrag-kg,lightrag-cache,lightrag-neo4j-data) docker compose start neo4j lightrag- SHA-256 manifest + README written alongside the snapshot
- SCP snapshot back to Mac Studio (
~/system/backups/lightrag/, local safety net, keep last 4) - Azure Blob upload —
az storage blob uploadper file,--tier Cool, blob path<container>/<timestamp>/<file> - Azure-side rotation — keep last 8 snapshots (~8 weeks), older ones deleted via
az storage blob delete-batch
Azure resource details:
- Storage account:
plockfrontstaging(swedencentral, Hot-tier account — a pre-existing Plock account being reused for this, not a dedicated ALAI backupstorageaccount) - Container:
Account - Resource group:
plock-staging-rg - Per-blob access tier: Cool
- Retention: last 8 snapshots offsite (vs. 4 kept locally)
- Cost: ~$0.05–0.10/month for ~4 GB retained
AZURE_BACKUP_SP_APPID=1a0b3018-0c31-474b-918f-531b0a29a669(SP name:alai-backup-writer) — same appid used as the general-purpose az-cli identity elsewhere in the system, not a narrowly-scoped backup-only SPAZURE_BACKUP_SP_TENANT=3454a03f-20b4-4bda-a116-2293c459aecdAZURE_SUBSCRIPTION_ID=5b0b4d9b-e677-464e-abf0-5170cbce3b8eVAULT_EXPORTER_APPID/VAULT_EXPORTER_SECRET= stillFILL_AFTER_SP_CREATION— the narrower-scoped SP intended for thebitwarden-exportscontainer only was never created. Do not assume it exists.- Re-verify RBAC on
alai-backups-rg/alaibackups0ebb— the current az-cli SP identity returnedAuthorizationFailedreading this account/RG as of 2026-07-28; access is not currently confirmed. - Re-enable the LaunchAgent: rename
com.alai.azure-db-backup.plist.disabled→com.alai.azure-db-backup.plist,launchctl load. - Re-point at least the P0-financial tier of
litestream.ymlreplicas back totype: abs. - Re-scope the account-wide Azure lifecycle rule fixed under #105490 (it lacked
prefixMatchand archived L0 blobs for 35 dormant DBs during the original incident) before turning replication back on. - ALAI Backup Strategy — full 4-layer picture (Git, SQLite, LightRAG, DR)
- LightRAG Backup (Azure-native + local safety net) — full restore procedure for the active Azure path
- Azure litestream egress saga + backup policy — MC #105462 — why the SQLite→Azure path was turned off
- Disaster Recovery Runbook
List available backups:Download latest backup:Verify SHA-256 checksum:Restore to target system:Cron:Hourly backup at :15 (15 * * * *)Log:directly),~/system/logs/azure-tools/lightrag-backup.logshAlert:(scriptHiveMindreadalertdirectly),ifcross-checkedbackupagainstfails 2 consecutive runs
lightrag-backup
Restore: full step-by-step (download-batch, checksum verify, volume restore to Azure VM or to Mac Studio) is documented in LightRAG Backup runbook — do not duplicate here, that page is the source of truth for restore procedure.
3. SCAFFOLDED / DISABLED: SQLite → alaibackups0ebb
This path exists in config and credentials but is intentionally not the live path as of 2026-07-28.
Storage account: alaibackups0ebb, resource group alai-backups-rg
Containers (LRS,per Standardoriginal tier)
| Container | Purpose | |
|---|---|---|
system-db-backups |
SQLite type: replicas) |
|
system-git-bundles |
Git |
|
|
Credentials: ~/system/config/azure-backup.env (mode 600)
LaunchAgent: com.alai.azure-db-backup.plist.disabled (file dated 2026-04-20) — filename literally carries .disabled, RunAtLoad=false, never renamed to an active .plist. Confirmed present but inert on disk 2026-07-28.
Litestream (~/system/config/litestream.yml): documents alai-backup-writer as the intended Azure auth for streaming SQLite replication, and originally shipped replicas of type: abs (Azure Blob Storage) pointed at system-db-backups. As of 2026-07-28 all 64 configured databases use type: file (local) replicas only — zero type: abs replicas remain.
Why it's off — CEO decision, MC #105462 (2026-07-13)
Litestream's per-sync-tick ListBlobs full-prefix enumeration against alaibackups0ebb (driven mainly by two hot telemetry DBs — flywheel ~113K blobs, mission-control ~102K blobs) drove egress up to 518 GB/day (~370 NOK/day). After sync-interval tuning and lifecycle-rule fixes proved insufficient to bring it under control, CEO decision (verbatim): "Prebaci lokalno! Nemamo placene korisnika kad to dodje ide backup na azure do tada save money gdje mozemo." — move everything to local replicas; Azure Blob SQLite backup returns once ALAI has paying customers. Full incident record: Azure litestream egress saga + backup policy — MC #105462.
Reactivation checklist (when triggered)
Service Principal4. Setup commands (as originally provisioned, for reference)
Creating the general-purpose backup SP (already done — this is what AZURE_BACKUP_SP_APPID above corresponds to):
# Create service principal
az ad sp create-for-rbac --name alai-backup-writer --skip-assignmentrole # Assign "Storage Blob Data ContributorContributor" to\
SA--scopes /subscriptions/5b0b4d9b-e677-464e-abf0-5170cbce3b8e/resourceGroups/alai-backups-rg/providers/Microsoft.Storage/storageAccounts/alaibackups0ebb
Creating the narrower vault-export-only SP (notdocumented, subscription)never STORAGE_ID=$(executed — VAULT_EXPORTER_APPID still unfilled):
az storagead accountsp showcreate-for-rbac --name alaibackups0ebb --query id -o tsv)
az role assignment create \
--assignee <service-principal-app-id> \alai-vault-exporter --role "Storage Blob Data Contributor" \
--scopescopes "$STORAGE_ID"/subscriptions/5b0b4d9b-e677-464e-abf0-5170cbce3b8e/resourceGroups/alai-backups-rg/providers/Microsoft.Storage/storageAccounts/alaibackups0ebb/blobServices/default/containers/bitwarden-exports
#
Related
Document Owner: Skillforge
Last Verified: 2026-07-28 — ~/Library/LaunchAgents/com.alai.azure-db-backup.plist.disabled (present, inert), ~/system/config/litestream.yml (64/64 DBs type: file), ~/system/config/azure-backup.env catand > ~/system/config/azure-lightrag-backup.env <
Lifecycle Policy
Hot → Cool: 30 days
Cool → Archive: 90 days
Archive → Delete: 365 days
Delete blobs: Last modified > 365 days
az storage account management-policy create \
--account-name alaibackups0ebb \
--policy @lifecycle-policy.jsonlifecycle-policy.json:
{
"rules": [
{
"enabled": true,
"name": "archive-old-backups",
"type": "Lifecycle",
"definition": {
"actions": {
"baseBlob": {
"tierToCool": {"daysAfterModificationGreaterThan": 30},
"tierToArchive": {"daysAfterModificationGreaterThan": 90},
"delete": {"daysAfterModificationGreaterThan": 365}
}
},
"filters": {"blobTypes": ["blockBlob"]}
}
}
]
}Backup Scripts
LightRAG to Azure Blob
#!/bin/bash
# ~/system/tools/migrate-lightrag-to-azure.sh
source ~/system/config/azure-backup.env
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="/tmp/lightrag-backup-$TIMESTAMP.tar.gz"
tar -czf "$BACKUP_FILE" ~/system/lightrag/
az storage blob upload \
--account-name alaibackups0ebb \
--container-name system-db-backups \
--name "lightrag-$TIMESTAMP.tar.gz" \
--file "$BACKUP_FILE" \
--auth-mode login
rm "$BACKUP_FILE"Ollama Models Export
#!/bin/bash
# ~/system/tools/ollama-models-export.sh --azure
source ~/system/config/azure-backup.env
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
EXPORT_DIR="/tmp/ollama-export-$TIMESTAMP"
mkdir -p "$EXPORT_DIR"
ollama list | tail -n +2 | awk '{print $1}' > "$EXPORT_DIR/model-list.txt"
whilecontents read -r model; do
ollama show "$model" --modelfile > "$EXPORT_DIR/$model.modelfile"
done < "$EXPORT_DIR/model-list.txt"
tar -czf "$EXPORT_DIR.tar.gz" "$EXPORT_DIR"
az storage blob upload \
--account-name alaibackups0ebb \
--container-name system-db-backups \
--name "ollama-models-$TIMESTAMP.tar.gz" \
--file "$EXPORT_DIR.tar.gz"
rm -rf "$EXPORT_DIR" "$EXPORT_DIR.tar.gz"Disaster Recovery Path
az storage blob list \
--account-name alaibackups0ebb \
--container-name system-db-backups \
--output tableaz storage blob download \
--account-name alaibackups0ebb \
--container-name system-db-backups \
--name "lightrag-20260420-143000.tar.gz" \
--file /tmp/restore-lightrag.tar.gzshasum -a 256 /tmp/restore-lightrag.tar.gztar -xzf /tmp/restore-lightrag.tar.gz -C ~/system/Monitoring
node~/system/agents/hivemind/hivemind.jsdocs/runbooks/alai-backup-strategy.mdpost(same-dayjohnverification).alert \ "Azure backup failed 2 consecutive runs — check ~/system/logs/azure-backup.log"