feat(l4d2-web): emit hostname in spec config with ephemeral fallback
This commit is contained in:
parent
d42383dc37
commit
963851c0e1
2 changed files with 69 additions and 3 deletions
|
|
@ -11,6 +11,7 @@ from l4d2web.models import (
|
||||||
Overlay,
|
Overlay,
|
||||||
OverlayWorkshopItem,
|
OverlayWorkshopItem,
|
||||||
Server,
|
Server,
|
||||||
|
User,
|
||||||
WorkshopItem,
|
WorkshopItem,
|
||||||
)
|
)
|
||||||
from l4d2web.services import host_commands
|
from l4d2web.services import host_commands
|
||||||
|
|
@ -30,6 +31,8 @@ def build_server_spec_payload(
|
||||||
server: Server,
|
server: Server,
|
||||||
blueprint: Blueprint,
|
blueprint: Blueprint,
|
||||||
overlay_rows: list[tuple[int, str, bool]],
|
overlay_rows: list[tuple[int, str, bool]],
|
||||||
|
*,
|
||||||
|
resolved_hostname: str = "",
|
||||||
) -> dict:
|
) -> dict:
|
||||||
overlays: list[dict] = []
|
overlays: list[dict] = []
|
||||||
for overlay_id, path, expose in overlay_rows:
|
for overlay_id, path, expose in overlay_rows:
|
||||||
|
|
@ -45,8 +48,10 @@ def build_server_spec_payload(
|
||||||
if expose
|
if expose
|
||||||
]
|
]
|
||||||
config_lines: list[str] = exec_lines + json.loads(blueprint.config)
|
config_lines: list[str] = exec_lines + json.loads(blueprint.config)
|
||||||
# rcon_password is appended LAST so neither overlays nor user blueprint
|
if resolved_hostname:
|
||||||
# config can override it (Source's cvar semantics are last-wins).
|
config_lines.append(f'hostname "{resolved_hostname}"')
|
||||||
|
# rcon_password is appended LAST so neither overlays, blueprint config,
|
||||||
|
# nor hostname can override it (Source's cvar semantics are last-wins).
|
||||||
if server.rcon_password:
|
if server.rcon_password:
|
||||||
config_lines.append(f'rcon_password "{server.rcon_password}"')
|
config_lines.append(f'rcon_password "{server.rcon_password}"')
|
||||||
return {
|
return {
|
||||||
|
|
@ -98,7 +103,17 @@ def initialize_server(server_id: int, on_stdout=None, on_stderr=None, should_can
|
||||||
# than mount a partial overlay (would silently leave maps missing in-game).
|
# than mount a partial overlay (would silently leave maps missing in-game).
|
||||||
_check_workshop_overlay_caches(blueprint_id=blueprint.id)
|
_check_workshop_overlay_caches(blueprint_id=blueprint.id)
|
||||||
|
|
||||||
spec_path = write_temp_spec(build_server_spec_payload(server, blueprint, overlay_rows))
|
# Resolve hostname — explicit override or ephemeral fallback
|
||||||
|
if server.hostname:
|
||||||
|
resolved_hostname = server.hostname
|
||||||
|
else:
|
||||||
|
with session_scope() as db:
|
||||||
|
user = db.get(User, server.user_id)
|
||||||
|
resolved_hostname = f"{user.username} {server.name}"
|
||||||
|
|
||||||
|
spec_path = write_temp_spec(build_server_spec_payload(
|
||||||
|
server, blueprint, overlay_rows, resolved_hostname=resolved_hostname,
|
||||||
|
))
|
||||||
try:
|
try:
|
||||||
host_commands.run_command(
|
host_commands.run_command(
|
||||||
["l4d2ctl", "initialize", server_unit_name(server.id), "-f", str(spec_path)],
|
["l4d2ctl", "initialize", server_unit_name(server.id), "-f", str(spec_path)],
|
||||||
|
|
|
||||||
|
|
@ -385,3 +385,54 @@ def test_build_server_spec_payload_omits_rcon_password_when_empty() -> None:
|
||||||
assert not line.startswith("rcon_password ")
|
assert not line.startswith("rcon_password ")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# build_server_spec_payload — hostname injection
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_server_spec_payload_injects_hostname() -> None:
|
||||||
|
from l4d2web.services.l4d2_facade import build_server_spec_payload
|
||||||
|
|
||||||
|
bp = Blueprint(id=1, user_id=1, name="bp", arguments="[]", config='["sv_consistency 1"]')
|
||||||
|
srv = Server(id=1, user_id=1, blueprint_id=1, name="alpha", port=27015, rcon_password="sekret")
|
||||||
|
spec = build_server_spec_payload(srv, bp, [], resolved_hostname="My Server")
|
||||||
|
cfg = spec["config"]
|
||||||
|
assert 'hostname "My Server"' in cfg
|
||||||
|
assert cfg[-1] == 'rcon_password "sekret"'
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_server_spec_payload_omits_hostname_when_empty() -> None:
|
||||||
|
from l4d2web.services.l4d2_facade import build_server_spec_payload
|
||||||
|
|
||||||
|
bp = Blueprint(id=1, user_id=1, name="bp", arguments="[]", config="[]")
|
||||||
|
srv = Server(id=1, user_id=1, blueprint_id=1, name="alpha", port=27015, rcon_password="sekret")
|
||||||
|
spec = build_server_spec_payload(srv, bp, [])
|
||||||
|
for line in spec["config"]:
|
||||||
|
assert not line.startswith("hostname ")
|
||||||
|
|
||||||
|
|
||||||
|
def test_initialize_server_resolves_fallback_hostname(
|
||||||
|
monkeypatch: pytest.MonkeyPatch, server_with_blueprint,
|
||||||
|
) -> None:
|
||||||
|
"""When server.hostname is empty, deploy emits hostname "<username> <server.name>"."""
|
||||||
|
from l4d2web.services.l4d2_facade import initialize_server
|
||||||
|
|
||||||
|
spec_contents: list[str] = []
|
||||||
|
|
||||||
|
def fake_run_command(cmd, **kwargs):
|
||||||
|
nonlocal spec_contents
|
||||||
|
spec_path = cmd[cmd.index("-f") + 1]
|
||||||
|
spec_contents.append(Path(spec_path).read_text())
|
||||||
|
return CommandResult(returncode=0, stdout="", stderr="")
|
||||||
|
|
||||||
|
monkeypatch.setattr("l4d2web.services.host_commands.run_command", fake_run_command)
|
||||||
|
|
||||||
|
server_id, _ = server_with_blueprint
|
||||||
|
initialize_server(server_id)
|
||||||
|
|
||||||
|
assert len(spec_contents) == 1
|
||||||
|
assert "hostname" in spec_contents[0]
|
||||||
|
# The fixture creates user "alice" and server named "alpha"
|
||||||
|
assert '"alice alpha"' in spec_contents[0]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue