thryyyyy commited on
Commit
a71c8bb
·
1 Parent(s): a827de0

more fixes

Browse files
Files changed (1) hide show
  1. backend/scripts/backup.py +46 -60
backend/scripts/backup.py CHANGED
@@ -8,18 +8,15 @@ from pathlib import Path
8
  from huggingface_hub import HfApi, hf_hub_download, CommitOperationAdd
9
 
10
  ###############################################################################
11
- # 1) Determine where the *actual* database file lives
12
- # - We read DATA_DIR from the environment, defaulting to "/app/backend/data"
13
- # - Then the DB is "webui.db" in that directory.
14
  ###############################################################################
15
  DATA_DIR = os.environ.get("DATA_DIR", "/app/backend/data")
16
  DB_FILE_PATH = os.path.join(DATA_DIR, "webui.db")
17
 
18
  ###############################################################################
19
- # 2) Choose a writable directory for our backup artifacts (encrypted .gpg, etc.)
20
- # By default, "/tmp" is writable on Hugging Face Spaces.
21
  ###############################################################################
22
- BACKUP_DIR = "/tmp/open_webui/db_backup"
23
  TIMESTAMP_FILE_PATH = os.path.join(BACKUP_DIR, "last_backup_time.txt")
24
  DB_GPG_PATH = os.path.join(BACKUP_DIR, "webui.db.gpg")
25
 
@@ -30,17 +27,14 @@ REPO_DB_GPG_FILE = "db_backup/webui.db.gpg"
30
 
31
  def ensure_directories():
32
  """
33
- Create and verify the /tmp/open_webui/db_backup directory.
34
- We only need to ensure the backup directory is writable, because
35
- the DB itself is at DATA_DIR, which might be read-only or read-write
36
- depending on your environment.
37
  """
38
  try:
39
  os.makedirs(BACKUP_DIR, mode=0o755, exist_ok=True)
40
  dir_stat = os.stat(BACKUP_DIR)
41
- print(f"Backup directory {BACKUP_DIR} created or exists with permissions: {oct(dir_stat.st_mode)[-3:]}")
42
 
43
- # Verify we can write to this directory
44
  test_file = os.path.join(BACKUP_DIR, '.write_test')
45
  with open(test_file, 'w') as f:
46
  f.write('test')
@@ -55,7 +49,7 @@ def ensure_directories():
55
 
56
  def verify_database():
57
  """
58
- Ensure the actual database file exists and passes a basic SQLite integrity check.
59
  """
60
  if not os.path.exists(DB_FILE_PATH):
61
  print(f"Database file not found at: {DB_FILE_PATH}")
@@ -74,33 +68,32 @@ def verify_database():
74
  tables = cursor.fetchall()
75
 
76
  if result.lower() == "ok" and len(tables) > 0:
77
- print("Database integrity verified successfully")
78
- print(f"Found {len(tables)} tables in database")
79
  return True
80
  else:
81
- print("Database integrity check failed")
82
  if result.lower() != "ok":
83
  print(f"Integrity check result: {result}")
84
  if len(tables) == 0:
85
- print("No tables found in database")
86
  return False
87
  except sqlite3.Error as e:
88
- print(f"SQLite error during verification: {e}")
89
  return False
90
  except Exception as e:
91
- print(f"Unexpected error during verification: {e}")
92
  return False
93
 
94
 
95
  def encrypt_database(passphrase):
96
  """
97
- Encrypt the real DB file (DB_FILE_PATH) using GPG, outputting
98
- the .gpg file to /tmp/open_webui/db_backup.
99
  """
100
  try:
101
  print("\nPreparing for database encryption...")
102
 
103
- # Ensure a GPG home directory for key storage
104
  gnupg_dir = '/root/.gnupg'
105
  os.makedirs(gnupg_dir, mode=0o700, exist_ok=True)
106
 
@@ -110,6 +103,7 @@ def encrypt_database(passphrase):
110
  "--batch",
111
  "--yes",
112
  "--passphrase", passphrase,
 
113
  "-c",
114
  "--cipher-algo", "AES256",
115
  "-o", DB_GPG_PATH,
