import json
import os
import re
import subprocess
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
HERE = os.path.dirname(os.path.abspath(__file__))
SCRIPT = os.path.join(HERE, "open-mobile.sh")
UP_SCRIPT = os.path.join(HERE, "up.sh")
PORT = int(os.environ.get("HOST_OPEN_PORT", "8091"))
RUN_DIR = os.environ.get("RUN_DIR") or os.path.dirname(HERE)
WORKSPACE = os.environ.get("RUN_WORKSPACE_DIR") or RUN_DIR
PACKAGE_DIR = os.environ.get("RUN_PACKAGE_DIR") or os.path.dirname(HERE)
SERVICE_NAME = re.compile(r"^[a-z0-9][a-z0-9_-]*$")
def parse_open_output(out):
url = None
lines = []
for line in out.splitlines():
if line.startswith("URL="):
url = line[4:].strip()
else:
lines.append(line)
return {"ok": True, "detail": "\n".join(lines).strip(), "url": url}
def run_up(services):
bad = [s for s in services if not SERVICE_NAME.match(s)]
if bad:
return {"ok": False, "detail": f"invalid service name: {bad[0]}"}, 400
if not services:
return {"ok": False, "detail": "no services to start"}, 400
env = os.environ.copy()
env["RUN_UP_BUILD"] = "0"
env.setdefault("RUN_WORKSPACE_DIR", WORKSPACE)
env.setdefault("RUN_PACKAGE_DIR", PACKAGE_DIR)
env.setdefault("RUN_DIR", RUN_DIR)
try:
out = subprocess.check_output(
["bash", UP_SCRIPT, *services],
stderr=subprocess.STDOUT,
text=True,
timeout=int(os.environ.get("HOST_OPEN_TIMEOUT", "3600")),
env=env,
cwd=WORKSPACE,
)
tail = "\n".join(out.splitlines()[-40:]).strip()
return {
"ok": True,
"detail": f"Started {' '.join(services)}",
"output": tail,
}, 200
except subprocess.CalledProcessError as exc:
tail = "\n".join((exc.output or str(exc)).splitlines()[-40:]).strip()
return {"ok": False, "detail": tail or "up failed", "output": tail}, 500
except Exception as exc:
return {"ok": False, "detail": str(exc)}, 500
class Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
def log_message(self, *args):
pass
def _json(self, code, payload):
body = json.dumps(payload).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
def _read_json(self):
length = int(self.headers.get("Content-Length") or 0)
raw = self.rfile.read(length) if length else b"{}"
try:
return json.loads(raw or b"{}")
except ValueError:
return None
def do_OPTIONS(self):
self.send_response(204)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Content-Type")
self.end_headers()
def do_GET(self):
if self.path == "/healthz":
return self._json(
200,
{"ok": True, "run_dir": RUN_DIR, "workspace": WORKSPACE},
)
self._json(404, {"ok": False, "detail": "not found"})
def do_POST(self):
path = self.path.strip("/")
if path == "up":
body = self._read_json()
if body is None:
return self._json(400, {"ok": False, "detail": "invalid JSON"})
services = list(body.get("services") or [])
if body.get("service"):
services = [body["service"]] + services
payload, code = run_up(services)
return self._json(code, payload)
platform = path.split("/")[-1]
if platform not in ("ios", "android", "ios-device", "android-device"):
return self._json(
404,
{
"ok": False,
"detail": "POST /up, /ios, /android, /ios-device or /android-device",
},
)
env = os.environ.copy()
try:
out = subprocess.check_output(
[SCRIPT, platform],
stderr=subprocess.STDOUT,
text=True,
timeout=int(os.environ.get("HOST_OPEN_TIMEOUT", "3600")),
env=env,
)
self._json(200, parse_open_output(out))
except subprocess.CalledProcessError as exc:
self._json(500, {"ok": False, "detail": (exc.output or str(exc)).strip()})
except Exception as exc:
self._json(500, {"ok": False, "detail": str(exc)})
if __name__ == "__main__":
print(f"[host-open] listening on 127.0.0.1:{PORT}", flush=True)
ThreadingHTTPServer(("127.0.0.1", PORT), Handler).serve_forever()