Test / entrypoint.sh
NIKHTU03's picture
Update entrypoint.sh
768b3a2 verified
Raw
History Blame Contribute Delete
10.9 kB
#!/bin/bash
# ============================================================
# Antigravity Manager - HF Spaces Entrypoint
# Supabase Storage 数据持久化(变更检测 + 空文件保护)
# ============================================================
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 Storage API
# ============================================================
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
}
# ============================================================
# 导出文件导入:从 Supabase 下载 accounts_export.json 并通过 API 导入
# ============================================================
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"
# 读取导出文件中的每个账号,通过 API 导入
# 管理接口优先使用 admin_password (WEB_PASSWORD),没有则回退 api_key
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
# 调用 API 添加账号(已存在的会自动更新 token)
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"
# 导入后重新保存快照(账号文件已被 app 更新)
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"
# accounts.json 保护:0 accounts 绝不上传
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"
# 检查 Supabase 上的导出文件是否有更新,有则自动导入
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
# Only patch port in an existing (valid) config downloaded from Supabase
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