ClubLedger/manage.py
Claude ea03355743
Add manage.py CLI for password reset and database wipe
Two commands, run from the server terminal:

  python manage.py reset-admin
    Interactively select an admin account and set a new password.
    The app does not need to be stopped first (SQLite WAL handles
    concurrent access safely). Existing sessions remain valid until
    they expire (8 h); restart the app to invalidate them immediately.

  python manage.py reset-db
    Deletes clubledger.db plus any -wal/-shm sidecar files.
    Requires the app to be stopped first. After restart the app
    recreates a fresh database with the default admin/admin account.
    Asks the user to type RESET to confirm before deleting anything.

https://claude.ai/code/session_01JuRTR5Xjx8emQsyerBgGU7
2026-05-30 17:02:40 +00:00

141 lines
4.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
ClubLedger management CLI
Run from the server terminal (NOT through the web UI).
The app does not need to be stopped first for reset-admin;
it MUST be stopped before reset-db.
Usage:
python manage.py reset-admin reset an admin account password
python manage.py reset-db wipe all data and start fresh
"""
import sys
import getpass
import sqlite3
from pathlib import Path
DB_PATH = Path("clubledger.db")
def _connect():
if not DB_PATH.exists():
print(f"Database not found: {DB_PATH}")
print("Start the app at least once to create it.")
sys.exit(1)
conn = sqlite3.connect(str(DB_PATH))
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL")
return conn
def cmd_reset_admin():
"""Interactively reset the password for an admin account."""
import bcrypt
conn = _connect()
admins = conn.execute(
"SELECT id, name, username FROM staff_accounts WHERE role='admin' ORDER BY name"
).fetchall()
if not admins:
print("No admin accounts exist.")
print("Start the app — it will create the default admin/admin account automatically.")
conn.close()
sys.exit(0)
if len(admins) == 1:
target = admins[0]
else:
print("Admin accounts:")
for i, a in enumerate(admins, 1):
print(f" {i}. {a['name']} ({a['username']})")
while True:
raw = input("Select account number: ").strip()
try:
target = admins[int(raw) - 1]
break
except (ValueError, IndexError):
print(" Invalid — enter the number shown above.")
print(f"\nResetting password for: {target['name']} ({target['username']})")
while True:
pw = getpass.getpass("New password: ")
if len(pw) < 4:
print(" Password must be at least 4 characters.")
continue
pw2 = getpass.getpass("Confirm password: ")
if pw != pw2:
print(" Passwords do not match — try again.")
continue
break
hashed = bcrypt.hashpw(pw.encode(), bcrypt.gensalt()).decode()
conn.execute(
"UPDATE staff_accounts SET password_hash=? WHERE id=?",
(hashed, target["id"]),
)
conn.commit()
conn.close()
print(f"\nDone. Password updated for '{target['username']}'.")
print("Any existing sessions for this account will still be valid until they expire (8 h).")
print("Restart the app now to invalidate all active sessions immediately.")
def cmd_reset_db():
"""Delete all data files and prepare for a clean start."""
print("=" * 60)
print(" DATABASE RESET")
print("=" * 60)
print()
print("This will permanently delete:")
print(" • All members and their balances")
print(" • All transactions and receipts")
print(" • All staff accounts")
print(" • All app settings")
print()
print("STOP THE APP before continuing.")
print()
confirm = input('Type RESET to confirm (anything else cancels): ').strip()
if confirm != "RESET":
print("Cancelled — nothing was changed.")
sys.exit(0)
deleted = []
for suffix in ("", "-wal", "-shm"):
p = DB_PATH.parent / (DB_PATH.name + suffix)
if p.exists():
p.unlink()
deleted.append(p.name)
if deleted:
print(f"\nDeleted: {', '.join(deleted)}")
else:
print("\nNo database files found — nothing to delete.")
print()
print("Reset complete. Start the app to create a fresh database.")
print("Default admin credentials after restart: username=admin password=admin")
print("Change the password immediately after logging in.")
# ---------------------------------------------------------------------------
COMMANDS = {
"reset-admin": (cmd_reset_admin, "Reset an admin account password"),
"reset-db": (cmd_reset_db, "Wipe all data and start fresh (irreversible)"),
}
def usage():
print(__doc__)
print("Commands:")
for name, (_, desc) in COMMANDS.items():
print(f" {name:<16} {desc}")
sys.exit(1)
if __name__ == "__main__":
if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS:
usage()
COMMANDS[sys.argv[1]][0]()