import os from pathlib import Path import click from sqlalchemy.exc import IntegrityError from sqlalchemy import select from l4d2web.auth import hash_password from l4d2web.db import session_scope from l4d2web.models import Overlay, User from l4d2web.services.overlay_creation import ( create_overlay_directory, generate_overlay_path, ) @click.command("promote-admin") @click.argument("username") def promote_admin(username: str) -> None: with session_scope() as db: user = db.scalar(select(User).where(User.username == username)) if user is None: raise click.ClickException("user not found") user.admin = True @click.command("create-user") @click.argument("username") @click.option("--admin", is_flag=True, default=False) def create_user(username: str, admin: bool) -> None: password = os.getenv("LEFT4ME_ADMIN_PASSWORD") if password is None: password = click.prompt("Password", hide_input=True, confirmation_prompt=True) if password == "": raise click.ClickException("password must not be empty") try: with session_scope() as db: existing = db.scalar(select(User).where(User.username == username)) if existing is not None: raise click.ClickException("user already exists") db.add(User(username=username, password_digest=hash_password(password), admin=admin)) except IntegrityError as exc: raise click.ClickException("user already exists") from exc click.echo(f"created user {username}") @click.command("seed-script-overlays") @click.argument( "directory", type=click.Path(exists=True, file_okay=False, path_type=Path), ) def seed_script_overlays(directory: Path) -> None: """Upsert one system-wide script overlay per *.sh file in DIRECTORY. Overlay name = filename stem; user_id stays NULL. Existing rows by name have their script refreshed in place. Hard-errors if a name collides with a non-script overlay. """ sh_files = sorted(p for p in directory.glob("*.sh") if p.stem) if not sh_files: click.echo(f"no *.sh files in {directory}", err=True) return with session_scope() as db: for sh in sh_files: name = sh.stem content = sh.read_text() existing = db.scalar( select(Overlay).where(Overlay.name == name, Overlay.user_id.is_(None)) ) if existing is not None: if existing.type != "script": raise click.ClickException( f"overlay {name!r} exists but is type={existing.type!r}, not script" ) existing.script = content click.echo(f"updated {name} (id={existing.id})") else: overlay = Overlay( name=name, path="", type="script", user_id=None, script=content ) db.add(overlay) db.flush() overlay.path = generate_overlay_path(overlay.id) db.flush() create_overlay_directory(overlay) click.echo(f"created {name} (id={overlay.id})") def register_cli(app) -> None: app.cli.add_command(promote_admin) app.cli.add_command(create_user) app.cli.add_command(seed_script_overlays)