diff --git a/service/forward-auth/service.pkl b/service/forward-auth/service.pkl index dc4a7d57009e9aad8c492f50bf5a8a2871794fd1..7d003455ee7984c493a24b017006d99a0b7abf86 100644 --- a/service/forward-auth/service.pkl +++ b/service/forward-auth/service.pkl @@ -14,7 +14,7 @@ requirements { name = module.meta.name redirectUris { when (isPreview) { "https://\(module.id).\(site.domain)/snow.oauth2/callback" } - when (!isPreview) { for (host in new Listing { "music"; "seedbox"; "ddns"; "redis"; "pg"; "snr"; "rdr"; "logs"; "metrics"; "traces"; "jkt" }) { + when (!isPreview) { for (host in new Listing { "music"; "seedbox"; "ddns"; "redis"; "pg"; "snr"; "rdr"; "logs"; "metrics"; "traces"; "jkt"; "openclaw" }) { "https://\(host).\(site.domain)/snow.oauth2/callback" } } } diff --git a/service/openclaw/Dockerfile.guest b/service/openclaw/Dockerfile.guest new file mode 100644 index 0000000000000000000000000000000000000000..978efed331fbd428a2182a6fe623cc63bf5a8c39 --- /dev/null +++ b/service/openclaw/Dockerfile.guest @@ -0,0 +1,4 @@ +FROM ghcr.io/openclaw/openclaw@sha256:032e25ae4a15aa2e6d2653699833e0965e99f580e679024e31e3ddda375e9bcd +USER root +RUN apt-get update && apt-get install -y --no-install-recommends python3 python3-venv python3-pip git ripgrep curl build-essential && rm -rf /var/lib/apt/lists/* +USER node diff --git a/service/openclaw/install.sh b/service/openclaw/install.sh new file mode 100644 index 0000000000000000000000000000000000000000..e1c9b6a9283da07393860f588c072e1ebe99748b --- /dev/null +++ b/service/openclaw/install.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -e +export http_proxy=http://10.0.2.100:3128 https_proxy=http://10.0.2.100:3128 +export no_proxy=localhost,127.0.0.1,10.0.2.101 +systemctl disable --now systemd-resolved +rm -f /etc/resolv.conf +install -m 644 /opt/openclaw/resolv.conf /etc/resolv.conf +if ! command -v docker >/dev/null; then + apt-get -o Acquire::Retries=10 update + DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io +fi +systemctl daemon-reload +systemctl restart docker +mkdir -p /opt/openclaw/state/workspace +if ! test -f /opt/openclaw/state/openclaw.json; then + cp /opt/openclaw/openclaw.json /opt/openclaw/state/openclaw.json +fi +chown -R 1000:1000 /opt/openclaw/state +chmod 700 /opt/openclaw/state +docker build --build-arg http_proxy --build-arg https_proxy -t openclaw-trial - < /opt/openclaw/Dockerfile +systemctl enable openclaw +systemctl restart openclaw diff --git a/service/openclaw/machine-openclaw.slice b/service/openclaw/machine-openclaw.slice new file mode 100644 index 0000000000000000000000000000000000000000..eb17d2708bfd35600215811897e7ca3376bbc858 --- /dev/null +++ b/service/openclaw/machine-openclaw.slice @@ -0,0 +1,8 @@ +[Unit] +Description=OpenClaw virtual machine +Before=libvirtd.service + +[Slice] +TasksMax=512 +MemoryMax=8G +CPUQuota=400% diff --git a/service/openclaw/nginx.conf b/service/openclaw/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..64be2982f65374df8900eca534d91085885209d8 --- /dev/null +++ b/service/openclaw/nginx.conf @@ -0,0 +1,47 @@ +worker_processes 1; +pid /tmp/nginx.pid; +error_log /dev/stderr; +events { worker_connections 256; } +http { + access_log off; + client_body_temp_path /tmp/client; + proxy_temp_path /tmp/proxy; + fastcgi_temp_path /tmp/fastcgi; + uwsgi_temp_path /tmp/uwsgi; + scgi_temp_path /tmp/scgi; + map $http_upgrade $connection_upgrade { default upgrade; '' close; } + server { + listen 127.0.0.1:18889; + client_max_body_size 32m; + location = /healthz { proxy_pass http://127.0.0.1:18890/healthz; } + location / { + if ($http_x_openclaw_user = '') { return 403; } + proxy_pass http://127.0.0.1:18890; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host openclaw.paperclover.net; + proxy_set_header X-Forwarded-Host openclaw.paperclover.net; + proxy_set_header X-Forwarded-Proto https; + proxy_set_header X-Forwarded-For $http_x_forwarded_for; + proxy_set_header X-OpenClaw-User $http_x_openclaw_user; + proxy_buffering off; + proxy_read_timeout 28800s; + } + } + server { + listen 127.0.0.1:18881; + location /v1/ { + proxy_pass https://ai.paperclover.net; + proxy_ssl_server_name on; + proxy_ssl_verify on; + proxy_ssl_verify_depth 5; + proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; + proxy_set_header Host ai.paperclover.net; + proxy_set_header Authorization "Bearer MODEL_API_KEY"; + proxy_buffering off; + proxy_read_timeout 28800s; + } + location / { return 404; } + } +} diff --git a/service/openclaw/openclaw-install.service b/service/openclaw/openclaw-install.service new file mode 100644 index 0000000000000000000000000000000000000000..960a870c904852c773d27091d97a116d91424c86 --- /dev/null +++ b/service/openclaw/openclaw-install.service @@ -0,0 +1,14 @@ +[Unit] +Description=Prepare OpenClaw trial +After=network-online.target +StartLimitIntervalSec=0 + +[Service] +Type=oneshot +RemainAfterExit=yes +Restart=on-failure +RestartSec=15 +ExecStart=/usr/local/sbin/openclaw-install + +[Install] +WantedBy=multi-user.target diff --git a/service/openclaw/openclaw.json b/service/openclaw/openclaw.json new file mode 100644 index 0000000000000000000000000000000000000000..9b6751b100a79ea6c416c16eccb8a0d16ff6e388 --- /dev/null +++ b/service/openclaw/openclaw.json @@ -0,0 +1,58 @@ +{ + "gateway": { + "mode": "local", + "bind": "lan", + "port": 18789, + "publicOrigin": "https://openclaw.paperclover.net", + "trustedProxies": ["10.0.2.2"], + "auth": { + "mode": "trusted-proxy", + "trustedProxy": { + "userHeader": "x-openclaw-user", + "requiredHeaders": ["x-forwarded-proto", "x-forwarded-host"], + "allowUsers": ["snow", "ai"], + "deviceAutoApprove": {"enabled": true} + }, + "identityScopes": {"snow": ["operator.admin"], "ai": ["operator.admin"]} + }, + "controlUi": {"allowedOrigins": ["https://openclaw.paperclover.net"]} + }, + "agents": { + "defaults": { + "workspace": "/home/node/.openclaw/workspace", + "model": {"primary": ""}, + "thinkingDefault": "medium", + "heartbeat": {"every": "0m"}, + "maxConcurrent": 1, + "sandbox": {"mode": "off"} + } + }, + "models": { + "mode": "replace", + "providers": { + "snowglobe": { + "baseUrl": "http://10.0.2.101:8080/v1", + "apiKey": "vm-inference-proxy", + "api": "openai-completions", + "models": [{ + "id": "", "name": "", "contextWindow": 0, + "reasoning": true, "input": ["text", "image"], "maxTokens": 16384, + "compat": {"toolSchemaProfile": "llamacpp", "supportsReasoningEffort": true} + }] + } + } + }, + "tools": { + "profile": "full", + "elevated": {"enabled": false}, + "exec": {"host": "gateway", "security": "full", "ask": "off"} + }, + "browser": { + "enabled": true, "headless": true, "noSandbox": true, + "ssrfPolicy": {"dangerouslyAllowPrivateNetwork": true}, + "defaultProfile": "openclaw", + "extraArgs": ["--proxy-server=http://10.0.2.100:3128", "--disable-gpu"] + }, + "discovery": {"mdns": {"mode": "off"}}, + "update": {"checkOnStart": false} +} diff --git a/service/openclaw/openclaw.service b/service/openclaw/openclaw.service new file mode 100644 index 0000000000000000000000000000000000000000..f339f42b0a93c7e4e755c96ca2601c93b2ee62f3 --- /dev/null +++ b/service/openclaw/openclaw.service @@ -0,0 +1,14 @@ +[Unit] +Description=OpenClaw trial +Requires=docker.service +After=docker.service network-online.target + +[Service] +Restart=always +RestartSec=5 +ExecStartPre=-/usr/bin/docker rm -f openclaw +ExecStart=/usr/bin/docker run --name openclaw --network host --memory 4g --cpus 3 --pids-limit 512 --cap-drop ALL --security-opt no-new-privileges --shm-size 512m -e HTTP_PROXY=http://10.0.2.100:3128 -e HTTPS_PROXY=http://10.0.2.100:3128 -e http_proxy=http://10.0.2.100:3128 -e https_proxy=http://10.0.2.100:3128 -e NO_PROXY=localhost,127.0.0.1,10.0.2.101 -e no_proxy=localhost,127.0.0.1,10.0.2.101 -e NODE_USE_ENV_PROXY=1 -e TZ=America/Los_Angeles -v /opt/openclaw/state:/home/node/.openclaw openclaw-trial +ExecStop=/usr/bin/docker stop -t 15 openclaw + +[Install] +WantedBy=multi-user.target diff --git a/service/openclaw/prepare.py b/service/openclaw/prepare.py new file mode 100644 index 0000000000000000000000000000000000000000..a5c4d0f1b1b917490246df18432868c2c3739f7c --- /dev/null +++ b/service/openclaw/prepare.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +import glob +import hashlib +import json +import os +from pathlib import Path +import secrets +import shutil +import subprocess +import sys +import urllib.request +import xml.etree.ElementTree as ET + + +data = json.load(sys.stdin) +root = Path(data["hostRoot"]) +if root.name != "openclaw": + raise ValueError("OpenClaw's dedicated VM is production-only") +source = Path(__file__).parent +vm = root / "vm" +vm.mkdir(mode=0o700, exist_ok=True) +cfg = root / "cfg" +cfg.mkdir(mode=0o700, exist_ok=True) +key = json.loads(subprocess.check_output( + ["nomad", "var", "get", "-out=json", "nomad/jobs/local-ai"], + text=True, stderr=subprocess.DEVNULL, +))["Items"]["api_key"] +if not key.isalnum(): + raise ValueError("Invalid model API credential") +nginx = source.joinpath("nginx.conf").read_text().replace("MODEL_API_KEY", key) +pending = cfg / ".nginx.conf.pending" +pending.write_text(nginx) +pending.chmod(0o600) +os.chown(pending, data["uid"], data["uid"]) +pending.replace(cfg / "nginx.conf") +os.chown(cfg, data["uid"], data["uid"]) + +if not (vm / "ssh_key").exists(): + subprocess.run(["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-f", str(vm / "ssh_key")], check=True) + +catalog = json.loads(source.parent.joinpath("local-ai/catalog.json").read_text())["models"][0] +config = json.loads(source.joinpath("openclaw.json").read_text()) +# Browser control needs its own password under trusted-proxy auth. +previous = json.loads((vm / "openclaw.json").read_text()) if (vm / "openclaw.json").exists() else {} +config["gateway"]["auth"]["password"] = previous.get("gateway", {}).get("auth", {}).get("password") or secrets.token_hex(24) +model = config["models"]["providers"]["snowglobe"]["models"][0] +model.update(id=catalog["slug"], name=catalog["display_name"], contextWindow=catalog["context_window"]) +config["agents"]["defaults"]["model"]["primary"] = "snowglobe/" + catalog["slug"] +(vm / "openclaw.json").write_text(json.dumps(config, indent=2) + "\n") + +if not (vm / "disk.qcow2").exists(): + image = vm / "ubuntu.img" + expected = "6a81c37564db9b1ee84e141922625e1d7c5b389b99bb3c572e0243607d5bb4d2" + if not image.exists(): + urllib.request.urlretrieve( + "https://cloud-images.ubuntu.com/noble/20260926/noble-server-cloudimg-amd64.img", image, + ) + with image.open("rb") as stream: + if hashlib.file_digest(stream, "sha256").hexdigest() != expected: + raise ValueError("Ubuntu image checksum mismatch") + subprocess.run(["qemu-img", "convert", "-O", "qcow2", str(image), str(vm / ".disk.pending")], check=True) + subprocess.run(["qemu-img", "resize", str(vm / ".disk.pending"), "32G"], check=True) + + writes = [ + {"path": "/etc/apt/apt.conf.d/90proxy", "content": 'Acquire::http::Proxy "http://10.0.2.100:3128";\nAcquire::https::Proxy "http://10.0.2.100:3128";\n'}, + {"path": "/etc/systemd/system/docker.service.d/proxy.conf", "content": "[Service]\nEnvironment=HTTP_PROXY=http://10.0.2.100:3128 HTTPS_PROXY=http://10.0.2.100:3128 NO_PROXY=localhost,127.0.0.1,10.0.2.101\n"}, + {"path": "/opt/openclaw/openclaw.json", "content": json.dumps(config, indent=2), "permissions": "0600"}, + {"path": "/opt/openclaw/Dockerfile", "content": source.joinpath("Dockerfile.guest").read_text()}, + {"path": "/opt/openclaw/resolv.conf", "content": source.joinpath("resolv.conf").read_text()}, + {"path": "/etc/systemd/system/openclaw.service", "content": source.joinpath("openclaw.service").read_text()}, + {"path": "/usr/local/sbin/openclaw-install", "content": source.joinpath("install.sh").read_text(), "permissions": "0755"}, + {"path": "/etc/systemd/system/openclaw-install.service", "content": source.joinpath("openclaw-install.service").read_text()}, + ] + cloud = { + "hostname": "openclaw", "manage_etc_hosts": True, "ssh_pwauth": False, + "disable_root": True, "users": [{"name": "ocadmin", "lock_passwd": True, + "shell": "/bin/bash", "sudo": "ALL=(ALL) NOPASSWD:ALL", + "ssh_authorized_keys": [(vm / "ssh_key.pub").read_text().strip()]}], + "write_files": writes, + "bootcmd": [["systemctl", "mask", "--now", "snapd.service", "snapd.socket", "snapd.seeded.service", "snapd.autoimport.service"]], + "runcmd": [["systemctl", "daemon-reload"], ["systemctl", "enable", "--now", "openclaw-install"]], + } + (vm / "user-data").write_text("#cloud-config\n" + json.dumps(cloud)) + (vm / "meta-data").write_text("instance-id: openclaw-trial\nlocal-hostname: openclaw\n") + iso = shutil.which("genisoimage") or sorted(glob.glob("/nix/store/*-cdrkit-*/bin/genisoimage"))[0] + subprocess.run([iso, "-quiet", "-output", str(vm / "seed.iso"), "-volid", "cidata", "-joliet", "-rock", str(vm / "user-data"), str(vm / "meta-data")], check=True) + (vm / ".disk.pending").replace(vm / "disk.qcow2") + image.unlink() + +qemu = "http://libvirt.org/schemas/domain/qemu/1.0" +units = Path("/etc/systemd/system.control") +units.mkdir(exist_ok=True) +shutil.copyfile(source / "machine-openclaw.slice", units / "machine-openclaw.slice") +dependency = units / "libvirtd.service.d" +dependency.mkdir(exist_ok=True) +(dependency / "openclaw.conf").write_text("[Unit]\nRequires=machine-openclaw.slice\nAfter=machine-openclaw.slice\n") +subprocess.run(["systemctl", "daemon-reload"], check=True) +subprocess.run(["systemctl", "start", "machine-openclaw.slice"], check=True) +ET.register_namespace("qemu", qemu) +domain = ET.fromstring(f""" + openclaw61444 + /machine/openclaw + hvm + + destroyrestartrestart + + + + + /dev/urandom + +""") +existing = subprocess.run(["virsh", "domuuid", "openclaw"], capture_output=True, text=True) +if existing.returncode == 0: + ET.SubElement(domain, "uuid").text = existing.stdout.strip() +args = ET.SubElement(domain, "{" + qemu + "}commandline") +capabilities = ET.SubElement(domain, "{" + qemu + "}capabilities") +# Libvirt's spawn/setsid filters block the fixed per-connection proxy commands. +ET.SubElement(capabilities, "{" + qemu + "}del", capability="seccomp-sandbox") +for value in [ + "-sandbox", "on,obsolete=deny,elevateprivileges=allow,spawn=allow,resourcecontrol=deny", + "-netdev", "user,id=ocnet,restrict=on,ipv6=off,hostfwd=tcp:127.0.0.1:18890-:18789,hostfwd=tcp:127.0.0.1:18892-:22,guestfwd=tcp:10.0.2.100:3128-cmd:/run/current-system/sw/bin/nc 127.0.0.1 18882,guestfwd=tcp:10.0.2.101:8080-cmd:/run/current-system/sw/bin/nc 127.0.0.1 18881,guestfwd=tcp:10.0.2.102:53-cmd:/run/current-system/sw/bin/nc 1.1.1.1 53", + "-device", "virtio-net-pci,netdev=ocnet,bus=pcie.0,addr=0x10", +]: + ET.SubElement(args, "{" + qemu + "}arg", value=value) +(vm / "domain.xml").write_text(ET.tostring(domain, encoding="unicode")) +subprocess.run(["virsh", "define", str(vm / "domain.xml")], check=True) +subprocess.run(["virsh", "autostart", "openclaw"], check=True) +if subprocess.check_output(["virsh", "domstate", "openclaw"], text=True).strip() != "running": + subprocess.run(["virsh", "start", "openclaw"], check=True) diff --git a/service/openclaw/resolv.conf b/service/openclaw/resolv.conf new file mode 100644 index 0000000000000000000000000000000000000000..e76c65026df3ffd521d75023d418e061ff9ff75d --- /dev/null +++ b/service/openclaw/resolv.conf @@ -0,0 +1,2 @@ +nameserver 10.0.2.102 +options use-vc timeout:3 attempts:2 diff --git a/service/openclaw/service.pkl b/service/openclaw/service.pkl new file mode 100644 index 0000000000000000000000000000000000000000..cf0b7054ae973da1f695a40d9c396a346e9460c2 --- /dev/null +++ b/service/openclaw/service.pkl @@ -0,0 +1,42 @@ +amends "../../config/Service.pkl" + +meta { name = "OpenClaw"; access = "infra-admin" } +enabled = (read?("env:STUDIO_LOCAL_AI") ?? "false") == "true" +stageIsolation = "fresh" +healthyDeadline = "30m" +healthRestartGrace = "30m" +prepare = "prepare.py" +setup = "setup.py" + +containers { + ["gateway"] { + image = "docker.io/library/nginx@sha256:17ae566734b63632e543c907ba74757e0c1a25d812ab9f10a07a6bed98dd199c" + hostNetwork = true + entrypoint = "/usr/sbin/nginx" + args { "-c"; "/cfg/nginx.conf"; "-g"; "daemon off;" } + cpu = 200 + memory = 256 + http { + containerPort = 18889 + hostPort = 18889 + loopback = true + subdomain = "openclaw" + authRole = "infra-admin" + userHeader = "X-OpenClaw-User" + checkPath = "/healthz" + } + volumes { + ["/cfg"] {} + ["/etc/ssl/certs/ca-certificates.crt"] { src = "/var/lib/studio/ca-bundle.crt"; readOnly = true } + } + } + ["internet"] { + image = "docker.io/ubuntu/squid@sha256:8fafd41d6ddceb295d26eea9938321d825ac5351c7e46cf6a8aa5d093b8ed1ce" + hostNetwork = true + entrypoint = "/usr/sbin/squid" + args { "-N"; "-f"; "/config/squid.conf" } + cpu = 200 + memory = 256 + volumes { ["/config/squid.conf"] { config = "squid.conf" } } + } +} diff --git a/service/openclaw/setup.py b/service/openclaw/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..0112a7a81aed61dc94f4af3480278cc95bbfb41a --- /dev/null +++ b/service/openclaw/setup.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import json +from pathlib import Path +import subprocess +import sys + + +data = json.load(sys.stdin) +vm = Path(data["hostRoot"]) / "vm" +source = Path(__file__).parent +options = ["-i", str(vm / "ssh_key"), "-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", + "-o", "ForwardAgent=no", "-o", "StrictHostKeyChecking=accept-new", + "-o", "UserKnownHostsFile=" + str(vm / "known_hosts")] +for file, destination in [(vm / "openclaw.json", "/tmp/openclaw.json"), + (source / "Dockerfile.guest", "/tmp/Dockerfile.guest"), + (source / "openclaw.service", "/tmp/openclaw.service"), + (source / "install.sh", "/tmp/openclaw-install"), + (source / "openclaw-install.service", "/tmp/openclaw-install.service"), + (source / "resolv.conf", "/tmp/resolv.conf")]: + subprocess.run(["scp", "-P", "18892", *options, str(file), "ocadmin@127.0.0.1:" + destination], check=True) +subprocess.run(["ssh", "-p", "18892", *options, "ocadmin@127.0.0.1", "sudo bash -se"], check=True, input=""" +set -e +install -m 755 /tmp/openclaw-install /usr/local/sbin/openclaw-install +install /tmp/openclaw-install.service /etc/systemd/system/openclaw-install.service +install /tmp/resolv.conf /opt/openclaw/resolv.conf +install /tmp/Dockerfile.guest /opt/openclaw/Dockerfile +install -o 1000 -g 1000 -m 600 /tmp/openclaw.json /opt/openclaw/state/openclaw.json +install /tmp/openclaw.service /etc/systemd/system/openclaw.service +systemctl daemon-reload +systemctl enable openclaw-install +systemctl restart openclaw-install +rm /tmp/openclaw.json /tmp/Dockerfile.guest /tmp/openclaw.service /tmp/openclaw-install /tmp/openclaw-install.service /tmp/resolv.conf +""", text=True) diff --git a/service/openclaw/squid.conf b/service/openclaw/squid.conf new file mode 100644 index 0000000000000000000000000000000000000000..a858a703255bacc9dbc2be8ccdda0c493b89c9a3 --- /dev/null +++ b/service/openclaw/squid.conf @@ -0,0 +1,17 @@ +http_port 127.0.0.1:18882 +acl local src 127.0.0.1/32 +acl private dst 0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 224.0.0.0/4 240.0.0.0/4 +acl private dst ::/128 ::1/128 ::ffff:0:0/96 64:ff9b::/96 64:ff9b:1::/48 100::/64 fc00::/7 fe80::/10 ff00::/8 +http_access deny private +http_access allow local +http_access deny all +cache deny all +cache_mem 8 MB +maximum_object_size 0 KB +access_log none +cache_log /dev/stderr +cache_store_log none +pid_filename /tmp/squid.pid +coredump_dir /tmp +netdb_filename none +shutdown_lifetime 1 seconds diff --git a/tools/openclaw-verify.py b/tools/openclaw-verify.py new file mode 100644 index 0000000000000000000000000000000000000000..05f31ec90119f57d4efa79d1c90772e7e08eedd2 --- /dev/null +++ b/tools/openclaw-verify.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import argparse +import shlex +import subprocess + + +parser = argparse.ArgumentParser() +parser.add_argument("--host", default="root@zenith") +task = parser.add_mutually_exclusive_group() +task.add_argument("--prepare-task", action="store_true") +task.add_argument("--check-task", action="store_true") +args = parser.parse_args() +guest = ["ssh", "-i", "/srv/prod/openclaw/vm/ssh_key", "-p", "18892", + "-o", "BatchMode=yes", "-o", "IdentitiesOnly=yes", "-o", "ForwardAgent=no", + "-o", "StrictHostKeyChecking=yes", "-o", "UserKnownHostsFile=/srv/prod/openclaw/vm/known_hosts", + "ocadmin@127.0.0.1", "sudo docker exec -i openclaw python3 -"] +script = ''' +import json, os, pathlib, socket, subprocess, urllib.error, urllib.request +for name in ["NO_PROXY", "no_proxy"]: + os.environ.pop(name, None) +proxy = urllib.request.build_opener(urllib.request.ProxyHandler({"http": "http://10.0.2.100:3128", "https": "http://10.0.2.100:3128"})) +with proxy.open("https://example.com", timeout=20) as response: + assert response.status == 200 +for target in ["127.0.0.1", "10.0.0.1", "169.254.169.254", "192.168.1.1", "[::1]"]: + try: + proxy.open("http://" + target, timeout=5) + raise AssertionError("Private proxy target accepted: " + target) + except urllib.error.HTTPError as error: + assert error.code == 403, (target, error.code) +for target, port in [("10.0.2.2", 4646), ("10.0.0.1", 22), ("1.1.1.1", 443)]: + try: + connection = socket.create_connection((target, port), timeout=2) + except OSError: + continue + connection.close() + raise AssertionError("Direct egress accepted: " + target) +direct = urllib.request.build_opener(urllib.request.ProxyHandler({})) +with direct.open("http://10.0.2.101:8080/v1/models", timeout=20) as response: + assert json.load(response)["data"] +assert socket.getaddrinfo("example.com", 443) +assert not pathlib.Path("/var/run/docker.sock").exists() +assert not pathlib.Path("/srv/clover").exists() +print(json.dumps({"public_internet": True, "private_proxy_denied": 5, "direct_egress_denied": 3, "inference": True, "dns": True, "host_mounts": False}), flush=True) +''' +if args.prepare_task or args.check_task: + script += r''' +directory = pathlib.Path("/home/node/.openclaw/workspace/openclaw-trial-check") +tests = "from median import median\nassert median([3,1,2]) == 2\nassert median([7,1,4,2]) == 3\na=[3,1,2]; median(a); assert a == [3,1,2]\nprint('MEDIAN_OK')\n" +''' + if args.prepare_task: + script += r''' +directory.mkdir(exist_ok=True) +(directory / "median.py").write_text("def median(items):\n items.sort()\n return items[len(items) // 2]\n") +(directory / "check.py").write_text(tests) +print("Prepared coding fixture: " + str(directory)) +''' + else: + script += """ +assert (directory / "check.py").read_text() == tests +subprocess.run(["python3", "check.py"], cwd=directory, check=True) +""" +subprocess.run(["ssh", "-o", "BatchMode=yes", args.host, shlex.join(guest)], + input=script, text=True, check=True)