#!/usr/bin/env python3 """ One-time script to migrate player passwords from plaintext to MD5 hashes. This script: 1. Connects to the SQLite database 2. Retrieves all players with their passwords 3. Skips passwords that already look like MD5 hashes (32 hex chars) 4. Hashes plaintext passwords with MD5 5. Updates the database with hashed passwords Usage: python scripts/migrate_passwords.py [path_to_db] If no path is provided, defaults to 'gralias.db' in the current directory. """ import hashlib import re import sqlite3 import sys from pathlib import Path def is_md5_hash(password: str) -> bool: """Check if password looks like an MD5 hash (32 hex characters).""" return bool(re.match(r"^[a-fA-F0-9]{32}$", password)) def hash_password(password: str) -> str: """Hash password with MD5.""" return hashlib.md5(password.encode("utf-8")).hexdigest() def migrate_passwords(db_path: str) -> None: """Migrate all player passwords to MD5 hashes.""" print(f"Connecting to database: {db_path}") if not Path(db_path).exists(): print(f"Error: Database file not found: {db_path}") sys.exit(1) conn = sqlite3.connect(db_path) cursor = conn.cursor() try: # Get all players cursor.execute("SELECT username, password FROM players;") players = cursor.fetchall() if not players: print("No players found in database.") return print(f"Found {len(players)} player(s)") print("-" * 60) migrated = 0 skipped = 0 errors = 0 for username, password in players: if not password: print(f"[SKIP] {username}: empty password") skipped += 1 continue if is_md5_hash(password): print(f"[SKIP] {username}: already MD5 hashed") skipped += 1 continue # Hash the password hashed = hash_password(password) try: cursor.execute( "UPDATE players SET password = ? WHERE username = ?;", (hashed, username), ) print(f"[MIGRATED] {username}: '{password}' -> '{hashed}'") migrated += 1 except sqlite3.Error as e: print(f"[ERROR] {username}: {e}") errors += 1 print("-" * 60) print(f"Summary: {migrated} migrated, {skipped} skipped, {errors} errors") # Commit changes conn.commit() print("Changes committed to database.") except sqlite3.Error as e: print(f"Database error: {e}") conn.rollback() sys.exit(1) finally: conn.close() def main(): # Get database path from command line or use default if len(sys.argv) > 1: db_path = sys.argv[1] else: db_path = "gralias.db" migrate_passwords(db_path) if __name__ == "__main__": main()