Spaces:
Paused
Paused
Use local SQLite runtime with bucket snapshots
Browse files
deploy/huggingface/sqlite_backup.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import os
|
| 5 |
+
import shutil
|
| 6 |
+
import sqlite3
|
| 7 |
+
import tempfile
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def _temp_path_for(destination: Path) -> Path:
|
| 12 |
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
| 13 |
+
fd, temp_name = tempfile.mkstemp(prefix=destination.stem + ".", suffix=".tmp", dir=destination.parent)
|
| 14 |
+
os.close(fd)
|
| 15 |
+
return Path(temp_name)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def backup_database(source: Path | str, destination: Path | str) -> bool:
|
| 19 |
+
source_path = Path(source)
|
| 20 |
+
destination_path = Path(destination)
|
| 21 |
+
if not source_path.exists():
|
| 22 |
+
print(f"[hf-space] backup skipped; source missing: {source_path}")
|
| 23 |
+
return False
|
| 24 |
+
|
| 25 |
+
temp_path = _temp_path_for(destination_path)
|
| 26 |
+
src_conn = None
|
| 27 |
+
dst_conn = None
|
| 28 |
+
try:
|
| 29 |
+
src_conn = sqlite3.connect(source_path)
|
| 30 |
+
dst_conn = sqlite3.connect(temp_path)
|
| 31 |
+
src_conn.backup(dst_conn)
|
| 32 |
+
dst_conn.commit()
|
| 33 |
+
dst_conn.close()
|
| 34 |
+
dst_conn = None
|
| 35 |
+
src_conn.close()
|
| 36 |
+
src_conn = None
|
| 37 |
+
os.replace(temp_path, destination_path)
|
| 38 |
+
print(f"[hf-space] backup updated: {destination_path}")
|
| 39 |
+
return True
|
| 40 |
+
finally:
|
| 41 |
+
if dst_conn is not None:
|
| 42 |
+
dst_conn.close()
|
| 43 |
+
if src_conn is not None:
|
| 44 |
+
src_conn.close()
|
| 45 |
+
if temp_path.exists():
|
| 46 |
+
temp_path.unlink(missing_ok=True)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def restore_database(source: Path | str, destination: Path | str) -> bool:
|
| 50 |
+
source_path = Path(source)
|
| 51 |
+
destination_path = Path(destination)
|
| 52 |
+
if not source_path.exists():
|
| 53 |
+
print(f"[hf-space] restore skipped; source missing: {source_path}")
|
| 54 |
+
return False
|
| 55 |
+
|
| 56 |
+
temp_path = _temp_path_for(destination_path)
|
| 57 |
+
try:
|
| 58 |
+
shutil.copy2(source_path, temp_path)
|
| 59 |
+
os.replace(temp_path, destination_path)
|
| 60 |
+
print(f"[hf-space] restore completed: {destination_path}")
|
| 61 |
+
return True
|
| 62 |
+
finally:
|
| 63 |
+
if temp_path.exists():
|
| 64 |
+
temp_path.unlink(missing_ok=True)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def main() -> int:
|
| 68 |
+
parser = argparse.ArgumentParser(description="Backup or restore a SQLite database for HF Spaces.")
|
| 69 |
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
| 70 |
+
|
| 71 |
+
for command in ("backup", "restore"):
|
| 72 |
+
command_parser = subparsers.add_parser(command)
|
| 73 |
+
command_parser.add_argument("--source", required=True)
|
| 74 |
+
command_parser.add_argument("--destination", required=True)
|
| 75 |
+
|
| 76 |
+
args = parser.parse_args()
|
| 77 |
+
if args.command == "backup":
|
| 78 |
+
backup_database(args.source, args.destination)
|
| 79 |
+
else:
|
| 80 |
+
restore_database(args.source, args.destination)
|
| 81 |
+
return 0
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
if __name__ == "__main__":
|
| 85 |
+
raise SystemExit(main())
|
deploy/huggingface/start.sh
CHANGED
|
@@ -2,38 +2,82 @@
|
|
| 2 |
set -euo pipefail
|
| 3 |
|
| 4 |
APP_ROOT="/app"
|
|
|
|
|
|
|
| 5 |
|
| 6 |
effective_host="${APP_HOST:-${WEBUI_HOST:-0.0.0.0}}"
|
| 7 |
effective_port="${APP_PORT:-${WEBUI_PORT:-${PORT:-1455}}}"
|
| 8 |
effective_password="${APP_ACCESS_PASSWORD:-${WEBUI_ACCESS_PASSWORD:-${SPACE_PASSWORD:-}}}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
if [[ -n "${APP_DATA_DIR:-}" ]]; then
|
| 11 |
data_dir="${APP_DATA_DIR}"
|
| 12 |
-
elif [[ -
|
| 13 |
-
data_dir="/data"
|
| 14 |
else
|
| 15 |
data_dir="${APP_ROOT}/data"
|
| 16 |
fi
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
export APP_HOST="${effective_host}"
|
| 19 |
export APP_PORT="${effective_port}"
|
| 20 |
export APP_DATA_DIR="${data_dir}"
|
| 21 |
-
export APP_LOGS_DIR="${
|
| 22 |
|
| 23 |
mkdir -p "${APP_DATA_DIR}" "${APP_LOGS_DIR}"
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
|
|
|
|
|
|
|
|
|
| 27 |
fi
|
| 28 |
|
| 29 |
if [[ -n "${effective_password}" ]]; then
|
| 30 |
export APP_ACCESS_PASSWORD="${effective_password}"
|
| 31 |
fi
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
echo "[hf-space] starting codex-console"
|
| 34 |
echo "[hf-space] listen: ${APP_HOST}:${APP_PORT}"
|
| 35 |
echo "[hf-space] data dir: ${APP_DATA_DIR}"
|
| 36 |
echo "[hf-space] logs dir: ${APP_LOGS_DIR}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
if [[ -n "${APP_ACCESS_PASSWORD:-}" ]]; then
|
| 38 |
echo "[hf-space] access password: configured"
|
| 39 |
else
|
|
@@ -45,4 +89,19 @@ if [[ -n "${APP_ACCESS_PASSWORD:-}" ]]; then
|
|
| 45 |
cmd+=(--access-password "${APP_ACCESS_PASSWORD}")
|
| 46 |
fi
|
| 47 |
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
set -euo pipefail
|
| 3 |
|
| 4 |
APP_ROOT="/app"
|
| 5 |
+
RUNTIME_ROOT="${HF_RUNTIME_ROOT:-/tmp/codex-console}"
|
| 6 |
+
PERSIST_ROOT=""
|
| 7 |
|
| 8 |
effective_host="${APP_HOST:-${WEBUI_HOST:-0.0.0.0}}"
|
| 9 |
effective_port="${APP_PORT:-${WEBUI_PORT:-${PORT:-1455}}}"
|
| 10 |
effective_password="${APP_ACCESS_PASSWORD:-${WEBUI_ACCESS_PASSWORD:-${SPACE_PASSWORD:-}}}"
|
| 11 |
+
configured_database_url="${APP_DATABASE_URL:-${DATABASE_URL:-}}"
|
| 12 |
+
|
| 13 |
+
if [[ -z "${configured_database_url}" && -d "/data" ]]; then
|
| 14 |
+
PERSIST_ROOT="/data"
|
| 15 |
+
fi
|
| 16 |
|
| 17 |
if [[ -n "${APP_DATA_DIR:-}" ]]; then
|
| 18 |
data_dir="${APP_DATA_DIR}"
|
| 19 |
+
elif [[ -n "${PERSIST_ROOT}" ]]; then
|
| 20 |
+
data_dir="${RUNTIME_ROOT}/data"
|
| 21 |
else
|
| 22 |
data_dir="${APP_ROOT}/data"
|
| 23 |
fi
|
| 24 |
|
| 25 |
+
if [[ -n "${APP_LOGS_DIR:-}" ]]; then
|
| 26 |
+
logs_dir="${APP_LOGS_DIR}"
|
| 27 |
+
elif [[ -n "${PERSIST_ROOT}" ]]; then
|
| 28 |
+
logs_dir="${RUNTIME_ROOT}/logs"
|
| 29 |
+
else
|
| 30 |
+
logs_dir="${data_dir}/logs"
|
| 31 |
+
fi
|
| 32 |
+
|
| 33 |
export APP_HOST="${effective_host}"
|
| 34 |
export APP_PORT="${effective_port}"
|
| 35 |
export APP_DATA_DIR="${data_dir}"
|
| 36 |
+
export APP_LOGS_DIR="${logs_dir}"
|
| 37 |
|
| 38 |
mkdir -p "${APP_DATA_DIR}" "${APP_LOGS_DIR}"
|
| 39 |
|
| 40 |
+
local_db_path="${APP_DATA_DIR}/database.db"
|
| 41 |
+
backup_pid=""
|
| 42 |
+
|
| 43 |
+
if [[ -z "${configured_database_url}" ]]; then
|
| 44 |
+
export APP_DATABASE_URL="sqlite:///${local_db_path}"
|
| 45 |
fi
|
| 46 |
|
| 47 |
if [[ -n "${effective_password}" ]]; then
|
| 48 |
export APP_ACCESS_PASSWORD="${effective_password}"
|
| 49 |
fi
|
| 50 |
|
| 51 |
+
if [[ -n "${PERSIST_ROOT}" ]]; then
|
| 52 |
+
persist_db_path="${PERSIST_ROOT}/database.db"
|
| 53 |
+
backup_interval="${HF_BACKUP_INTERVAL_SECONDS:-60}"
|
| 54 |
+
|
| 55 |
+
python /app/deploy/huggingface/sqlite_backup.py restore \
|
| 56 |
+
--source "${persist_db_path}" \
|
| 57 |
+
--destination "${local_db_path}" || true
|
| 58 |
+
|
| 59 |
+
backup_loop() {
|
| 60 |
+
while true; do
|
| 61 |
+
sleep "${backup_interval}"
|
| 62 |
+
python /app/deploy/huggingface/sqlite_backup.py backup \
|
| 63 |
+
--source "${local_db_path}" \
|
| 64 |
+
--destination "${persist_db_path}" || true
|
| 65 |
+
done
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
backup_loop &
|
| 69 |
+
backup_pid="$!"
|
| 70 |
+
fi
|
| 71 |
+
|
| 72 |
echo "[hf-space] starting codex-console"
|
| 73 |
echo "[hf-space] listen: ${APP_HOST}:${APP_PORT}"
|
| 74 |
echo "[hf-space] data dir: ${APP_DATA_DIR}"
|
| 75 |
echo "[hf-space] logs dir: ${APP_LOGS_DIR}"
|
| 76 |
+
if [[ -n "${PERSIST_ROOT}" ]]; then
|
| 77 |
+
echo "[hf-space] persistence: sqlite runtime at ${local_db_path}, bucket snapshot at ${persist_db_path}"
|
| 78 |
+
else
|
| 79 |
+
echo "[hf-space] persistence: direct filesystem"
|
| 80 |
+
fi
|
| 81 |
if [[ -n "${APP_ACCESS_PASSWORD:-}" ]]; then
|
| 82 |
echo "[hf-space] access password: configured"
|
| 83 |
else
|
|
|
|
| 89 |
cmd+=(--access-password "${APP_ACCESS_PASSWORD}")
|
| 90 |
fi
|
| 91 |
|
| 92 |
+
"${cmd[@]}" &
|
| 93 |
+
app_pid="$!"
|
| 94 |
+
set +e
|
| 95 |
+
wait "${app_pid}"
|
| 96 |
+
app_status="$?"
|
| 97 |
+
set -e
|
| 98 |
+
|
| 99 |
+
if [[ -n "${backup_pid}" ]]; then
|
| 100 |
+
python /app/deploy/huggingface/sqlite_backup.py backup \
|
| 101 |
+
--source "${local_db_path}" \
|
| 102 |
+
--destination "${persist_db_path}" || true
|
| 103 |
+
kill "${backup_pid}" 2>/dev/null || true
|
| 104 |
+
wait "${backup_pid}" 2>/dev/null || true
|
| 105 |
+
fi
|
| 106 |
+
|
| 107 |
+
exit "${app_status}"
|