left4me/docs/superpowers/specs/2026-05-13-server-hostname-design.md
2026-05-13 14:19:57 +02:00

3.5 KiB

Server Hostname (Source hostname cvar) Design

Goal: Allow users to set the L4D2 server name (hostname cvar) that players see in the server browser and MOTD, with an ephemeral auto-generated fallback.

Architecture: A new hostname VARCHAR(128) column on the servers table. Empty string means "auto-generate at deploy time." The fallback is resolved ephemerally in initialize_server — computed fresh from user.username + server.name on each deploy, never persisted. Explicit overrides are stored and emitted verbatim.


Model

Add one column to Server in l4d2web/models.py:

hostname: Mapped[str] = mapped_column(String(128), default="", nullable=False)

Default "" means auto-generate. Non-empty means explicit override.

Behavior

hostname value Deploy result
"" (empty) Emit hostname "<username> <server.name>" — computed fresh each deploy, never written to DB
"My Server" Emit hostname "My Server" verbatim
User clears the field Resets to "", next deploy auto-generates

The fallback is ephemeral — initialize_server resolves it in-memory for the spec YAML. The DB row stays empty. This means renames auto-propagate to the hostname on the next deploy without manual updates.

Spec Payload

build_server_spec_payload() gains an optional resolved_hostname: str = "" keyword parameter. When non-empty, a hostname "..." line is inserted into the config array, before the rcon_password line (so rcon remains last-wins).

initialize_server() resolves the hostname:

with session_scope() as db:
    user = db.get(User, server.user_id)
resolved = server.hostname or f"{user.username} {server.name}"

UI

On server_detail.html, a new row in the info <dl> block, placed after the RCON password row:

Hostname: [ _______________ ] [Save]
          Leave empty for auto: "alice alpha"
  • Input name="hostname", maxlength="128"
  • value="{{ server.hostname }}" (empty when not set)
  • placeholder="{{ user.username }} {{ server.name }}" (previews auto-generated value)
  • Form submits to POST /servers/<id> — same endpoint as the rename form
  • No hostname field in the create-server modal; new servers always start with hostname=""

Routes

POST /servers/<int:server_id> (update_server_form) — unchanged signature; just also saves request.form.get("hostname", "") to server.hostname.

POST /servers (create_server) — unchanged; hostname defaults to "" from the model default.

Files Touched

File Change
l4d2web/models.py Add hostname column to Server
l4d2web/alembic/versions/0011_server_hostname.py Migration — ADD COLUMN hostname VARCHAR(128) NOT NULL DEFAULT ''
l4d2web/routes/server_routes.py update_server_form saves hostname from form
l4d2web/services/l4d2_facade.py build_server_spec_payload accepts resolved_hostname=, emits hostname "..." line. initialize_server resolves fallback.
l4d2web/templates/server_detail.html Hostname form row in info <dl>
l4d2web/tests/test_servers.py Tests for create default, update, clear
l4d2web/tests/test_l4d2_facade.py Tests for hostname in spec, fallback resolution

Open / Closed

  • Explicit vs ephemeral: Explicit overrides persist; empty means auto at deploy time. No toggle, no "locked" mode needed in v1.
  • No hostname in create modal: Simplifies the form. Hostname is configured post-creation on the detail page.