# 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`: ```python 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 " "` — 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: ```python 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 `
` 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/` — same endpoint as the rename form - No hostname field in the create-server modal; new servers always start with `hostname=""` ## Routes **`POST /servers/`** (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 `
` | | `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.