daily-briefing/server/services/seed_service.py

161 lines
5.6 KiB
Python
Raw Permalink Normal View History

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