game-stream: add Meccha Chameleon launcher
This commit is contained in:
parent
9d0356a357
commit
9bc01aaaf0
@ -4,6 +4,7 @@ kind: Kustomization
|
|||||||
resources:
|
resources:
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
- vault-serviceaccount.yaml
|
- vault-serviceaccount.yaml
|
||||||
|
- wolf-config-ensure-configmap.yaml
|
||||||
- wolf-api-proxy-configmap.yaml
|
- wolf-api-proxy-configmap.yaml
|
||||||
- wolf-gatekeeper-configmap.yaml
|
- wolf-gatekeeper-configmap.yaml
|
||||||
- wolf-service.yaml
|
- wolf-service.yaml
|
||||||
|
|||||||
144
services/game-stream/wolf-config-ensure-configmap.yaml
Normal file
144
services/game-stream/wolf-config-ensure-configmap.yaml
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
# services/game-stream/wolf-config-ensure-configmap.yaml
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: wolf-config-ensure
|
||||||
|
namespace: game-stream
|
||||||
|
data:
|
||||||
|
wolf_config_ensure.py: |
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
CONFIG_PATH = Path(os.environ.get("WOLF_CFG_FILE", "/etc/wolf/cfg/config.toml"))
|
||||||
|
APP_TITLE = "MECCHA CHAMELEON"
|
||||||
|
APP_ID = "4704690"
|
||||||
|
|
||||||
|
APP_BLOCK = f"""
|
||||||
|
|
||||||
|
[[profiles.apps]]
|
||||||
|
title = "{APP_TITLE}"
|
||||||
|
render_node = "/dev/dri/renderD128"
|
||||||
|
start_audio_server = true
|
||||||
|
start_virtual_compositor = true
|
||||||
|
icon_png_path = "https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/{APP_ID}/capsule_616x353.jpg"
|
||||||
|
[profiles.apps.runner]
|
||||||
|
type = "docker"
|
||||||
|
name = "WolfSteam"
|
||||||
|
image = "ghcr.io/games-on-whales/steam:edge"
|
||||||
|
mounts = []
|
||||||
|
env = [
|
||||||
|
"PROTON_LOG=1",
|
||||||
|
"RUN_SWAY=true",
|
||||||
|
"GOW_REQUIRED_DEVICES=/dev/input/* /dev/dri/* /dev/nvidia*",
|
||||||
|
"STEAM_STARTUP_FLAGS=steam://rungameid/{APP_ID}"
|
||||||
|
]
|
||||||
|
devices = []
|
||||||
|
ports = []
|
||||||
|
base_create_json = \"\"\"
|
||||||
|
{{
|
||||||
|
"HostConfig": {{
|
||||||
|
"IpcMode": "host",
|
||||||
|
"CapAdd": ["SYS_ADMIN", "SYS_NICE", "SYS_PTRACE", "NET_RAW", "MKNOD", "NET_ADMIN"],
|
||||||
|
"SecurityOpt": ["seccomp=unconfined", "apparmor=unconfined"],
|
||||||
|
"Ulimits": [{{"Name":"nofile", "Hard":10240, "Soft":10240}}],
|
||||||
|
"Privileged": false,
|
||||||
|
"DeviceCgroupRules": ["c 13:* rmw", "c 244:* rmw"]
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
\"\"\"
|
||||||
|
""".rstrip()
|
||||||
|
|
||||||
|
PROFILE_HEADER = re.compile(r"(?m)^\s*\[\[profiles\]\]\s*$")
|
||||||
|
EMPTY_PROFILES = re.compile(r"(?m)^\s*profiles\s*=\s*\[\]\s*$\n?")
|
||||||
|
|
||||||
|
|
||||||
|
def _seed_config() -> str:
|
||||||
|
return f"""hostname = "wolf"
|
||||||
|
support_hevc = true
|
||||||
|
config_version = 2
|
||||||
|
uuid = "{uuid.uuid4()}"
|
||||||
|
|
||||||
|
paired_clients = []
|
||||||
|
gstreamer = {{}}
|
||||||
|
|
||||||
|
[[profiles]]
|
||||||
|
id = "moonlight-profile-id"
|
||||||
|
name = "Moonlight"
|
||||||
|
{APP_BLOCK}
|
||||||
|
|
||||||
|
[[profiles]]
|
||||||
|
id = "user"
|
||||||
|
name = "User"
|
||||||
|
{APP_BLOCK}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _has_app(section: str) -> bool:
|
||||||
|
return re.search(rf'(?m)^\s*title\s*=\s*["\']{re.escape(APP_TITLE)}["\']\s*$', section) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_app(text: str) -> str:
|
||||||
|
if not text.strip():
|
||||||
|
return _seed_config()
|
||||||
|
|
||||||
|
starts = [match.start() for match in PROFILE_HEADER.finditer(text)]
|
||||||
|
if not starts:
|
||||||
|
return EMPTY_PROFILES.sub("", text).rstrip() + "\n\n[[profiles]]\nid = \"moonlight-profile-id\"\nname = \"Moonlight\"" + APP_BLOCK + "\n"
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
cursor = 0
|
||||||
|
changed = False
|
||||||
|
for index, start in enumerate(starts):
|
||||||
|
end = starts[index + 1] if index + 1 < len(starts) else len(text)
|
||||||
|
parts.append(text[cursor:start])
|
||||||
|
section = text[start:end]
|
||||||
|
if _has_app(section):
|
||||||
|
parts.append(section)
|
||||||
|
else:
|
||||||
|
parts.append(section.rstrip() + APP_BLOCK + "\n\n")
|
||||||
|
changed = True
|
||||||
|
cursor = end
|
||||||
|
parts.append(text[cursor:])
|
||||||
|
if not changed:
|
||||||
|
return text
|
||||||
|
return "".join(parts).rstrip() + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_toml(text: str) -> None:
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
tomllib.loads(text)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
original = CONFIG_PATH.read_text(encoding="utf-8") if CONFIG_PATH.exists() else ""
|
||||||
|
updated = _ensure_app(original)
|
||||||
|
if updated == original:
|
||||||
|
print(f"{APP_TITLE} already present in {CONFIG_PATH}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
_validate_toml(updated)
|
||||||
|
if CONFIG_PATH.exists():
|
||||||
|
backup = CONFIG_PATH.with_suffix(CONFIG_PATH.suffix + ".pre-meccha-chameleon")
|
||||||
|
backup.write_text(original, encoding="utf-8")
|
||||||
|
|
||||||
|
tmp_path = CONFIG_PATH.with_suffix(CONFIG_PATH.suffix + ".tmp")
|
||||||
|
tmp_path.write_text(updated, encoding="utf-8")
|
||||||
|
tmp_path.replace(CONFIG_PATH)
|
||||||
|
print(f"ensured {APP_TITLE} Steam app {APP_ID} in {CONFIG_PATH}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
raise SystemExit(main())
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"failed to ensure {APP_TITLE}: {exc}", file=sys.stderr)
|
||||||
|
raise
|
||||||
@ -17,6 +17,8 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: wolf
|
app: wolf
|
||||||
atlas.bstein.dev/game-mode: wolf
|
atlas.bstein.dev/game-mode: wolf
|
||||||
|
annotations:
|
||||||
|
game-stream.bstein.dev/wolf-config-revision: "2026-07-13-meccha-chameleon"
|
||||||
spec:
|
spec:
|
||||||
hostNetwork: true
|
hostNetwork: true
|
||||||
dnsPolicy: ClusterFirstWithHostNet
|
dnsPolicy: ClusterFirstWithHostNet
|
||||||
@ -39,6 +41,19 @@ spec:
|
|||||||
mountPath: /wolf-runtime
|
mountPath: /wolf-runtime
|
||||||
- name: wolf-socket
|
- name: wolf-socket
|
||||||
mountPath: /wolf-socket
|
mountPath: /wolf-socket
|
||||||
|
- name: wolf-config-ensure
|
||||||
|
image: ghcr.io/games-on-whales/wolf:stable
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command: ["/usr/bin/python3", "/opt/wolf-config-ensure/wolf_config_ensure.py"]
|
||||||
|
env:
|
||||||
|
- name: WOLF_CFG_FILE
|
||||||
|
value: /etc/wolf/cfg/config.toml
|
||||||
|
volumeMounts:
|
||||||
|
- name: wolf-state
|
||||||
|
mountPath: /etc/wolf
|
||||||
|
- name: wolf-config-ensure
|
||||||
|
mountPath: /opt/wolf-config-ensure
|
||||||
|
readOnly: true
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
kubernetes.io/hostname: titan-24
|
kubernetes.io/hostname: titan-24
|
||||||
tolerations:
|
tolerations:
|
||||||
@ -175,6 +190,10 @@ spec:
|
|||||||
configMap:
|
configMap:
|
||||||
name: wolf-api-proxy
|
name: wolf-api-proxy
|
||||||
defaultMode: 0555
|
defaultMode: 0555
|
||||||
|
- name: wolf-config-ensure
|
||||||
|
configMap:
|
||||||
|
name: wolf-config-ensure
|
||||||
|
defaultMode: 0555
|
||||||
- name: wolfmanager-data
|
- name: wolfmanager-data
|
||||||
hostPath:
|
hostPath:
|
||||||
path: /etc/wolfmanager
|
path: /etc/wolfmanager
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user