e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
#!/usr/bin/env python
|
|
"""Run Docker Compose with port mappings rendered from JSON settings.
|
|
|
|
Docker Compose cannot read ``data/user/settings/system.json`` directly for
|
|
host port interpolation. This wrapper renders a tiny compose env file from the
|
|
JSON settings and then invokes ``docker compose --env-file``. It intentionally
|
|
does not read or migrate the project-root ``.env`` file.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from typing import Any
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
SETTINGS_DIR = PROJECT_ROOT / "data" / "user" / "settings"
|
|
DOCKER_ENV_PATH = SETTINGS_DIR / "docker.env"
|
|
|
|
DEFAULT_BACKEND_PORT = 8001
|
|
DEFAULT_FRONTEND_PORT = 3782
|
|
DEFAULT_POCKETBASE_PORT = 8090
|
|
|
|
|
|
def _read_json_object(path: Path) -> dict[str, Any]:
|
|
try:
|
|
loaded = json.loads(path.read_text(encoding="utf-8"))
|
|
except Exception:
|
|
return {}
|
|
return loaded if isinstance(loaded, dict) else {}
|
|
|
|
|
|
def _coerce_port(value: Any, default: int) -> int:
|
|
try:
|
|
port = int(str(value).strip())
|
|
except (TypeError, ValueError):
|
|
return default
|
|
return port if 1 <= port <= 65535 else default
|
|
|
|
|
|
def render_docker_env(
|
|
settings_dir: Path = SETTINGS_DIR,
|
|
output_path: Path = DOCKER_ENV_PATH,
|
|
) -> dict[str, str]:
|
|
"""Render compose interpolation vars from JSON settings only."""
|
|
system = _read_json_object(settings_dir / "system.json")
|
|
integrations = _read_json_object(settings_dir / "integrations.json")
|
|
values = {
|
|
"DEEPTUTOR_DOCKER_BACKEND_PORT": str(
|
|
_coerce_port(system.get("backend_port"), DEFAULT_BACKEND_PORT)
|
|
),
|
|
"DEEPTUTOR_DOCKER_FRONTEND_PORT": str(
|
|
_coerce_port(system.get("frontend_port"), DEFAULT_FRONTEND_PORT)
|
|
),
|
|
"DEEPTUTOR_DOCKER_POCKETBASE_PORT": str(
|
|
_coerce_port(integrations.get("pocketbase_port"), DEFAULT_POCKETBASE_PORT)
|
|
),
|
|
}
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
lines = [
|
|
"# Auto-generated by scripts/docker_compose.py from data/user/settings/*.json.",
|
|
"# Do not edit manually; update system.json/integrations.json instead.",
|
|
]
|
|
lines.extend(f"{key}={value}" for key, value in values.items())
|
|
output_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
return values
|
|
|
|
|
|
def _compose_command(args: list[str]) -> list[str]:
|
|
docker = shutil.which("docker")
|
|
if not docker:
|
|
raise SystemExit("docker was not found on PATH")
|
|
return [docker, "compose", "--env-file", str(DOCKER_ENV_PATH), *args]
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
args = list(argv if argv is not None else sys.argv[1:])
|
|
if not args:
|
|
args = ["up", "-d"]
|
|
|
|
values = render_docker_env()
|
|
print(
|
|
"Docker settings: "
|
|
f"backend={values['DEEPTUTOR_DOCKER_BACKEND_PORT']} "
|
|
f"frontend={values['DEEPTUTOR_DOCKER_FRONTEND_PORT']} "
|
|
f"pocketbase={values['DEEPTUTOR_DOCKER_POCKETBASE_PORT']}",
|
|
file=sys.stderr,
|
|
)
|
|
|
|
env = os.environ.copy()
|
|
# Keep Docker execution detached from host process overrides.
|
|
for key in (
|
|
"BACKEND_PORT",
|
|
"FRONTEND_PORT",
|
|
"POCKETBASE_PORT",
|
|
"AUTH_ENABLED",
|
|
"POCKETBASE_URL",
|
|
"NEXT_PUBLIC_API_BASE",
|
|
"NEXT_PUBLIC_API_BASE_EXTERNAL",
|
|
):
|
|
env.pop(key, None)
|
|
|
|
result = subprocess.run(_compose_command(args), cwd=str(PROJECT_ROOT), env=env, check=False)
|
|
return int(result.returncode)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|