@le-space/rootfs 0.9.4 → 0.9.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/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import ipaddress
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import subprocess
|
|
8
|
+
import sys
|
|
8
9
|
import threading
|
|
9
10
|
import time
|
|
10
11
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
@@ -92,6 +93,28 @@ def _parse_env_file(path: str) -> dict[str, str]:
|
|
|
92
93
|
return values
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
def _log(message: str) -> None:
|
|
97
|
+
"""Say it once, on stderr, where journalctl picks it up.
|
|
98
|
+
|
|
99
|
+
This file had no logging and the HTTP handler silenced its own, so a relay
|
|
100
|
+
that never configured itself left a journal with one "Started" line in it.
|
|
101
|
+
That silence is what made "the guest never acknowledged" an unexplainable
|
|
102
|
+
failure rather than a readable one.
|
|
103
|
+
"""
|
|
104
|
+
print(f"[bootstrap] {message}", file=sys.stderr, flush=True)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_LOGGED_ONCE: set[str] = set()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _log_once(key: str, message: str) -> None:
|
|
111
|
+
"""For states that repeat every few seconds and are worth saying once."""
|
|
112
|
+
if key in _LOGGED_ONCE:
|
|
113
|
+
return
|
|
114
|
+
_LOGGED_ONCE.add(key)
|
|
115
|
+
_log(message)
|
|
116
|
+
|
|
117
|
+
|
|
95
118
|
def _extract_guest_bootstrap_locator() -> tuple[str | None, str | None]:
|
|
96
119
|
"""Read the deployment locator the deployer left in our SSH key.
|
|
97
120
|
|
|
@@ -112,9 +135,15 @@ def _extract_guest_bootstrap_locator() -> tuple[str | None, str | None]:
|
|
|
112
135
|
deployment_token = deployment_token.strip()
|
|
113
136
|
if owner_address and deployment_token:
|
|
114
137
|
return owner_address, deployment_token
|
|
115
|
-
except (FileNotFoundError, ValueError):
|
|
138
|
+
except (FileNotFoundError, ValueError) as error:
|
|
139
|
+
_log_once("locator-error", f"cannot read {AUTHORIZED_KEYS_FILE}: {error!r}")
|
|
116
140
|
return None, None
|
|
117
141
|
|
|
142
|
+
_log_once(
|
|
143
|
+
"locator-missing",
|
|
144
|
+
f"no aleph-bootstrap-config marker in {AUTHORIZED_KEYS_FILE}; "
|
|
145
|
+
"nothing to poll for, the relay will stay unconfigured",
|
|
146
|
+
)
|
|
118
147
|
return None, None
|
|
119
148
|
|
|
120
149
|
|
|
@@ -374,6 +403,10 @@ def _load_vm_bootstrap_record() -> tuple[str | None, str | None, dict | None]:
|
|
|
374
403
|
|
|
375
404
|
|
|
376
405
|
def _poll_bootstrap_config_loop() -> None:
|
|
406
|
+
_log(
|
|
407
|
+
f"waiting for bootstrap config, polling every {BOOTSTRAP_CONFIG_POLL_SECONDS}s "
|
|
408
|
+
f"against {ALEPH_API_HOST}"
|
|
409
|
+
)
|
|
377
410
|
while not os.path.exists(READY_FILE):
|
|
378
411
|
_, deployment_token, record = _load_vm_bootstrap_record()
|
|
379
412
|
if deployment_token and isinstance(record, dict):
|
|
@@ -381,12 +414,20 @@ def _poll_bootstrap_config_loop() -> None:
|
|
|
381
414
|
if status == "pending":
|
|
382
415
|
try:
|
|
383
416
|
args = _build_configure_args_from_record(record)
|
|
384
|
-
except ValueError:
|
|
417
|
+
except ValueError as error:
|
|
418
|
+
# The record arrived and was unusable — which used to look
|
|
419
|
+
# exactly like never having received one.
|
|
420
|
+
_log_once("record-invalid", f"record cannot be turned into a config: {error}")
|
|
385
421
|
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
386
422
|
continue
|
|
387
|
-
|
|
423
|
+
_log("record found and pending, configuring the relay now")
|
|
424
|
+
ok, detail = _run_configure_process(args, record)
|
|
388
425
|
if ok:
|
|
426
|
+
_log("relay configured, setup endpoint is done")
|
|
389
427
|
return
|
|
428
|
+
_log(f"configure failed, will retry: {detail}")
|
|
429
|
+
else:
|
|
430
|
+
_log_once(f"status-{status}", f"record status is {status!r}, not acting on it")
|
|
390
431
|
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
391
432
|
|
|
392
433
|
|
|
Binary file
|
|
@@ -5,6 +5,7 @@ import ipaddress
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import subprocess
|
|
8
|
+
import sys
|
|
8
9
|
import threading
|
|
9
10
|
import time
|
|
10
11
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
@@ -85,6 +86,29 @@ def _validate_proxy_hostname(value: object) -> str | None:
|
|
|
85
86
|
return parsed.hostname
|
|
86
87
|
|
|
87
88
|
|
|
89
|
+
def _log(message: str) -> None:
|
|
90
|
+
"""Say it once, on stderr, where journalctl picks it up.
|
|
91
|
+
|
|
92
|
+
This file had no logging at all, and the HTTP handler silenced its own.
|
|
93
|
+
That is how a relay can sit for hours doing nothing while `journalctl -u
|
|
94
|
+
uc-go-peer-bootstrap` shows a single "Started" line — which is what it did
|
|
95
|
+
on 2026-09-02, leaving no way to tell whether the guest had found its
|
|
96
|
+
locator, asked Aleph and been told nothing, or never looked.
|
|
97
|
+
"""
|
|
98
|
+
print(f"[bootstrap] {message}", file=sys.stderr, flush=True)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_LOGGED_ONCE: set[str] = set()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _log_once(key: str, message: str) -> None:
|
|
105
|
+
"""For states that repeat every few seconds and are worth saying once."""
|
|
106
|
+
if key in _LOGGED_ONCE:
|
|
107
|
+
return
|
|
108
|
+
_LOGGED_ONCE.add(key)
|
|
109
|
+
_log(message)
|
|
110
|
+
|
|
111
|
+
|
|
88
112
|
def _extract_guest_bootstrap_locator() -> tuple[str | None, str | None]:
|
|
89
113
|
try:
|
|
90
114
|
with open(AUTHORIZED_KEYS_FILE, encoding="utf-8") as handle:
|
|
@@ -98,10 +122,25 @@ def _extract_guest_bootstrap_locator() -> tuple[str | None, str | None]:
|
|
|
98
122
|
owner_address = owner_address.strip()
|
|
99
123
|
deployment_token = deployment_token.strip()
|
|
100
124
|
if owner_address and deployment_token:
|
|
125
|
+
_log_once(
|
|
126
|
+
"locator",
|
|
127
|
+
f"locator found in authorized_keys: owner={owner_address} "
|
|
128
|
+
f"token={deployment_token}",
|
|
129
|
+
)
|
|
101
130
|
return owner_address, deployment_token
|
|
102
|
-
except (FileNotFoundError, ValueError):
|
|
131
|
+
except (FileNotFoundError, ValueError) as error:
|
|
132
|
+
_log_once("locator-error", f"cannot read {AUTHORIZED_KEYS_FILE}: {error!r}")
|
|
103
133
|
return None, None
|
|
104
134
|
|
|
135
|
+
# Without this the guest can never find its record, and every deployment
|
|
136
|
+
# spends its full config window before failing over. The browser appends
|
|
137
|
+
# the marker to the SSH key it puts in the instance message; if it is not
|
|
138
|
+
# here, either no key was configured or something stripped the comment.
|
|
139
|
+
_log_once(
|
|
140
|
+
"locator-missing",
|
|
141
|
+
f"no aleph-bootstrap-config marker in {AUTHORIZED_KEYS_FILE}; "
|
|
142
|
+
"nothing to poll for, the relay will stay unconfigured",
|
|
143
|
+
)
|
|
105
144
|
return None, None
|
|
106
145
|
|
|
107
146
|
|
|
@@ -415,10 +454,13 @@ def _load_vm_bootstrap_record() -> tuple[str | None, str | None, dict | None]:
|
|
|
415
454
|
with urlopen(request_url, timeout=10) as response:
|
|
416
455
|
payload = json.loads(response.read().decode("utf-8") or "{}")
|
|
417
456
|
except HTTPError as error:
|
|
418
|
-
|
|
419
|
-
|
|
457
|
+
# A 404 is ordinary early on: the browser publishes the aggregate only
|
|
458
|
+
# once the runtime is up. Anything else is worth naming.
|
|
459
|
+
key = "aggregate-404" if error.code == 404 else f"aggregate-{error.code}"
|
|
460
|
+
_log_once(key, f"aggregate query returned HTTP {error.code} for {owner_address}")
|
|
420
461
|
return owner_address, deployment_token, None
|
|
421
|
-
except (URLError, json.JSONDecodeError, TimeoutError):
|
|
462
|
+
except (URLError, json.JSONDecodeError, TimeoutError) as error:
|
|
463
|
+
_log_once("aggregate-unreachable", f"aggregate query failed: {error!r}")
|
|
422
464
|
return owner_address, deployment_token, None
|
|
423
465
|
|
|
424
466
|
aggregate = payload.get("data", {}).get(BOOTSTRAP_CONFIG_KEY, {})
|
|
@@ -426,25 +468,47 @@ def _load_vm_bootstrap_record() -> tuple[str | None, str | None, dict | None]:
|
|
|
426
468
|
return owner_address, deployment_token, None
|
|
427
469
|
|
|
428
470
|
record = aggregate.get(deployment_token)
|
|
429
|
-
|
|
471
|
+
if not isinstance(record, dict):
|
|
472
|
+
_log_once(
|
|
473
|
+
"record-missing",
|
|
474
|
+
f"aggregate has no record for token {deployment_token} "
|
|
475
|
+
f"({len(aggregate)} record(s) present)",
|
|
476
|
+
)
|
|
477
|
+
return owner_address, deployment_token, None
|
|
478
|
+
|
|
479
|
+
return owner_address, deployment_token, record
|
|
430
480
|
|
|
431
481
|
|
|
432
482
|
def _poll_bootstrap_config_loop() -> None:
|
|
483
|
+
_log(
|
|
484
|
+
f"waiting for bootstrap config, polling every {BOOTSTRAP_CONFIG_POLL_SECONDS}s "
|
|
485
|
+
f"against {ALEPH_API_HOST}"
|
|
486
|
+
)
|
|
433
487
|
while not os.path.exists(READY_FILE):
|
|
434
488
|
_, deployment_token, record = _load_vm_bootstrap_record()
|
|
435
489
|
if deployment_token and isinstance(record, dict):
|
|
436
490
|
status = str(record.get("status") or "").strip().lower()
|
|
437
491
|
if status != "pending":
|
|
492
|
+
# Somebody else's turn, or already done. Worth saying once per
|
|
493
|
+
# status: a record stuck on "applied" while the relay is down
|
|
494
|
+
# means something very different from no record at all.
|
|
495
|
+
_log_once(f"status-{status}", f"record status is {status!r}, not acting on it")
|
|
438
496
|
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
439
497
|
continue
|
|
440
498
|
try:
|
|
441
499
|
args = _build_configure_args(record)
|
|
442
|
-
except ValueError:
|
|
500
|
+
except ValueError as error:
|
|
501
|
+
# The record arrived and was unusable. Before this, that was
|
|
502
|
+
# indistinguishable from never having received one.
|
|
503
|
+
_log_once("record-invalid", f"record cannot be turned into a config: {error}")
|
|
443
504
|
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
444
505
|
continue
|
|
445
|
-
|
|
506
|
+
_log("record found and pending, configuring the relay now")
|
|
507
|
+
ok, detail = _run_configure_process(args, record)
|
|
446
508
|
if ok:
|
|
509
|
+
_log("relay configured, setup endpoint is done")
|
|
447
510
|
return
|
|
511
|
+
_log(f"configure failed, will retry: {detail}")
|
|
448
512
|
time.sleep(BOOTSTRAP_CONFIG_POLL_SECONDS)
|
|
449
513
|
|
|
450
514
|
|