Setup: prompt for first-run admin credentials
* feat(setup): prompt for admin credentials interactively on first run When setup.py runs in a terminal (TTY) without env vars set, it now asks the user to choose a username and password instead of generating a random one that scrolls off-screen. Includes confirmation prompt to catch typos. Existing behavior is preserved: - ODYSSEUS_ADMIN_USER + ODYSSEUS_ADMIN_PASSWORD env vars take priority - Non-interactive contexts (Docker, CI) still get a random password - ODYSSEUS_SKIP_ADMIN_PROMPT=1 opts out of the interactive prompt - Re-runs still skip if auth.json already exists Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(macos): use venv Python for pip install and uvicorn launch On PEP 668 systems (newer Homebrew Python), pip install outside a venv is rejected. The script creates a venv but then called the system $PY for pip and uvicorn. Switch to ./venv/bin/python for both. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Revert "fix(macos): use venv Python for pip install and uvicorn launch" This reverts commit 7a1be956659d86183da2edcde2114eb363efd3e4. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
58
setup.py
58
setup.py
@@ -43,6 +43,33 @@ def init_database():
|
||||
print(" [ok] Database initialized")
|
||||
|
||||
|
||||
def _prompt_admin_credentials():
|
||||
"""Interactively ask for admin username and password when running in a terminal."""
|
||||
import getpass
|
||||
|
||||
print()
|
||||
print(" Set up your admin account:")
|
||||
print(" (Press Enter to accept defaults)")
|
||||
print()
|
||||
|
||||
username = input(" Username [admin]: ").strip().lower()
|
||||
if not username:
|
||||
username = "admin"
|
||||
|
||||
while True:
|
||||
password = getpass.getpass(" Password: ")
|
||||
if not password:
|
||||
print(" Password cannot be empty.")
|
||||
continue
|
||||
confirm = getpass.getpass(" Confirm password: ")
|
||||
if password != confirm:
|
||||
print(" Passwords don't match. Try again.")
|
||||
continue
|
||||
break
|
||||
|
||||
return username, password
|
||||
|
||||
|
||||
def create_default_admin():
|
||||
"""Create an initial admin user if none exists."""
|
||||
auth_path = os.path.join(DATA_DIR, "auth.json")
|
||||
@@ -54,8 +81,22 @@ def create_default_admin():
|
||||
import bcrypt
|
||||
import json
|
||||
|
||||
username = os.getenv("ODYSSEUS_ADMIN_USER", "admin").strip().lower() or "admin"
|
||||
password = os.getenv("ODYSSEUS_ADMIN_PASSWORD") or __import__("secrets").token_urlsafe(18)
|
||||
# Priority: env vars > interactive prompt > random password
|
||||
username = os.getenv("ODYSSEUS_ADMIN_USER", "").strip().lower()
|
||||
password = os.getenv("ODYSSEUS_ADMIN_PASSWORD", "").strip()
|
||||
|
||||
if username and password:
|
||||
# Both provided via env — use them directly
|
||||
pass
|
||||
elif sys.stdin.isatty() and not os.getenv("ODYSSEUS_SKIP_ADMIN_PROMPT"):
|
||||
# Interactive terminal — ask the user
|
||||
username, password = _prompt_admin_credentials()
|
||||
else:
|
||||
# Non-interactive (Docker, CI) — fall back to generated password
|
||||
username = username or "admin"
|
||||
password = password or __import__("secrets").token_urlsafe(18)
|
||||
|
||||
username = username or "admin"
|
||||
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
||||
auth_data = {
|
||||
"users": {
|
||||
@@ -67,9 +108,14 @@ def create_default_admin():
|
||||
}
|
||||
with open(auth_path, "w", encoding="utf-8") as f:
|
||||
json.dump(auth_data, f, indent=2)
|
||||
print(f" [ok] Initial admin user created ({username})")
|
||||
print(f" Temporary password: {password}")
|
||||
print(f" ** Change it after first login. Set ODYSSEUS_ADMIN_PASSWORD to choose your own. **")
|
||||
|
||||
if sys.stdin.isatty() and not os.getenv("ODYSSEUS_ADMIN_PASSWORD"):
|
||||
print(f" [ok] Admin account created ({username})")
|
||||
else:
|
||||
print(f" [ok] Initial admin user created ({username})")
|
||||
if not os.getenv("ODYSSEUS_ADMIN_PASSWORD"):
|
||||
print(f" Temporary password: {password}")
|
||||
print(f" ** Change it after first login. Set ODYSSEUS_ADMIN_PASSWORD to choose your own. **")
|
||||
return "created"
|
||||
except ImportError:
|
||||
print(" [warn] bcrypt not installed — skipping admin user creation")
|
||||
@@ -160,7 +206,7 @@ def main():
|
||||
|
||||
# Cleaned, action-focused final instruction strings
|
||||
if admin_status == "created":
|
||||
print("Login with the admin username and temporary password printed above.\n")
|
||||
print("Login with your admin credentials.\n")
|
||||
elif admin_status == "exists":
|
||||
print("Login with your existing admin credentials.\n")
|
||||
elif admin_status == "skipped":
|
||||
|
||||
Reference in New Issue
Block a user