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