@le-space/rootfs 0.3.15 → 0.3.17
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
|
@@ -58,17 +58,20 @@ def parse_logs() -> tuple[str | None, list[str]]:
|
|
|
58
58
|
output = result.stdout or ""
|
|
59
59
|
|
|
60
60
|
peer_id = None
|
|
61
|
-
for
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
for line in output.splitlines():
|
|
62
|
+
for pattern in PEER_ID_PATTERNS:
|
|
63
|
+
match = pattern.search(line)
|
|
64
|
+
if match:
|
|
65
|
+
peer_id = match.group(1)
|
|
66
|
+
|
|
67
|
+
logged_addrs = LISTENING_PATTERN.findall(output)
|
|
68
|
+
if peer_id is None and logged_addrs:
|
|
69
|
+
peer_id = logged_addrs[-1][1]
|
|
66
70
|
|
|
67
71
|
listening_addrs = []
|
|
68
|
-
for addr, logged_peer_id in
|
|
69
|
-
if peer_id is None:
|
|
70
|
-
|
|
71
|
-
listening_addrs.append(addr)
|
|
72
|
+
for addr, logged_peer_id in logged_addrs:
|
|
73
|
+
if peer_id is None or logged_peer_id == peer_id:
|
|
74
|
+
listening_addrs.append(addr)
|
|
72
75
|
|
|
73
76
|
return peer_id, dedupe(listening_addrs)
|
|
74
77
|
|
|
@@ -6,6 +6,7 @@ import subprocess
|
|
|
6
6
|
import threading
|
|
7
7
|
import time
|
|
8
8
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
from urllib.error import HTTPError, URLError
|
|
10
11
|
from urllib.parse import urlencode, urlsplit
|
|
11
12
|
from urllib.request import urlopen
|
|
@@ -22,6 +23,8 @@ AUTHORIZED_KEYS_FILE = os.environ.get("AUTHORIZED_KEYS_FILE", "/root/.ssh/author
|
|
|
22
23
|
ALEPH_API_HOST = os.environ.get("ALEPH_API_HOST", "https://api2.aleph.im")
|
|
23
24
|
BOOTSTRAP_CONFIG_KEY = os.environ.get("BOOTSTRAP_CONFIG_KEY", "vm-bootstrap-config")
|
|
24
25
|
BOOTSTRAP_CONFIG_POLL_SECONDS = float(os.environ.get("BOOTSTRAP_CONFIG_POLL_SECONDS", "5"))
|
|
26
|
+
DESCRIBE_TIMEOUT_SECONDS = int(os.environ.get("DESCRIBE_TIMEOUT_SECONDS", "15"))
|
|
27
|
+
DESCRIBE_INTERVAL_SECONDS = float(os.environ.get("DESCRIBE_INTERVAL_SECONDS", "1"))
|
|
25
28
|
|
|
26
29
|
CONFIGURE_LOCK = threading.Lock()
|
|
27
30
|
|
|
@@ -64,7 +67,7 @@ def _extract_guest_bootstrap_locator() -> tuple[str | None, str | None]:
|
|
|
64
67
|
for part in line.split():
|
|
65
68
|
if part.startswith("aleph-bootstrap-config:"):
|
|
66
69
|
_, owner_address, deployment_token = part.split(":", 2)
|
|
67
|
-
owner_address = owner_address.strip()
|
|
70
|
+
owner_address = owner_address.strip()
|
|
68
71
|
deployment_token = deployment_token.strip()
|
|
69
72
|
if owner_address and deployment_token:
|
|
70
73
|
return owner_address, deployment_token
|
|
@@ -87,6 +90,16 @@ def _start_metadata_generation() -> None:
|
|
|
87
90
|
threading.Thread(target=_generate_metadata_files, daemon=True).start()
|
|
88
91
|
|
|
89
92
|
|
|
93
|
+
def _service_active(service_name: str) -> bool:
|
|
94
|
+
result = subprocess.run(
|
|
95
|
+
["systemctl", "is-active", service_name],
|
|
96
|
+
check=False,
|
|
97
|
+
capture_output=True,
|
|
98
|
+
text=True,
|
|
99
|
+
)
|
|
100
|
+
return result.returncode == 0 and result.stdout.strip() == "active"
|
|
101
|
+
|
|
102
|
+
|
|
90
103
|
def _build_configure_args(record: dict) -> list[str]:
|
|
91
104
|
runtime = record.get("runtime") if isinstance(record.get("runtime"), dict) else {}
|
|
92
105
|
bootstrap = record.get("bootstrap") if isinstance(record.get("bootstrap"), dict) else {}
|
|
@@ -222,34 +235,11 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
222
235
|
self._send_json(404, {"status": "not-found"})
|
|
223
236
|
return
|
|
224
237
|
|
|
225
|
-
self._send_json(
|
|
226
|
-
200,
|
|
227
|
-
{
|
|
228
|
-
"status": "waiting-for-port-mapping",
|
|
229
|
-
"ready": os.path.exists(READY_FILE),
|
|
230
|
-
"env_file": ENV_FILE,
|
|
231
|
-
"metadata_ready": os.path.exists(METADATA_FILE),
|
|
232
|
-
},
|
|
233
|
-
)
|
|
238
|
+
self._send_json(200, _health_payload())
|
|
234
239
|
|
|
235
240
|
def _handle_metadata(self) -> None:
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
metadata = json.load(handle)
|
|
239
|
-
self._send_json(200, {"status": "ready", "metadata": metadata})
|
|
240
|
-
threading.Thread(target=self.server.shutdown, daemon=True).start() # type: ignore[arg-type]
|
|
241
|
-
threading.Thread(target=_stop_bootstrap_service, daemon=True).start()
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
if os.path.exists(METADATA_ERROR_FILE):
|
|
245
|
-
with open(METADATA_ERROR_FILE, encoding="utf-8") as handle:
|
|
246
|
-
error_message = handle.read().strip() or "metadata generation failed"
|
|
247
|
-
self._send_json(500, {"status": "error", "error": error_message})
|
|
248
|
-
threading.Thread(target=self.server.shutdown, daemon=True).start() # type: ignore[arg-type]
|
|
249
|
-
threading.Thread(target=_stop_bootstrap_service, daemon=True).start()
|
|
250
|
-
return
|
|
251
|
-
|
|
252
|
-
self._send_json(202, {"status": "pending"})
|
|
241
|
+
status, payload = _metadata_status_payload()
|
|
242
|
+
self._send_json(status, payload)
|
|
253
243
|
|
|
254
244
|
def do_POST(self) -> None: # noqa: N802
|
|
255
245
|
if self._request_path() != "/configure":
|
|
@@ -349,26 +339,111 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
349
339
|
)
|
|
350
340
|
|
|
351
341
|
|
|
352
|
-
def
|
|
353
|
-
# Give the HTTP response a brief head start, then stop the temporary setup service.
|
|
354
|
-
time.sleep(1)
|
|
355
|
-
subprocess.run(["systemctl", "stop", BOOTSTRAP_SERVICE], check=False)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
def _generate_metadata_files() -> None:
|
|
342
|
+
def _describe_metadata() -> dict:
|
|
359
343
|
try:
|
|
360
344
|
describe = subprocess.run(
|
|
361
345
|
[DESCRIBE_SCRIPT],
|
|
362
346
|
check=True,
|
|
363
347
|
capture_output=True,
|
|
364
348
|
text=True,
|
|
349
|
+
env={
|
|
350
|
+
**os.environ,
|
|
351
|
+
"DESCRIBE_WAIT_TIMEOUT_SECONDS": str(DESCRIBE_TIMEOUT_SECONDS),
|
|
352
|
+
"DESCRIBE_WAIT_INTERVAL_SECONDS": str(DESCRIBE_INTERVAL_SECONDS),
|
|
353
|
+
},
|
|
365
354
|
)
|
|
366
|
-
|
|
367
|
-
with open(METADATA_FILE, "w", encoding="utf-8") as handle:
|
|
368
|
-
json.dump(payload, handle)
|
|
355
|
+
return json.loads(describe.stdout.strip() or "{}")
|
|
369
356
|
except (subprocess.CalledProcessError, json.JSONDecodeError) as error:
|
|
370
|
-
|
|
371
|
-
|
|
357
|
+
raise RuntimeError(str(error)) from error
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _write_metadata_files(payload: dict) -> None:
|
|
361
|
+
Path(METADATA_FILE).write_text(json.dumps(payload), encoding="utf-8")
|
|
362
|
+
try:
|
|
363
|
+
os.remove(METADATA_ERROR_FILE)
|
|
364
|
+
except FileNotFoundError:
|
|
365
|
+
pass
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _write_metadata_error(error_message: str) -> None:
|
|
369
|
+
try:
|
|
370
|
+
os.remove(METADATA_FILE)
|
|
371
|
+
except FileNotFoundError:
|
|
372
|
+
pass
|
|
373
|
+
Path(METADATA_ERROR_FILE).write_text(error_message, encoding="utf-8")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _generate_metadata_files() -> None:
|
|
377
|
+
try:
|
|
378
|
+
payload = _describe_metadata()
|
|
379
|
+
_write_metadata_files(payload)
|
|
380
|
+
except RuntimeError as error:
|
|
381
|
+
_write_metadata_error(str(error))
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _fresh_metadata_payload() -> tuple[dict | None, str | None]:
|
|
385
|
+
try:
|
|
386
|
+
payload = _describe_metadata()
|
|
387
|
+
_write_metadata_files(payload)
|
|
388
|
+
return payload, None
|
|
389
|
+
except RuntimeError as error:
|
|
390
|
+
return None, str(error)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _read_cached_metadata() -> dict | None:
|
|
394
|
+
try:
|
|
395
|
+
with open(METADATA_FILE, encoding="utf-8") as handle:
|
|
396
|
+
payload = json.load(handle)
|
|
397
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
398
|
+
return None
|
|
399
|
+
return payload if isinstance(payload, dict) else None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _read_cached_metadata_error() -> str | None:
|
|
403
|
+
try:
|
|
404
|
+
message = Path(METADATA_ERROR_FILE).read_text(encoding="utf-8").strip()
|
|
405
|
+
except FileNotFoundError:
|
|
406
|
+
return None
|
|
407
|
+
return message or "metadata generation failed"
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _metadata_status_payload() -> tuple[int, dict]:
|
|
411
|
+
if not os.path.exists(READY_FILE):
|
|
412
|
+
return 202, {"status": "pending"}
|
|
413
|
+
|
|
414
|
+
payload, error_message = _fresh_metadata_payload()
|
|
415
|
+
if payload is not None:
|
|
416
|
+
return 200, {"status": "ready", "metadata": payload}
|
|
417
|
+
|
|
418
|
+
cached_payload = _read_cached_metadata()
|
|
419
|
+
if cached_payload is not None:
|
|
420
|
+
return 200, {"status": "ready", "metadata": cached_payload, "stale": True}
|
|
421
|
+
|
|
422
|
+
cached_error = error_message or _read_cached_metadata_error()
|
|
423
|
+
if _service_active("uc-go-peer.service"):
|
|
424
|
+
return 202, {
|
|
425
|
+
"status": "pending",
|
|
426
|
+
"ready": True,
|
|
427
|
+
"relay_active": True,
|
|
428
|
+
"error": cached_error,
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return 500, {"status": "error", "error": cached_error or "metadata generation failed"}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _health_payload() -> dict:
|
|
435
|
+
relay_ready = os.path.exists(READY_FILE)
|
|
436
|
+
metadata_status, metadata_payload = _metadata_status_payload()
|
|
437
|
+
return {
|
|
438
|
+
"status": "ready" if relay_ready and metadata_status == 200 else "waiting-for-port-mapping",
|
|
439
|
+
"ready": relay_ready,
|
|
440
|
+
"env_file": ENV_FILE,
|
|
441
|
+
"metadata_ready": metadata_status == 200,
|
|
442
|
+
"relay_active": _service_active("uc-go-peer.service"),
|
|
443
|
+
"caddy_active": _service_active("caddy.service"),
|
|
444
|
+
"bootstrap_active": _service_active(BOOTSTRAP_SERVICE),
|
|
445
|
+
"metadata_status": metadata_payload.get("status"),
|
|
446
|
+
}
|
|
372
447
|
|
|
373
448
|
|
|
374
449
|
def main() -> None:
|