Files
gitlab-instance-0a899031_sh/d2ypp2/tools/render_neko_compose.py
2026-05-28 15:59:03 -05:00

142 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Render a dynamic Neko docker-compose file from the current environment."""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
def env(name: str, default: str) -> str:
value = os.environ.get(name)
return value if value not in {None, ""} else default
def env_enabled(name: str, default: bool = False) -> bool:
raw = os.environ.get(name)
if raw in {None, ""}:
return default
return raw.strip().lower() in {"1", "true", "yes", "on", "enable", "enabled"}
def q(value: str) -> str:
return json.dumps(str(value), ensure_ascii=False)
def instance_value(prefix: str, idx: int, default: str) -> str:
return env(f"{prefix}_{idx}", default)
def default_udp_range(idx: int) -> str:
if idx == 1:
return "52000-52099"
if idx == 2:
return "52101-52199"
if idx == 3:
return "52201-52299"
start = 52400 + ((idx - 4) * 100)
return f"{start}-{start + 99}"
def render(count: int) -> str:
image = env("NEKO_IMAGE", "ghcr.io/m1k1o/neko/chromium:3.0.0")
shm = env("NEKO_SHM_SIZE", "512mb")
screen = env("NEKO_DESKTOP_SCREEN", "1280x720@15")
user_pass = env("NEKO_USER_PASS", "12345678")
admin_pass = env("NEKO_ADMIN_PASS", "12345678")
icelite = env("NEKO_WEBRTC_ICELITE", "0")
nat1to1 = env("NEKO_WEBRTC_NAT1TO1", "")
max_fps = env("NEKO_MAX_FPS", "15")
video_bitrate = env("NEKO_VIDEO_BITRATE", "1200")
plugins_enabled = env("NEKO_PLUGINS_ENABLED", "true")
start_url = env("NEKO_START_URL", "https://accounts.google.com/ServiceLogin?continue=https://www.youtube.com/")
render_mode = env("NEKO_BROWSER_RENDER_MODE", "auto")
supervisor_conf = env("NEKO_BROWSER_SUPERVISOR_CONF", "chromium.conf")
base_profile = env("NEKO_PROFILE_HOST_DIR", str(Path.cwd() / "data" / "chromium-profile-v2"))
http_base = int(env("NEKO_HTTP_PORT", "9200"))
mux_base = int(env("NEKO_WEBRTC_TCPMUX", "52300"))
managed_policies = env_enabled("NEKO_MANAGED_POLICIES", True)
external_extensions = env_enabled("NEKO_EXTERNAL_EXTENSIONS", False)
policy_dir = "./policies" if managed_policies else "./no-managed-policies"
lines = [
"# Generated by tools/render_neko_compose.py - do not hand-edit",
"services:",
]
for idx in range(1, count + 1):
http_port = instance_value("NEKO_HTTP_PORT", idx, str(http_base + idx - 1))
tcp_mux = instance_value("NEKO_WEBRTC_TCPMUX", idx, str(mux_base + idx - 1))
udp_mux = instance_value("NEKO_WEBRTC_UDPMUX", idx, str(mux_base + idx - 1))
udp_range = instance_value("NEKO_WEBRTC_UDP_RANGE", idx, default_udp_range(idx))
epr = instance_value("NEKO_WEBRTC_EPR", idx, udp_range)
profile = instance_value("NEKO_PROFILE_HOST_DIR", idx, base_profile if idx == 1 else f"{base_profile}-{idx}")
lines.extend(
[
f" neko{idx}:",
f" image: {q(image)}",
" restart: unless-stopped",
f" shm_size: {q(shm)}",
" cap_add:",
" - SYS_ADMIN",
f" container_name: live-neko-{idx}",
" environment:",
' DISPLAY: ":99.0"',
f" NEKO_DESKTOP_SCREEN: {q(screen)}",
' NEKO_SERVER_BIND: ":8080"',
' NEKO_MEMBER_PROVIDER: "multiuser"',
f" NEKO_MEMBER_MULTIUSER_USER_PASSWORD: {q(user_pass)}",
f" NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD: {q(admin_pass)}",
f" NEKO_WEBRTC_ICELITE: {q(icelite)}",
f" NEKO_WEBRTC_NAT1TO1: {q(nat1to1)}",
f" NEKO_MAX_FPS: {q(max_fps)}",
f" NEKO_VIDEO_BITRATE: {q(video_bitrate)}",
f" NEKO_PLUGINS_ENABLED: {q(plugins_enabled)}",
' NEKO_PLUGINS_DIR: "/etc/neko/plugins/"',
f" NEKO_START_URL: {q(start_url)}",
f" NEKO_BROWSER_RENDER_MODE: {q(render_mode)}",
f" NEKO_MANAGED_POLICIES: {q('1' if managed_policies else '0')}",
f" NEKO_WEBRTC_EPR: {q(epr)}",
f" NEKO_WEBRTC_TCPMUX: {q(tcp_mux)}",
f" NEKO_WEBRTC_UDPMUX: {q(udp_mux)}",
" ports:",
f" - {q(f'{http_port}:8080')}",
f" - {q(f'{tcp_mux}:{tcp_mux}/tcp')}",
f" - {q(f'{udp_mux}:{udp_mux}/udp')}",
f" - {q(f'{udp_range}:{udp_range}/udp')}",
" volumes:",
f" - {q(f'{profile}:/home/neko/.config/chromium')}",
f" - {q(f'{profile}:/home/neko/.config/google-chrome')}",
f" - {q(f'{policy_dir}:/etc/chromium/policies/managed:ro')}",
f" - {q(f'{policy_dir}:/etc/chromium-browser/policies/managed:ro')}",
f" - {q(f'{policy_dir}:/etc/opt/chrome/policies/managed:ro')}",
' - "./tampermonkey-seed:/etc/neko/tampermonkey-seed:ro"',
' - "./provisioning:/var/www/provisioning:ro"',
' - "./browser-launch.sh:/etc/neko/browser-launch.sh:ro"',
f" - {q(f'./{supervisor_conf}:/etc/neko/supervisord/{supervisor_conf}:ro')}",
]
)
if external_extensions:
lines[-3:-3] = [
' - "./external-extensions:/opt/google/chrome/extensions:ro"',
' - "./external-extensions:/usr/share/chromium/extensions:ro"',
' - "./external-extensions:/usr/share/chromium-browser/extensions:ro"',
]
return "\n".join(lines) + "\n"
def main() -> int:
parser = argparse.ArgumentParser(description="Render Neko docker compose")
parser.add_argument("--count", type=int, required=True)
parser.add_argument("--output", required=True)
args = parser.parse_args()
count = max(2, min(64, args.count))
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(render(count), encoding="utf-8")
return 0
if __name__ == "__main__":
raise SystemExit(main())