Files
wehub-resource-sync cddb07a176
build container image / cpu (push) Waiting to run
build container image / cuda (push) Waiting to run
build container image / rocm (push) Waiting to run
frontend tests / frontend-tests (push) Waiting to run
openapi checks / openapi-checks (push) Waiting to run
python tests / py3.12: macos-default (push) Waiting to run
python tests / py3.11: windows-cpu (push) Waiting to run
python tests / py3.12: windows-cpu (push) Waiting to run
python tests / py3.11: linux-cpu (push) Waiting to run
python tests / py3.12: linux-cpu (push) Waiting to run
typegen checks / typegen-checks (push) Waiting to run
uv lock checks / uv-lock-checks (push) Waiting to run
frontend checks / frontend-checks (push) Waiting to run
lfs checks / lfs-check (push) Waiting to run
python checks / python-checks (push) Waiting to run
python tests / py3.11: macos-default (push) Waiting to run
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:06 +08:00

580 lines
20 KiB
Python

"""User management command entry points for InvokeAI.
These functions are registered as console scripts in pyproject.toml and can be
called from the command line after installing the package:
invoke-useradd -- add a user
invoke-userdel -- delete a user
invoke-userlist -- list users
invoke-usermod -- modify a user
"""
import argparse
import getpass
import json
import os
import sys
_root_help = (
"Path to the InvokeAI root directory. If omitted, the root is resolved in this order: "
"the $INVOKEAI_ROOT environment variable, the active virtual environment's parent directory, "
"or $HOME/invokeai."
)
# ---------------------------------------------------------------------------
# useradd
# ---------------------------------------------------------------------------
def _add_user_interactive() -> bool:
"""Add a user interactively by prompting for details."""
from invokeai.app.services.auth.password_utils import validate_password_strength
from invokeai.app.services.config import get_config
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.users.users_common import UserCreateRequest
from invokeai.app.services.users.users_default import UserService
from invokeai.backend.util.logging import InvokeAILogger
print("=== Add InvokeAI User ===\n")
email = input("Email address: ").strip()
if not email:
print("Error: Email is required")
return False
display_name = input("Display name (optional): ").strip() or None
while True:
password = getpass.getpass("Password: ")
password_confirm = getpass.getpass("Confirm password: ")
if password != password_confirm:
print("Error: Passwords do not match. Please try again.\n")
continue
is_valid, error_msg = validate_password_strength(password)
if not is_valid:
print(f"Error: {error_msg}\n")
continue
break
is_admin_input = input("Make this user an administrator? (y/N): ").strip().lower()
is_admin = is_admin_input in ("y", "yes")
try:
config = get_config()
db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger())
user_service = UserService(db)
user_data = UserCreateRequest(email=email, display_name=display_name, password=password, is_admin=is_admin)
user = user_service.create(user_data)
print("\n✅ User created successfully!")
print(f" User ID: {user.user_id}")
print(f" Email: {user.email}")
print(f" Display Name: {user.display_name or '(not set)'}")
print(f" Admin: {'Yes' if user.is_admin else 'No'}")
print(f" Active: {'Yes' if user.is_active else 'No'}")
return True
except ValueError as e:
print(f"\n❌ Error: {e}")
return False
except Exception as e:
print(f"\n❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
return False
def _add_user_cli(email: str, password: str, display_name: str | None = None, is_admin: bool = False) -> bool:
"""Add a user via CLI arguments."""
from invokeai.app.services.auth.password_utils import validate_password_strength
from invokeai.app.services.config import get_config
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.users.users_common import UserCreateRequest
from invokeai.app.services.users.users_default import UserService
from invokeai.backend.util.logging import InvokeAILogger
is_valid, error_msg = validate_password_strength(password)
if not is_valid:
print(f"❌ Password validation failed: {error_msg}")
return False
try:
config = get_config()
db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger())
user_service = UserService(db)
user_data = UserCreateRequest(email=email, display_name=display_name, password=password, is_admin=is_admin)
user = user_service.create(user_data)
print("✅ User created successfully!")
print(f" User ID: {user.user_id}")
print(f" Email: {user.email}")
print(f" Display Name: {user.display_name or '(not set)'}")
print(f" Admin: {'Yes' if user.is_admin else 'No'}")
print(f" Active: {'Yes' if user.is_active else 'No'}")
return True
except ValueError as e:
print(f"❌ Error: {e}")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
return False
def useradd() -> None:
"""Entry point for ``invoke-useradd``."""
parser = argparse.ArgumentParser(
description="Add a user to the InvokeAI database",
epilog="If no arguments are provided, the script will run in interactive mode.",
)
parser.add_argument("--root", "-r", help=_root_help)
parser.add_argument("--email", "-e", help="User email address")
parser.add_argument("--password", "-p", help="User password")
parser.add_argument("--name", "-n", help="User display name (optional)")
parser.add_argument("--admin", "-a", action="store_true", help="Make user an administrator")
args = parser.parse_args()
if args.root:
os.environ["INVOKEAI_ROOT"] = args.root
if args.email or args.password:
if not args.email or not args.password:
print("❌ Error: Both --email and --password are required when using CLI mode")
print(" Run without arguments for interactive mode")
sys.exit(1)
success = _add_user_cli(args.email, args.password, args.name, args.admin)
else:
success = _add_user_interactive()
sys.exit(0 if success else 1)
# ---------------------------------------------------------------------------
# userdel
# ---------------------------------------------------------------------------
def _delete_user_interactive() -> bool:
"""Delete a user interactively by prompting for email."""
from invokeai.app.services.config import get_config
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.users.users_default import UserService
from invokeai.backend.util.logging import InvokeAILogger
print("=== Delete InvokeAI User ===\n")
email = input("Email address of user to delete: ").strip()
if not email:
print("Error: Email is required")
return False
try:
config = get_config()
db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger())
user_service = UserService(db)
user = user_service.get_by_email(email)
if not user:
print(f"\n❌ Error: No user found with email '{email}'")
return False
print("\nUser to delete:")
print(f" User ID: {user.user_id}")
print(f" Email: {user.email}")
print(f" Display Name: {user.display_name or '(not set)'}")
print(f" Admin: {'Yes' if user.is_admin else 'No'}")
print(f" Active: {'Yes' if user.is_active else 'No'}")
confirm = input("\n⚠️ Are you sure you want to delete this user? (yes/no): ").strip().lower()
if confirm not in ("yes", "y"):
print("Deletion cancelled.")
return False
user_service.delete(user.user_id)
print("\n✅ User deleted successfully!")
return True
except ValueError as e:
print(f"\n❌ Error: {e}")
return False
except Exception as e:
print(f"\n❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
return False
def _delete_user_cli(email: str, force: bool = False) -> bool:
"""Delete a user via CLI arguments."""
from invokeai.app.services.config import get_config
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.users.users_default import UserService
from invokeai.backend.util.logging import InvokeAILogger
try:
config = get_config()
db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger())
user_service = UserService(db)
user = user_service.get_by_email(email)
if not user:
print(f"❌ Error: No user found with email '{email}'")
return False
if not force:
print("User to delete:")
print(f" User ID: {user.user_id}")
print(f" Email: {user.email}")
print(f" Display Name: {user.display_name or '(not set)'}")
print(f" Admin: {'Yes' if user.is_admin else 'No'}")
print(f" Active: {'Yes' if user.is_active else 'No'}")
confirm = input("\n⚠️ Are you sure you want to delete this user? (yes/no): ").strip().lower()
if confirm not in ("yes", "y"):
print("Deletion cancelled.")
return False
user_service.delete(user.user_id)
print("✅ User deleted successfully!")
return True
except ValueError as e:
print(f"❌ Error: {e}")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
return False
def userdel() -> None:
"""Entry point for ``invoke-userdel``."""
parser = argparse.ArgumentParser(
description="Delete a user from the InvokeAI database",
epilog="If no arguments are provided, the script will run in interactive mode.",
)
parser.add_argument("--root", "-r", help=_root_help)
parser.add_argument("--email", "-e", help="User email address")
parser.add_argument("--force", "-f", action="store_true", help="Delete without confirmation prompt")
args = parser.parse_args()
if args.root:
os.environ["INVOKEAI_ROOT"] = args.root
if args.email:
success = _delete_user_cli(args.email, args.force)
else:
success = _delete_user_interactive()
sys.exit(0 if success else 1)
# ---------------------------------------------------------------------------
# userlist
# ---------------------------------------------------------------------------
def _list_users_table() -> bool:
"""List all users in a formatted table."""
from invokeai.app.services.config import get_config
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.users.users_default import UserService
from invokeai.backend.util.logging import InvokeAILogger
config = get_config()
logger = InvokeAILogger.get_logger(config=config)
db = SqliteDatabase(config.db_path, logger)
user_service = UserService(db)
try:
users = user_service.list_users()
if not users:
print("No users found in database.")
return True
print("\n=== InvokeAI Users ===\n")
print(f"{'User ID':<36} {'Email':<30} {'Display Name':<20} {'Admin':<8} {'Active':<8}")
print("-" * 108)
for user in users:
user_id = user.user_id
email = user.email[:29] if len(user.email) > 29 else user.email
raw_name = user.display_name or ""
name = raw_name[:19] if len(raw_name) > 19 else raw_name
is_admin = "Yes" if user.is_admin else "No"
is_active = "Yes" if user.is_active else "No"
print(f"{user_id:<36} {email:<30} {name:<20} {is_admin:<8} {is_active:<8}")
print(f"\nTotal users: {len(users)}")
return True
except Exception as e:
print(f"Error listing users: {e}")
return False
def _list_users_json() -> bool:
"""List all users in JSON format."""
from invokeai.app.services.config import get_config
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.users.users_default import UserService
from invokeai.backend.util.logging import InvokeAILogger
config = get_config()
logger = InvokeAILogger.get_logger(config=config)
db = SqliteDatabase(config.db_path, logger)
user_service = UserService(db)
try:
users = user_service.list_users()
users_data = [
{
"id": user.user_id,
"email": user.email,
"name": user.display_name,
"is_admin": user.is_admin,
"is_active": user.is_active,
}
for user in users
]
print(json.dumps(users_data, indent=2))
return True
except Exception as e:
print(f'{{"error": "{e}"}}', file=sys.stderr)
return False
def userlist() -> None:
"""Entry point for ``invoke-userlist``."""
parser = argparse.ArgumentParser(
description="List users from the InvokeAI database",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
invoke-userlist
invoke-userlist --json
""",
)
parser.add_argument("--root", "-r", help=_root_help)
parser.add_argument(
"--json",
action="store_true",
help="Output users in JSON format instead of table",
)
args = parser.parse_args()
if args.root:
os.environ["INVOKEAI_ROOT"] = args.root
success = _list_users_json() if args.json else _list_users_table()
sys.exit(0 if success else 1)
# ---------------------------------------------------------------------------
# usermod
# ---------------------------------------------------------------------------
def _modify_user_interactive() -> bool:
"""Modify a user interactively by prompting for details."""
from invokeai.app.services.auth.password_utils import validate_password_strength
from invokeai.app.services.config import get_config
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.users.users_common import UserUpdateRequest
from invokeai.app.services.users.users_default import UserService
from invokeai.backend.util.logging import InvokeAILogger
print("=== Modify InvokeAI User ===\n")
email = input("Email address of user to modify: ").strip()
if not email:
print("Error: Email is required")
return False
try:
config = get_config()
db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger())
user_service = UserService(db)
user = user_service.get_by_email(email)
if not user:
print(f"\n❌ Error: No user found with email '{email}'")
return False
print("\nCurrent user details:")
print(f" User ID: {user.user_id}")
print(f" Email: {user.email}")
print(f" Display Name: {user.display_name or '(not set)'}")
print(f" Admin: {'Yes' if user.is_admin else 'No'}")
print(f" Active: {'Yes' if user.is_active else 'No'}")
print("\n--- What would you like to change? (leave blank to keep current value) ---\n")
new_name = input(f"New display name [{user.display_name or '(not set)'}]: ").strip()
display_name = new_name if new_name else None
change_password = input("Change password? (y/N): ").strip().lower()
password = None
if change_password in ("y", "yes"):
while True:
password = getpass.getpass("New password: ")
if not password:
print("Keeping existing password.")
password = None
break
password_confirm = getpass.getpass("Confirm new password: ")
if password != password_confirm:
print("Error: Passwords do not match. Please try again.\n")
continue
is_valid, error_msg = validate_password_strength(password)
if not is_valid:
print(f"Error: {error_msg}\n")
continue
break
change_admin = input("Change admin status? (y/N): ").strip().lower()
is_admin = None
if change_admin in ("y", "yes"):
is_admin_input = (
input(f"Make administrator? [current: {'Yes' if user.is_admin else 'No'}] (y/N): ").strip().lower()
)
is_admin = is_admin_input in ("y", "yes")
if display_name is None and password is None and is_admin is None:
print("\nNo changes requested. User not modified.")
return True
changes = UserUpdateRequest(display_name=display_name, password=password, is_admin=is_admin)
updated_user = user_service.update(user.user_id, changes)
print("\n✅ User updated successfully!")
print(f" User ID: {updated_user.user_id}")
print(f" Email: {updated_user.email}")
print(f" Display Name: {updated_user.display_name or '(not set)'}")
print(f" Admin: {'Yes' if updated_user.is_admin else 'No'}")
print(f" Active: {'Yes' if updated_user.is_active else 'No'}")
return True
except ValueError as e:
print(f"\n❌ Error: {e}")
return False
except Exception as e:
print(f"\n❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
return False
def _modify_user_cli(
email: str,
display_name: str | None = None,
password: str | None = None,
is_admin: bool | None = None,
) -> bool:
"""Modify a user via CLI arguments."""
from invokeai.app.services.auth.password_utils import validate_password_strength
from invokeai.app.services.config import get_config
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
from invokeai.app.services.users.users_common import UserUpdateRequest
from invokeai.app.services.users.users_default import UserService
from invokeai.backend.util.logging import InvokeAILogger
if password is not None:
is_valid, error_msg = validate_password_strength(password)
if not is_valid:
print(f"❌ Password validation failed: {error_msg}")
return False
try:
config = get_config()
db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger())
user_service = UserService(db)
user = user_service.get_by_email(email)
if not user:
print(f"❌ Error: No user found with email '{email}'")
return False
if display_name is None and password is None and is_admin is None:
print("❌ Error: No changes specified. Use --name, --password, --admin, or --no-admin")
return False
changes = UserUpdateRequest(display_name=display_name, password=password, is_admin=is_admin)
updated_user = user_service.update(user.user_id, changes)
print("✅ User updated successfully!")
print(f" User ID: {updated_user.user_id}")
print(f" Email: {updated_user.email}")
print(f" Display Name: {updated_user.display_name or '(not set)'}")
print(f" Admin: {'Yes' if updated_user.is_admin else 'No'}")
print(f" Active: {'Yes' if updated_user.is_active else 'No'}")
return True
except ValueError as e:
print(f"❌ Error: {e}")
return False
except Exception as e:
print(f"❌ Unexpected error: {e}")
import traceback
traceback.print_exc()
return False
def usermod() -> None:
"""Entry point for ``invoke-usermod``."""
parser = argparse.ArgumentParser(
description="Modify a user in the InvokeAI database",
epilog="If no arguments are provided, the script will run in interactive mode.",
)
parser.add_argument("--root", "-r", help=_root_help)
parser.add_argument("--email", "-e", help="User email address")
parser.add_argument("--name", "-n", help="New display name")
parser.add_argument("--password", "-p", help="New password")
admin_group = parser.add_mutually_exclusive_group()
admin_group.add_argument("--admin", "-a", action="store_true", help="Grant administrator privileges")
admin_group.add_argument("--no-admin", dest="no_admin", action="store_true", help="Remove administrator privileges")
args = parser.parse_args()
if args.root:
os.environ["INVOKEAI_ROOT"] = args.root
is_admin = None
if args.admin:
is_admin = True
elif args.no_admin:
is_admin = False
if args.email:
success = _modify_user_cli(args.email, args.name, args.password, is_admin)
else:
success = _modify_user_interactive()
sys.exit(0 if success else 1)