"""First-run seeder: populates DB from ENV defaults when tables are empty.""" from __future__ import annotations import json import logging import os import secrets from server.auth import hash_password from server.db import get_pool from server.services import settings_service logger = logging.getLogger(__name__) async def seed_if_empty() -> None: """Check if admin tables are empty and seed with ENV-derived values.""" pool = await get_pool() # ---- Admin User ---- user = await settings_service.get_admin_user() if user is None: admin_pw = os.getenv("ADMIN_PASSWORD", "") if admin_pw: # Explicit ENV password → seed admin user (backward compat) await settings_service.create_admin_user("admin", hash_password(admin_pw)) logger.info("Admin user seeded from ADMIN_PASSWORD env") else: # No password set → setup wizard will handle admin creation logger.info( "No ADMIN_PASSWORD set — setup wizard will handle first-time " "configuration. Visit the web UI to complete setup." ) # ---- Integrations ---- existing = await settings_service.get_integrations() existing_types = {i["type"] for i in existing} seed_integrations = [ { "type": "weather", "name": "Wetter (wttr.in)", "config": { "location": os.getenv("WEATHER_LOCATION", "Leverkusen"), "location_secondary": os.getenv("WEATHER_LOCATION_SECONDARY", "Rab,Croatia"), "location_tertiary": os.getenv("WEATHER_LOCATION_TERTIARY", "München"), }, "enabled": True, "display_order": 0, }, { "type": "news", "name": "News (PostgreSQL)", "config": { "max_age_hours": int(os.getenv("NEWS_MAX_AGE_HOURS", "48")), }, "enabled": True, "display_order": 1, }, { "type": "ha", "name": "Home Assistant", "config": { "url": os.getenv("HA_URL", ""), "token": os.getenv("HA_TOKEN", ""), }, "enabled": bool(os.getenv("HA_URL")), "display_order": 2, }, { "type": "vikunja", "name": "Vikunja Tasks", "config": { "url": os.getenv("VIKUNJA_URL", ""), "token": os.getenv("VIKUNJA_TOKEN", ""), "private_projects": [3, 4], "sams_projects": [2, 5], }, "enabled": bool(os.getenv("VIKUNJA_URL")), "display_order": 3, }, { "type": "unraid", "name": "Unraid Server", "config": { "servers": _parse_unraid_env(), }, "enabled": bool(os.getenv("UNRAID_SERVERS")), "display_order": 4, }, { "type": "mqtt", "name": "MQTT Broker", "config": { "host": os.getenv("MQTT_HOST", ""), "port": int(os.getenv("MQTT_PORT", "1883")), "username": os.getenv("MQTT_USERNAME", ""), "password": os.getenv("MQTT_PASSWORD", ""), "client_id": os.getenv("MQTT_CLIENT_ID", "daily-briefing"), "topics": _parse_mqtt_topics(), }, "enabled": bool(os.getenv("MQTT_HOST")), "display_order": 5, }, { "type": "n8n", "name": "n8n Webhooks", "config": { "url": os.getenv("N8N_URL", ""), "api_key": os.getenv("N8N_API_KEY", ""), }, "enabled": bool(os.getenv("N8N_URL")), "display_order": 6, }, ] for seed in seed_integrations: if seed["type"] not in existing_types: await settings_service.upsert_integration( type_name=seed["type"], name=seed["name"], config=seed["config"], enabled=seed["enabled"], display_order=seed["display_order"], ) logger.info("Seeded integration: %s", seed["type"]) # ---- App Settings ---- existing_settings = await settings_service.get_all_settings() if not existing_settings: default_settings = [ ("weather_cache_ttl", "1800", "int", "cache", "Wetter Cache TTL", "Sekunden"), ("ha_cache_ttl", "30", "int", "cache", "HA Cache TTL", "Sekunden"), ("vikunja_cache_ttl", "60", "int", "cache", "Vikunja Cache TTL", "Sekunden"), ("unraid_cache_ttl", "15", "int", "cache", "Unraid Cache TTL", "Sekunden"), ("news_cache_ttl", "300", "int", "cache", "News Cache TTL", "Sekunden"), ("ws_interval", "15", "int", "general", "WebSocket Intervall", "Sekunden"), ] for key, value, vtype, cat, label, desc in default_settings: await settings_service.set_setting(key, value, vtype, cat, label, desc) logger.info("Seeded %d default settings", len(default_settings)) def _parse_unraid_env() -> list: """Parse UNRAID_SERVERS env var.""" raw = os.getenv("UNRAID_SERVERS", "[]") try: return json.loads(raw) except (json.JSONDecodeError, TypeError): return [] def _parse_mqtt_topics() -> list: """Parse MQTT_TOPICS env var.""" raw = os.getenv("MQTT_TOPICS", "#") try: return json.loads(raw) except (json.JSONDecodeError, TypeError): return [t.strip() for t in raw.split(",") if t.strip()]