Some checks failed
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Failing after 7s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
"""Bootstrap first admin user, tenant, membership and human actor from environment."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from psycopg2.extras import RealDictCursor
|
|
|
|
from auth import create_session, hash_password
|
|
from db import get_connection
|
|
from services.actors import create_actor
|
|
from services.audit import log_audit
|
|
|
|
|
|
def _env(name: str) -> str:
|
|
return os.getenv(name, "").strip()
|
|
|
|
|
|
def bootstrap_admin_if_needed() -> None:
|
|
if _env("SKIP_BOOTSTRAP").lower() in ("1", "true", "yes"):
|
|
return
|
|
|
|
email = _env("KAIRO_BOOTSTRAP_ADMIN_EMAIL")
|
|
password = _env("KAIRO_BOOTSTRAP_ADMIN_PASSWORD")
|
|
if not email or not password:
|
|
return
|
|
|
|
tenant_slug = _env("KAIRO_BOOTSTRAP_TENANT_SLUG") or "default"
|
|
tenant_name = _env("KAIRO_BOOTSTRAP_TENANT_NAME") or "Default Tenant"
|
|
display_name = _env("KAIRO_BOOTSTRAP_ADMIN_NAME") or "Kairo Admin"
|
|
|
|
conn = get_connection()
|
|
try:
|
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
|
cur.execute("SELECT COUNT(*) AS n FROM users")
|
|
if cur.fetchone()["n"] > 0:
|
|
return
|
|
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO tenants (slug, name)
|
|
VALUES (%s, %s)
|
|
ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name
|
|
RETURNING id
|
|
""",
|
|
(tenant_slug, tenant_name),
|
|
)
|
|
tenant_row = cur.fetchone()
|
|
if not tenant_row:
|
|
cur.execute("SELECT id FROM tenants WHERE slug = %s", (tenant_slug,))
|
|
tenant_row = cur.fetchone()
|
|
tenant_id = str(tenant_row["id"])
|
|
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO users (email, password_hash, display_name, portal_role)
|
|
VALUES (%s, %s, %s, 'admin')
|
|
RETURNING id
|
|
""",
|
|
(email.lower(), hash_password(password), display_name),
|
|
)
|
|
user_id = str(cur.fetchone()["id"])
|
|
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO tenant_memberships (tenant_id, user_id, tenant_role)
|
|
VALUES (%s, %s, 'owner')
|
|
ON CONFLICT (tenant_id, user_id) DO NOTHING
|
|
""",
|
|
(tenant_id, user_id),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
create_actor(
|
|
tenant_id=tenant_id,
|
|
actor_type="human",
|
|
name=display_name,
|
|
user_id=user_id,
|
|
)
|
|
log_audit(
|
|
"bootstrap.admin_created",
|
|
user_id=user_id,
|
|
tenant_id=tenant_id,
|
|
details={"email": email.lower(), "tenant_slug": tenant_slug},
|
|
)
|
|
print(f"[bootstrap] Admin user and tenant '{tenant_slug}' created")
|