Files

152 lines
4.9 KiB
Python

from __future__ import annotations
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from pathlib import Path
import os
import platform
@dataclass(frozen=True)
class PlatformProvider:
name: str
process_supervisor: str
can_manage_daemons: bool
can_write_system_config: bool
state_root: Path
notes: tuple[str, ...] = ()
PLATFORM_NAMES = (
"android-termux",
"linux-systemd",
"linux-user",
"macos-launchd",
"unknown",
)
def detect_platform(
env: Mapping[str, str] | None = None,
*,
system_name: str | None = None,
path_exists: Callable[[Path], bool] | None = None,
) -> PlatformProvider:
environ = env if env is not None else os.environ
system = system_name or platform.system()
exists = path_exists or Path.exists
if _looks_like_termux(environ):
prefix = Path(environ.get("PREFIX", "/data/data/com.termux/files/usr"))
return PlatformProvider(
name="android-termux",
process_supervisor="termux-services",
can_manage_daemons=True,
can_write_system_config=False,
state_root=prefix / "var/lib/ampg",
notes=(
"User-space daemons only.",
"Public ports and battery/background policy remain device-owned.",
),
)
if system == "Linux":
if exists(Path("/run/systemd/system")):
return PlatformProvider(
name="linux-systemd",
process_supervisor="systemd",
can_manage_daemons=True,
can_write_system_config=True,
state_root=Path("/var/lib/ampg"),
notes=("System service management is available after operator approval.",),
)
return PlatformProvider(
name="linux-user",
process_supervisor="user-process",
can_manage_daemons=True,
can_write_system_config=False,
state_root=_home(environ) / ".local/state/ampg",
notes=("Runs AMPG-owned daemons in user space.",),
)
if system == "Darwin":
return PlatformProvider(
name="macos-launchd",
process_supervisor="launchd-user",
can_manage_daemons=True,
can_write_system_config=False,
state_root=_home(environ) / "Library/Application Support/AMPG",
notes=("Uses user LaunchAgents or foreground processes.",),
)
return PlatformProvider(
name="unknown",
process_supervisor="manual",
can_manage_daemons=False,
can_write_system_config=False,
state_root=_home(environ) / ".ampg",
notes=("AMPG can render and plan, but daemon management is disabled.",),
)
def platform_by_name(name: str) -> PlatformProvider:
if name == "android-termux":
return PlatformProvider(
name=name,
process_supervisor="termux-services",
can_manage_daemons=True,
can_write_system_config=False,
state_root=Path("/data/data/com.termux/files/usr/var/lib/ampg"),
notes=("User-space daemons only.",),
)
if name == "linux-systemd":
return PlatformProvider(
name=name,
process_supervisor="systemd",
can_manage_daemons=True,
can_write_system_config=True,
state_root=Path("/var/lib/ampg"),
notes=("System service management is available after operator approval.",),
)
if name == "linux-user":
return PlatformProvider(
name=name,
process_supervisor="user-process",
can_manage_daemons=True,
can_write_system_config=False,
state_root=Path("~/.local/state/ampg").expanduser(),
notes=("Runs AMPG-owned daemons in user space.",),
)
if name == "macos-launchd":
return PlatformProvider(
name=name,
process_supervisor="launchd-user",
can_manage_daemons=True,
can_write_system_config=False,
state_root=Path("~/Library/Application Support/AMPG").expanduser(),
notes=("Uses user LaunchAgents or foreground processes.",),
)
if name == "unknown":
return PlatformProvider(
name=name,
process_supervisor="manual",
can_manage_daemons=False,
can_write_system_config=False,
state_root=Path("~/.ampg").expanduser(),
notes=("Daemon management is disabled.",),
)
raise ValueError(f"unknown platform provider {name!r}")
def _looks_like_termux(env: Mapping[str, str]) -> bool:
prefix = env.get("PREFIX", "")
return (
"com.termux" in prefix
or bool(env.get("TERMUX_VERSION"))
or (bool(env.get("ANDROID_ROOT")) and "com.termux" in env.get("HOME", ""))
)
def _home(env: Mapping[str, str]) -> Path:
return Path(env.get("HOME", "~")).expanduser()