cjovs commited on
Commit
4da2f34
·
verified ·
1 Parent(s): affea2b

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 [[ -d "/data" ]]; then
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="${APP_LOGS_DIR:-${data_dir}/logs}"
22
 
23
  mkdir -p "${APP_DATA_DIR}" "${APP_LOGS_DIR}"
24
 
25
- if [[ -z "${APP_DATABASE_URL:-}" && -z "${DATABASE_URL:-}" ]]; then
26
- export APP_DATABASE_URL="sqlite:///${APP_DATA_DIR}/database.db"
 
 
 
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
- exec "${cmd[@]}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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}"