mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-10-11 03:40:03 +00:00
Some checks failed
CI / Python packages / Build and verify distributions (push) Waiting to run
CI / Python quality / GitHub Actions (push) Waiting to run
CI / Python quality / Pre-commit (push) Waiting to run
CI / Python tests / Unit Tests - py3.11 (push) Waiting to run
CI / Python tests / Unit Tests - py3.12 (push) Waiting to run
CI / Python tests / Unit Tests - py3.13 (push) Waiting to run
CI / Python tests / Unit Tests - py3.14 (push) Waiting to run
CI / Windows / CLI smoke - py3.11 (push) Waiting to run
Deploy / Documentation / Build documentation (push) Waiting to run
Deploy / Documentation / deploy (push) Blocked by required conditions
CI and Release / Docker / Build and test / amd64 (push) Waiting to run
CI and Release / Docker / Build and test / arm64 (push) Waiting to run
CI and Release / Docker / Publish multi-platform tags (push) Blocked by required conditions
Security / CodeQL / Analyze javascript-typescript (push) Waiting to run
Security / CodeQL / Analyze python (push) Waiting to run
CI / ReMe Studio / Studio checks (push) Has been cancelled
* feat(docker): add container deployment and multi-platform release workflow * fix(llm): initialize providers on model access and preserve runtime injection * test(llm): keep rejected model updates independent of credentials
100 lines
4.1 KiB
Python
100 lines
4.1 KiB
Python
"""Container startup and health checks using ReMe's existing CLI contract."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
from urllib.request import ProxyHandler, Request, build_opener
|
|
|
|
HEALTH_STATE_PATH = Path("/tmp/reme-health.json")
|
|
_ENV_OVERRIDES = {
|
|
"REME_CONFIG": "config",
|
|
"REME_WORKSPACE_DIR": "workspace_dir",
|
|
"REME_HOST": "service.host",
|
|
"REME_PORT": "service.port",
|
|
"REME_TIMEZONE": "timezone",
|
|
}
|
|
|
|
|
|
def start_command(arguments: list[str], environment: dict[str, str]) -> list[str]:
|
|
"""Add container defaults only to `start`; explicit CLI arguments win."""
|
|
from reme.config import deep_merge_config, parse_kwargs
|
|
|
|
defaults = parse_kwargs(
|
|
"log_to_file=false",
|
|
*[f"{key}={json.dumps(environment[name])}" for name, key in _ENV_OVERRIDES.items() if environment.get(name)],
|
|
)
|
|
# Docker environment values are strings; the HTTP service expects an integer port.
|
|
if environment.get("REME_PORT"):
|
|
defaults["service"]["port"] = int(environment["REME_PORT"])
|
|
overrides = deep_merge_config(defaults, parse_kwargs(*arguments))
|
|
return ["reme", "start", *[f"{key}={json.dumps(value, ensure_ascii=False)}" for key, value in overrides.items()]]
|
|
|
|
|
|
def write_health_state(command: list[str], state_path: Path = HEALTH_STATE_PATH) -> None:
|
|
"""Record only the effective HTTP address, never credentials or user data."""
|
|
from reme.components.service.cli_service import prepare_start_config
|
|
from reme.config import parse_kwargs
|
|
from reme.constants import REME_DEFAULT_HOST, REME_DEFAULT_PORT, normalize_connect_host
|
|
from reme.plugin import resolve_plugin_runtime
|
|
|
|
config = resolve_plugin_runtime(prepare_start_config(parse_kwargs(*command[2:]))).config
|
|
service = config.get("service") or {}
|
|
if service.get("backend") != "http":
|
|
return
|
|
host = normalize_connect_host(service.get("host") or REME_DEFAULT_HOST)
|
|
if host == "::":
|
|
host = "::1"
|
|
port = int(service.get("port", REME_DEFAULT_PORT))
|
|
if not 1 <= port <= 65535:
|
|
raise ValueError("service.port must be between 1 and 65535")
|
|
host = f"[{host}]" if ":" in host else host
|
|
with state_path.open("w", encoding="utf-8") as state_file:
|
|
os.chmod(state_path, 0o600)
|
|
json.dump({"url": f"http://{host}:{port}/health_check"}, state_file)
|
|
|
|
|
|
def healthcheck(state_path: Path = HEALTH_STATE_PATH) -> int:
|
|
"""Require both a successful Job and a healthy component snapshot."""
|
|
try:
|
|
state = json.loads(state_path.read_text(encoding="utf-8"))
|
|
request = Request(state["url"], data=b"{}", headers={"Content-Type": "application/json"}, method="POST")
|
|
# A deployment's outbound proxy must not intercept its local probe.
|
|
with build_opener(ProxyHandler({})).open(request, timeout=4) as response:
|
|
payload = json.load(response)
|
|
healthy = payload.get("metadata", {}).get("health", {}).get("healthy")
|
|
if payload.get("success") is True and healthy is True:
|
|
return 0
|
|
except (OSError, ValueError, KeyError, TypeError, AttributeError):
|
|
pass
|
|
print("ReMe HTTP health check failed; inspect the container logs and POST /health_check", file=sys.stderr)
|
|
return 1
|
|
|
|
|
|
def main() -> int:
|
|
"""Prepare startup, then replace this process so signals reach ReMe."""
|
|
arguments = sys.argv[1:]
|
|
if arguments == ["--healthcheck"]:
|
|
return healthcheck()
|
|
|
|
HEALTH_STATE_PATH.unlink(missing_ok=True)
|
|
Path(os.environ.get("HOME", "/tmp/reme-home")).mkdir(parents=True, exist_ok=True)
|
|
if not arguments:
|
|
arguments = ["start"]
|
|
start_actions = {"start", "-start", "--start"}
|
|
if len(arguments) >= 2 and arguments[0] == "reme" and arguments[1] in start_actions:
|
|
arguments = arguments[1:]
|
|
if arguments[0] in start_actions:
|
|
from reme.utils import load_env
|
|
|
|
load_env()
|
|
arguments = start_command(arguments[1:], dict(os.environ))
|
|
write_health_state(arguments)
|
|
os.execvp(arguments[0], arguments)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|