#!/bin/bash # # Backup Script for Hugging Face Space → GitHub # =============================================== # Periodically copies data files and the wiki directory # to a private GitHub repository. # # Required environment variables: # GITHUB_TOKEN – personal access token with write access # GITHUB_BACKUP_REPO – full URL of the repo (e.g. https://github.com/user/repo) # Optional (for Telegram alerts): # TELEGRAM_BOT_TOKEN # TELEGRAM_CHAT_ID # BACKUP_INTERVAL_HOURS – default 6, can be overridden via file /app/backup_interval_hours.txt # # Status written to /app/backup_status.txt : status | UTC timestamp # History appended to /app/backup_history.txt : timestamp | status # Last restore time stored in /app/last_restore.txt # ── Configuration ──────────────────────────────────────────────── GITHUB_TOKEN=${GITHUB_TOKEN} GITHUB_REPO=${GITHUB_BACKUP_REPO} BACKUP_DIR=/app/backup_repo STATUS_FILE=/app/backup_status.txt HISTORY_FILE=/app/backup_history.txt RESTORE_MARKER=/app/last_restore.txt INTERVAL_FILE=/app/backup_interval_hours.txt DEFAULT_INTERVAL_HOURS="${BACKUP_INTERVAL_HOURS:-6}" # ── Early exit if credentials missing ──────────────────────────── if [ -z "$GITHUB_TOKEN" ] || [ -z "$GITHUB_REPO" ]; then echo "[Backup] Missing credentials. Backup disabled." echo "disabled|$(date -u +'%Y-%m-%d %H:%M UTC')" > "$STATUS_FILE" exit 0 fi REPO_URL="${GITHUB_REPO#https://}" # ── Telegram alert helper ──────────────────────────────────────── send_telegram_alert() { local msg="$1" local token="${TELEGRAM_BOT_TOKEN}" local chat_id="${TELEGRAM_CHAT_ID}" if [ -z "$token" ] || [ -z "$chat_id" ]; then echo "[Backup] Telegram alert skipped – missing token/chat_id." return 1 fi curl -s -X POST "https://api.telegram.org/bot${token}/sendMessage" \ -d "chat_id=${chat_id}" -d "text=${msg}" -d "parse_mode=HTML" >/dev/null } # ── Clone or pull the backup repository ────────────────────────── if [ ! -d "$BACKUP_DIR" ]; then echo "[Backup] Cloning backup repository..." git clone "https://${GITHUB_TOKEN}@${REPO_URL}" "$BACKUP_DIR" cd "$BACKUP_DIR" || exit 1 # If empty repo, create main branch if ! git rev-parse --verify HEAD >/dev/null 2>&1; then echo "[Backup] Repository is empty – creating 'main' branch." git checkout -b main fi else echo "[Backup] Pulling latest backup..." cd "$BACKUP_DIR" && git pull origin main fi # ── Set Git identity ───────────────────────────────────────────── if ! git config user.name >/dev/null 2>&1; then git config user.name "HuggingFace Backup Bot" fi if ! git config user.email >/dev/null 2>&1; then git config user.email "backup@example.com" fi # ── Restore local files if missing (after clone/pull) ──────────── RESTORED_ANY=false if [ ! -d /app/wiki ] || [ -z "$(ls -A /app/wiki 2>/dev/null)" ]; then echo "[Backup] Wiki empty – restoring from backup..." mkdir -p /app/wiki if [ -d "$BACKUP_DIR/wiki" ]; then cp -r "$BACKUP_DIR/wiki/"* /app/wiki/ 2>/dev/null RESTORED_ANY=true fi fi for f in notes.txt flags.json virtual_positions.json capital_state.json dashboard_report.json llm_error_types.txt; do if [ ! -f "/app/$f" ] && [ -f "$BACKUP_DIR/$f" ]; then cp "$BACKUP_DIR/$f" "/app/$f" RESTORED_ANY=true fi done if [ "$RESTORED_ANY" = true ]; then date -u +'%Y-%m-%d %H:%M UTC' > "$RESTORE_MARKER" echo "[Backup] Restore completed at $(cat $RESTORE_MARKER)" fi # ── Function: do_backup ────────────────────────────────────────── do_backup() { echo "[Backup] Copying data files..." cd "$BACKUP_DIR" || return cp /app/notes.txt . 2>/dev/null cp /app/flags.json . 2>/dev/null cp /app/virtual_positions.json . 2>/dev/null cp /app/capital_state.json . 2>/dev/null cp /app/virtual_capital_state.json . 2>/dev/null cp /app/dashboard_report.json . 2>/dev/null cp /app/llm_error_types.txt . 2>/dev/null if [ -d /app/wiki ]; then rm -rf wiki cp -r /app/wiki wiki echo "[Backup] Wiki directory copied ($(ls -A wiki | wc -l) files)." fi echo "[Backup] Current Git branch:" git branch git add -A if git commit -m "Auto backup $(date -u +'%Y-%m-%d %H:%M UTC')" 2>/dev/null; then echo "[Backup] Committed. Pushing to main..." if git push origin HEAD:main 2>&1; then now_utc=$(date -u +'%Y-%m-%d %H:%M UTC') echo "[Backup] Backup completed at $now_utc" echo "success|$now_utc" > "$STATUS_FILE" echo "$now_utc | success" >> "$HISTORY_FILE" else now_utc=$(date -u +'%Y-%m-%d %H:%M UTC') echo "[Backup] Push failed" echo "failed|$now_utc" > "$STATUS_FILE" echo "$now_utc | failed" >> "$HISTORY_FILE" send_telegram_alert "❌ Backup FAILED at $now_utc" fi else now_utc=$(date -u +'%Y-%m-%d %H:%M UTC') echo "[Backup] No changes to commit" echo "no_changes|$now_utc" > "$STATUS_FILE" echo "$now_utc | no_changes" >> "$HISTORY_FILE" fi } # ── Wait for first data before initial backup ──────────────────── echo "[Backup] Waiting for data files to be written..." while true; do if [ -s /app/notes.txt ] || [ -s /app/flags.json ] || [ -s /app/virtual_positions.json ] || ([ -d /app/wiki ] && [ -n "$(ls -A /app/wiki)" ]); then echo "[Backup] Valid data detected. Proceeding." break fi sleep 30 done do_backup # ── Loop with adjustable interval ───────────────────────────────── while true; do if [ -f "$INTERVAL_FILE" ]; then interval_hours=$(cat "$INTERVAL_FILE" | tr -d '[:space:]') interval_hours="${interval_hours:-$DEFAULT_INTERVAL_HOURS}" else interval_hours="$DEFAULT_INTERVAL_HOURS" fi sleep_seconds=$(( 3600 * interval_hours )) [ "$sleep_seconds" -lt 3600 ] && sleep_seconds=3600 # minimum 1 hour echo "[Backup] Next backup in ${interval_hours}h ($sleep_seconds s)." sleep "$sleep_seconds" do_backup done