lablink-cli 0.1.0__py3-none-any.whl
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.
- lablink_cli/__init__.py +8 -0
- lablink_cli/api.py +428 -0
- lablink_cli/app.py +938 -0
- lablink_cli/byo_detect.py +112 -0
- lablink_cli/commands/__init__.py +0 -0
- lablink_cli/commands/cleanup.py +647 -0
- lablink_cli/commands/deploy.py +863 -0
- lablink_cli/commands/deploy_compose.py +1203 -0
- lablink_cli/commands/doctor.py +549 -0
- lablink_cli/commands/export_metrics.py +244 -0
- lablink_cli/commands/launch.py +236 -0
- lablink_cli/commands/logs.py +434 -0
- lablink_cli/commands/register.py +839 -0
- lablink_cli/commands/reset_overlay.py +109 -0
- lablink_cli/commands/setup.py +347 -0
- lablink_cli/commands/stats.py +133 -0
- lablink_cli/commands/status.py +934 -0
- lablink_cli/commands/unregister.py +188 -0
- lablink_cli/commands/utils.py +552 -0
- lablink_cli/config/__init__.py +0 -0
- lablink_cli/config/schema.py +212 -0
- lablink_cli/deployment_metrics.py +94 -0
- lablink_cli/docker.py +419 -0
- lablink_cli/log_shipper.py +441 -0
- lablink_cli/templates/docker-compose.tailscale-override.yml +55 -0
- lablink_cli/templates/docker-compose.yml +67 -0
- lablink_cli/tofu_source.py +169 -0
- lablink_cli/tui/__init__.py +0 -0
- lablink_cli/tui/logs_viewer.py +413 -0
- lablink_cli/tui/wizard.py +1814 -0
- lablink_cli-0.1.0.dist-info/METADATA +76 -0
- lablink_cli-0.1.0.dist-info/RECORD +35 -0
- lablink_cli-0.1.0.dist-info/WHEEL +5 -0
- lablink_cli-0.1.0.dist-info/entry_points.txt +2 -0
- lablink_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Auto-detection helpers for `lablink client register` (BYO-box facts).
|
|
2
|
+
|
|
3
|
+
Each helper has a single, narrow responsibility and returns a value or
|
|
4
|
+
None / a benign default — never raises on detection failure. The
|
|
5
|
+
`register` command turns "essential field is None" into a user-facing
|
|
6
|
+
error; the helpers themselves are pure.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import shutil
|
|
12
|
+
import socket
|
|
13
|
+
import subprocess
|
|
14
|
+
import uuid
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
_MACHINE_ID_PATHS: list[Path] = [
|
|
18
|
+
Path("/etc/machine-id"),
|
|
19
|
+
Path("/var/lib/dbus/machine-id"),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def detect_hostname() -> str | None:
|
|
24
|
+
"""Return the box's hostname, or None if empty."""
|
|
25
|
+
name = socket.gethostname()
|
|
26
|
+
return name or None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def detect_lan_ip() -> str | None:
|
|
30
|
+
"""Return the IP of the interface that routes to the public internet.
|
|
31
|
+
|
|
32
|
+
Uses the UDP-socket trick: connecting a SOCK_DGRAM socket to a public
|
|
33
|
+
address resolves the route locally without sending a packet. The
|
|
34
|
+
socket's local address is the LAN IP that would carry outgoing
|
|
35
|
+
traffic. Returns None on any OSError (no route, no network, etc.).
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
39
|
+
s.connect(("8.8.8.8", 80))
|
|
40
|
+
return s.getsockname()[0]
|
|
41
|
+
except OSError:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def resolve_machine_identity(
|
|
46
|
+
*, fallback_path: Path | None = None
|
|
47
|
+
) -> str:
|
|
48
|
+
"""Return a stable identifier for this box, creating one if needed.
|
|
49
|
+
|
|
50
|
+
Get-or-create, not pure detection: tries /etc/machine-id, then
|
|
51
|
+
/var/lib/dbus/machine-id, and as a last resort **writes** a UUID to
|
|
52
|
+
fallback_path (default ~/.lablink/machine_identity) so the value is
|
|
53
|
+
stable across reboots even on systems without machine-id (e.g.,
|
|
54
|
+
older distributions, custom containers). Callers that only want to
|
|
55
|
+
inspect — without persisting anything — should read the candidate
|
|
56
|
+
paths directly.
|
|
57
|
+
"""
|
|
58
|
+
for path in _MACHINE_ID_PATHS:
|
|
59
|
+
try:
|
|
60
|
+
content = path.read_text().strip()
|
|
61
|
+
except OSError:
|
|
62
|
+
continue
|
|
63
|
+
if content:
|
|
64
|
+
return content
|
|
65
|
+
|
|
66
|
+
if fallback_path is None:
|
|
67
|
+
fallback_path = Path.home() / ".lablink" / "machine_identity"
|
|
68
|
+
|
|
69
|
+
if fallback_path.exists():
|
|
70
|
+
existing = fallback_path.read_text().strip()
|
|
71
|
+
if existing:
|
|
72
|
+
return existing
|
|
73
|
+
|
|
74
|
+
value = uuid.uuid4().hex
|
|
75
|
+
fallback_path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
fallback_path.write_text(value)
|
|
77
|
+
return value
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def detect_gpu() -> tuple[bool, str | None]:
|
|
81
|
+
"""Return (present, model) by invoking `nvidia-smi -L`.
|
|
82
|
+
|
|
83
|
+
Returns (False, None) if nvidia-smi is missing, returns non-zero, or
|
|
84
|
+
its output doesn't parse. Model is extracted from the first line of
|
|
85
|
+
`nvidia-smi -L` (typical format: "GPU 0: NVIDIA T4 (UUID: ...)").
|
|
86
|
+
Never raises.
|
|
87
|
+
"""
|
|
88
|
+
if shutil.which("nvidia-smi") is None:
|
|
89
|
+
return (False, None)
|
|
90
|
+
try:
|
|
91
|
+
result = subprocess.run(
|
|
92
|
+
["nvidia-smi", "-L"],
|
|
93
|
+
capture_output=True,
|
|
94
|
+
text=True,
|
|
95
|
+
timeout=5,
|
|
96
|
+
)
|
|
97
|
+
except (OSError, subprocess.SubprocessError):
|
|
98
|
+
return (False, None)
|
|
99
|
+
if result.returncode != 0:
|
|
100
|
+
return (False, None)
|
|
101
|
+
first = (result.stdout or "").splitlines()[0:1]
|
|
102
|
+
if not first:
|
|
103
|
+
return (False, None)
|
|
104
|
+
# "GPU 0: NVIDIA T4 (UUID: GPU-xxx)" -> "NVIDIA T4"
|
|
105
|
+
line = first[0]
|
|
106
|
+
after_colon = line.split(":", 1)
|
|
107
|
+
if len(after_colon) != 2:
|
|
108
|
+
return (False, None)
|
|
109
|
+
model = after_colon[1].split("(")[0].strip()
|
|
110
|
+
if not model:
|
|
111
|
+
return (False, None)
|
|
112
|
+
return (True, model)
|
|
File without changes
|