@le-space/rootfs 0.3.11 → 0.3.12
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/package.json
CHANGED
|
@@ -74,7 +74,14 @@ https://${proxy_hostname} {
|
|
|
74
74
|
disable_http_challenge
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
|
|
78
|
+
handle_path /bootstrap/* {
|
|
79
|
+
reverse_proxy http://127.0.0.1:80
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
handle {
|
|
83
|
+
reverse_proxy http://127.0.0.1:${WS_BACKEND_PORT}
|
|
84
|
+
}
|
|
78
85
|
}
|
|
79
86
|
EOF
|
|
80
87
|
}
|
|
@@ -6,7 +6,9 @@ import subprocess
|
|
|
6
6
|
import threading
|
|
7
7
|
import time
|
|
8
8
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
9
|
-
from urllib.
|
|
9
|
+
from urllib.error import HTTPError, URLError
|
|
10
|
+
from urllib.parse import urlencode, urlsplit
|
|
11
|
+
from urllib.request import urlopen
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
ENV_FILE = os.environ.get("ENV_FILE", "/etc/default/uc-go-peer")
|
|
@@ -16,6 +18,12 @@ DESCRIBE_SCRIPT = "/usr/local/sbin/uc-go-peer-describe.py"
|
|
|
16
18
|
BOOTSTRAP_SERVICE = os.environ.get("BOOTSTRAP_SERVICE", "uc-go-peer-bootstrap.service")
|
|
17
19
|
METADATA_FILE = os.environ.get("METADATA_FILE", "/run/uc-go-peer-setup-metadata.json")
|
|
18
20
|
METADATA_ERROR_FILE = os.environ.get("METADATA_ERROR_FILE", "/run/uc-go-peer-setup-metadata.error")
|
|
21
|
+
AUTHORIZED_KEYS_FILE = os.environ.get("AUTHORIZED_KEYS_FILE", "/root/.ssh/authorized_keys")
|
|
22
|
+
ALEPH_API_HOST = os.environ.get("ALEPH_API_HOST", "https://api2.aleph.im")
|
|
23
|
+
BOOTSTRAP_CONFIG_KEY = os.environ.get("BOOTSTRAP_CONFIG_KEY", "vm-bootstrap-config")
|
|
24
|
+
BOOTSTRAP_CONFIG_POLL_SECONDS = float(os.environ.get("BOOTSTRAP_CONFIG_POLL_SECONDS", "5"))
|
|
25
|
+
|
|
26
|
+
CONFIGURE_LOCK = threading.Lock()
|
|
19
27
|
|
|
20
28
|
|
|
21
29
|
def _cors_headers(handler: BaseHTTPRequestHandler) -> None:
|
|
@@ -46,6 +54,142 @@ def _validate_proxy_hostname(value: object) -> str | None:
|
|
|
46
54
|
return parsed.hostname
|
|
47
55
|
|
|
48
56
|
|
|
57
|
+
def _extract_guest_bootstrap_locator() -> tuple[str | None, str | None]:
|
|
58
|
+
try:
|
|
59
|
+
with open(AUTHORIZED_KEYS_FILE, encoding="utf-8") as handle:
|
|
60
|
+
for raw_line in handle:
|
|
61
|
+
line = raw_line.strip()
|
|
62
|
+
if not line or line.startswith("#"):
|
|
63
|
+
continue
|
|
64
|
+
for part in line.split():
|
|
65
|
+
if part.startswith("aleph-bootstrap-config:"):
|
|
66
|
+
_, owner_address, deployment_token = part.split(":", 2)
|
|
67
|
+
owner_address = owner_address.strip().lower()
|
|
68
|
+
deployment_token = deployment_token.strip()
|
|
69
|
+
if owner_address and deployment_token:
|
|
70
|
+
return owner_address, deployment_token
|
|
71
|
+
except (FileNotFoundError, ValueError):
|
|
72
|
+
return None, None
|
|
73
|
+
|
|
74
|
+
return None, None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _clear_metadata_state() -> None:
|
|
78
|
+
for path in (METADATA_FILE, METADATA_ERROR_FILE):
|
|
79
|
+
try:
|
|
80
|
+
os.remove(path)
|
|
81
|
+
except FileNotFoundError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _start_metadata_generation() -> None:
|
|
86
|
+
_clear_metadata_state()
|
|
87
|
+
threading.Thread(target=_generate_metadata_files, daemon=True).start()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _build_configure_args(record: dict) -> list[str]:
|
|
91
|
+
runtime = record.get("runtime") if isinstance(record.get("runtime"), dict) else {}
|
|
92
|
+
bootstrap = record.get("bootstrap") if isinstance(record.get("bootstrap"), dict) else {}
|
|
93
|
+
public_ipv4 = str(runtime.get("publicIpv4") or "").strip()
|
|
94
|
+
if not public_ipv4:
|
|
95
|
+
raise ValueError("bootstrap config missing runtime.publicIpv4")
|
|
96
|
+
|
|
97
|
+
mapped_ports = runtime.get("mappedPorts") if isinstance(runtime.get("mappedPorts"), dict) else {}
|
|
98
|
+
relay_tcp = mapped_ports.get("9095") if isinstance(mapped_ports.get("9095"), dict) else {}
|
|
99
|
+
relay_ws = mapped_ports.get("9097") if isinstance(mapped_ports.get("9097"), dict) else {}
|
|
100
|
+
relay_udp = mapped_ports.get("9095") if isinstance(mapped_ports.get("9095"), dict) else {}
|
|
101
|
+
|
|
102
|
+
args = [CONFIGURE_SCRIPT, "--public-ipv4", public_ipv4]
|
|
103
|
+
|
|
104
|
+
public_ipv6 = str(runtime.get("publicIpv6") or "").strip()
|
|
105
|
+
if public_ipv6:
|
|
106
|
+
args.extend(["--public-ipv6", public_ipv6])
|
|
107
|
+
|
|
108
|
+
proxy_hostname = _validate_proxy_hostname(runtime.get("proxyUrl"))
|
|
109
|
+
if proxy_hostname:
|
|
110
|
+
args.extend(["--proxy-hostname", proxy_hostname])
|
|
111
|
+
|
|
112
|
+
relay_tcp_host = relay_tcp.get("host")
|
|
113
|
+
relay_ws_host = relay_ws.get("host")
|
|
114
|
+
relay_udp_host = relay_udp.get("host") if relay_udp.get("udp") is True else None
|
|
115
|
+
|
|
116
|
+
if isinstance(relay_tcp_host, int) and relay_tcp_host > 0:
|
|
117
|
+
args.extend(["--tcp-port", str(relay_tcp_host)])
|
|
118
|
+
if isinstance(relay_ws_host, int) and relay_ws_host > 0:
|
|
119
|
+
args.extend(["--ws-port", str(relay_ws_host)])
|
|
120
|
+
if isinstance(relay_udp_host, int) and relay_udp_host > 0:
|
|
121
|
+
args.extend(["--udp-port", str(relay_udp_host)])
|
|
122
|
+
|
|
123
|
+
registration_id = str(bootstrap.get("registrationId") or "").strip()
|
|
124
|
+
if registration_id:
|
|
125
|
+
args.extend(["--bootstrap-registration-id", registration_id])
|
|
126
|
+
owner_auth = str(bootstrap.get("ownerAuthorizationBase64") or "").strip()
|
|
127
|
+
if owner_auth:
|
|
128
|
+
args.extend(["--bootstrap-owner-authorization-b64", owner_auth])
|
|
129
|
+
|
|
130
|
+
return args
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _run_configure_process(args: list[str]) -> tuple[bool, str]:
|
|
134
|
+
with CONFIGURE_LOCK:
|
|
135
|
+
if os.path.exists(READY_FILE):
|
|
136
|
+
return True, "ready"
|
|
137
|
+
try:
|
|
138
|
+
result = subprocess.run(args, check=True, capture_output=True, text=True)
|
|
139
|
+
except subprocess.CalledProcessError as error:
|
|
140
|
+
return False, error.stderr.strip() or error.stdout.strip() or str(error)
|
|
141
|
+
|
|
142
|
+
_start_metadata_generation()
|
|
143
|
+
return True, result.stdout.strip()
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _load_vm_bootstrap_record() -> tuple[str | None, str | None, dict | None]:
|
|
147
|
+
owner_address, deployment_token = _extract_guest_bootstrap_locator()
|
|
148
|
+
if not owner_address or not deployment_token:
|
|
149
|
+
return owner_address, deployment_token, None
|
|
150
|
+
|
|
151
|
+
request_url = (
|
|
152
|
+
f"{ALEPH_API_HOST.rstrip('/')}/api/v0/aggregates/{owner_address}.json?"
|
|
153
|
+
f"{urlencode({'keys': BOOTSTRAP_CONFIG_KEY})}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
with urlopen(request_url, timeout=10) as response:
|
|
158
|
+
payload = json.loads(response.read().decode("utf-8") or "{}")
|
|
159
|
+
except HTTPError as error:
|
|
160
|
+
if error.code == 404:
|
|
161
|
+
return owner_address, deployment_token, None
|
|
162
|
+
return owner_address, deployment_token, None
|
|
163
|
+
except (URLError, json.JSONDecodeError, TimeoutError):
|
|
164
|
+
return owner_address, deployment_token, None
|
|
165
|
+
|
|
166
|
+
aggregate = payload.get("data", {}).get(BOOTSTRAP_CONFIG_KEY, {})
|
|
167
|
+
if not isinstance(aggregate, dict):
|
|
168
|
+
return owner_address, deployment_token, None
|
|
169
|
+
|
|
170
|
+
record = aggregate.get(deployment_token)
|
|
171
|
+
return owner_address, deployment_token, record if isinstance(record, dict) else None
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _poll_bootstrap_config_loop() -> None:
|
|
175
|
+
while not os.path.exists(READY_FILE):
|
|
176
|
+
_, deployment_token, record = _load_vm_bootstrap_record()
|
|
177
|
+
if deployment_token and isinstance(record, dict):
|
|
178
|
+
status = str(record.get("status") or "").strip().lower()
|
|
179
|
+
if status != "pending":
|
|
180
|
+
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
181
|
+
continue
|
|
182
|
+
try:
|
|
183
|
+
args = _build_configure_args(record)
|
|
184
|
+
except ValueError:
|
|
185
|
+
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
186
|
+
continue
|
|
187
|
+
ok, _ = _run_configure_process(args)
|
|
188
|
+
if ok:
|
|
189
|
+
return
|
|
190
|
+
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
191
|
+
|
|
192
|
+
|
|
49
193
|
class Handler(BaseHTTPRequestHandler):
|
|
50
194
|
server_version = "UcGoPeerSetup/1.0"
|
|
51
195
|
|
|
@@ -184,26 +328,22 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
184
328
|
self._send_json(400, {"status": "bad-request", "error": str(error)})
|
|
185
329
|
return
|
|
186
330
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
except subprocess.CalledProcessError as error:
|
|
331
|
+
ok, output = _run_configure_process(args)
|
|
332
|
+
if not ok:
|
|
190
333
|
self._send_json(
|
|
191
334
|
500,
|
|
192
335
|
{
|
|
193
336
|
"status": "error",
|
|
194
|
-
"error":
|
|
337
|
+
"error": output or "configure failed",
|
|
195
338
|
},
|
|
196
339
|
)
|
|
197
340
|
return
|
|
198
341
|
|
|
199
|
-
_clear_metadata_state()
|
|
200
|
-
threading.Thread(target=_generate_metadata_files, daemon=True).start()
|
|
201
|
-
|
|
202
342
|
self._send_json(
|
|
203
343
|
200,
|
|
204
344
|
{
|
|
205
345
|
"status": "configured",
|
|
206
|
-
"stdout":
|
|
346
|
+
"stdout": output,
|
|
207
347
|
"metadata_pending": True,
|
|
208
348
|
},
|
|
209
349
|
)
|
|
@@ -215,14 +355,6 @@ def _stop_bootstrap_service() -> None:
|
|
|
215
355
|
subprocess.run(["systemctl", "stop", BOOTSTRAP_SERVICE], check=False)
|
|
216
356
|
|
|
217
357
|
|
|
218
|
-
def _clear_metadata_state() -> None:
|
|
219
|
-
for path in (METADATA_FILE, METADATA_ERROR_FILE):
|
|
220
|
-
try:
|
|
221
|
-
os.remove(path)
|
|
222
|
-
except FileNotFoundError:
|
|
223
|
-
pass
|
|
224
|
-
|
|
225
|
-
|
|
226
358
|
def _generate_metadata_files() -> None:
|
|
227
359
|
try:
|
|
228
360
|
describe = subprocess.run(
|
|
@@ -240,6 +372,7 @@ def _generate_metadata_files() -> None:
|
|
|
240
372
|
|
|
241
373
|
|
|
242
374
|
def main() -> None:
|
|
375
|
+
threading.Thread(target=_poll_bootstrap_config_loop, daemon=True).start()
|
|
243
376
|
server = ThreadingHTTPServer(("0.0.0.0", 80), Handler)
|
|
244
377
|
server.serve_forever()
|
|
245
378
|
|