Download sync_data.sh from gallygc/guma: direct link, hf CLI and curl.
- Browser
- Download file 8.03 kB
-
https://huggingface.co/spaces/gallygc/guma/resolve/main/sync_data.sh
- Command line
-
hf download hf://spaces/gallygc/guma/sync_data.sh
-
curl -L -o sync_data.sh https://huggingface.co/spaces/gallygc/guma/resolve/main/sync_data.sh
8.03 kB
| set -eu | |
| APP_DIR="/home/node/bbb" | |
| DATA_DIR="${APP_DIR}/data" | |
| cd "$APP_DIR" | |
| echo "===== Uptime Kuma startup =====" | |
| echo "App dir: $APP_DIR" | |
| echo "Data dir: $DATA_DIR" | |
| # 双保险:如果外部没传,就默认 bypass | |
| export UPTIME_KUMA_WS_ORIGIN_CHECK="${UPTIME_KUMA_WS_ORIGIN_CHECK:-bypass}" | |
| echo "UPTIME_KUMA_WS_ORIGIN_CHECK=$UPTIME_KUMA_WS_ORIGIN_CHECK" | |
| # 如果没有配置 WebDAV,就直接启动 Uptime Kuma | |
| if [ -z "${WEBDAV_URL:-}" ] || [ -z "${WEBDAV_USERNAME:-}" ] || [ -z "${WEBDAV_PASSWORD:-}" ]; then | |
| echo "Starting without backup functionality - missing WEBDAV_URL, WEBDAV_USERNAME, or WEBDAV_PASSWORD" | |
| exec node server/server.js | |
| fi | |
| # 组装 WebDAV 地址 | |
| WEBDAV_BACKUP_PATH="${WEBDAV_BACKUP_PATH:-}" | |
| FULL_WEBDAV_URL="${WEBDAV_URL}" | |
| if [ -n "$WEBDAV_BACKUP_PATH" ]; then | |
| FULL_WEBDAV_URL="${WEBDAV_URL%/}/${WEBDAV_BACKUP_PATH#/}" | |
| fi | |
| export FULL_WEBDAV_URL | |
| export APP_DIR | |
| export DATA_DIR | |
| echo "WebDAV URL: $FULL_WEBDAV_URL" | |
| restore_backup() { | |
| echo "Downloading latest backup from WebDAV..." | |
| python3 - <<'PY' | |
| import os | |
| import sys | |
| import tarfile | |
| import shutil | |
| import requests | |
| from pathlib import Path | |
| from webdav3.client import Client | |
| FULL_WEBDAV_URL = os.environ["FULL_WEBDAV_URL"] | |
| WEBDAV_USERNAME = os.environ["WEBDAV_USERNAME"] | |
| WEBDAV_PASSWORD = os.environ["WEBDAV_PASSWORD"] | |
| DATA_DIR = os.environ["DATA_DIR"] | |
| TMP_DIR = Path("/tmp/uptime_kuma_restore") | |
| TMP_DIR.mkdir(parents=True, exist_ok=True) | |
| def safe_extract(tar: tarfile.TarFile, path: str): | |
| base = Path(path).resolve() | |
| for member in tar.getmembers(): | |
| member_path = (base / member.name).resolve() | |
| if not str(member_path).startswith(str(base)): | |
| raise Exception(f"Unsafe path in tar archive: {member.name}") | |
| tar.extractall(path) | |
| options = { | |
| "webdav_hostname": FULL_WEBDAV_URL, | |
| "webdav_login": WEBDAV_USERNAME, | |
| "webdav_password": WEBDAV_PASSWORD, | |
| } | |
| client = Client(options) | |
| try: | |
| entries = client.list() | |
| except Exception as e: | |
| print(f"[restore] Failed to list WebDAV directory: {e}") | |
| sys.exit(1) | |
| backups =[f for f in entries if f.endswith(".tar.gz") and f.startswith("uptime_kuma_backup_")] | |
| if not backups: | |
| print("[restore] No backup file found on WebDAV. Skip restore.") | |
| sys.exit(0) | |
| latest_backup = sorted(backups)[-1] | |
| print(f"[restore] Latest backup: {latest_backup}") | |
| backup_path = TMP_DIR / latest_backup | |
| extract_path = TMP_DIR / "extract" | |
| if extract_path.exists(): | |
| shutil.rmtree(extract_path) | |
| extract_path.mkdir(parents=True, exist_ok=True) | |
| download_url = f"{FULL_WEBDAV_URL.rstrip('/')}/{latest_backup}" | |
| try: | |
| with requests.get(download_url, auth=(WEBDAV_USERNAME, WEBDAV_PASSWORD), stream=True, timeout=120) as r: | |
| if r.status_code != 200: | |
| print(f"[restore] Download failed: HTTP {r.status_code}") | |
| sys.exit(1) | |
| with open(backup_path, "wb") as f: | |
| for chunk in r.iter_content(chunk_size=1024 * 1024): | |
| if chunk: | |
| f.write(chunk) | |
| except Exception as e: | |
| print(f"[restore] Exception while downloading backup: {e}") | |
| sys.exit(1) | |
| print(f"[restore] Downloaded to: {backup_path}") | |
| try: | |
| with tarfile.open(backup_path, "r:gz") as tar: | |
| safe_extract(tar, str(extract_path)) | |
| except Exception as e: | |
| print(f"[restore] Failed to extract backup: {e}") | |
| sys.exit(1) | |
| restored_data_dir = extract_path / "data" | |
| if not restored_data_dir.exists() or not restored_data_dir.is_dir(): | |
| print("[restore] Invalid backup: 'data/' directory not found in archive") | |
| sys.exit(1) | |
| # 至少要求 db-config.json 存在,否则会再次进入 setup 模式 | |
| db_config = restored_data_dir / "db-config.json" | |
| if not db_config.exists(): | |
| print("[restore] Invalid backup: missing data/db-config.json") | |
| print("[restore] Skip replacing current data directory to avoid broken restore.") | |
| sys.exit(1) | |
| target_data_dir = Path(DATA_DIR) | |
| backup_old_dir = Path(f"{DATA_DIR}.bak") | |
| if backup_old_dir.exists(): | |
| shutil.rmtree(backup_old_dir) | |
| if target_data_dir.exists(): | |
| print(f"[restore] Backing up current data directory to: {backup_old_dir}") | |
| shutil.move(str(target_data_dir), str(backup_old_dir)) | |
| try: | |
| shutil.copytree(restored_data_dir, target_data_dir) | |
| print(f"[restore] Restore completed successfully to: {target_data_dir}") | |
| except Exception as e: | |
| print(f"[restore] Failed to place restored data: {e}") | |
| if target_data_dir.exists(): | |
| shutil.rmtree(target_data_dir) | |
| if backup_old_dir.exists(): | |
| shutil.move(str(backup_old_dir), str(target_data_dir)) | |
| sys.exit(1) | |
| if backup_old_dir.exists(): | |
| shutil.rmtree(backup_old_dir, ignore_errors=True) | |
| print("[restore] Backup restore finished.") | |
| PY | |
| } | |
| sync_data_loop() { | |
| while true; do | |
| echo "Starting sync process at $(date)" | |
| if [ -d "$DATA_DIR" ]; then | |
| timestamp=$(date +%Y%m%d_%H%M%S) | |
| backup_file="uptime_kuma_backup_${timestamp}.tar.gz" | |
| backup_path="/tmp/${backup_file}" | |
| STAGING_DIR="/tmp/kuma_backup_stage_${timestamp}" | |
| rm -rf "${STAGING_DIR}" | |
| mkdir -p "${STAGING_DIR}" | |
| # 1. 复制现有的 data 目录所有配置、缓存文件 | |
| cp -a "$DATA_DIR" "${STAGING_DIR}/" || true | |
| # 2. 从暂存区删除正在被占用的数据库文件 | |
| rm -f "${STAGING_DIR}/data/kuma.db" "${STAGING_DIR}/data/kuma.db-wal" "${STAGING_DIR}/data/kuma.db-shm" | |
| # 3. 使用 sqlite3 将安全的数据库快照写出到暂存区 | |
| if [ -f "$DATA_DIR/kuma.db" ]; then | |
| sqlite3 "$DATA_DIR/kuma.db" ".backup '${STAGING_DIR}/data/kuma.db'" | |
| fi | |
| # 4. 在绝对静止的暂存目录中进行打包 | |
| cd "${STAGING_DIR}" | |
| tar -czf "$backup_path" data | |
| cd "$APP_DIR" | |
| # 5. 清理暂存区 | |
| rm -rf "${STAGING_DIR}" | |
| # 开始上传打包好的文件 | |
| if curl -fsS -u "$WEBDAV_USERNAME:$WEBDAV_PASSWORD" \ | |
| -T "$backup_path" \ | |
| "${FULL_WEBDAV_URL%/}/${backup_file}"; then | |
| echo "Successfully uploaded ${backup_file} to WebDAV" | |
| else | |
| echo "Failed to upload ${backup_file} to WebDAV" | |
| rm -f "$backup_path" | |
| SYNC_INTERVAL="${SYNC_INTERVAL:-600}" | |
| echo "Next sync in ${SYNC_INTERVAL} seconds..." | |
| sleep "$SYNC_INTERVAL" | |
| continue | |
| fi | |
| # 上传成功后执行清理旧备份的 Python 脚本 | |
| python3 - <<'PY' | |
| import os | |
| from webdav3.client import Client | |
| FULL_WEBDAV_URL = os.environ["FULL_WEBDAV_URL"] | |
| WEBDAV_USERNAME = os.environ["WEBDAV_USERNAME"] | |
| WEBDAV_PASSWORD = os.environ["WEBDAV_PASSWORD"] | |
| options = { | |
| "webdav_hostname": FULL_WEBDAV_URL, | |
| "webdav_login": WEBDAV_USERNAME, | |
| "webdav_password": WEBDAV_PASSWORD, | |
| } | |
| client = Client(options) | |
| try: | |
| entries = client.list() | |
| backups =[f for f in entries if f.endswith(".tar.gz") and f.startswith("uptime_kuma_backup_")] | |
| backups.sort() | |
| if len(backups) > 10: | |
| old_files = backups[:len(backups) - 10] | |
| for file in old_files: | |
| client.clean(file) | |
| print(f"Successfully deleted old backup: {file}") | |
| else: | |
| print(f"Only {len(backups)} backups found, no cleanup needed.") | |
| except Exception as e: | |
| print(f"Cleanup failed: {e}") | |
| PY | |
| rm -f "$backup_path" | |
| else | |
| echo "$DATA_DIR does not exist, waiting for next sync..." | |
| fi | |
| SYNC_INTERVAL="${SYNC_INTERVAL:-600}" | |
| echo "Next sync in ${SYNC_INTERVAL} seconds..." | |
| sleep "$SYNC_INTERVAL" | |
| done | |
| } | |
| # 启动前尝试恢复一次 | |
| if restore_backup; then | |
| echo "Restore step completed." | |
| else | |
| echo "Restore failed or skipped. Continue starting app." | |
| fi | |
| # 后台同步(只负责备份) | |
| sync_data_loop & | |
| # 前台启动 Uptime Kuma(只启动一次) | |
| exec node server/server.js |