thryyyyy commited on
Commit
83c0c6e
·
1 Parent(s): 3d80f82

restore from memory

Browse files
Files changed (1) hide show
  1. backend/scripts/restore.py +96 -74
backend/scripts/restore.py CHANGED
@@ -4,6 +4,7 @@ import sys
4
  import subprocess
5
  import datetime
6
  import sqlite3
 
7
  from pathlib import Path
8
  from huggingface_hub import HfApi, hf_hub_download
9
 
@@ -12,21 +13,17 @@ SCRIPT_DIR = Path(__file__).parent.resolve()
12
  BACKEND_DIR = SCRIPT_DIR.parent
13
  sys.path.append(str(BACKEND_DIR))
14
 
15
- # Database paths
16
  DATA_DIR = os.environ.get("DATA_DIR", "/app/backend/data")
17
  DB_FILE_PATH = os.path.join(DATA_DIR, "webui.db")
18
 
19
- # Restore paths
20
- RESTORE_BACKUP_DIR = os.environ.get("BACKUP_DIR", "/tmp/open_webui/db_backup")
21
- DB_GPG_PATH = os.path.join(RESTORE_BACKUP_DIR, "webui.db.gpg")
22
- TIMESTAMP_FILE_PATH = os.path.join(RESTORE_BACKUP_DIR, "last_backup_time.txt")
23
-
24
- # Hugging Face repo paths
25
- REPO_TIMESTAMP_FILE = "db_backup/last_backup_time.txt"
26
  REPO_DB_GPG_FILE = "db_backup/webui.db.gpg"
 
27
 
28
 
29
- def check_requirements():
 
30
  try:
31
  subprocess.run(["gpg", "--version"], check=True, capture_output=True)
32
  return True
@@ -35,33 +32,33 @@ def check_requirements():
35
  return False
36
 
37
 
38
- def validate_secrets():
39
- required_vars = ["BACKUP_PASSPHRASE", "HF_TOKEN", "SPACE_ID"]
40
- missing = [var for var in required_vars if not os.environ.get(var)]
41
-
42
  if missing:
43
- print(f"Error: Missing required environment variables: {', '.join(missing)}")
44
  return False
45
  return True
46
 
47
 
48
- def ensure_directories():
 
49
  try:
50
  os.makedirs(DATA_DIR, mode=0o755, exist_ok=True)
51
- os.makedirs(RESTORE_BACKUP_DIR, mode=0o755, exist_ok=True)
52
  return True
53
  except Exception as e:
54
- print(f"Error creating directories: {e}")
55
  return False
56
 
57
 
58
  def get_latest_backup_info(repo_id, hf_token):
 
59
  api = HfApi()
60
  try:
61
  files = api.list_repo_files(repo_id=repo_id, repo_type="space", token=hf_token)
62
 
63
- backup_exists = (REPO_DB_GPG_FILE in files)
64
- if not backup_exists:
65
  print("No backup file found in repository")
66
  return False, None
67
 
@@ -87,52 +84,59 @@ def get_latest_backup_info(repo_id, hf_token):
87
  return False, None
88
 
89
 
90
- def download_backup(repo_id, hf_token):
 
91
  try:
92
- print("Downloading encrypted database backup...")
93
- temp_file = hf_hub_download(
94
- repo_id=repo_id,
95
- repo_type="space",
96
- filename=REPO_DB_GPG_FILE,
97
- token=hf_token
98
- )
99
- os.replace(temp_file, DB_GPG_PATH)
100
- print("Backup downloaded successfully")
101
- return True
102
- except Exception as e:
103
- print(f"Error downloading backup: {e}")
104
- return False
105
-
106
 
107
- def decrypt_database(passphrase):
108
- if not os.path.exists(DB_GPG_PATH):
109
- print("No encrypted backup found locally. Starting with fresh DB")
110
- return True
111
-
112
- try:
113
- print("Decrypting database...")
114
- os.makedirs("/root/.gnupg", mode=0o700, exist_ok=True)
115
-
116
- decrypt_cmd = [
117
- "gpg",
118
- "--batch",
119
- "--yes",
120
- "--passphrase", passphrase,
121
- "-d",
122
- "-o", DB_FILE_PATH,
123
- DB_GPG_PATH
124
- ]
125
- subprocess.run(decrypt_cmd, check=True, stderr=subprocess.PIPE)
126
- print(f"Database decrypted successfully to {DB_FILE_PATH}")
127
- return True
 
 
 
 
 
 
 
