Files
stackpilot/backend/services/settings_service.py
T
menzeljandClaude Opus 5.5 ba788e8b03
CI / check (push) Successful in 8m20s
CI / build-and-push (push) Successful in 4m19s
Link port chips to the Docker host, not the proxy's domain (0.67.3)
Ports published on 0.0.0.0 were linked at window.location.hostname. Opened
through a reverse proxy such as Nginx Proxy Manager that is the proxy's
domain, and domain:8080 ended up at the proxy instead of the container.

The backend now reads the host's address from the host's routing table
(${HOST_PROC_PATH}/1/net/route + fib_trie, the /proc mount the compose file
already has): the IPv4 address on the interface with the best default
route. There is no fallback to /proc/net, which in a bridged container is
the container's own namespace with an unreachable 172.x address.

The links use, in order: the new "Host address for port links" setting
(Settings -> General), the page's hostname when it is an IP or localhost,
the detected address, and the page's hostname as a last resort. Ports
bound to one specific address still link there; IPv6 hosts are bracketed.
The setting is validated before anything is written, so a bad value no
longer leaves the interval half-saved in the same request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 23:19:09 +02:00

53 lines
1.5 KiB
Python

"""Runtime settings stored in the DB (key/value), with env fallbacks."""
from __future__ import annotations
import json
from typing import Any, Optional
from sqlmodel import Session
from config import settings as env_settings
from database import engine
from models.setting import Setting
KEY_UPDATE_INTERVAL = "update_check_interval_minutes"
KEY_PORT_LINK_HOST = "port_link_host"
def get(session: Session, key: str, default: Any = None) -> Any:
row = session.get(Setting, key)
if row is None:
return default
try:
return json.loads(row.value)
except json.JSONDecodeError:
return default
def set_value(session: Session, key: str, value: Any) -> None:
row = session.get(Setting, key)
encoded = json.dumps(value)
if row is None:
session.add(Setting(key=key, value=encoded))
else:
row.value = encoded
session.add(row)
session.commit()
def get_update_interval(session: Optional[Session] = None) -> int:
"""Effective update-check interval in minutes (DB override or env default)."""
if session is None:
with Session(engine) as own:
return get_update_interval(own)
val = get(session, KEY_UPDATE_INTERVAL)
if isinstance(val, int) and val > 0:
return val
return env_settings.UPDATE_CHECK_INTERVAL_MINUTES
def get_port_link_host(session: Session) -> str:
"""Host that port links point at, as configured; "" means detect it."""
val = get(session, KEY_PORT_LINK_HOST)
return val if isinstance(val, str) else ""