feat(l4d2): implement initialize flow and systemd user template management
This commit is contained in:
parent
3c92721973
commit
60bb709916
5 changed files with 111 additions and 0 deletions
46
components/l4d2-host-lib/src/l4d2host/instances.py
Normal file
46
components/l4d2-host-lib/src/l4d2host/instances.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
from l4d2host.spec import load_spec
|
||||
from l4d2host.systemd_user import daemon_reload, ensure_template_unit
|
||||
|
||||
|
||||
DEFAULT_ROOT = Path("/opt/l4d2")
|
||||
|
||||
|
||||
def initialize_instance(
|
||||
name: str,
|
||||
spec_path: Path,
|
||||
*,
|
||||
root: Path = DEFAULT_ROOT,
|
||||
on_stdout: Callable[[str], None] | None = None,
|
||||
on_stderr: Callable[[str], None] | None = None,
|
||||
passthrough: bool = False,
|
||||
) -> None:
|
||||
spec = load_spec(spec_path)
|
||||
|
||||
instance_dir = root / "instances" / name
|
||||
runtime_dir = root / "runtime" / name
|
||||
(runtime_dir / "upper").mkdir(parents=True, exist_ok=True)
|
||||
(runtime_dir / "work").mkdir(parents=True, exist_ok=True)
|
||||
(runtime_dir / "merged").mkdir(parents=True, exist_ok=True)
|
||||
instance_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
lowerdirs = [str(root / "overlays" / overlay) for overlay in spec.overlays]
|
||||
lowerdirs.append(str(root / "installation"))
|
||||
|
||||
instance_env = "\n".join(
|
||||
[
|
||||
f"L4D2_PORT={spec.port}",
|
||||
f"L4D2_ARGS={' '.join(spec.arguments)}",
|
||||
f"L4D2_LOWERDIRS={':'.join(lowerdirs)}",
|
||||
]
|
||||
) + "\n"
|
||||
(instance_dir / "instance.env").write_text(instance_env)
|
||||
|
||||
server_cfg = "\n".join(spec.config) if spec.config else ""
|
||||
(instance_dir / "server.cfg").write_text(server_cfg)
|
||||
|
||||
if root.resolve() == DEFAULT_ROOT:
|
||||
ensure_template_unit()
|
||||
daemon_reload(on_stdout=on_stdout, on_stderr=on_stderr, passthrough=passthrough)
|
||||
29
components/l4d2-host-lib/src/l4d2host/systemd_user.py
Normal file
29
components/l4d2-host-lib/src/l4d2host/systemd_user.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
from importlib import resources
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
from l4d2host.process import run_command
|
||||
|
||||
|
||||
def ensure_template_unit(target_dir: Path | None = None) -> Path:
|
||||
if target_dir is None:
|
||||
target_dir = Path.home() / ".config/systemd/user"
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
target_file = target_dir / "l4d2@.service"
|
||||
body = resources.files("l4d2host.templates").joinpath("l4d2@.service").read_text()
|
||||
target_file.write_text(body)
|
||||
return target_file
|
||||
|
||||
|
||||
def daemon_reload(
|
||||
*,
|
||||
on_stdout: Callable[[str], None] | None = None,
|
||||
on_stderr: Callable[[str], None] | None = None,
|
||||
passthrough: bool = False,
|
||||
) -> None:
|
||||
run_command(
|
||||
["systemctl", "--user", "daemon-reload"],
|
||||
on_stdout=on_stdout,
|
||||
on_stderr=on_stderr,
|
||||
passthrough=passthrough,
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=L4D2 dedicated server instance %i
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/opt/l4d2/instances/%i/instance.env
|
||||
WorkingDirectory=/opt/l4d2/runtime/%i/merged/left4dead2
|
||||
ExecStart=/opt/l4d2/installation/srcds_run -game left4dead2 +hostport ${L4D2_PORT} ${L4D2_ARGS}
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
22
components/l4d2-host-lib/tests/test_initialize.py
Normal file
22
components/l4d2-host-lib/tests/test_initialize.py
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
from pathlib import Path
|
||||
|
||||
from l4d2host.instances import initialize_instance
|
||||
|
||||
|
||||
def test_initialize_writes_files(tmp_path: Path) -> None:
|
||||
spec = tmp_path / "spec.yaml"
|
||||
spec.write_text("port: 27015\noverlays: [a,b]\nconfig: ['sv_consistency 1']\n")
|
||||
|
||||
initialize_instance("alpha", spec, root=tmp_path)
|
||||
|
||||
assert (tmp_path / "instances/alpha/instance.env").exists()
|
||||
assert (tmp_path / "instances/alpha/server.cfg").exists()
|
||||
|
||||
|
||||
def test_empty_config_writes_empty_server_cfg(tmp_path: Path) -> None:
|
||||
spec = tmp_path / "spec.yaml"
|
||||
spec.write_text("port: 27015\n")
|
||||
|
||||
initialize_instance("alpha", spec, root=tmp_path)
|
||||
|
||||
assert (tmp_path / "instances/alpha/server.cfg").read_text() == ""
|
||||
Loading…
Reference in a new issue