Kairo-Jinkendo/backend/bootstrap.py
Lars 564008be13
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
AP0.2: Auth, Tenant, Actor Foundation mit Sessions, TenantContext und Tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 20:09:54 +02:00

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")