Skip to content

Commit 6078b64

Browse files
authored
Merge pull request #1867 from transformerlab/fix/admin-account-change
Allow changing admin account during cli setup
2 parents 0161385 + cccfdc7 commit 6078b64

6 files changed

Lines changed: 42 additions & 14 deletions

File tree

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ SMTP_PASSWORD="your_email_password"
4242
EMAIL_FROM="your_email@example.com"
4343

4444
## Authentication Providers:
45+
# Default admin user seeded on first startup (password is always admin123 initially)
46+
# TLAB_DEFAULT_ADMIN_EMAIL="admin@example.com"
4547
EMAIL_AUTH_ENABLED="true"
4648
EMAIL_METHOD="dev"
4749

api/transformerlab/services/experiment_init.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,26 @@
1616
import logging
1717

1818
logger = logging.getLogger(__name__)
19+
DEFAULT_ADMIN_EMAIL_ENV = "TRANSFORMERLAB_DEFAULT_ADMIN_EMAIL"
20+
DEFAULT_ADMIN_EMAIL_FALLBACK = "admin@example.com"
21+
22+
23+
def _get_default_admin_email() -> str:
24+
configured = os.getenv(DEFAULT_ADMIN_EMAIL_ENV, "").strip()
25+
if not configured:
26+
return DEFAULT_ADMIN_EMAIL_FALLBACK
27+
return configured
1928

2029

2130
async def seed_default_admin_user():
22-
"""Create a default admin user with credentials admin@example.com / admin123 if one doesn't exist."""
31+
"""Create a default admin user with configurable email and password admin123 if one doesn't exist."""
2332
try:
33+
admin_email = _get_default_admin_email()
34+
# Backward compatibility: avoid creating a second admin if env changes after initial seed.
35+
candidate_admin_emails = {admin_email, DEFAULT_ADMIN_EMAIL_FALLBACK}
2436
async with AsyncSessionLocal() as session:
2537
# Check if admin user already exists
26-
stmt = select(User).where(User.email == "admin@example.com")
38+
stmt = select(User).where(User.email.in_(candidate_admin_emails))
2739
result = await session.execute(stmt)
2840
existing_admin = result.unique().scalar_one_or_none()
2941

@@ -75,7 +87,7 @@ async def seed_default_admin_user():
7587

