@le-space/rootfs 0.3.16 → 0.3.18

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/rootfs",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
4
4
  "description": "Shared rootfs contract parsing, reference profile assets, and build helpers.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,6 +10,7 @@ AUTOTLS_CADDY_READY_FILE="${AUTOTLS_CADDY_READY_FILE:-/etc/default/uc-go-peer.ca
10
10
  SERVICE_NAME="${SERVICE_NAME:-uc-go-peer.service}"
11
11
  AUTOTLS_REFRESH_SERVICE="${AUTOTLS_REFRESH_SERVICE:-uc-go-peer-autotls-refresh.service}"
12
12
  BOOTSTRAP_REFRESH_TIMER="${BOOTSTRAP_REFRESH_TIMER:-uc-go-peer-bootstrap-refresh.timer}"
13
+ BOOTSTRAP_REFRESH_SERVICE="${BOOTSTRAP_REFRESH_SERVICE:-uc-go-peer-bootstrap-refresh.service}"
13
14
  CADDY_SERVICE="${CADDY_SERVICE:-caddy.service}"
14
15
  CADDYFILE="${CADDYFILE:-/etc/caddy/Caddyfile}"
15
16
  BOOTSTRAP_SERVICE="${BOOTSTRAP_SERVICE:-uc-go-peer-bootstrap.service}"
@@ -261,6 +262,7 @@ if [ "${START_SERVICE}" -eq 1 ]; then
261
262
  systemctl enable "${AUTOTLS_REFRESH_SERVICE}"
262
263
  systemctl enable "${BOOTSTRAP_REFRESH_TIMER}"
263
264
  systemctl start "${BOOTSTRAP_REFRESH_TIMER}"
265
+ systemctl restart --no-block "${BOOTSTRAP_REFRESH_SERVICE}" || true
264
266
  if [ -n "${PROXY_HOSTNAME}" ]; then
265
267
  systemctl enable "${CADDY_SERVICE}"
266
268
  systemctl restart "${CADDY_SERVICE}"
@@ -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 pattern in PEER_ID_PATTERNS:
62
- match = pattern.search(output)
63
- if match:
64
- peer_id = match.group(1)
65
- break
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 LISTENING_PATTERN.findall(output):
69
- if peer_id is None:
70
- peer_id = logged_peer_id
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
 
@@ -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
- if os.path.exists(METADATA_FILE):
237
- with open(METADATA_FILE, encoding="utf-8") as handle:
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 _stop_bootstrap_service() -> None:
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
- payload = json.loads(describe.stdout.strip() or "{}")
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
- with open(METADATA_ERROR_FILE, "w", encoding="utf-8") as handle:
371
- handle.write(str(error))
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: