fix
This commit is contained in:
@@ -44,8 +44,7 @@ DEFAULT_SETTINGS = [
|
||||
("context.compress_threshold", settings.default_compress_threshold, "Trigger compression at this count"),
|
||||
("context.summary_messages", settings.default_summary_messages, "Number of messages per summary block"),
|
||||
("context.max_tokens_total", 6000, "Soft token budget for context window (small models)"),
|
||||
("triggers.enabled", True, "Enable deferred trigger processing"),
|
||||
("triggers.check_interval", 30, "Trigger checker interval, seconds"),
|
||||
("triggers.enabled", True, "Enable trigger firing on in-game time changes"),
|
||||
# Embeddings / RAG
|
||||
("embedding.provider", settings.default_embedding_provider, "Embeddings provider: 'hash' (offline fallback) or 'openai' (real semantic embeddings)"),
|
||||
("embedding.base_url", settings.default_embedding_base_url, "OpenAI-compatible embeddings base URL. Empty = reuse llm.base_url"),
|
||||
@@ -56,6 +55,23 @@ DEFAULT_SETTINGS = [
|
||||
]
|
||||
|
||||
|
||||
# Keys whose values come from environment variables (via Settings fields).
|
||||
# These are re-applied on EVERY startup so .env is the source of truth.
|
||||
# Admin-panel changes to these keys are runtime overrides that get reset on
|
||||
# restart unless the operator also updates .env.
|
||||
ENV_DERIVED_SETTING_KEYS = {
|
||||
"llm.base_url",
|
||||
"llm.api_key",
|
||||
"llm.model",
|
||||
"embedding.provider",
|
||||
"embedding.base_url",
|
||||
"embedding.api_key",
|
||||
"embedding.model",
|
||||
"embedding.dim",
|
||||
"embedding.request_timeout",
|
||||
}
|
||||
|
||||
|
||||
async def init_db() -> None:
|
||||
setup_logging()
|
||||
log.info("creating_tables")
|
||||
@@ -75,6 +91,7 @@ async def init_db() -> None:
|
||||
)
|
||||
)
|
||||
await _seed_settings(session)
|
||||
await _sync_env_derived_settings(session)
|
||||
await _seed_builtin_presets(session)
|
||||
await session.commit()
|
||||
except Exception as e:
|
||||
@@ -82,12 +99,19 @@ async def init_db() -> None:
|
||||
log.warning("advisory_lock_unavailable_proceeding", error=f"{type(e).__name__}: {e}")
|
||||
async with AsyncSessionLocal() as session:
|
||||
await _seed_settings(session)
|
||||
await _sync_env_derived_settings(session)
|
||||
await _seed_builtin_presets(session)
|
||||
await session.commit()
|
||||
|
||||
# Ensure admin_setup_token is set; if empty, generate and print
|
||||
# Ensure admin_setup_token is set and print it on every startup.
|
||||
#
|
||||
# The admin-setup endpoint refuses to create a second admin (see app/api/auth.py),
|
||||
# so it's safe to always print the token — even after an admin exists, the token
|
||||
# is useless. We print on every startup (not just first run) so the operator can
|
||||
# always find the URL in the logs without having to dig through old logs.
|
||||
token = settings.admin_setup_token.strip()
|
||||
if not token:
|
||||
# No token forced via env — generate one and persist it (idempotent).
|
||||
import secrets as _s
|
||||
token = _s.token_urlsafe(24)
|
||||
async with AsyncSessionLocal() as session:
|
||||
@@ -97,17 +121,25 @@ async def init_db() -> None:
|
||||
session.add(Setting(key="admin.setup_token", value=token, description="One-time token for /admin/setup"))
|
||||
try:
|
||||
await session.commit()
|
||||
print("=" * 60)
|
||||
print("ADMIN SETUP TOKEN (use at /admin/setup):")
|
||||
print(token)
|
||||
print("=" * 60)
|
||||
log.info("admin_setup_token_generated")
|
||||
except IntegrityError:
|
||||
# Another process inserted it concurrently — fine.
|
||||
# Another process inserted it concurrently — re-read.
|
||||
await session.rollback()
|
||||
log.info("admin_setup_token_already_set")
|
||||
await session.rollback()
|
||||
existing = await session.execute(select(Setting).where(Setting.key == "admin.setup_token"))
|
||||
existing_obj = existing.scalars().first()
|
||||
if existing_obj is not None:
|
||||
token = str(existing_obj.value)
|
||||
else:
|
||||
log.info("admin_setup_token_already_set")
|
||||
# Use the persisted token (env was empty, DB has one).
|
||||
token = str(existing_obj.value)
|
||||
# Always print — operator convenience.
|
||||
print("=" * 60)
|
||||
print("ADMIN SETUP URL:")
|
||||
print(f" /admin/setup")
|
||||
print("ADMIN SETUP TOKEN:")
|
||||
print(f" {token}")
|
||||
print("=" * 60)
|
||||
log.info("admin_setup_token_printed")
|
||||
|
||||
|
||||
async def _seed_settings(session) -> None:
|
||||
@@ -135,6 +167,46 @@ async def _seed_settings(session) -> None:
|
||||
log.info("settings_already_exist")
|
||||
|
||||
|
||||
async def _sync_env_derived_settings(session) -> None:
|
||||
"""Re-apply env-derived setting values from .env on every startup.
|
||||
|
||||
This makes .env the source of truth for these keys: changing .env and
|
||||
restarting the container takes effect immediately. Admin-panel edits to
|
||||
these keys are runtime overrides that are reset on the next restart
|
||||
(unless the operator also updates .env).
|
||||
|
||||
Only the env-derived keys (see ENV_DERIVED_SETTING_KEYS) are touched;
|
||||
other settings (temperature, context params, etc.) are preserved as
|
||||
configured via the admin panel.
|
||||
"""
|
||||
env_values = {key: value for key, value, _desc in DEFAULT_SETTINGS if key in ENV_DERIVED_SETTING_KEYS}
|
||||
updated = 0
|
||||
for key, new_value in env_values.items():
|
||||
result = await session.execute(select(Setting).where(Setting.key == key))
|
||||
row = result.scalars().first()
|
||||
if row is None:
|
||||
# Shouldn't happen (seeded above) but handle defensively.
|
||||
session.add(Setting(key=key, value=new_value, description="Env-derived"))
|
||||
updated += 1
|
||||
else:
|
||||
if row.value != new_value:
|
||||
log.info(
|
||||
"env_setting_resynced",
|
||||
key=key,
|
||||
old_value=str(row.value)[:80],
|
||||
new_value=str(new_value)[:80],
|
||||
)
|
||||
row.value = new_value
|
||||
updated += 1
|
||||
if updated:
|
||||
try:
|
||||
await session.commit()
|
||||
log.info("env_settings_synced", count=updated)
|
||||
except IntegrityError:
|
||||
await session.rollback()
|
||||
log.warning("env_settings_sync_failed_concurrent")
|
||||
|
||||
|
||||
async def _seed_builtin_presets(session) -> None:
|
||||
"""Insert built-in presets if none exist yet."""
|
||||
result = await session.execute(select(Preset).where(Preset.is_builtin.is_(True)))
|
||||
|
||||
Reference in New Issue
Block a user