# 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 "<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:

```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 `<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.
