ITYS I Told You So

1 min

3-2-1-1-0 Backup Automation

Matthew’s 3-2-1-1-0 Backup Automation Makefile

This is the full version of Matthew’s 3-2-1-1-0 Backup Automation Makefile. It’s formatted for correct rendering in Codex / Zola Markdown without overflow.

# ===========================================
#  WRITING BACKUP AUTOMATION — 3-2-1-1-0
# ===========================================

include .env
DATE := $(shell date +%F_%T)
RESTORE_DIR := /tmp/writing-restore-test

.PHONY: all git-save backup-local mirror-local backup-cloud backup-cloud-scaleway manifest verify verify-restore offline-copy cron-install full

# Run everything
all: git-save backup-local mirror-local backup-cloud backup-cloud-scaleway manifest verify
full: all verify-restore

# -------------------------------------------------
# Git auto-commit and push
# -------------------------------------------------
git-save:
	@echo "[GIT] Auto-committing changes..."
	cd $(SRC_DIR) && \
		git add . && \
		git commit -m "Auto backup $(DATE)" || echo "No changes to commit." && \
		git push origin main || echo "Push skipped/offline."

# -------------------------------------------------
# Local backups (encrypted via restic + unencrypted mirror)
# -------------------------------------------------
backup-local:
	@echo "[LOCAL] Syncing to CF-Express card (encrypted restic sync)..."
	rsync -avh --delete --exclude=".git" $(SRC_DIR)/ $(LOCAL_BACKUP)/ && sync

mirror-local:
	@echo "[LOCAL] Creating unencrypted rsync mirror to CF-Express..."
	mkdir -p $(LOCAL_BACKUP)/unencrypted
	rsync -avh --delete $(SRC_DIR)/ $(LOCAL_BACKUP)/unencrypted/writing-latest/
	@echo "[LOCAL] Creating versioned timestamped copy..."
	rsync -avh --delete $(SRC_DIR)/ $(LOCAL_BACKUP)/unencrypted/writing-$(DATE)/
	sync
	@echo "[LOCAL] Unencrypted mirror complete: $(LOCAL_BACKUP)/unencrypted/writing-$(DATE)"

# -------------------------------------------------
# Backblaze B2 Cloud Backup
# -------------------------------------------------
backup-cloud:
	@echo "[CLOUD] Backing up to Backblaze B2..."
	. $(PWD)/.env; \
	RESTIC_PASSWORD_FILE=/etc/restic/restic_pass \
	restic -r $$RESTIC_REPOSITORY backup $$SRC_DIR --verbose && \
	RESTIC_PASSWORD_FILE=/etc/restic/restic_pass \
	restic -r $$RESTIC_REPOSITORY forget --keep-daily 7 --keep-weekly 4 --prune

# -------------------------------------------------
# Scaleway Paris Cloud Backup
# -------------------------------------------------
backup-cloud-scaleway:
	@echo "[CLOUD] Backing up to Scaleway Paris..."
	bash -c '\
		set -e; \
		source "$(PWD)/.env"; \
		export RESTIC_PASSWORD_FILE=/etc/restic/restic_pass; \
		export AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID_SCL"; \
		export AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY_SCL"; \
		echo "[SCL] Repository: $$RESTIC_REPOSITORY_SCL"; \
		restic -r "$$RESTIC_REPOSITORY_SCL" backup "$$SRC_DIR" --verbose; \
		restic -r "$$RESTIC_REPOSITORY_SCL" forget --keep-daily 7 --keep-weekly 4 --prune; \
	'

# -------------------------------------------------
# Manifests (SHA-256)
# -------------------------------------------------
manifest:
	@echo "[MANIFEST] Generating SHA256 manifest..."
	find $(SRC_DIR) -type f -print0 | xargs -0 sha256sum > $(SRC_DIR)/MANIFEST-$(DATE).sha256
	cp $(SRC_DIR)/MANIFEST-$(DATE).sha256 $(LOCAL_BACKUP)/ || true
	. $(PWD)/.env; \
	RESTIC_PASSWORD_FILE=/etc/restic/restic_pass \
	restic -r $$RESTIC_REPOSITORY backup $(SRC_DIR)/MANIFEST-$(DATE).sha256
	bash -c '\
		source "$(PWD)/.env"; \
		export RESTIC_PASSWORD_FILE=/etc/restic/restic_pass; \
		export AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID_SCL"; \
		export AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY_SCL"; \
		restic -r "$$RESTIC_REPOSITORY_SCL" backup "$(SRC_DIR)/MANIFEST-$(DATE).sha256"; \
	'

# -------------------------------------------------
# Verify repository integrity (both clouds)
# -------------------------------------------------
verify:
	@echo "[VERIFY] Checking repository integrity..."
	. /home/mjh/writing/.env; \
	echo " - Backblaze B2"; \
	restic -r $$RESTIC_REPOSITORY check --with-cache; \
	echo " - Scaleway Paris"; \
	AWS_ACCESS_KEY_ID=$$AWS_ACCESS_KEY_ID_SCL AWS_SECRET_ACCESS_KEY=$$AWS_SECRET_ACCESS_KEY_SCL restic -r $$RESTIC_REPOSITORY_SCL check --with-cache

# -------------------------------------------------
# Verify restore & file integrity (using manifests)
# -------------------------------------------------
verify-restore:
	@echo "[VERIFY] Testing restore from both clouds..."
	rm -rf $(RESTORE_DIR)
	mkdir -p $(RESTORE_DIR)
	. $(PWD)/.env; \
	RESTIC_PASSWORD_FILE=/etc/restic/restic_pass \
	restic -r $$RESTIC_REPOSITORY restore latest --target $(RESTORE_DIR)/b2
	bash -c '\
		source "$(PWD)/.env"; \
		export RESTIC_PASSWORD_FILE=/etc/restic/restic_pass; \
		export AWS_ACCESS_KEY_ID="$$AWS_ACCESS_KEY_ID_SCL"; \
		export AWS_SECRET_ACCESS_KEY="$$AWS_SECRET_ACCESS_KEY_SCL"; \
		restic -r "$$RESTIC_REPOSITORY_SCL" restore latest --target "$(RESTORE_DIR)/scaleway"; \
	'
	@echo "[VERIFY] Comparing manifests..."
	@if ls $(SRC_DIR)/MANIFEST-*.sha256 >/dev/null 2>&1; then \
		LATEST_MANIFEST=$$(ls -t $(SRC_DIR)/MANIFEST-*.sha256 | head -n 1); \
		echo " - Using manifest $$LATEST_MANIFEST"; \
		cd $(RESTORE_DIR)/b2 && sha256sum -c $$LATEST_MANIFEST || true; \
		cd $(RESTORE_DIR)/scaleway && sha256sum -c $$LATEST_MANIFEST || true; \
	else \
		echo "No manifest found, skipping file verification."; \
	fi
	@echo "[VERIFY] Restore verification complete."

# -------------------------------------------------
# Offline cold copy
# -------------------------------------------------
offline-copy:
	@echo "[OFFLINE] Creating offline tarball..."
	tar -czf /media/mjh/0000/writing-$(DATE).tar.gz -C $(SRC_DIR) .
	sync
	@echo "[OFFLINE] Safely eject the CF-Express card for cold storage."

# -------------------------------------------------
# Cron (every 6 hours)
# -------------------------------------------------
cron-install:
	@echo "0 */6 * * * cd $(PWD) && make full >> $(PWD)/backup.log 2>&1" | crontab -
	@echo "[CRON] Installed job (runs every 6 hours, full backup)"