@@ -128,42 +122,37 @@ def encrypt_database(passphrase):
128
  return False
129
 
130
  if os.path.exists(DB_GPG_PATH):
131
- encrypted_size = os.path.getsize(DB_GPG_PATH)
132
- print(f"Encryption successful. Encrypted file size: {encrypted_size:,} bytes")
133
  return True
134
  else:
135
- print("GPG reported success but the .gpg file was not found.")
136
  return False
137
  except Exception as e:
138
- print(f"Encryption failed with exception: {e}")
139
  return False
140
 
141
 
142
  def get_last_backup_time(repo_id, hf_token):
143
  """
144
- Fetch last backup timestamp from the Hugging Face Space (if present).
145
  """
146
  try:
147
  api = HfApi()
148
- files = api.list_repo_files(
149
- repo_id=repo_id,
150
- repo_type="space",
151
- token=hf_token
152
- )
153
  if REPO_TIMESTAMP_FILE not in files:
154
- print("No timestamp file found in repository")
155
  return None
156
 
157
- temp_file = hf_hub_download(
158
  repo_id=repo_id,
159
  repo_type="space",
160
  filename=REPO_TIMESTAMP_FILE,
161
  token=hf_token
162
  )
163
- with open(temp_file, "r", encoding="utf-8") as f:
164
- timestamp_str = f.read().strip()
165
-
166
- return datetime.datetime.fromisoformat(timestamp_str)
167
  except Exception as e:
168
  print(f"Error getting last backup time: {e}")
169
  return None
@@ -171,7 +160,7 @@ def get_last_backup_time(repo_id, hf_token):
171
 
172
  def save_timestamp_locally():
173
  """
174
- Save the current UTC time to /tmp/open_webui/db_backup/last_backup_time.txt.
175
  """
176
  try:
177
  now = datetime.datetime.now(datetime.timezone.utc)
@@ -190,28 +179,27 @@ def save_timestamp_locally():
190
 
191
  def backup_db():
192
  """
193
- Main backup entry point:
194
- 1. Validate env variables
195
- 2. Ensure /tmp/open_webui/db_backup is writable
196
- 3. Check threshold to skip if recently backed up
197
- 4. Verify DB
198
- 5. Encrypt DB
199
- 6. Save local timestamp
200
- 7. Upload .gpg + timestamp to Hugging Face
201
  """
202
  passphrase = os.environ.get("BACKUP_PASSPHRASE")
203
  hf_token = os.environ.get("HF_TOKEN")
204
  space_id = os.environ.get("SPACE_ID")
205
 
206
  if not all([passphrase, hf_token, space_id]):
207
- print("Error: Missing required environment variables for backup (BACKUP_PASSPHRASE, HF_TOKEN, SPACE_ID).")
208
  return False
209
 
210
  if not ensure_directories():
211
  print("Failed to create or verify backup directories.")
212
  return False
213
 
214
- # Check threshold
215
  threshold_minutes = int(os.environ.get("BACKUP_THRESHOLD_MINUTES", 120))
216
  if threshold_minutes > 0:
217
  last_backup_dt = get_last_backup_time(space_id, hf_token)
@@ -221,28 +209,27 @@ def backup_db():
221
  last_backup_dt = last_backup_dt.replace(tzinfo=datetime.timezone.utc)
222
  elapsed = now - last_backup_dt
223
  if elapsed.total_seconds() < threshold_minutes * 60:
224
- print(f"Last backup was only {elapsed.total_seconds()/3600:.2f} hours ago")
225
- print(f"Threshold is {threshold_minutes} minutes")
226
- print("Skipping backup to avoid frequent rebuilds")
227
  return True
228
  else:
229
- print("Backup threshold check disabled (BACKUP_THRESHOLD_MINUTES=0).")
230
 
231
- # Verify local DB
232
  if not verify_database():
233
  print("Database verification failed, aborting backup.")
234
  return False
235
 
236
- # Encrypt the DB
237
  if not encrypt_database(passphrase):
238
  print("Database encryption failed, aborting backup.")
239
  return False
240
 
241
- # Save local timestamp
242
  if not save_timestamp_locally():