128
  except subprocess.CalledProcessError as e:
129
- print(f"Decryption failed: {e.stderr.decode(errors='ignore')}")
 
 
 
130
  return False
131
 
132
 
133
  def verify_database():
 
134
  if not os.path.exists(DB_FILE_PATH):
135
- return True
 
136
 
137
  try:
138
  print("Verifying database integrity...")
@@ -146,42 +150,60 @@ def verify_database():
146
  if result.lower() == "ok" and len(tables) > 0:
147
  print(f"Database verified: {len(tables)} tables found")
148
  return True
149
- else:
150
- print("Database verification failed")
151
- return False
152
  except Exception as e:
153
  print(f"Database verification error: {e}")
154
  return False
155
 
156
 
157
  def restore_db():
158
- if not check_requirements() or not validate_secrets():
 
159
  return False
160
-
161
- if not ensure_directories():
162
- return False
163
-
164
  passphrase = os.environ["BACKUP_PASSPHRASE"]
165
  hf_token = os.environ["HF_TOKEN"]
166
  space_id = os.environ["SPACE_ID"]
167
 
168
  backup_exists, timestamp = get_latest_backup_info(space_id, hf_token)
169
- if backup_exists:
170
- if not download_backup(space_id, hf_token):
171
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
 
173
- if not decrypt_database(passphrase):
 
174
  return False
175
 
176
  if not verify_database():
 
177
  if os.path.exists(DB_FILE_PATH):
178
  os.unlink(DB_FILE_PATH)
179
  return False
180
- else:
181
- print("No backup found - starting with fresh database")
182
-
183
- print("Database restore completed successfully!")
184
- return True
 
 
 
 
185
 
186
 
187
  if __name__ == "__main__":
 
4
  import subprocess
5
  import datetime
6
  import sqlite3
7
+ import tempfile
8
  from pathlib import Path
9
  from huggingface_hub import HfApi, hf_hub_download
10
 
 
13
  BACKEND_DIR = SCRIPT_DIR.parent
14
  sys.path.append(str(BACKEND_DIR))
15
 
16
+ # Database path (actual application database)
17
  DATA_DIR = os.environ.get("DATA_DIR", "/app/backend/data")
18
  DB_FILE_PATH = os.path.join(DATA_DIR, "webui.db")
19
 
20
+ # Hugging Face repo paths (virtual paths in HF storage)
 
 
 
 
 
 
21
  REPO_DB_GPG_FILE = "db_backup/webui.db.gpg"
22
+ REPO_TIMESTAMP_FILE = "db_backup/last_backup_time.txt"
23
 
24
 
25
+ def check_gpg():
26
+ """Verify GPG is available."""
27
  try:
28
  subprocess.run(["gpg", "--version"], check=True, capture_output=True)
29
  return True
 
32
  return False
33
 
34
 
35
+ def validate_environment():
36
+ """Verify all required environment variables are set."""
37
+ required = ["BACKUP_PASSPHRASE", "HF_TOKEN", "SPACE_ID"]
38
+ missing = [var for var in required if not os.environ.get(var)]
39
  if missing:
40
+ print(f"Error: Missing environment variables: {', '.join(missing)}")
41
  return False
42
  return True
43
 
44
 
45
+ def ensure_data_dir():
46
+ """Ensure the database directory exists."""
47
  try:
48
  os.makedirs(DATA_DIR, mode=0o755, exist_ok=True)
 
49
  return True
50
  except Exception as e:
51
+ print(f"Error creating data directory: {e}")
52
  return False
53
 
54
 
55
  def get_latest_backup_info(repo_id, hf_token):
56
+ """Check if backup exists and get its timestamp."""
57
  api = HfApi()
58
  try:
59
  files = api.list_repo_files(repo_id=repo_id, repo_type="space", token=hf_token)
60
 
61
+ if REPO_DB_GPG_FILE not in files:
 
62
  print("No backup file found in repository")
63
  return False, None
64
 
 
84
  return False, None
85
 
86
 
87
+ def decrypt_database_from_memory(encrypted_data, passphrase):
88
+ """Decrypt database directly from memory."""
89
  try:
