-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathremote-setup-server.py
More file actions
116 lines (98 loc) · 3.78 KB
/
Copy pathremote-setup-server.py
File metadata and controls
116 lines (98 loc) · 3.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#!/usr/bin/env python3
"""Ephemeral HTTP server for remote device setup approval.
Flow:
1. Operator runs `setup-claude-remote` (interactive terminal)
2. Remote device curls http://<host>:4141/setup (Caddy proxies non-browser to here)
3. Operator sees request details and approves/denies
4. Approved requests receive the setup script with token embedded
5. Server exits after serving one setup script
"""
import http.server
import os
import socket
import sys
import textwrap
import threading
PORT = int(os.environ.get("SETUP_PORT", "4143"))
PROXY_AUTH_TOKEN = os.environ.get("PROXY_AUTH_TOKEN", "")
PROXY_HOST = os.environ.get("PROXY_HOST", "127.0.0.1:4141")
DEVTUNNEL_ID = os.environ.get("DEVTUNNEL_ID", "")
DEVTUNNEL_URL = os.environ.get("DEVTUNNEL_URL", "")
SETUP_SCRIPT_PATH = "/etc/caddy/remote-setup.sh"
class SetupHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self._serve_script()
def _serve_script(self):
"""Approval flow — serve setup script after operator approval."""
remote_ip = self.headers.get("X-Forwarded-For", self.client_address[0])
user_agent = self.headers.get("User-Agent", "unknown")
try:
remote_hostname = socket.gethostbyaddr(remote_ip)[0]
except (socket.herror, socket.gaierror):
remote_hostname = None
print(f"\n{'='*50}")
if remote_hostname:
print(f" Setup request from: {remote_hostname} ({remote_ip})")
else:
print(f" Setup request from: {remote_ip}")
print(f" User-Agent: {user_agent}")
print(f"{'='*50}")
try:
answer = input(" Approve? [y/N] ").strip().lower()
except (EOFError, KeyboardInterrupt):
answer = ""
if answer == "y":
print(f" Approved.")
try:
with open(SETUP_SCRIPT_PATH, "r") as f:
script = f.read()
script = script.replace("{{PROXY_HOST}}", PROXY_HOST)
script = script.replace("{{AUTH_TOKEN}}", PROXY_AUTH_TOKEN)
script = script.replace("{{DEVTUNNEL_ID}}", DEVTUNNEL_ID)
except FileNotFoundError:
script = "#!/bin/sh\necho 'Error: setup script not found'\nexit 1\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(script.encode())
else:
print(f" Denied.")
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(textwrap.dedent("""\
#!/bin/sh
echo ""
echo "Setup request was denied."
echo ""
exit 1
""").encode())
# Exit after one script request
threading.Thread(target=self.server.shutdown).start()
def log_message(self, format, *args):
pass
def main():
if not PROXY_AUTH_TOKEN:
print("Error: PROXY_AUTH_TOKEN not set", file=sys.stderr)
sys.exit(1)
server = http.server.HTTPServer(("0.0.0.0", PORT), SetupHandler)
print(f"Remote setup server running on port {PORT}")
print(f"")
if DEVTUNNEL_URL:
print(f"On remote device, open the setup page:")
print(f" {DEVTUNNEL_URL}/setup")
else:
print(f"On remote device, open the setup page:")
print(f" http://{PROXY_HOST}/setup")
print(f"")
print(f"Then click Download and approve below when prompted.")
print(f"")
print(f"Waiting for request... (Ctrl+C to cancel)")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\nCancelled.")
finally:
server.server_close()
if __name__ == "__main__":
main()