7688
# Create admin user using UserCreate schema
7789
user_create = UserCreate(
78-
email="admin@example.com",
90+
email=admin_email,
7991
password="admin123",
8092
is_active=True,
8193
is_superuser=True,
@@ -127,7 +139,7 @@ async def seed_default_admin_user():
127139
await migrate_workspace_to_org(team_id)
128140

129141
print(
130-
f"✅ Created and verified admin user admin@example.com (id={admin_user_id}, is_verified={admin_user.is_verified})"
142+
f"✅ Created and verified admin user {admin_email} (id={admin_user_id}, is_verified={admin_user.is_verified})"
131143
)
132144
except Exception as e:
133145
print(f"⚠️ Error in seed_default_admin_user: {e}")

cli/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "transformerlab-cli"
7-
version = "0.0.28"
7+
version = "0.0.29"
88
description = "Transformer Lab CLI"
99
requires-python = ">=3.10"
1010
authors = [{ name = "Transformer Lab", email = "hello@transformerlab.ai" }]

cli/src/transformerlab_cli/commands/server.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,16 +231,20 @@ def _prompt_storage(existing: dict[str, str]) -> dict[str, str]:
231231
return env
232232

233233

234-
def _prompt_admin() -> dict[str, str]:
235-
"""Display admin account info. The API seeds a hardcoded admin on first startup."""
234+
def _prompt_admin(existing: dict[str, str]) -> dict[str, str]:
235+
"""Prompt for the default admin email used during first API startup."""
236236
console.print("\n[bold header]3. Admin Account[/bold header]")
237+
default_email = existing.get("TLAB_DEFAULT_ADMIN_EMAIL", "admin@example.com")
238+
admin_email = typer.prompt("Default admin email", default=default_email).strip()
239+
if not admin_email:
240+
admin_email = "admin@example.com"
237241
console.print(
238242
"[dim]A default admin account is created automatically on first startup:[/dim]"
239-
"\n Email: [bold]admin@example.com[/bold]"
243+
f"\n Email: [bold]{admin_email}[/bold]"
240244
"\n Password: [bold]admin123[/bold]"
241245
"\n[warning]Change the default password immediately after first login![/warning]"
242246
)
243-
return {}
247+
return {"TLAB_DEFAULT_ADMIN_EMAIL": admin_email}
244248

245249

246250
def _prompt_compute(existing: dict[str, str]) -> dict[str, str]:
@@ -508,6 +512,7 @@ def _offer_install_script() -> int:
508512
(
509513
"Authentication",
510514
[
515+
"TLAB_DEFAULT_ADMIN_EMAIL",
511516
"EMAIL_AUTH_ENABLED",
512517
"GOOGLE_OAUTH_ENABLED",
513518
"GOOGLE_OAUTH_CLIENT_ID",
@@ -592,11 +597,12 @@ def _write_env_file(path: str, env_vars: dict[str, str]) -> None:
592597
def _print_next_steps(env_vars: dict[str, str]) -> None:
593598
"""Print post-install guidance."""
594599
frontend_url = env_vars.get("FRONTEND_URL", "http://localhost:8338")
600+
admin_email = env_vars.get("TLAB_DEFAULT_ADMIN_EMAIL", "admin@example.com")
595601
console.print("\n[bold header]Next Steps[/bold header]")
596602
console.print(" 1. Start the server:")
597603
console.print(" [bold]cd ~/.transformerlab/src && ./run.sh[/bold]")
598604
console.print(f" 2. Open {frontend_url} in your browser")
599-
console.print(" 3. Log in with [bold]admin@example.com[/bold] / [bold]admin123[/bold]")
605+
console.print(f" 3. Log in with [bold]{admin_email}[/bold] / [bold]admin123[/bold]")
600606
console.print(" [warning]Change the default password immediately![/warning]")
601607

602608

@@ -748,7 +754,7 @@ def _install_interactive(dry_run: bool) -> None:
748754
provider=env_vars.get("TFL_STORAGE_PROVIDER", "unknown"),
749755
)
750756

751-
env_vars.update(_prompt_admin())
757+
env_vars.update(_prompt_admin(existing))
752758

753759
env_vars.update(_prompt_compute(existing))
754760
if env_vars.get("DEFAULT_COMPUTE_PROVIDER"):

cli/tests/commands/test_server.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ def test_server_install_dry_run_defaults(tmp_path):
8686
"""Test a full dry-run with all defaults accepted."""
8787
fake_env = os.path.join(str(tmp_path), ".env")
8888

89-
# Flow: frontend URL -> storage type (aws) -> compute (skip) -> email? (n) -> auth? (n)
89+
# Flow: frontend URL -> storage type (aws) -> admin email -> compute (skip) -> email? (n) -> auth? (n)
9090
user_input = "\n".join(
9191
[
9292
"", # frontend URL (accept default)
9393
"2", # storage type: aws
94+
"", # admin email (accept default)
9495
"5", # compute: skip
9596
"n", # skip email
9697
"n", # skip auth
@@ -114,6 +115,7 @@ def test_server_install_writes_file(tmp_path):
114115
[
115116
"http://myserver.com:8338", # frontend URL
116117
"2", # storage type: aws
118+
"owner@myserver.com", # admin email
117119
"5", # compute: skip
118120
"n", # skip email
119121
"n", # skip auth
@@ -132,6 +134,7 @@ def test_server_install_writes_file(tmp_path):
132134
assert 'FRONTEND_URL="http://myserver.com:8338"' in content
133135
assert 'TL_API_URL="http://myserver.com:8338/"' in content
134136
assert 'TFL_STORAGE_PROVIDER="aws"' in content
137+
assert 'TLAB_DEFAULT_ADMIN_EMAIL="owner@myserver.com"' in content
135138
assert 'MULTIUSER="true"' in content
136139
assert "TRANSFORMERLAB_JWT_SECRET=" in content
137140
assert "TRANSFORMERLAB_REFRESH_SECRET=" in content
@@ -151,6 +154,7 @@ def test_server_install_preserves_jwt_secrets(tmp_path):
151154
[
152155
"", # frontend URL (accept existing default)
153156
"2", # storage: aws
157+
"", # admin email
154158
"5", # compute: skip
155159
"n", # skip email
156160
"n", # skip auth
@@ -175,6 +179,7 @@ def test_server_install_storage_localfs(tmp_path):
175179
"", # frontend URL
176180
"1", # storage type: localfs
177181
"/mnt/shared", # storage path
182+
"", # admin email
178183
"5", # compute: skip
179184
"n", # skip email
180185
"n", # skip auth
@@ -198,6 +203,7 @@ def test_server_install_email_configured(tmp_path):
198203
[
199204
"", # frontend URL
200205
"2", # storage: aws
206+
"", # admin email
201207
"5", # compute: skip
202208
"y", # configure email
203209
"smtp.gmail.com", # smtp server
@@ -225,6 +231,7 @@ def test_server_install_email_skip(tmp_path):
225231
[
226232
"", # frontend URL
227233
"2", # storage: aws
234+
"", # admin email
228235
"5", # compute: skip
229236
"n", # skip email
230237
"n", # skip auth
@@ -246,6 +253,7 @@ def test_server_install_admin_info_displayed(tmp_path):
246253
[
247254
"", # frontend URL
248255
"2", # storage: aws
256+
"owner@example.com", # admin email
249257
"5", # compute: skip
250258
"n", # skip email
251259
"n", # skip auth
@@ -256,7 +264,7 @@ def test_server_install_admin_info_displayed(tmp_path):
256264
result = runner.invoke(app, ["server", "install", "--dry-run"], input=user_input)
257265

258266
assert result.exit_code == 0
259-
assert "admin@example.com" in result.output
267+
assert "owner@example.com" in result.output
260268
assert "admin123" in result.output
261269
assert "Change the default password" in result.output
262270

cli/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)