"""CLI для управления пользователями панели. Публичная регистрация в панели закрыта — пользователей создаёт администратор либо через этот скрипт, либо через эндпоинт /admin/users. Запуск внутри контейнера backend: docker compose exec backend python -m app.manage_users <команда> [аргументы] Команды: list — показать всех пользователей create [role] — создать пользователя role: admin | client (по умолчанию client) activate — активировать аккаунт (is_active=True) deactivate — деактивировать аккаунт (is_active=False) set-role — сменить роль (admin | client) reset-password — задать новый пароль delete — удалить пользователя (осторожно) Примеры: docker compose exec backend python -m app.manage_users list docker compose exec backend python -m app.manage_users create user@example.com 'Str0ng!Pass' client docker compose exec backend python -m app.manage_users set-role user@example.com admin docker compose exec backend python -m app.manage_users deactivate user@example.com """ import argparse import getpass import sys from typing import Iterable from app import models from app.database import SessionLocal from app.security import hash_password VALID_ROLES = ("admin", "client") def _normalize_email(email: str) -> str: return email.lower().strip() def list_users() -> Iterable[models.User]: db = SessionLocal() try: users = db.query(models.User).order_by(models.User.created_at.desc()).all() if not users: print("В базе нет пользователей.") return print(f"{'ID':>4} {'EMAIL':<32} {'ROLE':<7} {'ACTIVE':<6} CREATED_AT") print("-" * 78) for u in users: print( f"{u.id:>4} {u.email:<32} {u.role.value:<7} " f"{'да' if u.is_active else 'нет':<6} {u.created_at:%Y-%m-%d %H:%M}" ) finally: db.close() def create_user(email: str, password: str, role: str) -> None: if role not in VALID_ROLES: print(f"❌ Неверная роль: {role}. Допустимо: {', '.join(VALID_ROLES)}") sys.exit(1) email = _normalize_email(email) if len(password) < 6: print("❌ Пароль слишком короткий (минимум 6 символов).") sys.exit(1) db = SessionLocal() try: if db.query(models.User).filter(models.User.email == email).first(): print(f"❌ Пользователь {email} уже существует.") sys.exit(1) user = models.User( email=email, password_hash=hash_password(password), role=models.Role(role), is_active=True, ) db.add(user) db.commit() db.refresh(user) print(f"✅ Создан пользователь {email} (role={role}, id={user.id})") finally: db.close() def set_active(email: str, active: bool) -> None: email = _normalize_email(email) db = SessionLocal() try: user = db.query(models.User).filter(models.User.email == email).first() if not user: print(f"❌ Пользователь {email} не найден.") sys.exit(1) user.is_active = active db.commit() state = "активирован" if active else "деактивирован" print(f"✅ {email}: {state}") finally: db.close() def set_role(email: str, role: str) -> None: if role not in VALID_ROLES: print(f"❌ Неверная роль: {role}. Допустимо: {', '.join(VALID_ROLES)}") sys.exit(1) email = _normalize_email(email) db = SessionLocal() try: user = db.query(models.User).filter(models.User.email == email).first() if not user: print(f"❌ Пользователь {email} не найден.") sys.exit(1) # Защита от случайного понижения единственного админа. if user.role == models.Role.admin and role == "client": admins = ( db.query(models.User) .filter(models.User.role == models.Role.admin, models.User.is_active.is_(True)) .count() ) if admins <= 1: print("❌ Нельзя понизить единственного активного администратора. Сначала назначьте другого admin.") sys.exit(1) user.role = models.Role(role) db.commit() print(f"✅ {email}: роль изменена на {role}") finally: db.close() def reset_password(email: str, new_password: str) -> None: if len(new_password) < 6: print("❌ Пароль слишком короткий (минимум 6 символов).") sys.exit(1) email = _normalize_email(email) db = SessionLocal() try: user = db.query(models.User).filter(models.User.email == email).first() if not user: print(f"❌ Пользователь {email} не найден.") sys.exit(1) user.password_hash = hash_password(new_password) db.commit() print(f"✅ {email}: пароль изменён") finally: db.close() def delete_user(email: str) -> None: email = _normalize_email(email) db = SessionLocal() try: user = db.query(models.User).filter(models.User.email == email).first() if not user: print(f"❌ Пользователь {email} не найден.") sys.exit(1) # Защита от удаления единственного админа. if user.role == models.Role.admin: admins = ( db.query(models.User) .filter(models.User.role == models.Role.admin, models.User.is_active.is_(True)) .count() ) if admins <= 1: print("❌ Нельзя удалить единственного активного администратора.") sys.exit(1) db.delete(user) db.commit() print(f"✅ Пользователь {email} удалён") finally: db.close() def build_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser( prog="manage_users", description="Управление пользователями Proxmox VPS Panel.", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__, ) sub = p.add_subparsers(dest="command", required=True) sub.add_parser("list", help="Список всех пользователей") pc = sub.add_parser("create", help="Создать пользователя") pc.add_argument("email") pc.add_argument("password", nargs="?", help="Если не задан — спросит интерактивно") pc.add_argument("role", nargs="?", default="client", choices=VALID_ROLES) pa = sub.add_parser("activate", help="Активировать аккаунт") pa.add_argument("email") pd = sub.add_parser("deactivate", help="Деактивировать аккаунт") pd.add_argument("email") pr = sub.add_parser("set-role", help="Сменить роль пользователя") pr.add_argument("email") pr.add_argument("role", choices=VALID_ROLES) pp = sub.add_parser("reset-password", help="Сменить пароль пользователя") pp.add_argument("email") pp.add_argument("new_password", nargs="?") pdel = sub.add_parser("delete", help="Удалить пользователя") pdel.add_argument("email") return p def main(argv: list[str] | None = None) -> int: parser = build_parser() args = parser.parse_args(argv) if args.command == "list": list_users() elif args.command == "create": password = args.password or getpass.getpass("Пароль для нового пользователя: ") create_user(args.email, password, args.role) elif args.command == "activate": set_active(args.email, True) elif args.command == "deactivate": set_active(args.email, False) elif args.command == "set-role": set_role(args.email, args.role) elif args.command == "reset-password": password = args.new_password or getpass.getpass("Новый пароль: ") reset_password(args.email, password) elif args.command == "delete": delete_user(args.email) else: parser.print_help() return 1 return 0 if __name__ == "__main__": sys.exit(main())