Container starts with only DB credentials. On first visit, a step-by-step wizard guides through admin password, weather, HA, Vikunja, Unraid, MQTT, n8n and news configuration. Backward-compat: ADMIN_PASSWORD env skips wizard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
159 lines
5.5 KiB
Python
159 lines
5.5 KiB
Python
"""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"),
|
|
},
|
|
"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()]
|