@le-space/rootfs 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +17 -3
- package/package.json +1 -1
- package/reference/uc-go-peer/contract.json +58 -0
- package/reference/uc-go-peer/rootfs/Dockerfile.rootfs +24 -0
- package/reference/uc-go-peer/rootfs/build-rootfs-image.sh +94 -0
- package/reference/uc-go-peer/rootfs/build-rootfs.sh +490 -0
- package/reference/uc-go-peer/rootfs/read-rootfs-contract.py +72 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-autotls-refresh.py +144 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-autotls-refresh.service +23 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-bootstrap.service +17 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-bootstrap.sh +118 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-configure.sh +204 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-describe.py +195 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer-setup-server.py +221 -0
- package/reference/uc-go-peer/rootfs/uc-go-peer.service +19 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
ENV_FILE = os.environ.get("ENV_FILE", "/etc/default/uc-go-peer")
|
|
10
|
+
SERVICE_NAME = os.environ.get("SERVICE_NAME", "uc-go-peer.service")
|
|
11
|
+
WAIT_TIMEOUT_SECONDS = int(os.environ.get("DESCRIBE_WAIT_TIMEOUT_SECONDS", "240"))
|
|
12
|
+
WAIT_INTERVAL_SECONDS = float(os.environ.get("DESCRIBE_WAIT_INTERVAL_SECONDS", "2"))
|
|
13
|
+
AUTOTLS_EXTRA_WAIT_SECONDS = int(os.environ.get("DESCRIBE_AUTOTLS_EXTRA_WAIT_SECONDS", "120"))
|
|
14
|
+
|
|
15
|
+
PEER_ID_PATTERNS = [
|
|
16
|
+
re.compile(r"PeerID:\s+(\S+)"),
|
|
17
|
+
re.compile(r"Host created with PeerID:\s+(\S+)"),
|
|
18
|
+
]
|
|
19
|
+
LISTENING_PATTERN = re.compile(r"Listening on:\s+(\S+)/p2p/(\S+)")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_env_file(path: str) -> dict[str, str]:
|
|
23
|
+
values: dict[str, str] = {}
|
|
24
|
+
if not os.path.exists(path):
|
|
25
|
+
return values
|
|
26
|
+
|
|
27
|
+
with open(path, encoding="utf-8") as handle:
|
|
28
|
+
for line in handle:
|
|
29
|
+
stripped = line.strip()
|
|
30
|
+
if not stripped or stripped.startswith("#") or "=" not in stripped:
|
|
31
|
+
continue
|
|
32
|
+
key, value = stripped.split("=", 1)
|
|
33
|
+
values[key.strip()] = value.strip()
|
|
34
|
+
return values
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def dedupe(values: list[str]) -> list[str]:
|
|
38
|
+
seen: set[str] = set()
|
|
39
|
+
result: list[str] = []
|
|
40
|
+
for value in values:
|
|
41
|
+
if value and value not in seen:
|
|
42
|
+
seen.add(value)
|
|
43
|
+
result.append(value)
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def append_peer_id(addr: str, peer_id: str) -> str:
|
|
48
|
+
return addr if "/p2p/" in addr else f"{addr}/p2p/{peer_id}"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_logs() -> tuple[str | None, list[str]]:
|
|
52
|
+
result = subprocess.run(
|
|
53
|
+
["journalctl", "-u", SERVICE_NAME, "-n", "500", "--no-pager"],
|
|
54
|
+
capture_output=True,
|
|
55
|
+
text=True,
|
|
56
|
+
check=False,
|
|
57
|
+
)
|
|
58
|
+
output = result.stdout or ""
|
|
59
|
+
|
|
60
|
+
peer_id = None
|
|
61
|
+
for pattern in PEER_ID_PATTERNS:
|
|
62
|
+
match = pattern.search(output)
|
|
63
|
+
if match:
|
|
64
|
+
peer_id = match.group(1)
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
listening_addrs = []
|
|
68
|
+
for addr, logged_peer_id in LISTENING_PATTERN.findall(output):
|
|
69
|
+
if peer_id is None:
|
|
70
|
+
peer_id = logged_peer_id
|
|
71
|
+
listening_addrs.append(addr)
|
|
72
|
+
|
|
73
|
+
return peer_id, dedupe(listening_addrs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_probe_multiaddrs(env_values: dict[str, str], peer_id: str, listening_addrs: list[str]) -> dict[str, list[str]]:
|
|
77
|
+
announce_addrs = [
|
|
78
|
+
entry.strip()
|
|
79
|
+
for entry in env_values.get("LIBP2P_ANNOUNCE_ADDRS", "").split(",")
|
|
80
|
+
if entry.strip()
|
|
81
|
+
]
|
|
82
|
+
probe_multiaddrs: list[str] = []
|
|
83
|
+
direct_tcp_multiaddrs: list[str] = []
|
|
84
|
+
autotls_multiaddrs: list[str] = []
|
|
85
|
+
proxy_multiaddrs: list[str] = []
|
|
86
|
+
webtransport_multiaddrs: list[str] = []
|
|
87
|
+
webrtc_direct_multiaddrs: list[str] = []
|
|
88
|
+
|
|
89
|
+
for addr in announce_addrs:
|
|
90
|
+
if "/tcp/" in addr and "/tls/" not in addr and "/ws" not in addr:
|
|
91
|
+
direct_tcp_multiaddrs.append(append_peer_id(addr, peer_id))
|
|
92
|
+
|
|
93
|
+
ws_port = env_values.get("EXTERNAL_RELAY_WS_PORT", "").strip()
|
|
94
|
+
for addr in listening_addrs:
|
|
95
|
+
if "/tls/" not in addr or not addr.endswith("/ws"):
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
dns_match = re.search(r"/dns[46]/([^/]+)/tcp/(\d+)/tls/ws$", addr)
|
|
99
|
+
if dns_match:
|
|
100
|
+
host = dns_match.group(1)
|
|
101
|
+
autotls_multiaddrs.append(f"/dns4/{host}/tcp/{ws_port or dns_match.group(2)}/tls/ws/p2p/{peer_id}")
|
|
102
|
+
autotls_multiaddrs.append(f"/dns6/{host}/tcp/{ws_port or dns_match.group(2)}/tls/ws/p2p/{peer_id}")
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
sni_match = re.search(r"/tls/sni/([^/]+)/ws$", addr)
|
|
106
|
+
if sni_match:
|
|
107
|
+
host = sni_match.group(1)
|
|
108
|
+
if ws_port:
|
|
109
|
+
autotls_multiaddrs.append(f"/dns4/{host}/tcp/{ws_port}/tls/ws/p2p/{peer_id}")
|
|
110
|
+
autotls_multiaddrs.append(f"/dns6/{host}/tcp/{ws_port}/tls/ws/p2p/{peer_id}")
|
|
111
|
+
|
|
112
|
+
proxy_hostname = env_values.get("PROXY_HOSTNAME", "").strip()
|
|
113
|
+
if proxy_hostname:
|
|
114
|
+
proxy_multiaddrs.append(f"/dns4/{proxy_hostname}/tcp/443/tls/ws/p2p/{peer_id}")
|
|
115
|
+
proxy_multiaddrs.append(f"/dns6/{proxy_hostname}/tcp/443/tls/ws/p2p/{peer_id}")
|
|
116
|
+
|
|
117
|
+
public_ipv4 = env_values.get("PUBLIC_IPV4", "").strip()
|
|
118
|
+
public_ipv6 = env_values.get("PUBLIC_IPV6", "").strip()
|
|
119
|
+
udp_port = env_values.get("EXTERNAL_RELAY_UDP_PORT", "").strip()
|
|
120
|
+
if udp_port:
|
|
121
|
+
if public_ipv4:
|
|
122
|
+
webtransport_multiaddrs.append(f"/ip4/{public_ipv4}/udp/{udp_port}/quic-v1/webtransport/p2p/{peer_id}")
|
|
123
|
+
webrtc_direct_multiaddrs.append(f"/ip4/{public_ipv4}/udp/{udp_port}/webrtc-direct/p2p/{peer_id}")
|
|
124
|
+
if public_ipv6:
|
|
125
|
+
webtransport_multiaddrs.append(f"/ip6/{public_ipv6}/udp/{udp_port}/quic-v1/webtransport/p2p/{peer_id}")
|
|
126
|
+
webrtc_direct_multiaddrs.append(f"/ip6/{public_ipv6}/udp/{udp_port}/webrtc-direct/p2p/{peer_id}")
|
|
127
|
+
|
|
128
|
+
probe_multiaddrs.extend(direct_tcp_multiaddrs)
|
|
129
|
+
probe_multiaddrs.extend(autotls_multiaddrs)
|
|
130
|
+
probe_multiaddrs.extend(proxy_multiaddrs)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
"direct_tcp_multiaddrs": dedupe(direct_tcp_multiaddrs),
|
|
134
|
+
"autotls_wss_multiaddrs": dedupe(autotls_multiaddrs),
|
|
135
|
+
"proxy_wss_multiaddrs": dedupe(proxy_multiaddrs),
|
|
136
|
+
"webtransport_multiaddrs": dedupe(webtransport_multiaddrs),
|
|
137
|
+
"webrtc_direct_multiaddrs": dedupe(webrtc_direct_multiaddrs),
|
|
138
|
+
"browser_bootstrap_multiaddrs": dedupe(
|
|
139
|
+
autotls_multiaddrs + proxy_multiaddrs + webtransport_multiaddrs + webrtc_direct_multiaddrs
|
|
140
|
+
),
|
|
141
|
+
"probe_multiaddrs": dedupe(probe_multiaddrs),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def main() -> None:
|
|
146
|
+
started_at = time.monotonic()
|
|
147
|
+
deadline = time.monotonic() + WAIT_TIMEOUT_SECONDS
|
|
148
|
+
peer_id = None
|
|
149
|
+
listening_addrs: list[str] = []
|
|
150
|
+
grouped = {
|
|
151
|
+
"direct_tcp_multiaddrs": [],
|
|
152
|
+
"autotls_wss_multiaddrs": [],
|
|
153
|
+
"proxy_wss_multiaddrs": [],
|
|
154
|
+
"webtransport_multiaddrs": [],
|
|
155
|
+
"webrtc_direct_multiaddrs": [],
|
|
156
|
+
"browser_bootstrap_multiaddrs": [],
|
|
157
|
+
"probe_multiaddrs": [],
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
while time.monotonic() < deadline:
|
|
161
|
+
env_values = parse_env_file(ENV_FILE)
|
|
162
|
+
peer_id, listening_addrs = parse_logs()
|
|
163
|
+
if not peer_id:
|
|
164
|
+
time.sleep(WAIT_INTERVAL_SECONDS)
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
grouped = build_probe_multiaddrs(env_values, peer_id, listening_addrs)
|
|
168
|
+
proxy_hostname = env_values.get("PROXY_HOSTNAME", "").strip()
|
|
169
|
+
if grouped["autotls_wss_multiaddrs"]:
|
|
170
|
+
break
|
|
171
|
+
if proxy_hostname and grouped["proxy_wss_multiaddrs"] and time.monotonic() - started_at >= AUTOTLS_EXTRA_WAIT_SECONDS:
|
|
172
|
+
break
|
|
173
|
+
if not proxy_hostname and time.monotonic() - started_at >= AUTOTLS_EXTRA_WAIT_SECONDS:
|
|
174
|
+
break
|
|
175
|
+
time.sleep(WAIT_INTERVAL_SECONDS)
|
|
176
|
+
|
|
177
|
+
if not peer_id:
|
|
178
|
+
raise SystemExit("unable to discover relay peer ID from service logs")
|
|
179
|
+
|
|
180
|
+
env_values = parse_env_file(ENV_FILE)
|
|
181
|
+
payload = {
|
|
182
|
+
"peer_id": peer_id,
|
|
183
|
+
"announce_addrs": [
|
|
184
|
+
entry.strip()
|
|
185
|
+
for entry in env_values.get("LIBP2P_ANNOUNCE_ADDRS", "").split(",")
|
|
186
|
+
if entry.strip()
|
|
187
|
+
],
|
|
188
|
+
"listening_addrs": listening_addrs,
|
|
189
|
+
**grouped,
|
|
190
|
+
}
|
|
191
|
+
print(json.dumps(payload))
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__":
|
|
195
|
+
main()
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import ipaddress
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
9
|
+
from urllib.parse import urlsplit
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
ENV_FILE = os.environ.get("ENV_FILE", "/etc/default/uc-go-peer")
|
|
13
|
+
READY_FILE = os.environ.get("READY_FILE", "/etc/default/uc-go-peer.ready")
|
|
14
|
+
CONFIGURE_SCRIPT = "/usr/local/sbin/uc-go-peer-configure.sh"
|
|
15
|
+
DESCRIBE_SCRIPT = "/usr/local/sbin/uc-go-peer-describe.py"
|
|
16
|
+
BOOTSTRAP_SERVICE = os.environ.get("BOOTSTRAP_SERVICE", "uc-go-peer-bootstrap.service")
|
|
17
|
+
METADATA_FILE = os.environ.get("METADATA_FILE", "/run/uc-go-peer-setup-metadata.json")
|
|
18
|
+
METADATA_ERROR_FILE = os.environ.get("METADATA_ERROR_FILE", "/run/uc-go-peer-setup-metadata.error")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _cors_headers(handler: BaseHTTPRequestHandler) -> None:
|
|
22
|
+
handler.send_header("Access-Control-Allow-Origin", "*")
|
|
23
|
+
handler.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
24
|
+
handler.send_header("Access-Control-Allow-Headers", "content-type")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _validate_port(value: object, field_name: str) -> str:
|
|
28
|
+
if not isinstance(value, int) or value < 1 or value > 65535:
|
|
29
|
+
raise ValueError(f"{field_name} must be an integer TCP/UDP port between 1 and 65535")
|
|
30
|
+
return str(value)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _validate_proxy_hostname(value: object) -> str | None:
|
|
34
|
+
if value is None:
|
|
35
|
+
return None
|
|
36
|
+
if not isinstance(value, str):
|
|
37
|
+
raise ValueError("proxy_url must be a string when provided")
|
|
38
|
+
|
|
39
|
+
candidate = value.strip()
|
|
40
|
+
if not candidate:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
parsed = urlsplit(candidate if "://" in candidate else f"https://{candidate}")
|
|
44
|
+
if not parsed.hostname:
|
|
45
|
+
raise ValueError("proxy_url must include a valid hostname")
|
|
46
|
+
return parsed.hostname
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Handler(BaseHTTPRequestHandler):
|
|
50
|
+
server_version = "UcGoPeerSetup/1.0"
|
|
51
|
+
|
|
52
|
+
def _request_path(self) -> str:
|
|
53
|
+
return urlsplit(self.path).path
|
|
54
|
+
|
|
55
|
+
def _send_json(self, status: int, payload: dict) -> None:
|
|
56
|
+
body = json.dumps(payload).encode("utf-8")
|
|
57
|
+
self.send_response(status)
|
|
58
|
+
_cors_headers(self)
|
|
59
|
+
self.send_header("Content-Type", "application/json")
|
|
60
|
+
self.send_header("Content-Length", str(len(body)))
|
|
61
|
+
self.end_headers()
|
|
62
|
+
self.wfile.write(body)
|
|
63
|
+
|
|
64
|
+
def log_message(self, format: str, *args) -> None: # noqa: A003
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
def do_OPTIONS(self) -> None: # noqa: N802
|
|
68
|
+
self.send_response(204)
|
|
69
|
+
_cors_headers(self)
|
|
70
|
+
self.end_headers()
|
|
71
|
+
|
|
72
|
+
def do_GET(self) -> None: # noqa: N802
|
|
73
|
+
if self._request_path() == "/metadata":
|
|
74
|
+
self._handle_metadata()
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
if self._request_path() not in ("/", "/health"):
|
|
78
|
+
self._send_json(404, {"status": "not-found"})
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
self._send_json(
|
|
82
|
+
200,
|
|
83
|
+
{
|
|
84
|
+
"status": "waiting-for-port-mapping",
|
|
85
|
+
"ready": os.path.exists(READY_FILE),
|
|
86
|
+
"env_file": ENV_FILE,
|
|
87
|
+
"metadata_ready": os.path.exists(METADATA_FILE),
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _handle_metadata(self) -> None:
|
|
92
|
+
if os.path.exists(METADATA_FILE):
|
|
93
|
+
with open(METADATA_FILE, encoding="utf-8") as handle:
|
|
94
|
+
metadata = json.load(handle)
|
|
95
|
+
self._send_json(200, {"status": "ready", "metadata": metadata})
|
|
96
|
+
threading.Thread(target=self.server.shutdown, daemon=True).start() # type: ignore[arg-type]
|
|
97
|
+
threading.Thread(target=_stop_bootstrap_service, daemon=True).start()
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
if os.path.exists(METADATA_ERROR_FILE):
|
|
101
|
+
with open(METADATA_ERROR_FILE, encoding="utf-8") as handle:
|
|
102
|
+
error_message = handle.read().strip() or "metadata generation failed"
|
|
103
|
+
self._send_json(500, {"status": "error", "error": error_message})
|
|
104
|
+
threading.Thread(target=self.server.shutdown, daemon=True).start() # type: ignore[arg-type]
|
|
105
|
+
threading.Thread(target=_stop_bootstrap_service, daemon=True).start()
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
self._send_json(202, {"status": "pending"})
|
|
109
|
+
|
|
110
|
+
def do_POST(self) -> None: # noqa: N802
|
|
111
|
+
if self._request_path() != "/configure":
|
|
112
|
+
self._send_json(404, {"status": "not-found"})
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
content_length = int(self.headers.get("Content-Length", "0"))
|
|
117
|
+
except ValueError:
|
|
118
|
+
self._send_json(400, {"status": "bad-request", "error": "Invalid Content-Length"})
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
payload = json.loads(self.rfile.read(content_length).decode("utf-8") or "{}")
|
|
123
|
+
except json.JSONDecodeError as error:
|
|
124
|
+
self._send_json(400, {"status": "bad-request", "error": f"Invalid JSON body: {error}"})
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
public_ipv4 = str(ipaddress.ip_address(payload.get("public_ipv4")))
|
|
129
|
+
public_ipv6 = payload.get("public_ipv6")
|
|
130
|
+
if public_ipv6 is not None:
|
|
131
|
+
public_ipv6 = str(ipaddress.ip_address(public_ipv6))
|
|
132
|
+
proxy_hostname = _validate_proxy_hostname(payload.get("proxy_url"))
|
|
133
|
+
tcp_port = payload.get("tcp_port")
|
|
134
|
+
ws_port = payload.get("ws_port")
|
|
135
|
+
udp_port = payload.get("udp_port")
|
|
136
|
+
quic_port = payload.get("quic_port")
|
|
137
|
+
webrtc_port = payload.get("webrtc_port")
|
|
138
|
+
args = [
|
|
139
|
+
CONFIGURE_SCRIPT,
|
|
140
|
+
"--public-ipv4",
|
|
141
|
+
public_ipv4,
|
|
142
|
+
]
|
|
143
|
+
if tcp_port is not None:
|
|
144
|
+
args.extend(["--tcp-port", _validate_port(tcp_port, "tcp_port")])
|
|
145
|
+
if ws_port is not None:
|
|
146
|
+
args.extend(["--ws-port", _validate_port(ws_port, "ws_port")])
|
|
147
|
+
if proxy_hostname is not None:
|
|
148
|
+
args.extend(["--proxy-hostname", proxy_hostname])
|
|
149
|
+
if public_ipv6 is not None:
|
|
150
|
+
args.extend(["--public-ipv6", public_ipv6])
|
|
151
|
+
udp_candidate = udp_port if udp_port is not None else quic_port
|
|
152
|
+
if udp_candidate is None:
|
|
153
|
+
udp_candidate = webrtc_port
|
|
154
|
+
if udp_candidate is not None:
|
|
155
|
+
args.extend(["--udp-port", _validate_port(udp_candidate, "udp_port")])
|
|
156
|
+
except ValueError as error:
|
|
157
|
+
self._send_json(400, {"status": "bad-request", "error": str(error)})
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
result = subprocess.run(args, check=True, capture_output=True, text=True)
|
|
162
|
+
except subprocess.CalledProcessError as error:
|
|
163
|
+
self._send_json(
|
|
164
|
+
500,
|
|
165
|
+
{
|
|
166
|
+
"status": "error",
|
|
167
|
+
"error": error.stderr.strip() or error.stdout.strip() or str(error),
|
|
168
|
+
},
|
|
169
|
+
)
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
_clear_metadata_state()
|
|
173
|
+
threading.Thread(target=_generate_metadata_files, daemon=True).start()
|
|
174
|
+
|
|
175
|
+
self._send_json(
|
|
176
|
+
200,
|
|
177
|
+
{
|
|
178
|
+
"status": "configured",
|
|
179
|
+
"stdout": result.stdout.strip(),
|
|
180
|
+
"metadata_pending": True,
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _stop_bootstrap_service() -> None:
|
|
186
|
+
# Give the HTTP response a brief head start, then stop the temporary setup service.
|
|
187
|
+
time.sleep(1)
|
|
188
|
+
subprocess.run(["systemctl", "stop", BOOTSTRAP_SERVICE], check=False)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _clear_metadata_state() -> None:
|
|
192
|
+
for path in (METADATA_FILE, METADATA_ERROR_FILE):
|
|
193
|
+
try:
|
|
194
|
+
os.remove(path)
|
|
195
|
+
except FileNotFoundError:
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _generate_metadata_files() -> None:
|
|
200
|
+
try:
|
|
201
|
+
describe = subprocess.run(
|
|
202
|
+
[DESCRIBE_SCRIPT],
|
|
203
|
+
check=True,
|
|
204
|
+
capture_output=True,
|
|
205
|
+
text=True,
|
|
206
|
+
)
|
|
207
|
+
payload = json.loads(describe.stdout.strip() or "{}")
|
|
208
|
+
with open(METADATA_FILE, "w", encoding="utf-8") as handle:
|
|
209
|
+
json.dump(payload, handle)
|
|
210
|
+
except (subprocess.CalledProcessError, json.JSONDecodeError) as error:
|
|
211
|
+
with open(METADATA_ERROR_FILE, "w", encoding="utf-8") as handle:
|
|
212
|
+
handle.write(str(error))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def main() -> None:
|
|
216
|
+
server = ThreadingHTTPServer(("0.0.0.0", 80), Handler)
|
|
217
|
+
server.serve_forever()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
if __name__ == "__main__":
|
|
221
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=universal-connectivity go-peer relay node
|
|
3
|
+
Wants=network-online.target
|
|
4
|
+
After=network-online.target
|
|
5
|
+
ConditionPathExists=/etc/default/uc-go-peer.ready
|
|
6
|
+
|
|
7
|
+
[Service]
|
|
8
|
+
Type=simple
|
|
9
|
+
User=uc-go-peer
|
|
10
|
+
Group=uc-go-peer
|
|
11
|
+
WorkingDirectory=/var/lib/uc-go-peer
|
|
12
|
+
EnvironmentFile=-/etc/default/uc-go-peer
|
|
13
|
+
ExecStart=/bin/sh -lc 'exec /usr/local/bin/universal-chat-go -headless -identity "${GO_PEER_IDENTITY_PATH:-/var/lib/uc-go-peer/identity.key}" -port "${GO_PEER_TCP_PORT:-9095}" -ws-port "${GO_PEER_WS_PORT:-9096}" -wss-port "${GO_PEER_WSS_PORT:-9097}"'
|
|
14
|
+
Restart=always
|
|
15
|
+
RestartSec=5
|
|
16
|
+
NoNewPrivileges=true
|
|
17
|
+
|
|
18
|
+
[Install]
|
|
19
|
+
WantedBy=multi-user.target
|