| #!/bin/bash |
| |
| |
| |
| |
| set -e |
|
|
| DATA_DIR="${ABV_DATA_DIR:-/home/user/.antigravity_tools}" |
| SYNC_INTERVAL="${SUPABASE_SYNC_INTERVAL:-300}" |
| SNAPSHOT_DIR="/tmp/.sync_snapshot" |
| EXPORT_FILE="/tmp/.accounts_export.json" |
| EXPORT_REMOTE="config/accounts_export.json" |
|
|
| mkdir -p "$DATA_DIR/accounts" "$SNAPSHOT_DIR" |
|
|
| |
| |
| |
|
|
| supabase_enabled() { |
| [ -n "$SUPABASE_URL" ] && [ -n "$SUPABASE_KEY" ] && [ -n "$SUPABASE_BUCKET" ] |
| } |
|
|
| sb_download() { |
| local remote="$1" local_path="$2" |
| local code |
| code=$(curl -sf -o "$local_path" -w "%{http_code}" \ |
| "${SUPABASE_URL}/storage/v1/object/${SUPABASE_BUCKET}/${remote}" \ |
| -H "Authorization: Bearer ${SUPABASE_KEY}" \ |
| -H "apikey: ${SUPABASE_KEY}" 2>/dev/null) || true |
| if [ "$code" = "200" ]; then |
| echo "[sync] ✓ downloaded: $remote" |
| return 0 |
| else |
| rm -f "$local_path"; return 1 |
| fi |
| } |
|
|
| sb_upload() { |
| local local_path="$1" remote="$2" |
| [ -f "$local_path" ] || return 1 |
| local code |
| code=$(curl -sf -o /dev/null -w "%{http_code}" \ |
| -X POST \ |
| "${SUPABASE_URL}/storage/v1/object/${SUPABASE_BUCKET}/${remote}" \ |
| -H "Authorization: Bearer ${SUPABASE_KEY}" \ |
| -H "apikey: ${SUPABASE_KEY}" \ |
| -H "x-upsert: true" \ |
| -F "file=@${local_path}" 2>/dev/null) || true |
| if [ "$code" = "200" ] || [ "$code" = "201" ]; then |
| echo "[sync] ✓ uploaded: $remote"; return 0 |
| else |
| echo "[sync] ✗ upload failed ($code): $remote"; return 1 |
| fi |
| } |
|
|
| sb_list() { |
| local prefix="$1" |
| curl -sf \ |
| "${SUPABASE_URL}/storage/v1/object/list/${SUPABASE_BUCKET}" \ |
| -H "Authorization: Bearer ${SUPABASE_KEY}" \ |
| -H "apikey: ${SUPABASE_KEY}" \ |
| -H "Content-Type: application/json" \ |
| -d "{\"prefix\":\"${prefix}\",\"limit\":1000}" 2>/dev/null |
| } |
|
|
| |
| |
| |
|
|
| file_hash() { md5sum "$1" 2>/dev/null | cut -d' ' -f1; } |
|
|
| save_snapshot() { |
| local file="$1" key="$2" |
| if [ -f "$file" ]; then |
| file_hash "$file" > "$SNAPSHOT_DIR/$key" |
| fi |
| } |
|
|
| file_changed() { |
| local file="$1" key="$2" |
| [ ! -f "$file" ] && return 1 |
| local old new |
| old=$(cat "$SNAPSHOT_DIR/$key" 2>/dev/null || echo "") |
| new=$(file_hash "$file") |
| [ "$old" != "$new" ] |
| } |
|
|
| upload_if_changed() { |
| local file="$1" remote="$2" key="$3" |
| if file_changed "$file" "$key"; then |
| if sb_upload "$file" "$remote"; then |
| save_snapshot "$file" "$key" |
| fi |
| fi |
| } |
|
|
| |
| |
| |
|
|
| import_export_file() { |
| supabase_enabled || return 0 |
|
|
| local tmp_export="/tmp/.accounts_export_new.json" |
| sb_download "$EXPORT_REMOTE" "$tmp_export" || { echo "[import] No export file on Supabase"; return 0; } |
|
|
| |
| local old_hash new_hash |
| old_hash=$(file_hash "$EXPORT_FILE" 2>/dev/null || echo "none") |
| new_hash=$(file_hash "$tmp_export") |
| if [ "$old_hash" = "$new_hash" ]; then |
| echo "[import] Export file unchanged, skipping" |
| rm -f "$tmp_export" |
| return 0 |
| fi |
|
|
| echo "[import] Export file updated, importing accounts..." |
| cp "$tmp_export" "$EXPORT_FILE" |
| rm -f "$tmp_export" |
|
|
| |
| |
| local port="${PORT:-7860}" |
| local auth_token="${WEB_PASSWORD:-}" |
| [ -z "$auth_token" ] && auth_token="${API_KEY:-}" |
| [ -z "$auth_token" ] && auth_token=$(jq -r '.admin_password // .api_key // empty' "$DATA_DIR/gui_config.json" 2>/dev/null || echo "") |
|
|
| local count=0 total |
| total=$(jq 'length' "$EXPORT_FILE" 2>/dev/null || echo "0") |
|
|
| for i in $(seq 0 $((total - 1))); do |
| local email rt |
| email=$(jq -r ".[$i].email // empty" "$EXPORT_FILE") |
| rt=$(jq -r ".[$i].refresh_token // empty" "$EXPORT_FILE") |
| [ -z "$rt" ] && continue |
|
|
| |
| local resp code |
| code=$(curl -sf -o /tmp/.import_resp.json -w "%{http_code}" \ |
| -X POST "http://127.0.0.1:${port}/api/accounts" \ |
| -H "Authorization: Bearer ${auth_token}" \ |
| -H "Content-Type: application/json" \ |
| -d "{\"refreshToken\":\"${rt}\"}" 2>/dev/null) || true |
|
|
| if [ "$code" = "200" ] || [ "$code" = "201" ]; then |
| echo "[import] ✓ imported: ${email:-unknown}" |
| count=$((count + 1)) |
| else |
| local err |
| err=$(jq -r '.error // empty' /tmp/.import_resp.json 2>/dev/null || echo "HTTP $code") |
| echo "[import] ✗ failed: ${email:-unknown} ($err)" |
| fi |
| done |
|
|
| echo "[import] Done: $count/$total accounts imported" |
|
|
| |
| sleep 2 |
| save_snapshot "$DATA_DIR/accounts.json" "accounts.json" |
| for f in "$DATA_DIR/accounts"/*.json; do |
| if [ -f "$f" ]; then |
| save_snapshot "$f" "acc_$(basename "$f")" |
| fi |
| done |
| } |
|
|
| |
| |
| |
| restore_data() { |
| supabase_enabled || { echo "[sync] Supabase not configured, skipping"; return 0; } |
|
|
| echo "[sync] Restoring data from Supabase..." |
|
|
| sb_download "config/gui_config.json" "$DATA_DIR/gui_config.json" || true |
| sb_download "config/accounts.json" "$DATA_DIR/accounts.json" || true |
|
|
| local file_list |
| file_list=$(sb_list "accounts/") |
| if [ -n "$file_list" ] && [ "$file_list" != "[]" ]; then |
| echo "$file_list" | jq -r '.[].name // empty' 2>/dev/null | while read -r fname; do |
| [ -n "$fname" ] && [ "$fname" != ".emptyFolderPlaceholder" ] && \ |
| sb_download "accounts/${fname}" "$DATA_DIR/accounts/${fname}" || true |
| done |
| fi |
|
|
| sb_download "db/token_stats.db" "$DATA_DIR/token_stats.db" || true |
| sb_download "db/security.db" "$DATA_DIR/security.db" || true |
| sb_download "db/user_tokens.db" "$DATA_DIR/user_tokens.db" || true |
|
|
| echo "[sync] Restore complete" |
|
|
| |
| if [ -f "$DATA_DIR/accounts.json" ]; then |
| local count |
| count=$(jq '.accounts | length' "$DATA_DIR/accounts.json" 2>/dev/null || echo "?") |
| echo "[sync] accounts.json contains $count account(s)" |
| fi |
|
|
| |
| save_snapshot "$DATA_DIR/gui_config.json" "gui_config.json" |
| save_snapshot "$DATA_DIR/accounts.json" "accounts.json" |
| save_snapshot "$DATA_DIR/token_stats.db" "token_stats.db" |
| save_snapshot "$DATA_DIR/security.db" "security.db" |
| save_snapshot "$DATA_DIR/user_tokens.db" "user_tokens.db" |
| for f in "$DATA_DIR/accounts"/*.json; do |
| if [ -f "$f" ]; then |
| save_snapshot "$f" "acc_$(basename "$f")" |
| fi |
| done |
| } |
|
|
| |
| |
| |
| sync_data() { |
| supabase_enabled || return 0 |
| echo "[sync] Checking for changes... ($(date '+%H:%M:%S'))" |
|
|
| upload_if_changed "$DATA_DIR/gui_config.json" "config/gui_config.json" "gui_config.json" |
|
|
| |
| if [ -f "$DATA_DIR/accounts.json" ]; then |
| local acc_count |
| acc_count=$(jq '.accounts | length' "$DATA_DIR/accounts.json" 2>/dev/null || echo "0") |
| if [ "$acc_count" -gt 0 ]; then |
| upload_if_changed "$DATA_DIR/accounts.json" "config/accounts.json" "accounts.json" |
| else |
| echo "[sync] ⚠ Skipping accounts.json (0 accounts, protecting remote)" |
| fi |
| fi |
|
|
| for f in "$DATA_DIR/accounts"/*.json; do |
| if [ -f "$f" ]; then |
| upload_if_changed "$f" "accounts/$(basename "$f")" "acc_$(basename "$f")" |
| fi |
| done |
|
|
| upload_if_changed "$DATA_DIR/token_stats.db" "db/token_stats.db" "token_stats.db" |
| upload_if_changed "$DATA_DIR/security.db" "db/security.db" "security.db" |
| upload_if_changed "$DATA_DIR/user_tokens.db" "db/user_tokens.db" "user_tokens.db" |
|
|
| |
| import_export_file |
|
|
| echo "[sync] Done" |
| } |
|
|
| background_sync() { |
| while true; do sleep "$SYNC_INTERVAL"; sync_data; done |
| } |
|
|
| graceful_shutdown() { |
| echo "[hf] Shutdown, final sync..." |
| sync_data; exit 0 |
| } |
| trap graceful_shutdown SIGTERM SIGINT |
|
|
| |
| |
| |
| echo "============================================" |
| echo " Antigravity Manager - HF Spaces" |
| echo "============================================" |
| echo " Data dir : $DATA_DIR" |
| echo " Port : ${PORT:-7860}" |
| echo " Supabase : $(supabase_enabled && echo 'enabled' || echo 'disabled')" |
| echo "============================================" |
|
|
| restore_data |
|
|
| CONFIG_FILE="$DATA_DIR/gui_config.json" |
| HF_PORT="${PORT:-7860}" |
| if [ -f "$CONFIG_FILE" ]; then |
| |
| if jq --argjson port "$HF_PORT" '.proxy.port = $port' "$CONFIG_FILE" > "${CONFIG_FILE}.tmp" 2>/dev/null; then |
| mv "${CONFIG_FILE}.tmp" "$CONFIG_FILE" |
| echo "[hf] Port set to $HF_PORT in existing config" |
| else |
| rm -f "${CONFIG_FILE}.tmp" |
| echo "[hf] Warning: existing config invalid, removing so app creates fresh default" |
| rm -f "$CONFIG_FILE" |
| fi |
| else |
| echo "[hf] No config file — app will create default on first start" |
| fi |
|
|
| if supabase_enabled; then |
| echo "[sync] Background sync every ${SYNC_INTERVAL}s (change-based)" |
| background_sync & |
| fi |
|
|
| |
| /app/antigravity-tools --headless & |
| APP_PID=$! |
|
|
| |
| echo "[hf] Waiting for app to start..." |
| for i in $(seq 1 30); do |
| if curl -sf "http://127.0.0.1:${HF_PORT}/health" >/dev/null 2>&1; then |
| echo "[hf] App ready" |
| break |
| fi |
| sleep 1 |
| done |
|
|
| |
| import_export_file |
|
|
| |
| wait $APP_PID || true |