90
+ # Create a secure temporary directory for GPG operations
91
+ with tempfile.TemporaryDirectory(prefix='gpg_home_') as gpg_home:
92
+ os.chmod(gpg_home, 0o700)
93
+
94
+ # Create a temporary file for the encrypted data
95
+ with tempfile.NamedTemporaryFile(mode='wb', suffix='.gpg', delete=False) as temp_encrypted:
96
+ temp_encrypted.write(encrypted_data)
97
+ temp_encrypted_path = temp_encrypted.name
 
 
 
 
 
 
98
 
99
+ try:
100
+ print(f"Decrypting database ({len(encrypted_data)} bytes)...")
101
+ decrypt_cmd = [
102
+ "gpg",
103
+ "--batch",
104
+ "--yes",
105
+ "--homedir", gpg_home,
106
+ "--passphrase", passphrase,
107
+ "--pinentry-mode", "loopback",
108
+ "-d",
109
+ "-o", DB_FILE_PATH,
110
+ temp_encrypted_path
111
+ ]
112
+
113
+ result = subprocess.run(decrypt_cmd, capture_output=True, check=True)
114
+
115
+ if os.path.exists(DB_FILE_PATH) and os.path.getsize(DB_FILE_PATH) > 0:
116
+ print(f"Database decrypted successfully ({os.path.getsize(DB_FILE_PATH)} bytes)")
117
+ return True
118
+ else:
119
+ print("Error: Decrypted database is missing or empty")
120
+ return False
121
+
122
+ finally:
123
+ # Clean up the temporary encrypted file
124
+ if os.path.exists(temp_encrypted_path):
125
+ os.unlink(temp_encrypted_path)
126
+
127
  except subprocess.CalledProcessError as e:
128
+ print(f"Decryption failed: {e.stderr.decode()}")
129
+ return False
130
+ except Exception as e:
131
+ print(f"Decryption error: {e}")
132
  return False
133
 
134
 
135
  def verify_database():
136
+ """Verify the restored database integrity."""
137
  if not os.path.exists(DB_FILE_PATH):
138
+ print(f"Error: Database file not found at {DB_FILE_PATH}")
139
+ return False
140
 
141
  try:
142
  print("Verifying database integrity...")
 
150
  if result.lower() == "ok" and len(tables) > 0:
151
  print(f"Database verified: {len(tables)} tables found")
152
  return True
153
+ print("Database verification failed")
154
+ return False
 
155
  except Exception as e:
156
  print(f"Database verification error: {e}")
157
  return False
158
 
159
 
160
  def restore_db():
161
+ """Main restore function using in-memory approach."""
162
+ if not check_gpg() or not validate_environment() or not ensure_data_dir():
163
  return False
164
+
 
 
 
165
  passphrase = os.environ["BACKUP_PASSPHRASE"]
166
  hf_token = os.environ["HF_TOKEN"]
167
  space_id = os.environ["SPACE_ID"]
168
 
169
  backup_exists, timestamp = get_latest_backup_info(space_id, hf_token)
170
+ if not backup_exists:
171
+ print("No backup found - starting with fresh database")
172
+ return True
173
+
174
+ try:
175
+ print("Downloading encrypted database...")
176
+ encrypted_file = hf_hub_download(
177
+ repo_id=space_id,
178
+ repo_type="space",
179
+ filename=REPO_DB_GPG_FILE,
180
+ token=hf_token
181
+ )
182
+
183
+ # Read encrypted data into memory
184
+ with open(encrypted_file, 'rb') as f:
185
+ encrypted_data = f.read()
186
+
187
+ print(f"Downloaded encrypted data: {len(encrypted_data)} bytes")
188
 
189
+ if not decrypt_database_from_memory(encrypted_data, passphrase):
190
+ print("Failed to decrypt database")
191
  return False
192
 
193
  if not verify_database():
194
+ print("Failed to verify database")
195
  if os.path.exists(DB_FILE_PATH):
196
  os.unlink(DB_FILE_PATH)
197
  return False
198
+
199
+ print("Database restore completed successfully!")
200
+ return True
201
+
202
+ except Exception as e:
203
+ print(f"Restore error: {e}")
204
+ if os.path.exists(DB_FILE_PATH):
205
+ os.unlink(DB_FILE_PATH)
206
+ return False
207
 
208
 
209
  if __name__ == "__main__":