#!/usr/bin/env python3 """Test the shipped archive in isolation; never print generated credentials.""" import http.cookiejar import json import os from pathlib import Path import socket import stat import subprocess import sys import tarfile import tempfile import threading import time import urllib.request def free_port(): with socket.socket() as sock: sock.bind(("127.0.0.1", 0)) return sock.getsockname()[1] with tempfile.TemporaryDirectory(prefix="mirror package ") as temporary: root = Path(temporary) with tarfile.open(sys.argv[1]) as archive: archive.extractall(root, filter="data") package = root / "mirror-tool-linux-x64" env = os.environ.copy() env.pop("MIRROR_PASSWORD", None) subprocess.run([str(package / "run.sh"), "check"], cwd=root, env=env, check=True) password_path = package / "data/panel-password" password = password_path.read_text() assert len(password) == 48 assert stat.S_IMODE(password_path.stat().st_mode) == 0o600 config = package / "mirror.toml" web_port = free_port() config.write_text(config.read_text().replace("127.0.0.1:6700", f"127.0.0.1:{web_port}")) original_config = config.read_bytes() base = f"http://127.0.0.1:{web_port}" upstream = socket.socket() upstream.bind(("127.0.0.1", 0)) upstream.listen() upstream.settimeout(15) received = bytearray() def echo(): connection, _ = upstream.accept() with connection: connection.settimeout(10) while data := connection.recv(65536): received.extend(data) connection.sendall(data) thread = threading.Thread(target=echo, daemon=True) thread.start() proxy_port = free_port() for restart in (False, True): with (root / "server.log").open("wb") as log: process = subprocess.Popen([str(package / "run.sh")], cwd=root, env=env, stdout=log, stderr=log) try: opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar())) for _ in range(100): if process.poll() is not None: raise RuntimeError("packaged process exited during startup") try: with opener.open(base + "/", timeout=1) as response: assert b"html" in response.read().lower() break except OSError: time.sleep(0.1) else: raise RuntimeError("packaged server did not become ready") def request(path, value=None, method=None, csrf=None): headers = {"Content-Type": "application/json"} if csrf: headers["x-csrf-token"] = csrf req = urllib.request.Request(base + path, data=None if value is None else json.dumps(value).encode(), headers=headers, method=method) with opener.open(req, timeout=3) as response: return json.load(response) csrf = request("/api/login", {"password": password})["csrf"] if not restart: snapshot = request("/api/snapshot") assert snapshot["teams"] == [] and snapshot["services"] == [] assert snapshot["flag_protection"] is None request("/api/teams/local", {"id": "local", "name": "Local", "address": "127.0.0.1"}, "PUT", csrf) service = {"id": "echo", "name": "Echo", "listen": f"127.0.0.1:{proxy_port}", "upstream_port": upstream.getsockname()[1], "primary": "local", "replicas": [], "enabled": True, "auto_switch": False} request("/api/services/echo", service, "PUT", csrf) payload = bytes(range(256)) * 1024 with socket.create_connection(("127.0.0.1", proxy_port), timeout=5) as connection: connection.sendall(payload) connection.shutdown(socket.SHUT_WR) reply = bytearray() while part := connection.recv(65536): reply.extend(part) thread.join(5) assert not thread.is_alive() assert reply == received == payload snapshot = request("/api/snapshot") assert snapshot["services"][0]["flag_protection"] is False assert snapshot["services"][0]["enabled"] is True assert password_path.read_text() == password assert config.read_bytes() == original_config finally: process.terminate() try: process.wait(timeout=20) except subprocess.TimeoutExpired: process.kill() process.wait() raise assert process.returncode == 0 upstream.close() print("Package OK: first start, private password, empty topology, login, API persistence/restart, exact 256KiB TCP half-close, flag substitution off.")