from __future__ import annotations import json from functools import lru_cache from pathlib import Path from typing import Any, Iterable REPO_ROOT = Path(__file__).resolve().parent INVENTORY_PATH = REPO_ROOT / "deploy" / "channel-inventory.json" STACK_UNIT = "metin-server.service" DB_UNIT = "metin-db.service" DB_READY_UNIT = "metin-db-ready.service" AUTH_UNIT = "metin-auth.service" @lru_cache(maxsize=1) def load_inventory() -> dict[str, Any]: return json.loads(INVENTORY_PATH.read_text(encoding="utf-8")) def get_db() -> dict[str, Any]: return dict(load_inventory()["db"]) def get_auth() -> dict[str, Any]: return dict(load_inventory()["auth"]) def iter_channels() -> list[dict[str, Any]]: channels = load_inventory()["channels"] return sorted((dict(channel) for channel in channels), key=lambda channel: int(channel["id"])) def get_channel(channel_id: int) -> dict[str, Any]: for channel in iter_channels(): if int(channel["id"]) == int(channel_id): return channel raise KeyError(f"Unknown channel id: {channel_id}") def get_core(channel_id: int, core_id: int) -> dict[str, Any]: channel = get_channel(channel_id) for core in channel["cores"]: if int(core["id"]) == int(core_id): return dict(core) raise KeyError(f"Unknown core {core_id} for channel {channel_id}") def get_channel_ids() -> list[int]: return [int(channel["id"]) for channel in iter_channels()] def get_public_channel_ids( selected_channel_ids: Iterable[int] | None = None, *, client_visible_only: bool = False, ) -> list[int]: selected = None if selected_channel_ids is None else {int(channel_id) for channel_id in selected_channel_ids} result: list[int] = [] for channel in iter_channels(): channel_id = int(channel["id"]) if selected is not None and channel_id not in selected: continue if not channel.get("public"): continue if client_visible_only and not channel.get("client_visible"): continue result.append(channel_id) return result def has_public_channel( selected_channel_ids: Iterable[int] | None = None, *, client_visible_only: bool = False, ) -> bool: return bool(get_public_channel_ids(selected_channel_ids, client_visible_only=client_visible_only)) def get_channel_map() -> dict[int, dict[int, str]]: result: dict[int, dict[int, str]] = {} for channel in iter_channels(): result[int(channel["id"])] = { int(core["id"]): str(core["map_allow"]) for core in channel["cores"] } return result def instance_name(channel_id: int, core_id: int) -> str: return f"channel{int(channel_id)}_core{int(core_id)}" def game_unit(instance: str) -> str: return f"metin-game@{instance}.service" def get_instances(selected_channel_ids: Iterable[int] | None = None) -> list[str]: selected = None if selected_channel_ids is None else {int(channel_id) for channel_id in selected_channel_ids} instances: list[str] = [] for channel in iter_channels(): channel_id = int(channel["id"]) if selected is not None and channel_id not in selected: continue for core in sorted(channel["cores"], key=lambda core: int(core["id"])): instances.append(instance_name(channel_id, int(core["id"]))) return instances def get_game_units(selected_channel_ids: Iterable[int] | None = None) -> list[str]: return [game_unit(instance) for instance in get_instances(selected_channel_ids)] def resolve_selected_channels( channel_limit: int | None = None, explicit_channels: Iterable[int] | None = None, ) -> list[int]: available = set(get_channel_ids()) if explicit_channels: selected = {int(channel_id) for channel_id in explicit_channels} elif channel_limit is None or channel_limit == 0: selected = set(available) else: selected = {channel_id for channel_id in available if channel_id <= channel_limit} for channel in iter_channels(): if channel.get("always_include"): selected.add(int(channel["id"])) unknown = sorted(selected - available) if unknown: raise ValueError(f"Unknown channels requested: {unknown}") return sorted(selected)