Files
pic/scripts/reset_admin_password.py
T
2026-05-01 11:50:56 -04:00

120 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""
Admin password management utility.
Usage:
reset_admin_password.py --generate # generate a random password and set it
reset_admin_password.py <new_password> # set a specific password
"""
import sys
import os
import secrets
import string
_here = os.path.dirname(os.path.abspath(__file__))
# In container the script lives at /app/scripts/; on host at <repo>/scripts/
_in_container = os.path.abspath(_here) == '/app/scripts'
if _in_container:
sys.path.insert(0, '/app/api')
_DATA_DIR = '/app/data'
else:
ROOT = os.path.normpath(os.path.join(_here, '..'))
sys.path.insert(0, os.path.join(ROOT, 'api'))
_DATA_DIR = os.path.join(ROOT, 'data', 'api')
INIT_PW_FILE = os.path.join(_DATA_DIR, '.admin_initial_password')
TEST_PW_FILE = os.path.join(_DATA_DIR, '.test_admin_pass')
def _generate_password(length: int = 20) -> str:
alphabet = string.ascii_letters + string.digits + '!@#$%^&*'
while True:
pw = ''.join(secrets.choice(alphabet) for _ in range(length))
# Ensure at least one of each character class required by the validator
if (any(c.isupper() for c in pw)
and any(c.islower() for c in pw)
and any(c.isdigit() for c in pw)
and any(c in '!@#$%^&*' for c in pw)):
return pw
def _set_password(new_password: str) -> None:
from auth_manager import AuthManager
data_dir = _DATA_DIR
os.makedirs(data_dir, exist_ok=True)
mgr = AuthManager(data_dir=data_dir, config_dir='/tmp')
if mgr.set_password_admin('admin', new_password):
print('[OK] Admin password updated in auth_users.json')
else:
print('[WARN] Admin user not found — creating it now')
mgr.create_user('admin', new_password, 'admin')
print('[OK] Admin user created')
def _print_banner(password: str) -> None:
border = '=' * 60
print(border)
print(' ADMIN PASSWORD')
print(border)
print(f' Username : admin')
print(f' Password : {password}')
print(border)
print(' Save this password — it will NOT be shown again.')
print(border)
def main() -> None:
if len(sys.argv) < 2:
print(__doc__, file=sys.stderr)
sys.exit(1)
arg = sys.argv[1]
if arg == '--show':
# Show the initial password file if the API hasn't consumed it yet
if os.path.exists(INIT_PW_FILE):
pw = open(INIT_PW_FILE).read().strip()
_print_banner(pw)
print()
print(f' (file: {INIT_PW_FILE})')
print(' The API will delete this file on first start.')
else:
print('Initial password file not found.')
print('The API has already consumed it, or setup has not been run.')
print()
print('To set a new password run:')
print(' make reset-admin-password')
return
if arg == '--generate':
password = _generate_password()
else:
password = arg
if len(password) < 10:
print('Error: password must be at least 10 characters', file=sys.stderr)
sys.exit(1)
_set_password(password)
# Write the initial password file (API reads it on first start, then deletes it)
os.makedirs(os.path.dirname(INIT_PW_FILE), exist_ok=True)
with open(INIT_PW_FILE, 'w') as f:
f.write(password)
# Write the persistent test password file (never deleted by the API)
with open(TEST_PW_FILE, 'w') as f:
f.write(password)
os.chmod(TEST_PW_FILE, 0o600)
_print_banner(password)
print(f'\n Also saved to: {INIT_PW_FILE}')
print(f' Test file: {TEST_PW_FILE} (persists across API restarts)')
print(' Restart the API container for the change to take effect:')
print(' docker restart cell-api')
if __name__ == '__main__':
main()