Add regularbm.py
This commit is contained in:
parent
80b541c621
commit
0f39be7301
1 changed files with 163 additions and 0 deletions
163
regularbm.py
Normal file
163
regularbm.py
Normal file
|
@ -0,0 +1,163 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import sqlite3
|
||||
import datetime
|
||||
import shutil
|
||||
import subprocess
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import boto3
|
||||
import lz4.frame
|
||||
except ImportError:
|
||||
print("Error: Required Python packages 'boto3' or 'lz4' are not installed.")
|
||||
print("Please run: pip3 install boto3 lz4")
|
||||
sys.exit(1)
|
||||
|
||||
def main(args):
|
||||
"""Main function to orchestrate the backup process."""
|
||||
print(f"Starting Open WebUI backup process at {datetime.datetime.now().isoformat()}")
|
||||
|
||||
# 1. Setup temporary directory for staging backup files
|
||||
timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
backup_staging_dir = os.path.join(args.tmp_dir, f"openwebui_backup_{timestamp}")
|
||||
db_local_path = os.path.join(backup_staging_dir, "webui.db")
|
||||
|
||||
try:
|
||||
os.makedirs(backup_staging_dir, exist_ok=True)
|
||||
print(f"Created temporary staging directory: {backup_staging_dir}")
|
||||
|
||||
# 2. Copy database from Docker container
|
||||
copy_db_from_docker(args.container_name, db_local_path)
|
||||
|
||||
# 3. Export data from the database
|
||||
export_data_from_db(db_local_path, backup_staging_dir)
|
||||
|
||||
# 4. Compress the backup directory
|
||||
archive_path = compress_backup(backup_staging_dir, timestamp)
|
||||
|
||||
# 5. Upload to S3
|
||||
upload_to_s3(archive_path, args.s3_bucket, args.s3_region, args.s3_prefix)
|
||||
|
||||
except Exception as e:
|
||||
print(f"An error occurred during the backup process: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
finally:
|
||||
# 6. Cleanup local files
|
||||
print("Cleaning up local temporary files...")
|
||||
if os.path.exists(backup_staging_dir):
|
||||
shutil.rmtree(backup_staging_dir)
|
||||
if 'archive_path' in locals() and os.path.exists(archive_path):
|
||||
os.remove(archive_path)
|
||||
print("Cleanup complete.")
|
||||
|
||||
print(f"Backup process finished successfully at {datetime.datetime.now().isoformat()}")
|
||||
|
||||
def copy_db_from_docker(container_name, local_path):
|
||||
"""Copies the SQLite database from the Docker container to a local path."""
|
||||
db_container_path = "/app/backend/data/webui.db"
|
||||
command = ["docker", "cp", f"{container_name}:{db_container_path}", local_path]
|
||||
print(f"Executing: {' '.join(command)}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(command, check=True, capture_output=True, text=True)
|
||||
print("Successfully copied database from Docker container.")
|
||||
except FileNotFoundError:
|
||||
print("Error: 'docker' command not found. Is Docker installed and in your PATH?", file=sys.stderr)
|
||||
raise
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error copying database from Docker container '{container_name}'.", file=sys.stderr)
|
||||
print(f"Stderr: {e.stderr}", file=sys.stderr)
|
||||
print("Please ensure the container is running and the name is correct.", file=sys.stderr)
|
||||
raise
|
||||
|
||||
def export_data_from_db(db_path, output_dir):
|
||||
"""Connects to the SQLite DB and exports chats and config."""
|
||||
print("Connecting to the database and exporting data...")
|
||||
conn = None
|
||||
try:
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Export all user chats
|
||||
cursor.execute("""
|
||||
SELECT user.email, user.name, chat.title, chat.chat
|
||||
FROM chat JOIN user ON chat.user_id = user.id
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
user_chats = defaultdict(list)
|
||||
for email, name, title, chat_json_str in rows:
|
||||
try:
|
||||
chat_content = json.loads(chat_json_str)
|
||||
except json.JSONDecodeError:
|
||||
chat_content = {"error": "Failed to parse chat JSON."}
|
||||
|
||||
user_chats[email].append({"title": title, "messages": chat_content})
|
||||
|
||||
for email, chats in user_chats.items():
|
||||
safe_email = email.replace('@', '_at_').replace('.', '_')
|
||||
filename = os.path.join(output_dir, f"chats_{safe_email}.json")
|
||||
with open(filename, "w") as f:
|
||||
json.dump(chats, f, indent=2)
|
||||
print(f"Exported chats for {len(user_chats)} users.")
|
||||
|
||||
# Export config
|
||||
cursor.execute("SELECT data FROM config ORDER BY id DESC LIMIT 1")
|
||||
config_row = cursor.fetchone()
|
||||
if config_row:
|
||||
config_data = json.loads(config_row[0])
|
||||
with open(os.path.join(output_dir, "openwebui_config.json"), "w") as f:
|
||||
json.dump(config_data, f, indent=2)
|
||||
print("Successfully exported configuration.")
|
||||
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def compress_backup(source_dir, timestamp):
|
||||
"""Creates a .tar archive and then compresses it with lz4."""
|
||||
print("Compressing backup files...")
|
||||
base_name = f"openwebui_backup_{timestamp}"
|
||||
tar_path = os.path.join(os.path.dirname(source_dir), f"{base_name}.tar")
|
||||
lz4_path = f"{tar_path}.lz4"
|
||||
|
||||
# Create a tar archive first
|
||||
shutil.make_archive(base_name=tar_path.replace('.tar',''), format='tar', root_dir=source_dir)
|
||||
|
||||
# Compress the tar file with lz4
|
||||
with open(tar_path, 'rb') as f_in, open(lz4_path, 'wb') as f_out:
|
||||
f_out.write(lz4.frame.compress(f_in.read()))
|
||||
|
||||
# Clean up the intermediate tar file
|
||||
os.remove(tar_path)
|
||||
print(f"Successfully created compressed archive: {lz4_path}")
|
||||
return lz4_path
|
||||
|
||||
def upload_to_s3(file_path, bucket, region, prefix):
|
||||
"""Uploads a file to an S3 bucket."""
|
||||
s3_client = boto3.client("s3", region_name=region)
|
||||
s3_key = f"{prefix}{os.path.basename(file_path)}"
|
||||
|
||||
print(f"Uploading {os.path.basename(file_path)} to s3://{bucket}/{s3_key}...")
|
||||
try:
|
||||
s3_client.upload_file(file_path, bucket, s3_key)
|
||||
print("Upload successful.")
|
||||
except Exception as e:
|
||||
print(f"Error uploading to S3: {e}", file=sys.stderr)
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Backup Open WebUI data to an S3 bucket.")
|
||||
parser.add_argument("--s3-bucket", required=True, help="S3 bucket name for backups.")
|
||||
parser.add_argument("--s3-region", required=True, help="AWS region of the S3 bucket.")
|
||||
parser.add_argument("--container-name", default="open-webui", help="Name of the Open WebUI Docker container.")
|
||||
parser.add_argument("--s3-prefix", default="openwebui_backups/", help="Prefix (folder) in the S3 bucket.")
|
||||
parser.add_argument("--tmp-dir", default="/tmp", help="Temporary directory for staging files.")
|
||||
|
||||
args = parser.parse_args()
|
||||
main(args)
|
Loading…
Add table
Add a link
Reference in a new issue