File size: 6,509 Bytes
805b3d2
 
 
 
 
 
 
 
 
 
 
 
 
 
46c1b7f
 
ef38f88
 
805b3d2
 
 
ef38f88
 
805b3d2
 
 
 
 
 
 
9c0b6ad
 
805b3d2
9c0b6ad
805b3d2
 
9c0b6ad
 
 
 
805b3d2
4f6ca23
805b3d2
 
 
4f6ca23
 
 
b8d0759
4f6ca23
 
 
 
 
 
 
 
 
 
805b3d2
9c0b6ad
 
 
 
 
805b3d2
 
 
 
 
 
 
 
9c0b6ad
805b3d2
 
 
 
 
 
 
 
 
9c0b6ad
 
 
805b3d2
9c0b6ad
 
 
805b3d2
9c0b6ad
805b3d2
9c0b6ad
805b3d2
 
9c0b6ad
 
 
805b3d2
9c0b6ad
805b3d2
9c0b6ad
805b3d2
 
 
e499dd1
 
b8d0759
 
805b3d2
 
 
 
 
 
b8d0759
805b3d2
 
 
 
 
 
9c0b6ad
 
805b3d2
 
9c0b6ad
805b3d2
46c1b7f
805b3d2
 
 
 
 
 
 
9c0b6ad
 
 
805b3d2
9c0b6ad
 
 
805b3d2
 
 
 
 
 
 
 
 
 
4f6ca23
805b3d2
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python3
"""
OmniRoute Cloud Vault Synchronizer
===================================
Provides zero-loss persistence across arbitrary container restarts, rebuilds,
and ephemeral-disk resets by maintaining a bidirectional sync between
/data/omniroute and a private Hugging Face Dataset repository.
"""

import os
import sys
import shutil
import sqlite3
import logging
from datetime import datetime, timezone

os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
os.environ["TQDM_DISABLE"] = "1"

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] [VaultSync] %(message)s")
logger = logging.getLogger("VaultSync")
for noisy in ("httpx", "httpcore", "urllib3", "huggingface_hub"):
    logging.getLogger(noisy).setLevel(logging.WARNING)

VAULT_REPO = os.environ.get("OMNIROUTE_VAULT_REPO", "Jishnupg/omniroute-storage-vault")
DATA_DIR = os.environ.get("DATA_DIR", "/data/omniroute")
DB_PATH = os.path.join(DATA_DIR, "storage.sqlite")
HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_API_KEY")

def get_hf_api():
    token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_API_KEY")
    if not token:
        logger.warning("HF_TOKEN is not set; cloud vault sync is disabled.")
        return None, "HF_TOKEN is not set in environment"
    try:
        from huggingface_hub import HfApi
        return HfApi(token=token), None
    except ImportError as exc:
        logger.error(f"huggingface_hub import error: {exc}")
        return None, f"huggingface_hub import error: {exc}"

def restore_from_vault(force: bool = False) -> tuple[bool, str]:
    """Restores storage.sqlite from private HF Dataset if local database is absent or empty."""
    os.makedirs(DATA_DIR, exist_ok=True)
    
    # If a valid local database already exists with positive size AND passes quick_check, keep local state
    if not force and os.path.exists(DB_PATH) and os.path.getsize(DB_PATH) > 0:
        try:
            chk_conn = sqlite3.connect(str(Path(DB_PATH).resolve()), timeout=3.0)
            res = chk_conn.execute("PRAGMA quick_check;").fetchone()
            chk_conn.close()
            if res and res[0] == "ok":
                msg = f"Authoritative local database found at {DB_PATH} ({os.path.getsize(DB_PATH)} bytes) and verified healthy."
                logger.info(msg)
                return True, msg
            else:
                logger.warning(f"Local database exists but failed quick_check: {res}. Forcing restore from cloud vault...")
        except Exception as e:
            logger.warning(f"Local database check threw exception: {e}. Forcing restore from cloud vault...")

    token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_API_KEY")
    if not token:
        msg = "No HF_TOKEN available to restore from cloud vault."
        logger.info(msg)
        return False, msg

    try:
        from huggingface_hub import hf_hub_download
        logger.info(f"Attempting to restore database from cloud vault {VAULT_REPO}...")
        downloaded = hf_hub_download(
            repo_id=VAULT_REPO,
            filename="storage.sqlite",
            repo_type="dataset",
            token=token,
            force_download=True
        )
        if downloaded and os.path.exists(downloaded) and os.path.getsize(downloaded) > 0:
            shutil.copy2(downloaded, DB_PATH)
            # Remove any stale WAL / SHM files
            for ext in ("-wal", "-shm"):
                p = DB_PATH + ext
                if os.path.exists(p):
                    os.remove(p)
            msg = f"Successfully restored database from cloud vault ({os.path.getsize(DB_PATH)} bytes) to {DB_PATH}."
            logger.info(msg)
            return True, msg
    except Exception as e:
        msg = f"No existing cloud vault snapshot found or restore failed: {e}"
        logger.warning(msg)
        return False, msg

    return False, "Restore failed"

def backup_to_vault() -> tuple[bool, str]:
    """Creates a consistent SQLite snapshot and uploads it to the private HF Dataset."""
    if not os.path.exists(DB_PATH) or os.path.getsize(DB_PATH) == 0:
        msg = f"Database at {DB_PATH} does not exist or is empty; skipping backup."
        logger.debug(msg)
        return False, msg

    api, err = get_hf_api()
    if not api:
        return False, err

    snapshot_path = os.path.join(DATA_DIR, "storage.sqlite.snapshot")
    try:
        # Create a consistent online backup using SQLite Backup API in read-only mode
        # to avoid locking or colliding with active Node.js WAL transactions
        src_conn = sqlite3.connect(str(Path(DB_PATH).resolve()), timeout=10.0)
        dst_conn = sqlite3.connect(str(Path(snapshot_path).resolve()), timeout=10.0)
        with dst_conn:
            src_conn.backup(dst_conn, pages=100)
        dst_conn.close()
        src_conn.close()

        # Validate snapshot integrity before upload
        check_conn = sqlite3.connect(str(Path(snapshot_path).resolve()), timeout=5.0)
        cur = check_conn.cursor()
        cur.execute("PRAGMA quick_check;")
        res = cur.fetchone()
        check_conn.close()

        if not res or res[0] != "ok":
            msg = f"Snapshot integrity check failed: {res}. Skipping upload."
            logger.error(msg)
            if os.path.exists(snapshot_path):
                os.remove(snapshot_path)
            return False, msg

        commit_msg = f"Automated OmniRoute storage checkpoint [{datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}]"
        api.upload_file(
            path_or_fileobj=snapshot_path,
            path_in_repo="storage.sqlite",
            repo_id=VAULT_REPO,
            repo_type="dataset",
            commit_message=commit_msg
        )
        msg = f"Database checkpoint ({os.path.getsize(snapshot_path)} bytes) synced to cloud vault {VAULT_REPO}."
        logger.info(msg)
        return True, msg
    except Exception as e:
        msg = f"Failed to sync database to cloud vault: {e}"
        logger.error(msg)
        return False, msg
    finally:
        if os.path.exists(snapshot_path):
            try:
                os.remove(snapshot_path)
            except Exception:
                pass

if __name__ == "__main__":
    action = sys.argv[1] if len(sys.argv) > 1 else "backup"
    if action == "restore":
        restore_from_vault(force=("--force" in sys.argv))
    elif action == "backup":
        backup_to_vault()
    else:
        print(f"Usage: {sys.argv[0]} [restore|backup]")