243
- print("Warning: Failed to save timestamp locally, but continuing to upload.")
244
 
245
- # Upload to Hugging Face
246
  print("\nUploading to Hugging Face Spaces...")
247
  try:
248
  api = HfApi()
@@ -256,7 +243,6 @@ def backup_db():
256
  path_or_fileobj=TIMESTAMP_FILE_PATH
257
  )
258
  ]
259
-
260
  api.create_commit(
261
  repo_id=space_id,
262
  repo_type="space",
@@ -267,7 +253,7 @@ def backup_db():
267
  print("Backup files uploaded successfully!")
268
  return True
269
  except Exception as e:
270
- print(f"Error uploading backup to HuggingFace: {e}")
271
  return False
272
 
273
 
 
8
  from huggingface_hub import HfApi, hf_hub_download, CommitOperationAdd
9
 
10
  ###############################################################################
11
+ # 1) Determine DB location from your env.py (DATA_DIR), defaulting to /app/backend/data
 
 
12
  ###############################################################################
13
  DATA_DIR = os.environ.get("DATA_DIR", "/app/backend/data")
14
  DB_FILE_PATH = os.path.join(DATA_DIR, "webui.db")
15
 
16
  ###############################################################################
17
+ # 2) Use /workspace (guaranteed writable on HF Spaces) for backups.
 
18
  ###############################################################################
19
+ BACKUP_DIR = "/workspace/open_webui/db_backup"
20
  TIMESTAMP_FILE_PATH = os.path.join(BACKUP_DIR, "last_backup_time.txt")
21
  DB_GPG_PATH = os.path.join(BACKUP_DIR, "webui.db.gpg")
22
 
 
27
 
28
  def ensure_directories():
29
  """
30
+ Create/verify /workspace/open_webui/db_backup.
 
 
 
31
  """
32
  try:
33
  os.makedirs(BACKUP_DIR, mode=0o755, exist_ok=True)
34
  dir_stat = os.stat(BACKUP_DIR)
35
+ print(f"Backup directory {BACKUP_DIR} exists with permissions: {oct(dir_stat.st_mode)[-3:]}")
36
 
37
+ # Quick test to verify we can write
38
  test_file = os.path.join(BACKUP_DIR, '.write_test')
39
  with open(test_file, 'w') as f:
40
  f.write('test')
 
49
 
50
  def verify_database():
51
  """
52
+ Check that webui.db exists and passes PRAGMA integrity_check.
53
  """
54
  if not os.path.exists(DB_FILE_PATH):
55
  print(f"Database file not found at: {DB_FILE_PATH}")
 
68
  tables = cursor.fetchall()
69
 
70
  if result.lower() == "ok" and len(tables) > 0:
71
+ print("Database integrity verified successfully.")
72
+ print(f"Found {len(tables)} tables in database.")
73
  return True
74
  else:
75
+ print("Database integrity check failed.")
76
  if result.lower() != "ok":
77
  print(f"Integrity check result: {result}")
78
  if len(tables) == 0:
79
+ print("No tables found in database.")
80
  return False
81
  except sqlite3.Error as e:
82
+ print(f"SQLite error: {e}")
83
  return False
84
  except Exception as e:
85
+ print(f"Unexpected error: {e}")
86
  return False
87
 
88
 
89
  def encrypt_database(passphrase):
90
  """
91
+ Encrypt /app/backend/data/webui.db to /workspace/open_webui/db_backup/webui.db.gpg.
 
92
  """
93
  try:
94
  print("\nPreparing for database encryption...")
95
 
96
+ # Make sure .gnupg is created with correct permissions
97
  gnupg_dir = '/root/.gnupg'
98
  os.makedirs(gnupg_dir, mode=0o700, exist_ok=True)
99
 
 
103
  "--batch",
104
  "--yes",
105
  "--passphrase", passphrase,
106
+ "--pinentry-mode", "loopback", # <--- important in containers
107
  "-c",
108
  "--cipher-algo", "AES256",
109
  "-o", DB_GPG_PATH,
 
122
  return False
123
 
124
  if os.path.exists(DB_GPG_PATH):
125
+ size = os.path.getsize(DB_GPG_PATH)
126
+ print(f"Encryption successful. Encrypted file size: {size:,} bytes")
127
  return True
128
  else:
129
+ print("GPG reported success, but webui.db.gpg not found.")
130
  return False
131
  except Exception as e:
132
+ print(f"Encryption failed: {e}")
133
  return False
134
 
135
 
136
  def get_last_backup_time(repo_id, hf_token):
137
  """
138
+ Retrieve last backup timestamp from the HF repo (if it exists).
139
  """
140
  try:
141
  api = HfApi()
142
+ files = api.list_repo_files(repo_id=repo_id, repo_type="space", token=hf_token)
 
 
 
 
143
  if REPO_TIMESTAMP_FILE not in files:
144
+ print("No timestamp file found in repository.")
145
  return None
146
 
147
+ tmp_file = hf_hub_download(
148
  repo_id=repo_id,
149
  repo_type="space",
150
  filename=REPO_TIMESTAMP_FILE,
151
  token=hf_token
152
  )
153
+ with open(tmp_file, "r", encoding="utf-8") as f:
154
+ stamp_str = f.read().strip()
155
+ return datetime.datetime.fromisoformat(stamp_str)
 
156
  except Exception as e:
157
  print(f"Error getting last backup time: {e}")
158
  return None
 
160
 
161
  def save_timestamp_locally():
162
  """
163
+ Save the current UTC time to /workspace/open_webui/db_backup/last_backup_time.txt
164
  """
165
  try:
166
  now = datetime.datetime.now(datetime.timezone.utc)
 
179
 
180
  def backup_db():
181
  """
182
+ Main backup process:
183
+ - Read passphrase, HF creds
184
+ - Create /workspace/open_webui/db_backup
185
+ - Possibly skip if threshold not met
186
+ - Verify DB
187
+ - Encrypt DB
188
+ - Save timestamp
189
+ - Upload to HF
190
  """
191
  passphrase = os.environ.get("BACKUP_PASSPHRASE")
192
  hf_token = os.environ.get("HF_TOKEN")
193
  space_id = os.environ.get("SPACE_ID")
194
 
195
  if not all([passphrase, hf_token, space_id]):
196
+ print("Error: Missing required environment variables (BACKUP_PASSPHRASE, HF_TOKEN, SPACE_ID).")
197
  return False
198
 
199
  if not ensure_directories():
200
  print("Failed to create or verify backup directories.")
201
  return False
202
 
 
203
  threshold_minutes = int(os.environ.get("BACKUP_THRESHOLD_MINUTES", 120))
204
  if threshold_minutes > 0:
205
  last_backup_dt = get_last_backup_time(space_id, hf_token)
 
209
  last_backup_dt = last_backup_dt.replace(tzinfo=datetime.timezone.utc)
210
  elapsed = now - last_backup_dt
211
  if elapsed.total_seconds() < threshold_minutes * 60:
212
+ print(f"Last backup was only {elapsed.total_seconds()/60:.1f} min ago.")
213
+ print(f"Threshold is {threshold_minutes} minutes. Skipping backup.")
 
214
  return True
215
  else:
216
+ print("Backup threshold check disabled (0).")
217
 
218
+ # Verify DB
219
  if not verify_database():
220
  print("Database verification failed, aborting backup.")
221
  return False
222
 
223
+ # Encrypt
224
  if not encrypt_database(passphrase):
225
  print("Database encryption failed, aborting backup.")
226
  return False
227
 
228
+ # Save timestamp
229
  if not save_timestamp_locally():
230
+ print("Warning: Failed to save timestamp, but continuing upload.")
231
 
232
+ # Upload to HF
233
  print("\nUploading to Hugging Face Spaces...")
234
  try:
235
  api = HfApi()
 
243
  path_or_fileobj=TIMESTAMP_FILE_PATH
244
  )
245
  ]
 
246
  api.create_commit(
247
  repo_id=space_id,
248
  repo_type="space",
 
253
  print("Backup files uploaded successfully!")
254
  return True
255
  except Exception as e:
256
+ print(f"Error uploading backup to HF: {e}")
257
  return False
258
 
259