(args: argparse.Namespace)
| 268 | |
| 269 | |
| 270 | def detect_environment(args: argparse.Namespace) -> DeploymentPlan: |
| 271 | if platform.system() not in ("Linux", "Darwin"): |
| 272 | fatal("environment", "This installer only supports Linux and macOS.") |
| 273 | |
| 274 | machine = platform.machine() |
| 275 | if ARCH_MAP.get(machine) is None: |
| 276 | fatal("environment", f"Unsupported architecture: {machine}") |
| 277 | |
| 278 | docker_ok = has_docker() |
| 279 | compose_ok = docker_ok and has_docker_compose() |
| 280 | systemd_ok = has_systemd() and can_write_systemd() |
| 281 | |
| 282 | info(f"Docker available: {'yes' if docker_ok else 'no'}") |
| 283 | info(f"Docker Compose v2: {'yes' if compose_ok else 'no'}") |
| 284 | info(f"systemd writable: {'yes' if systemd_ok else 'no'}") |
| 285 | |
| 286 | # Console start mode |
| 287 | if args.console_start == "auto": |
| 288 | if compose_ok: |
| 289 | console_start = "docker" |
| 290 | elif systemd_ok: |
| 291 | console_start = "systemd" |
| 292 | else: |
| 293 | console_start = "foreground" |
| 294 | else: |
| 295 | console_start = args.console_start |
| 296 | if console_start == "docker" and not compose_ok: |
| 297 | fatal("environment", "--console-start=docker requires Docker and Docker Compose v2.") |
| 298 | if console_start == "systemd" and not systemd_ok: |
| 299 | fatal("environment", "--console-start=systemd requires systemd with write permission.") |
| 300 | |
| 301 | # Worker runtime |
| 302 | if args.worker_runtime == "auto": |
| 303 | worker_runtime = "docker" if docker_ok else "boxlite" |
| 304 | else: |
| 305 | worker_runtime = args.worker_runtime |
| 306 | if worker_runtime == "docker" and not docker_ok: |
| 307 | fatal("environment", "--worker-runtime=docker requires Docker.") |
| 308 | |
| 309 | if platform.system() == "Darwin" and worker_runtime == "boxlite" and ARCH_MAP.get(machine) == "amd64": |
| 310 | fatal("environment", "worker-boxlite is not available for macOS amd64. Use --worker-runtime=docker.") |
| 311 | |
| 312 | # Worker start mode |
| 313 | if args.worker_start == "auto": |
| 314 | worker_start = "systemd" if systemd_ok else "foreground" |
| 315 | else: |
| 316 | worker_start = args.worker_start |
| 317 | if worker_start == "systemd" and not systemd_ok: |
| 318 | fatal("environment", "--worker-start=systemd requires systemd with write permission.") |
| 319 | |
| 320 | # Constraint: if any component is foreground, all must be foreground. |
| 321 | # Mixed lifecycle (e.g. docker compose + foreground worker) adds complexity |
| 322 | # without benefit — go all-foreground instead. |
| 323 | if worker_start == "foreground" and console_start != "foreground": |
| 324 | console_start = "foreground" |
| 325 | info("Note: console forced to foreground (worker is foreground)") |
| 326 | if console_start == "foreground" and worker_start != "foreground": |
| 327 | worker_start = "foreground" |
no test coverage detected