embedded-lab-control 0.2.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.
- embedded_lab_control-0.2.0.dist-info/METADATA +855 -0
- embedded_lab_control-0.2.0.dist-info/RECORD +64 -0
- embedded_lab_control-0.2.0.dist-info/WHEEL +5 -0
- embedded_lab_control-0.2.0.dist-info/entry_points.txt +2 -0
- embedded_lab_control-0.2.0.dist-info/licenses/LICENSE +21 -0
- embedded_lab_control-0.2.0.dist-info/top_level.txt +1 -0
- labctl/__init__.py +8 -0
- labctl/actuators/__init__.py +56 -0
- labctl/actuators/base.py +183 -0
- labctl/actuators/lcus1.py +232 -0
- labctl/actuators/mock.py +81 -0
- labctl/actuators/runtime.py +1178 -0
- labctl/cli.py +7485 -0
- labctl/core/__init__.py +36 -0
- labctl/core/admin_files.py +388 -0
- labctl/core/audit.py +269 -0
- labctl/core/claim_keepalive.py +342 -0
- labctl/core/config.py +1068 -0
- labctl/core/database.py +880 -0
- labctl/core/images.py +503 -0
- labctl/core/manager.py +2815 -0
- labctl/core/models.py +815 -0
- labctl/core/safe_open.py +109 -0
- labctl/extras.py +128 -0
- labctl/health/__init__.py +43 -0
- labctl/health/alerts.py +275 -0
- labctl/health/checks.py +533 -0
- labctl/health/daemon.py +397 -0
- labctl/mcp_server.py +5254 -0
- labctl/power/__init__.py +13 -0
- labctl/power/base.py +145 -0
- labctl/power/kasa.py +446 -0
- labctl/power/shelly.py +89 -0
- labctl/power/tasmota.py +95 -0
- labctl/sdwire/__init__.py +10 -0
- labctl/sdwire/controller.py +1116 -0
- labctl/sdwire/power_guard.py +190 -0
- labctl/serial/__init__.py +18 -0
- labctl/serial/boot_test.py +342 -0
- labctl/serial/capture.py +430 -0
- labctl/serial/keys.py +89 -0
- labctl/serial/proxy.py +802 -0
- labctl/serial/ser2net.py +338 -0
- labctl/serial/udev.py +273 -0
- labctl/services.py +210 -0
- labctl/web/__init__.py +9 -0
- labctl/web/activity_broadcaster.py +204 -0
- labctl/web/api.py +1003 -0
- labctl/web/app.py +202 -0
- labctl/web/auth.py +97 -0
- labctl/web/claims.py +232 -0
- labctl/web/static/css/style.css +477 -0
- labctl/web/static/js/app.js +76 -0
- labctl/web/templates/activity.html +202 -0
- labctl/web/templates/base.html +49 -0
- labctl/web/templates/claim_refused.html +38 -0
- labctl/web/templates/console.html +237 -0
- labctl/web/templates/dashboard.html +100 -0
- labctl/web/templates/login.html +25 -0
- labctl/web/templates/sbc_detail.html +306 -0
- labctl/web/templates/settings.html +78 -0
- labctl/web/templates/status_history.html +65 -0
- labctl/web/views.py +496 -0
- labctl/web/websocket.py +208 -0
|
@@ -0,0 +1,855 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: embedded-lab-control
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Centralized management system for embedded development lab resources
|
|
5
|
+
Author: John Jezl
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/johnjezl/Embedded-Lab-Control
|
|
8
|
+
Project-URL: Repository, https://github.com/johnjezl/Embedded-Lab-Control
|
|
9
|
+
Project-URL: Issues, https://github.com/johnjezl/Embedded-Lab-Control/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/johnjezl/Embedded-Lab-Control/blob/main/CHANGELOG.md
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: System :: Hardware
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: click>=8.0
|
|
26
|
+
Requires-Dist: pyyaml>=6.0
|
|
27
|
+
Requires-Dist: requests>=2.28
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black==26.5.1; extra == "dev"
|
|
32
|
+
Requires-Dist: isort==9.0.1; extra == "dev"
|
|
33
|
+
Requires-Dist: flake8==7.4.1; extra == "dev"
|
|
34
|
+
Provides-Extra: web
|
|
35
|
+
Requires-Dist: flask>=3.0; extra == "web"
|
|
36
|
+
Requires-Dist: flask-socketio>=5.0; extra == "web"
|
|
37
|
+
Provides-Extra: mcp
|
|
38
|
+
Requires-Dist: mcp<3,>=2.2; extra == "mcp"
|
|
39
|
+
Provides-Extra: sdwire
|
|
40
|
+
Requires-Dist: sdwire>=0.3; python_version >= "3.12" and extra == "sdwire"
|
|
41
|
+
Provides-Extra: kasa
|
|
42
|
+
Requires-Dist: python-kasa>=0.12; python_version >= "3.11" and extra == "kasa"
|
|
43
|
+
Requires-Dist: python-kasa<0.8,>=0.7.7; python_version < "3.11" and extra == "kasa"
|
|
44
|
+
Provides-Extra: actuators
|
|
45
|
+
Requires-Dist: pyserial>=3.5; extra == "actuators"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# Embedded Lab Control (labctl)
|
|
49
|
+
|
|
50
|
+
Centralized management system for embedded development lab resources. Provides deterministic USB serial access, power control, and resource tracking for multiple single-board computers (SBCs).
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Deterministic USB Serial Naming** - udev rules create stable `/dev/lab/<sbc>` symlinks
|
|
55
|
+
- **Remote Serial Console** - TCP access via ser2net with multi-client support
|
|
56
|
+
- **Smart Plug Power Control** - Supports Tasmota, Kasa, and Shelly devices
|
|
57
|
+
- **Web Dashboard** - Browser-based monitoring and control
|
|
58
|
+
- **REST API** - Programmatic access to all features
|
|
59
|
+
- **Health Monitoring** - Automated ping, serial, and power checks
|
|
60
|
+
- **Activity Stream** - Unified audit trail with CLI tailing, live web feed, and API queries
|
|
61
|
+
- **Hardware Claims** - Exclusive-access coordination for long-running SBC workflows
|
|
62
|
+
- **Session Logging** - Capture serial output with rotation and compression
|
|
63
|
+
- **Authentication** - Session-based web login and API key auth (optional, disabled by default)
|
|
64
|
+
- **Claude Code Skill** - an example `/deploy-and-test` skill orchestrates build, deploy, boot, and serial capture over MCP ([install](#claude-code-skill-deploy-and-test))
|
|
65
|
+
|
|
66
|
+
## Installation
|
|
67
|
+
|
|
68
|
+
### Prerequisites
|
|
69
|
+
|
|
70
|
+
- Linux, Python 3.10+. Some extras need newer Python: Kasa devices that use
|
|
71
|
+
KLAP v2 authentication need 3.11+ (`python-kasa` 0.12), and the `sdwire`
|
|
72
|
+
extra installs nothing below 3.12 (SDWire commands then say so).
|
|
73
|
+
- `ser2net` for remote serial consoles (`sudo apt install ser2net`).
|
|
74
|
+
|
|
75
|
+
### Install from PyPI
|
|
76
|
+
|
|
77
|
+
The distribution is `embedded-lab-control`; the command is `labctl`.
|
|
78
|
+
With [pipx](https://pipx.pypa.io/) (its own virtual environment, `labctl`
|
|
79
|
+
on your `PATH`):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pipx install "embedded-lab-control[web,mcp,kasa,sdwire,actuators]"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
or with pip, in a virtual environment:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python3 -m venv ~/.venvs/labctl
|
|
89
|
+
~/.venvs/labctl/bin/pip install "embedded-lab-control[web,mcp,kasa,sdwire,actuators]"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Pick the extras you need; the core package (SBC records, serial ports,
|
|
93
|
+
ser2net, Tasmota and Shelly plugs, claims) needs none:
|
|
94
|
+
|
|
95
|
+
| Extra | Enables | Installs |
|
|
96
|
+
|-------|---------|----------|
|
|
97
|
+
| `web` | `labctl web`: dashboard and REST API; `labctl user hash-password`, `add` and `verify` (password hashing for web logins and API-key users) | Flask, Flask-SocketIO |
|
|
98
|
+
| `mcp` | `labctl mcp`: MCP server for AI tools | `mcp` SDK 2.x |
|
|
99
|
+
| `kasa` | TP-Link Kasa plugs and strips | `python-kasa` (0.12+ on Python 3.11+, 0.7.7 on 3.10: no KLAP v2) |
|
|
100
|
+
| `sdwire` | SDWire SD card muxes (`labctl sdwire ...`) | `sdwire`, Python 3.12+ only |
|
|
101
|
+
| `actuators` | USB relay boards (LCUS-1) | `pyserial` |
|
|
102
|
+
|
|
103
|
+
A command whose extra is missing says which one to install, e.g.
|
|
104
|
+
`pip install "embedded-lab-control[web]"`, or with pipx,
|
|
105
|
+
`pipx inject embedded-lab-control "flask>=3.0" "flask-socketio>=5.0"`.
|
|
106
|
+
|
|
107
|
+
labctl runs on built-in defaults; to configure it, start from
|
|
108
|
+
[`config/labctl.yaml.example`](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/config/labctl.yaml.example) (also in the
|
|
109
|
+
source distribution) saved as `~/.config/labctl/config.yaml` (see
|
|
110
|
+
[Config file search order](#config-file-search-order)).
|
|
111
|
+
|
|
112
|
+
The PyPI package is the CLI and services' code only. The shared,
|
|
113
|
+
system-wide install with systemd services (`scripts/install-services.sh`)
|
|
114
|
+
runs from a git checkout; see [Systemd Services](#systemd-services).
|
|
115
|
+
|
|
116
|
+
### Install from source
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# System packages
|
|
120
|
+
sudo apt install python3-venv ser2net
|
|
121
|
+
|
|
122
|
+
# Clone repository
|
|
123
|
+
git clone https://github.com/johnjezl/Embedded-Lab-Control.git
|
|
124
|
+
cd Embedded-Lab-Control
|
|
125
|
+
|
|
126
|
+
# Create virtual environment
|
|
127
|
+
python3 -m venv .venv
|
|
128
|
+
source .venv/bin/activate
|
|
129
|
+
|
|
130
|
+
# Install with the extras you need (see the table above)
|
|
131
|
+
pip install -e ".[web,mcp,kasa,sdwire,actuators]"
|
|
132
|
+
|
|
133
|
+
# Copy example config (a personal config; see "Config file search order")
|
|
134
|
+
mkdir -p ~/.config/labctl
|
|
135
|
+
cp config/labctl.yaml.example ~/.config/labctl/config.yaml
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Serial device names
|
|
139
|
+
|
|
140
|
+
Deterministic `/dev/lab/<name>` serial names come from udev rules generated
|
|
141
|
+
from the adapters you register:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
labctl serial discover # find adapters and their USB paths
|
|
145
|
+
labctl serial add board1-console 1-2.1 # name the adapter on USB path 1-2.1
|
|
146
|
+
labctl serial udev > 99-lab-serial.rules # generate the rules
|
|
147
|
+
cat 99-lab-serial.rules # check: rules, not a message
|
|
148
|
+
sudo install -m 644 99-lab-serial.rules /etc/udev/rules.d/
|
|
149
|
+
sudo udevadm control --reload-rules && sudo udevadm trigger
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The devices are `root:dialout 0660`: join `dialout` to open them directly
|
|
153
|
+
(`sudo usermod -aG dialout $USER`, then log in again). On a system-wide
|
|
154
|
+
install, `install-services.sh` makes the rules file writable by the
|
|
155
|
+
`labctl` group (`root:labctl 0664`), and members of that group can update
|
|
156
|
+
it with `labctl serial udev --install --reload`, without `sudo`; on such a
|
|
157
|
+
host use that instead of the `sudo install` above, which resets the file
|
|
158
|
+
to `root:root 0644`. Prefer `--install` wherever you can write the file:
|
|
159
|
+
it refuses to remove or re-point a link a board claimed by someone else
|
|
160
|
+
uses, while installing a file by hand checks nothing, so look at
|
|
161
|
+
`labctl claims list` first.
|
|
162
|
+
|
|
163
|
+
[docs/HARDWARE_MAP.md](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/HARDWARE_MAP.md) walks through finding USB
|
|
164
|
+
paths, registering adapters, installing the rules and troubleshooting.
|
|
165
|
+
|
|
166
|
+
### Config file search order
|
|
167
|
+
|
|
168
|
+
labctl uses the first of these that loads. A file in the search path that
|
|
169
|
+
exists but can't be loaded (bad YAML or a bad value) is skipped with a
|
|
170
|
+
warning, and the next one is used; a file named with `-c` or
|
|
171
|
+
`$LABCTL_CONFIG` is refused instead. `labctl web` and `labctl mcp --http`
|
|
172
|
+
refuse to start on any config that didn't load.
|
|
173
|
+
|
|
174
|
+
1. `-c/--config PATH` (only that file)
|
|
175
|
+
2. `$LABCTL_CONFIG`
|
|
176
|
+
3. `~/.config/labctl/config.yaml` (or `$XDG_CONFIG_HOME/labctl/config.yaml`)
|
|
177
|
+
4. `/etc/labctl/config.yaml`
|
|
178
|
+
5. Built-in defaults
|
|
179
|
+
|
|
180
|
+
> **Warning:** a personal `~/.config/labctl/config.yaml` wins over the shared
|
|
181
|
+
> `/etc/labctl/config.yaml`. On a system install, a personal config left over
|
|
182
|
+
> from a personal install silently points labctl at a different database than the
|
|
183
|
+
> services use. Remove it, or pass `-c /etc/labctl/config.yaml`. Either way,
|
|
184
|
+
> you must be in the `labctl` group: `install-services.sh` makes
|
|
185
|
+
> `/etc/labctl` readable only by it (`sudo usermod -aG labctl $USER`, then
|
|
186
|
+
> log in again). Without that, labctl can't read the shared config and falls
|
|
187
|
+
> back to the built-in defaults and a personal database. To write the
|
|
188
|
+
> services' database too, its file and directory must be writable by the
|
|
189
|
+
> group. A new install's database (`/var/lib/labctl/db/labctl.db`) is; move
|
|
190
|
+
> an older one there (see [Moving the database](#moving-the-database)), or
|
|
191
|
+
> run commands as `sudo -u labctl labctl ...`.
|
|
192
|
+
|
|
193
|
+
## Quick Start
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Add an SBC
|
|
197
|
+
labctl add rpi4 --description "Raspberry Pi 4"
|
|
198
|
+
|
|
199
|
+
# Assign its console serial port (type: console, jtag or debug)
|
|
200
|
+
labctl port assign rpi4 console /dev/lab/rpi4 --baud 115200
|
|
201
|
+
|
|
202
|
+
# Assign a power plug (Tasmota example)
|
|
203
|
+
labctl plug assign rpi4 tasmota 192.168.1.100
|
|
204
|
+
|
|
205
|
+
# View status
|
|
206
|
+
labctl status
|
|
207
|
+
|
|
208
|
+
# Connect to serial console
|
|
209
|
+
labctl console rpi4
|
|
210
|
+
|
|
211
|
+
# Control power
|
|
212
|
+
labctl power on rpi4
|
|
213
|
+
labctl power cycle rpi4 --delay 5
|
|
214
|
+
|
|
215
|
+
# Start web dashboard
|
|
216
|
+
labctl web --port 5000
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## CLI Reference
|
|
220
|
+
|
|
221
|
+
Every command has `--help`. This section lists the commands; see
|
|
222
|
+
[docs/OPERATIONS.md](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/OPERATIONS.md) for what each one changes.
|
|
223
|
+
|
|
224
|
+
### Global Options
|
|
225
|
+
|
|
226
|
+
| Option | Description |
|
|
227
|
+
|--------|-------------|
|
|
228
|
+
| `-v`, `--verbose` | Enable verbose (DEBUG) output |
|
|
229
|
+
| `-q`, `--quiet` | Suppress non-essential output |
|
|
230
|
+
| `-d`, `--delay <seconds>` | Wait before executing the command (put it before the command name) |
|
|
231
|
+
| `-c`, `--config <path>` | Use this config file only (see [search order](#config-file-search-order)) |
|
|
232
|
+
| `--version` | Show the version and exit |
|
|
233
|
+
|
|
234
|
+
### Common Flags
|
|
235
|
+
|
|
236
|
+
| Flag | Description |
|
|
237
|
+
|------|-------------|
|
|
238
|
+
| `-y`, `--yes` | Skip the confirmation on destructive commands. The question is asked only when stdin is a terminal, so cron, CI, pipes and agents are never prompted. `remove`, `power-all`, `sdwire remove`, `serial remove` and `actuator remove` also ask on a terminal, but without one they refuse unless `-y` is given. |
|
|
239
|
+
| `--override-claim` | Act on a board another user has claimed (or that shares hardware with one). The override is recorded in the activity log. Without it, such commands refuse. |
|
|
240
|
+
| `--dry-run` | On `power cycle`, `sdwire flash` and `sdwire update`: print the plan and change nothing |
|
|
241
|
+
|
|
242
|
+
### SBC Management
|
|
243
|
+
|
|
244
|
+
| Command | Description |
|
|
245
|
+
|---------|-------------|
|
|
246
|
+
| `labctl add <name>` | Add a new SBC |
|
|
247
|
+
| `labctl remove <name>` | Remove an SBC |
|
|
248
|
+
| `labctl list` | List all SBCs |
|
|
249
|
+
| `labctl info <name>` | Show SBC details |
|
|
250
|
+
| `labctl edit <name>` | Edit SBC properties (--rename, --project, etc.) |
|
|
251
|
+
| `labctl edit <name> --cycle-delay N` | Per-SBC off→on delay in seconds (0–86400); the default and minimum for every power cycle of the board: `power cycle`, the reboot after `sdwire flash`/`update`, `boot-test`, recovery, and MCP/REST/web cycles. Without one, 3 s is the default and minimum. Boards sharing a plug outlet are cycled together, so the largest of their minimums applies, a board without a stored delay counting as 3 s (negative clears it) |
|
|
252
|
+
| `labctl network set <sbc> ethernet\|wifi <ip>` | Set an SBC's IP address (`-m` MAC, `-h` hostname); `network remove` deletes it |
|
|
253
|
+
| `labctl ssh <sbc>` | SSH to the SBC's configured IP (`-u` user) |
|
|
254
|
+
| `labctl status` | Show status overview |
|
|
255
|
+
| `labctl status --watch` | Continuous status monitoring |
|
|
256
|
+
| `labctl status --fast` | Skip live power probes; show the monitor daemon's cached power state |
|
|
257
|
+
| `labctl export [-f yaml\|json] [-o file]` | Export SBCs with their ports, network addresses, plugs and SDWires, plus serial adapters |
|
|
258
|
+
| `labctl import <file> [--update]` | Import an export file (skips existing SBCs unless `--update`) |
|
|
259
|
+
|
|
260
|
+
### Activity Stream
|
|
261
|
+
|
|
262
|
+
| Command | Description |
|
|
263
|
+
|---------|-------------|
|
|
264
|
+
| `labctl activity tail` | Show recent activity events |
|
|
265
|
+
| `labctl activity tail --follow` | Stream new activity events by polling the database |
|
|
266
|
+
| `labctl activity tail --actor <actor>` | Filter events by actor |
|
|
267
|
+
| `labctl activity tail --source <source>` | Filter events by source (`cli`, `mcp`, `api`, `web`, `daemon`, `internal`) |
|
|
268
|
+
| `labctl activity tail --result <result>` | Filter events by result (`ok`, `error`, `forbidden`) |
|
|
269
|
+
| `labctl activity tail --sbc <name>` | Filter events for one SBC |
|
|
270
|
+
| `labctl activity export --format ndjson` | Export activity events as NDJSON |
|
|
271
|
+
|
|
272
|
+
### Claims (Exclusive Access Coordination)
|
|
273
|
+
|
|
274
|
+
| Command | Description |
|
|
275
|
+
|---------|-------------|
|
|
276
|
+
| `labctl claim <sbc> -r "<reason>" [-d 2h]` | Claim exclusive access to an SBC (`--reason` is required) |
|
|
277
|
+
| `labctl release <sbc>` | Release your active claim |
|
|
278
|
+
| `labctl renew <sbc>` | Extend your active claim |
|
|
279
|
+
| `labctl force-release <sbc> -r "<reason>"` | Override: release the active claim, whoever holds it (yours too) |
|
|
280
|
+
| `labctl request-release <sbc> -r "<reason>"` | Politely ask the claimant to release |
|
|
281
|
+
| `labctl claims list` | List all active claims |
|
|
282
|
+
| `labctl claims show <sbc>` | Show the active claim on one SBC |
|
|
283
|
+
| `labctl claims history <sbc>` | Show released claim history for one SBC |
|
|
284
|
+
| `labctl claims expire` | Run one expiry/dead-session sweep (also prunes old released claims) |
|
|
285
|
+
| `labctl claims stats` | Show aggregate claim statistics |
|
|
286
|
+
|
|
287
|
+
A claim's deadline moves each time you use the board. Long operations
|
|
288
|
+
keep a claim you already hold alive while they run (CLI `boot-test`; MCP
|
|
289
|
+
`boot_test`, `serial_capture`, `sdwire_update`, `flash_image`) and never
|
|
290
|
+
claim a board for you. A boot test stops between runs once your claim is
|
|
291
|
+
lost or someone else claims the board, reporting `Result: INCOMPLETE,
|
|
292
|
+
...` (or `Result: STOPPED before the first run ...`) instead of a pass
|
|
293
|
+
rate; claim it for the whole test.
|
|
294
|
+
|
|
295
|
+
### SDWire (SD Card Multiplexer)
|
|
296
|
+
|
|
297
|
+
| Command | Description |
|
|
298
|
+
|---------|-------------|
|
|
299
|
+
| `labctl sdwire discover` | Scan for connected SDWire devices |
|
|
300
|
+
| `labctl sdwire add <name> <serial>` | Register an SDWire device |
|
|
301
|
+
| `labctl sdwire remove <name>` | Unregister an SDWire device |
|
|
302
|
+
| `labctl sdwire list` | List all registered SDWire devices |
|
|
303
|
+
| `labctl sdwire assign <sbc> <device>` | Assign SDWire to an SBC |
|
|
304
|
+
| `labctl sdwire unassign <sbc>` | Remove SDWire assignment |
|
|
305
|
+
| `labctl sdwire dut <sbc>` | Switch SD card to SBC (boot from SD) |
|
|
306
|
+
| `labctl sdwire host <sbc>` | Switch SD card to host (for flashing). Only when the plug reports the board off: refuses when it's on, has no plug, or its state can't be read (`--force` skips the check) |
|
|
307
|
+
| `labctl sdwire flash <sbc> <image>` | Power off, flash full image to SD card, power-cycle (`--no-reboot` leaves the SBC off). A board with no power plug is refused unless `--no-plug-ok` (you've made sure it is off) |
|
|
308
|
+
| `labctl sdwire update <sbc> -p N -c src:dest -r old:new -d file` | Power off, then copy, rename, or delete files on a partition (`--reboot` to power-cycle; without it the SBC is left off). No plug: as `flash` (`--no-plug-ok`) |
|
|
309
|
+
| `labctl sdwire ls <sbc> -p N [--path /] [--recursive] [--force]` | List directory contents on a partition. A card already on the host is read in place; otherwise the board must be confirmed off, as for `sdwire host` (`--force` skips the check) |
|
|
310
|
+
| `labctl sdwire cat <sbc> -p N --path /file [--encoding text|base64|hex] [--force]` | Read a file from a partition (as `ls`) |
|
|
311
|
+
| `labctl sdwire info <sbc> [--force]` | Show partition table and filesystem metadata (as `ls`) |
|
|
312
|
+
|
|
313
|
+
Partition numbers are 1-based (e.g., `-p 1` for the first partition, which maps to `/dev/sdb1`).
|
|
314
|
+
`ls`, `cat` and `info` borrow the card: they switch it to the host and back,
|
|
315
|
+
and refuse while the SBC is powered on.
|
|
316
|
+
|
|
317
|
+
SDWire operations shell out (via `sudo`) to `mount`, `umount`, `dd`, `sync`,
|
|
318
|
+
`partprobe`, `parted` and `blkid`. They do not use `sd-mux-ctrl`, and they do
|
|
319
|
+
not require a fixed privileged mountpoint such as `/mnt/sdwire`.
|
|
320
|
+
|
|
321
|
+
To reduce friction for interactive use and MCP/service flows, add a narrowly
|
|
322
|
+
scoped sudoers rule. Use its own file: `scripts/install-services.sh` rewrites
|
|
323
|
+
`/etc/sudoers.d/labctl`.
|
|
324
|
+
```bash
|
|
325
|
+
echo '<user> ALL=(root) NOPASSWD: /usr/bin/mount, /usr/bin/umount, /usr/bin/dd, /bin/dd, /usr/bin/sync, /bin/sync, /usr/sbin/partprobe, /sbin/partprobe, /usr/sbin/parted, /sbin/parted, /usr/sbin/blkid, /sbin/blkid' | sudo tee /etc/sudoers.d/labctl-sdwire
|
|
326
|
+
sudo chmod 440 /etc/sudoers.d/labctl-sdwire
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Keep this scoped to the exact commands above. `NOPASSWD` sudo is still privileged;
|
|
330
|
+
the point here is to allow SDWire workflows without granting broader root access.
|
|
331
|
+
|
|
332
|
+
### Serial Device Management
|
|
333
|
+
|
|
334
|
+
| Command | Description |
|
|
335
|
+
|---------|-------------|
|
|
336
|
+
| `labctl serial discover` | Scan for connected USB-serial adapters |
|
|
337
|
+
| `labctl serial add <name> <usb_path>` | Register a USB-serial adapter |
|
|
338
|
+
| `labctl serial remove <name>` | Unregister an adapter (refused when a board someone else has claimed has a port on it; `--override-claim`) |
|
|
339
|
+
| `labctl serial list` | List all registered adapters |
|
|
340
|
+
| `labctl serial rename <name> <new>` | Rename an adapter |
|
|
341
|
+
| `labctl serial udev --install --reload` | Generate and apply udev rules (refused when they would remove or re-point a `/dev/lab` link a board someone else has claimed uses; `--override-claim`) |
|
|
342
|
+
| `labctl serial repair [--apply]` | Re-link port assignments to registered adapters (dry run by default) |
|
|
343
|
+
| `labctl serial capture <port> --timeout N --until <pattern>` | Capture serial output |
|
|
344
|
+
| `labctl serial send <port> <data> --capture N` | Send data (plus CRLF unless `--raw`) to a serial port |
|
|
345
|
+
| `labctl ser2net generate [--install] [--override-claim]` | Generate the ser2net config from the database. Without `--install`, ports with no TCP port are left out and the port each would get is shown; nothing is saved. `--install` first assigns and saves a TCP port for each such port (with `port assign`'s claim checks), then writes `ser2net.config_file` (default `/etc/ser2net.yaml`): atomically when labctl can create files in its directory (e.g. with sudo), otherwise rewritten in place. It refuses, saving and writing nothing, if such a port's board (or one sharing its console device) is claimed by another user, unless `--override-claim`; a port whose save fails is left out of the config and the command exits 1 |
|
|
346
|
+
| `labctl ser2net reload` | Restart the ser2net service |
|
|
347
|
+
|
|
348
|
+
### Boot Testing
|
|
349
|
+
|
|
350
|
+
| Command | Description |
|
|
351
|
+
|---------|-------------|
|
|
352
|
+
| `labctl boot-test <sbc> -i <image> -d <dest> -e <pattern> -r N` | Deploy a file to the SD card, then power-cycle N times and count boots that print the pattern |
|
|
353
|
+
| `labctl boot-test <sbc> --no-deploy -e <pattern> -r N` | Same, without deploying |
|
|
354
|
+
|
|
355
|
+
### Serial Console
|
|
356
|
+
|
|
357
|
+
| Command | Description |
|
|
358
|
+
|---------|-------------|
|
|
359
|
+
| `labctl port assign <sbc> <type> [device]` | Assign a `console`, `jtag` or `debug` port (`-a/--alias`, `-s/--serial-device`, `-t/--tcp-port`, `-b/--baud`) |
|
|
360
|
+
| `labctl port remove <sbc> <type>` | Remove a port assignment |
|
|
361
|
+
| `labctl port list` | List port assignments |
|
|
362
|
+
| `labctl port list --unassigned` | Show unassigned /dev/lab/* devices |
|
|
363
|
+
| `labctl ports [--all]` | List `/dev/lab` symlinks and their ser2net TCP ports; `--all` also lists USB serial devices (`/dev/ttyUSB*`, `/dev/ttyACM*`) no symlink points at |
|
|
364
|
+
| `labctl connect <alias\|sbc\|device>` | Connect by alias, SBC name, or device |
|
|
365
|
+
| `labctl console <sbc>` | Connect to serial console |
|
|
366
|
+
| `labctl log <sbc>` | Capture serial output to a log file until Ctrl+C (`-n N` to stop after N lines) |
|
|
367
|
+
| `labctl log <sbc> --follow` | Same, also echoing each line to the terminal |
|
|
368
|
+
|
|
369
|
+
### Power Control
|
|
370
|
+
|
|
371
|
+
| Command | Description |
|
|
372
|
+
|---------|-------------|
|
|
373
|
+
| `labctl plug assign <sbc> <type> <addr>` | Assign power plug (--index for strips) |
|
|
374
|
+
| `labctl plug remove <sbc>` | Remove the plug assignment |
|
|
375
|
+
| `labctl power on <sbc>` | Turn on (sets pre_power relays first; see below) |
|
|
376
|
+
| `labctl power off <sbc>` | Turn off |
|
|
377
|
+
| `labctl power cycle <sbc> [--delay N] [--dry-run]` | Off, wait, set pre_power relays, on. The delay is 0–86400 s (negative is an error); the default and minimum are the SBC's `--cycle-delay`, otherwise 3 s, or a larger one of a board sharing its plug outlet (smaller values are raised to it, with a warning naming that board). |
|
|
378
|
+
| `labctl power status <sbc>` | Check power state |
|
|
379
|
+
| `labctl power-all on/off` | Control all SBCs (claimed boards are skipped) |
|
|
380
|
+
|
|
381
|
+
### Relays, Bindings and Recovery
|
|
382
|
+
|
|
383
|
+
USB relays ("actuators") can drive board straps and buttons: recovery mode,
|
|
384
|
+
boot select, reset. An actuator channel is *bound* to an (SBC, purpose) pair.
|
|
385
|
+
Only the `lcus1_serial` driver is implemented so far; it needs the
|
|
386
|
+
`actuators` extra (pyserial).
|
|
387
|
+
|
|
388
|
+
| Command | Description |
|
|
389
|
+
|---------|-------------|
|
|
390
|
+
| `labctl actuator add <name> --driver lcus1_serial --device <path> --channels N` | Register a relay board |
|
|
391
|
+
| `labctl actuator list` / `probe <name>` / `remove <name>` | List, check reachability (read-only), or remove (with its bindings) |
|
|
392
|
+
| `labctl actuator set <name> <channel> open\|closed` | Drive a channel directly, for checking wiring |
|
|
393
|
+
| `labctl bind <sbc> <purpose> --actuator A --channel N --mode latch\|momentary [--phase pre-power]` | Bind a channel (`--pulse-ms` for momentary; `--active-when` for polarity) |
|
|
394
|
+
| `labctl unbind <sbc> <purpose>` | Remove a binding |
|
|
395
|
+
| `labctl bindings list [--target <sbc>]` / `status <sbc> <purpose>` | Show bindings and their desired vs. last state |
|
|
396
|
+
| `labctl bindings actuate\|release <sbc> <purpose>` | Assert or release a latch binding |
|
|
397
|
+
| `labctl bindings press <sbc> <purpose>` | Pulse a momentary binding |
|
|
398
|
+
| `labctl enter-recovery <sbc>` | Power off, wait, assert the `recovery_mode` binding, power on |
|
|
399
|
+
| `labctl exit-recovery <sbc>` | Same, releasing the strap for a normal boot |
|
|
400
|
+
|
|
401
|
+
**pre_power bindings** (`--phase pre-power`) are straps that must be in place
|
|
402
|
+
before the board gets power. Every power-on (`power on`, `power cycle`,
|
|
403
|
+
`power-all on`, the reboot after `sdwire flash`/`update`, `boot-test`, MCP and
|
|
404
|
+
REST) first checks those relays on the board and on every board sharing its
|
|
405
|
+
outlet. If a relay can't be reached, nothing is switched. labctl then drives
|
|
406
|
+
each relay to its desired state and only then applies power. If a strap fails
|
|
407
|
+
after a cycle's power-off, the boards are left off rather than booting
|
|
408
|
+
without it. There is no `--phase post-power` yet (bindings stored with it
|
|
409
|
+
by older versions are listed as inactive). When the
|
|
410
|
+
monitor daemon starts, it returns unheld relay channels to their default
|
|
411
|
+
state and leaves asserted bindings alone.
|
|
412
|
+
|
|
413
|
+
### Multi-Client Access
|
|
414
|
+
|
|
415
|
+
Use `labctl proxy start <sbc>` when several people need to watch the same
|
|
416
|
+
console. The proxy keeps viewers attached across normal SBC restarts and is
|
|
417
|
+
view-only unless you opt in with `--allow-write`. While a proxy is active,
|
|
418
|
+
`labctl connect` and `labctl console` refuse direct access for that SBC so
|
|
419
|
+
people do not silently bypass the shared session.
|
|
420
|
+
|
|
421
|
+
| Command | Description |
|
|
422
|
+
|---------|-------------|
|
|
423
|
+
| `labctl proxy start <sbc>` | Start a shared serial-console proxy for one SBC; reconnects across SBC restarts and is read-only by default (`--allow-write` to enable input, `--exit-on-disconnect` to stop instead) |
|
|
424
|
+
| `labctl proxy list` | Explain the current shared-console proxy mode |
|
|
425
|
+
| `labctl sessions <sbc>` | Show shared-console clients once proxy daemon mode exists |
|
|
426
|
+
|
|
427
|
+
### Health Monitoring
|
|
428
|
+
|
|
429
|
+
| Command | Description |
|
|
430
|
+
|---------|-------------|
|
|
431
|
+
| `labctl health-check` | Run all health checks |
|
|
432
|
+
| `labctl health-check --type ping` | Ping check only |
|
|
433
|
+
| `labctl health-check --sbc <name>` | Check single SBC |
|
|
434
|
+
| `labctl monitor` | Run the monitoring daemon in the foreground (`-i` interval, `--no-alerts`) |
|
|
435
|
+
| `labctl services status` | Show the state of `labctl-monitor`, `labctl-mcp`, `labctl-web` and `ser2net` (exit 1 if any is not running) |
|
|
436
|
+
|
|
437
|
+
### User Management
|
|
438
|
+
|
|
439
|
+
| Command | Description |
|
|
440
|
+
|---------|-------------|
|
|
441
|
+
| `labctl user hash-password` | Generate password hash for config |
|
|
442
|
+
| `labctl user generate-key` | Generate random API key |
|
|
443
|
+
| `labctl user add <username>` | Interactive user creation with YAML output |
|
|
444
|
+
| `labctl user verify <username>` | Verify password against config |
|
|
445
|
+
|
|
446
|
+
### MCP Server (AI Integration)
|
|
447
|
+
|
|
448
|
+
| Command | Description |
|
|
449
|
+
|---------|-------------|
|
|
450
|
+
| `labctl mcp` | Start MCP server (stdio transport) |
|
|
451
|
+
| `labctl mcp --http 8080` | Start MCP server (HTTP transport, binds 127.0.0.1) |
|
|
452
|
+
| `labctl mcp --http 8080 --host 0.0.0.0` | Listen on other interfaces (requires `auth.enabled` with API keys) |
|
|
453
|
+
| `labctl mcp --http 8080 --cert C --key K` | HTTP transport over TLS |
|
|
454
|
+
| `labctl mcp [--http 8080 ...] --check` | Run the startup checks and exit: whether clients need API keys, or why the server would refuse to start |
|
|
455
|
+
|
|
456
|
+
### Shell Completion
|
|
457
|
+
|
|
458
|
+
```bash
|
|
459
|
+
eval "$(labctl completion bash)" # or zsh; fish: labctl completion fish > ~/.config/fish/completions/labctl.fish
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Aliases
|
|
463
|
+
|
|
464
|
+
For convenience, common aliases are supported:
|
|
465
|
+
- `ls` → `list`
|
|
466
|
+
- `rm`, `delete` → `remove`
|
|
467
|
+
- `show` → `info`
|
|
468
|
+
- `on` → `power on`
|
|
469
|
+
- `off` → `power off`
|
|
470
|
+
|
|
471
|
+
## Configuration
|
|
472
|
+
|
|
473
|
+
Default config location: `~/.config/labctl/config.yaml`
|
|
474
|
+
|
|
475
|
+
System installs use `/etc/labctl/config.yaml`. That file contains credentials and
|
|
476
|
+
is intended to be group-readable by the `labctl` group, not world-readable. Any
|
|
477
|
+
user who should run `labctl` against the shared lab inventory must be added to
|
|
478
|
+
the `labctl` group and start a new login session afterward.
|
|
479
|
+
|
|
480
|
+
```yaml
|
|
481
|
+
serial:
|
|
482
|
+
dev_dir: /dev/lab # udev symlink directory
|
|
483
|
+
base_tcp_port: 4000 # lowest auto-assigned ser2net TCP port
|
|
484
|
+
default_baud: 115200
|
|
485
|
+
|
|
486
|
+
ser2net:
|
|
487
|
+
config_file: /etc/ser2net.yaml
|
|
488
|
+
enabled: true
|
|
489
|
+
|
|
490
|
+
# Authentication (disabled by default)
|
|
491
|
+
auth:
|
|
492
|
+
enabled: false
|
|
493
|
+
secret_key: "generate-a-random-string"
|
|
494
|
+
session_lifetime_minutes: 480
|
|
495
|
+
users:
|
|
496
|
+
- username: admin
|
|
497
|
+
password_hash: "generate with: labctl user hash-password"
|
|
498
|
+
api_key: "generate with: labctl user generate-key"
|
|
499
|
+
|
|
500
|
+
# HTTPS (disabled by default)
|
|
501
|
+
web:
|
|
502
|
+
cert_file: "/etc/labctl/ssl/cert.pem"
|
|
503
|
+
key_file: "/etc/labctl/ssl/key.pem"
|
|
504
|
+
|
|
505
|
+
# TP-Link Kasa credentials (for KLAP-authenticated devices)
|
|
506
|
+
kasa:
|
|
507
|
+
username: "your-tplink-email@example.com"
|
|
508
|
+
password: "your-tplink-password"
|
|
509
|
+
|
|
510
|
+
database_path: ~/.config/labctl/labctl.db
|
|
511
|
+
log_level: WARNING
|
|
512
|
+
|
|
513
|
+
# SQLite tuning. WAL journal mode is always on; this controls how long
|
|
514
|
+
# a transaction will wait for an exclusive lock before giving up.
|
|
515
|
+
database:
|
|
516
|
+
timeout_seconds: 10.0
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
> **Note on backups:** WAL mode creates `-wal` and `-shm` sidecar files
|
|
520
|
+
> next to `labctl.db`. Backup scripts should copy all three together,
|
|
521
|
+
> or quiesce the daemon and run `sqlite3 labctl.db 'PRAGMA wal_checkpoint(TRUNCATE)'`
|
|
522
|
+
> before copying just the main file.
|
|
523
|
+
|
|
524
|
+
## Web Interface
|
|
525
|
+
|
|
526
|
+
Start the web server:
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
labctl web --port 5000
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Access at `http://localhost:5000`
|
|
533
|
+
|
|
534
|
+
Features:
|
|
535
|
+
- Dashboard with SBC status overview
|
|
536
|
+
- Power control buttons
|
|
537
|
+
- Web-based serial console (xterm.js)
|
|
538
|
+
- Live `/activity` page backed by Server-Sent Events
|
|
539
|
+
- Claim badges on the dashboard and SBC detail pages; changes to a board
|
|
540
|
+
someone else has claimed are refused (operators may override, logged)
|
|
541
|
+
- Settings and configuration view
|
|
542
|
+
- Optional login authentication (enable `auth.enabled` in config)
|
|
543
|
+
- Optional HTTPS with SSL/TLS certificates
|
|
544
|
+
|
|
545
|
+
### Enabling HTTPS
|
|
546
|
+
|
|
547
|
+
labctl supports native HTTPS via SSL/TLS certificates. This is recommended when
|
|
548
|
+
authentication is enabled, to protect credentials in transit.
|
|
549
|
+
|
|
550
|
+
**1. Generate a self-signed certificate:**
|
|
551
|
+
|
|
552
|
+
```bash
|
|
553
|
+
sudo mkdir -p /etc/labctl/ssl
|
|
554
|
+
sudo openssl req -x509 -newkey rsa:4096 -nodes \
|
|
555
|
+
-keyout /etc/labctl/ssl/key.pem \
|
|
556
|
+
-out /etc/labctl/ssl/cert.pem \
|
|
557
|
+
-days 365 \
|
|
558
|
+
-subj "/CN=labctl"
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
To include additional hostnames or IPs (recommended for avoiding browser warnings
|
|
562
|
+
on your local network):
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
sudo openssl req -x509 -newkey rsa:4096 -nodes \
|
|
566
|
+
-keyout /etc/labctl/ssl/key.pem \
|
|
567
|
+
-out /etc/labctl/ssl/cert.pem \
|
|
568
|
+
-days 365 \
|
|
569
|
+
-subj "/CN=labctl" \
|
|
570
|
+
-addext "subjectAltName=DNS:labctl,DNS:tarrasque,DNS:tarrasque.local,IP:192.168.1.100"
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**2a. Use via CLI flags (ad-hoc):**
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
labctl web --cert /etc/labctl/ssl/cert.pem --key /etc/labctl/ssl/key.pem
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
**2b. Or configure in config.yaml (persistent, recommended for systemd):**
|
|
580
|
+
|
|
581
|
+
```yaml
|
|
582
|
+
web:
|
|
583
|
+
cert_file: "/etc/labctl/ssl/cert.pem"
|
|
584
|
+
key_file: "/etc/labctl/ssl/key.pem"
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
**3. Set permissions for the systemd service:**
|
|
588
|
+
|
|
589
|
+
```bash
|
|
590
|
+
sudo chown root:labctl /etc/labctl/ssl/key.pem
|
|
591
|
+
sudo chmod 640 /etc/labctl/ssl/key.pem
|
|
592
|
+
sudo systemctl restart labctl-web
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
Access at `https://localhost:5000`. Your browser will warn about the self-signed
|
|
596
|
+
certificate — this is expected for lab use.
|
|
597
|
+
|
|
598
|
+
## REST API
|
|
599
|
+
|
|
600
|
+
Base URL: `http://localhost:5000/api` (or `https://` when SSL is configured)
|
|
601
|
+
|
|
602
|
+
When authentication is enabled, API requests require an `X-API-Key` header:
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
curl -H "X-API-Key: your-api-key" https://localhost:5000/api/sbcs
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
For self-signed certificates, use `curl -k` or `--cacert cert.pem` to bypass
|
|
609
|
+
certificate verification:
|
|
610
|
+
|
|
611
|
+
```bash
|
|
612
|
+
curl -k -H "X-API-Key: your-api-key" https://localhost:5000/api/sbcs
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
Key activity and claims endpoints:
|
|
616
|
+
|
|
617
|
+
| Endpoint | Description |
|
|
618
|
+
|----------|-------------|
|
|
619
|
+
| `GET /api/activity` | Query recent activity events with `limit`, `actor`, `source`, `sbc`, `result`, `since`, and `after_id` filters |
|
|
620
|
+
| `GET /api/claims` | List all active claims |
|
|
621
|
+
| `GET /api/claims/<sbc>` | Show the current claim on one SBC |
|
|
622
|
+
| `GET /api/claims/<sbc>/history` | Show released claim history for one SBC |
|
|
623
|
+
| `POST /api/claims/<sbc>` | Create a claim (`duration_minutes`, `reason`, `agent_name`; with `claims.require_agent_name` and auth off, `agent_name` is required) |
|
|
624
|
+
| `POST /api/claims/<sbc>/renew` | Renew a claim held by the current caller |
|
|
625
|
+
| `POST /api/claims/<sbc>/release` | Release a claim held by the current caller |
|
|
626
|
+
| `POST /api/claims/<sbc>/force-release` | Operator override for an active claim |
|
|
627
|
+
| `POST /api/claims/<sbc>/request-release` | Record a polite release request for the current claimant |
|
|
628
|
+
|
|
629
|
+
| Endpoint | Method | Description |
|
|
630
|
+
|----------|--------|-------------|
|
|
631
|
+
| `/sbcs` | GET | List all SBCs |
|
|
632
|
+
| `/sbcs/<name>` | GET | Get SBC details |
|
|
633
|
+
| `/sbcs` | POST | Create SBC |
|
|
634
|
+
| `/sbcs/<name>` | PUT | Update SBC |
|
|
635
|
+
| `/sbcs/<name>` | DELETE | Remove SBC |
|
|
636
|
+
| `/sbcs/<name>/power` | GET | Get power state |
|
|
637
|
+
| `/sbcs/<name>/power` | POST | Control power (see below) |
|
|
638
|
+
| `/sbcs/<name>/ports` | POST | Assign a serial port |
|
|
639
|
+
| `/sbcs/<name>/console/info` | GET | Console connection info (TCP port, baud rate, and the proxy *settings* from config: `port_base`, write policy, client limit; not a running proxy's port) |
|
|
640
|
+
| `/sbcs/<name>/history` | GET | Status history |
|
|
641
|
+
| `/sbcs/<name>/uptime` | GET | Uptime statistics |
|
|
642
|
+
| `/ports` | GET | List all serial port assignments |
|
|
643
|
+
| `/status` | GET | Status overview of all SBCs |
|
|
644
|
+
| `/health/check` | GET, POST | Run a health check now (both methods; nothing is cached): pings, opens console ports and queries plugs |
|
|
645
|
+
| `/health` | GET | Service liveness and version (always open, no auth required) |
|
|
646
|
+
|
|
647
|
+
`POST /sbcs/<name>/power` takes `{"action": "on"|"off"|"cycle"}`, plus an
|
|
648
|
+
optional `delay` for a cycle (seconds, 0–86400; the default and the
|
|
649
|
+
minimum are the largest stored cycle delay among the boards on the plug
|
|
650
|
+
outlet, a board without one counting as 3.0) and
|
|
651
|
+
`"override_claim": true` to act on a board another user has claimed.
|
|
652
|
+
Power-on and cycle set the pre_power relays first
|
|
653
|
+
(see [Relays, Bindings and Recovery](#relays-bindings-and-recovery)). Responses:
|
|
654
|
+
|
|
655
|
+
| Status | Meaning |
|
|
656
|
+
|--------|---------|
|
|
657
|
+
| 200 | Done; the body includes the new `state` (and `delay_seconds` for a cycle) |
|
|
658
|
+
| 400 | Bad `action` or `delay`, an out-of-range stored cycle delay, or no plug assigned |
|
|
659
|
+
| 403 | An `agent`-role user tried `override_claim` (`not_operator`) |
|
|
660
|
+
| 404 | No such SBC |
|
|
661
|
+
| 409 | Refused with nothing switched: the board is claimed (`sbc_claimed`) or must be claimed first (`claim_required`), `on`/`off` while a cycle of the board's outlet is running, or a pre_power relay couldn't be checked or set before power-on (`{"error": ..., "switched": false}`) |
|
|
662
|
+
| 429 | A cycle requested while another cycle of the same outlet is running, or within 5 s of the outlet's last one |
|
|
663
|
+
| 500 | The plug failed, or a strap failed after a cycle's power-off (the board may be off) |
|
|
664
|
+
|
|
665
|
+
See [docs/OPERATIONS.md](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/OPERATIONS.md) for the full claim and REST
|
|
666
|
+
error codes.
|
|
667
|
+
|
|
668
|
+
## MCP Server (AI Integration)
|
|
669
|
+
|
|
670
|
+
labctl includes an MCP (Model Context Protocol) server for AI assistant integration.
|
|
671
|
+
This allows tools like Claude Desktop and Claude Code to manage lab resources directly.
|
|
672
|
+
|
|
673
|
+
Install the MCP dependency from a checkout (the `[mcp]` extra):
|
|
674
|
+
|
|
675
|
+
```bash
|
|
676
|
+
pip install -e ".[mcp]"
|
|
677
|
+
# or from PyPI: pip install 'embedded-lab-control[mcp]'
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
**Resources** (read-only data): `lab://sbcs`, `lab://sbcs/{name}`, `lab://power/{name}`,
|
|
681
|
+
`lab://serial-devices`, `lab://sdwire-devices`, `lab://ports`, `lab://health/{name}`,
|
|
682
|
+
`lab://status`, `lab://claims`, `lab://claims/{name}`, `lab://claims/history/{name}`,
|
|
683
|
+
`lab://claims/metrics`, `lab://activity/recent`, `lab://activity/{name}`
|
|
684
|
+
|
|
685
|
+
**Tools** (actions): `power_on`, `power_off`, `power_cycle`, `run_health_check`,
|
|
686
|
+
`add_sbc`, `remove_sbc`, `update_sbc`, `assign_serial_port`, `assign_power_plug`,
|
|
687
|
+
`set_network_address`, `sdwire_to_dut`, `sdwire_to_host`, `sdwire_update`,
|
|
688
|
+
`flash_image`, `serial_capture`, `serial_send`, `boot_test`, `list_images`,
|
|
689
|
+
`delete_image` (and more: see the docs)
|
|
690
|
+
|
|
691
|
+
**Uploads**: clients upload builds over HTTP to the MCP server
|
|
692
|
+
(`PUT /images/<name>`, same API key) and pass the bare name to
|
|
693
|
+
`flash_image`, `sdwire_update` or `boot_test`, so an agent can build on
|
|
694
|
+
another machine ([Uploading images](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/MCP_SERVER.md#uploading-images)).
|
|
695
|
+
|
|
696
|
+
**Prompts**: `debug_sbc` (guided SBC debugging), `lab_report` (comprehensive status)
|
|
697
|
+
|
|
698
|
+
See `docs/MCP_SERVER.md` for full tool parameters and usage.
|
|
699
|
+
|
|
700
|
+
**Safety:** destructive tools (power off, flashing, `serial_send`, deletions,
|
|
701
|
+
…) act only when called a second time with a confirmation token; host files
|
|
702
|
+
are reachable only under directories you allowlist in `mcp.allowed_*_paths`
|
|
703
|
+
(none by default); the HTTP transport binds loopback and requires the web
|
|
704
|
+
users' API keys (`Authorization: Bearer`) when `auth.enabled` is set. See
|
|
705
|
+
[Security Considerations](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/MCP_SERVER.md#security-considerations).
|
|
706
|
+
|
|
707
|
+
### Claude Desktop / Claude Code Configuration
|
|
708
|
+
|
|
709
|
+
For several Claude Code agents sharing the lab (each with its own API key,
|
|
710
|
+
so claims keep them apart), see
|
|
711
|
+
[Connecting Claude Code agents](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/CLAUDE_CODE_AGENTS.md).
|
|
712
|
+
|
|
713
|
+
For a single local client over stdio, add to your MCP client configuration
|
|
714
|
+
(reading `/etc/labctl/config.yaml` requires the `labctl` group; otherwise
|
|
715
|
+
point `-c` at your own config):
|
|
716
|
+
|
|
717
|
+
```json
|
|
718
|
+
{
|
|
719
|
+
"mcpServers": {
|
|
720
|
+
"labctl": {
|
|
721
|
+
"command": "/opt/labctl/venv/bin/labctl",
|
|
722
|
+
"args": ["-c", "/etc/labctl/config.yaml", "mcp"]
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
### Claude Code skill: /deploy-and-test
|
|
729
|
+
|
|
730
|
+
`skills/deploy-and-test.md` is an example skill that builds, deploys over
|
|
731
|
+
SDWire, power-cycles and checks the serial console using the labctl MCP
|
|
732
|
+
tools. It is not active in this repository. Claude Code loads a skill from a
|
|
733
|
+
`SKILL.md` file, with `name`/`description` frontmatter, inside a directory
|
|
734
|
+
named after the skill. To install it for one project (or for all your
|
|
735
|
+
projects, under `~/.claude/skills/`):
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
mkdir -p <your-project>/.claude/skills/deploy-and-test
|
|
739
|
+
cp skills/deploy-and-test.md <your-project>/.claude/skills/deploy-and-test/SKILL.md
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
It expects a connected labctl MCP server, and reads per-target settings from
|
|
743
|
+
`.claude/deploy-config.json`.
|
|
744
|
+
|
|
745
|
+
## Systemd Services
|
|
746
|
+
|
|
747
|
+
Install labctl system-wide with systemd services:
|
|
748
|
+
|
|
749
|
+
```bash
|
|
750
|
+
sudo ./scripts/install-services.sh
|
|
751
|
+
|
|
752
|
+
# Optional: MCP server over HTTP on 127.0.0.1:8080
|
|
753
|
+
sudo systemctl enable --now labctl-mcp
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
`install-services.sh` creates the `labctl` user and group and installs
|
|
757
|
+
labctl with the `web,mcp,kasa,sdwire` extras into `/opt/labctl/venv`, linked as
|
|
758
|
+
`/usr/local/bin/labctl`. It sets up `/etc/labctl/config.yaml` (root:labctl,
|
|
759
|
+
640) and `/var/lib/labctl`, and makes the udev rules file,
|
|
760
|
+
`/etc/ser2net.yaml` and the services' database writable by the `labctl`
|
|
761
|
+
group. A new config puts the database in `/var/lib/labctl/db` (setgid
|
|
762
|
+
2775, the file 664); if `database_path` names another file, the script
|
|
763
|
+
leaves it alone and prints how to move it there. It writes
|
|
764
|
+
`/etc/sudoers.d/labctl` for `udevadm` and `systemctl restart ser2net`.
|
|
765
|
+
It enables and starts `labctl-web` and `labctl-monitor`; `labctl-mcp` is
|
|
766
|
+
installed but not enabled. For USB relays, also install the `actuators` extra
|
|
767
|
+
into the venv (`sudo /opt/labctl/venv/bin/pip install "<checkout>[actuators]"`).
|
|
768
|
+
|
|
769
|
+
`labctl-mcp` runs `labctl mcp --http 8080`, which binds 127.0.0.1. To
|
|
770
|
+
reach it from other machines, enable `auth.enabled` with API keys and
|
|
771
|
+
override the command in a drop-in (`sudo systemctl edit labctl-mcp`). The
|
|
772
|
+
drop-in must clear `ExecStart` before setting it again:
|
|
773
|
+
|
|
774
|
+
```ini
|
|
775
|
+
[Service]
|
|
776
|
+
ExecStart=
|
|
777
|
+
ExecStart=/opt/labctl/venv/bin/labctl -c /etc/labctl/config.yaml mcp --http 8080 --host 0.0.0.0
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
To update an existing install after `git pull` (keeps config and database):
|
|
781
|
+
|
|
782
|
+
```bash
|
|
783
|
+
sudo ./scripts/update.sh
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
When the configured database is `/var/lib/labctl/db/labctl.db` (also when
|
|
787
|
+
written through a symlinked `/var/lib/labctl`), both scripts keep it (and
|
|
788
|
+
its directory) writable by the `labctl` group, and make sure group members
|
|
789
|
+
can traverse `/var/lib/labctl`: they add group search (`g+x`) when it
|
|
790
|
+
belongs to the `labctl` group, and warn when members still can't reach it
|
|
791
|
+
(e.g. it belongs to another group). Otherwise they print a note and leave
|
|
792
|
+
the database alone.
|
|
793
|
+
|
|
794
|
+
### Moving the database
|
|
795
|
+
|
|
796
|
+
Installs from before the `/var/lib/labctl/db` directory keep the database
|
|
797
|
+
elsewhere (e.g. `/var/lib/labctl/labctl.db` or
|
|
798
|
+
`/var/lib/labctl/.config/labctl/labctl.db`), which only the `labctl` user
|
|
799
|
+
can write. To let `labctl` group members write it from the CLI, move it:
|
|
800
|
+
|
|
801
|
+
Every step runs as root: your own account may not be able to see into
|
|
802
|
+
`/var/lib/labctl` at all.
|
|
803
|
+
|
|
804
|
+
```bash
|
|
805
|
+
sudo systemctl stop labctl-web labctl-monitor labctl-mcp
|
|
806
|
+
OLD=/var/lib/labctl/labctl.db # your current database_path (named labctl.db)
|
|
807
|
+
sudo install -d -o labctl -g labctl -m 2775 /var/lib/labctl/db
|
|
808
|
+
# A clean stop normally leaves no -wal/-shm; move them too if present.
|
|
809
|
+
sudo sh -c 'for f in "$1" "$1-wal" "$1-shm"; do
|
|
810
|
+
if [ -e "$f" ]; then mv -v "$f" /var/lib/labctl/db/; fi
|
|
811
|
+
done' sh "$OLD"
|
|
812
|
+
# Check the database arrived before going on: if it isn't there,
|
|
813
|
+
# update.sh creates a new, EMPTY database at the new path.
|
|
814
|
+
sudo ls -l /var/lib/labctl/db/labctl.db
|
|
815
|
+
# Set database_path: /var/lib/labctl/db/labctl.db, then copy the config to
|
|
816
|
+
# the service copy (update.sh refuses to run while the two differ):
|
|
817
|
+
sudoedit /etc/labctl/config.yaml
|
|
818
|
+
sudo -u labctl cp /etc/labctl/config.yaml /var/lib/labctl/.config/labctl/config.yaml
|
|
819
|
+
sudo ./scripts/update.sh # or install-services.sh: sets 2775/664, restarts
|
|
820
|
+
sudo systemctl start labctl-web labctl-monitor # and labctl-mcp if you use it
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
`update.sh` restarts the enabled services itself; the last line covers any
|
|
824
|
+
you started by hand. As a group member, a write such as
|
|
825
|
+
`labctl claim <sbc> -r test` then `labctl release <sbc>` should now work
|
|
826
|
+
without `sudo -u labctl`.
|
|
827
|
+
|
|
828
|
+
## Development
|
|
829
|
+
|
|
830
|
+
```bash
|
|
831
|
+
# Run tests
|
|
832
|
+
pytest tests/ -v
|
|
833
|
+
|
|
834
|
+
# Format code
|
|
835
|
+
black src/ tests/
|
|
836
|
+
isort src/ tests/
|
|
837
|
+
|
|
838
|
+
# Lint
|
|
839
|
+
flake8 src/ tests/
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
## Documentation
|
|
843
|
+
|
|
844
|
+
- [Implementation Plan](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/IMPLEMENTATION.md) - Development roadmap and status
|
|
845
|
+
- [Mapping USB Serial Adapters](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/HARDWARE_MAP.md) - Stable `/dev/lab` names from USB paths
|
|
846
|
+
- [Decision Log](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/DECISIONS.md) - Architecture decisions
|
|
847
|
+
- [MCP Server](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/MCP_SERVER.md) - AI integration, security model
|
|
848
|
+
- [Claude Code Agents](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/CLAUDE_CODE_AGENTS.md) - One API key per agent, setup and launch
|
|
849
|
+
- [Operations](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/OPERATIONS.md) - What every command and tool changes
|
|
850
|
+
- [Security Policy](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/SECURITY.md) - Reporting vulnerabilities
|
|
851
|
+
- [Releasing](https://github.com/johnjezl/Embedded-Lab-Control/blob/main/docs/RELEASING.md) - Publishing a version to PyPI
|
|
852
|
+
|
|
853
|
+
## License
|
|
854
|
+
|
|
855
|
+
MIT
|