Skip to content

Commit 78c094d

Browse files
committed
Added new Host bridge service for use with Admin UI
1 parent 0a555a0 commit 78c094d

4 files changed

Lines changed: 114 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Added new Host bridge service for use with Admin UI
13+
1014
### Changed
1115

1216
- Based on pi-gen `2025-10-01-raspios-trixie`

tree/stage2/07-sys-configurator/01-run.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,16 @@ on_chroot << EOF
1212
systemctl daemon-reload
1313
systemctl enable offspot-runtime.service
1414
EOF
15+
16+
on_chroot << EOF
17+
python3 -m venv /usr/local/mekhenet-python
18+
/usr/local/mekhenet-python/bin/pip install fastapi==0.121.0 uvicorn==0.38.0
19+
EOF
20+
21+
install -m 755 files/mekhenet.py "${ROOTFS_DIR}/usr/local/mekhenet-python/lib/python3.13/site-packages/"
22+
install -m 755 files/mekhenet.service "${ROOTFS_DIR}/etc/systemd/system/"
23+
24+
on_chroot << EOF
25+
systemctl daemon-reload
26+
systemctl enable mekhenet.service
27+
EOF
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""mekhenet - simple HTTP API to pass command from services to host via socket
2+
3+
curl --unix-socket /run/offspot/mekhenet.sock http://host/service-is-enabled/ssh
4+
"""
5+
6+
import subprocess
7+
from pathlib import Path
8+
from typing import Annotated
9+
10+
from fastapi import FastAPI
11+
from fastapi import Path as FastAPIPath
12+
13+
allowed_toggle_actions = ("enable", "disable")
14+
allowed_services = ("ssh",)
15+
systemctl_path = Path("/usr/bin/systemctl")
16+
17+
app = FastAPI()
18+
19+
20+
@app.get("/reboot/{after_seconds}")
21+
async def request_host_reboot(
22+
after_seconds: Annotated[
23+
int, FastAPIPath(title="Nb. of seconds after which to reboot")
24+
],
25+
):
26+
reboot = subprocess.run(
27+
[
28+
str(systemctl_path),
29+
"reboot",
30+
"--when",
31+
f"+{after_seconds!s}s",
32+
],
33+
check=False,
34+
)
35+
return {"success": reboot.returncode == 0}
36+
37+
38+
@app.get("/toggle-service/{action}/{name}")
39+
async def request_service_toggle(
40+
action: Annotated[str, FastAPIPath(title="Action to use (enable/disable)")],
41+
name: Annotated[str, FastAPIPath(title="Name of service to toggle")],
42+
):
43+
if action not in allowed_toggle_actions:
44+
return {
45+
"success": False,
46+
"details": f"Forbidden action. Only {', '.join(allowed_toggle_actions)}",
47+
}
48+
if name not in allowed_services:
49+
return {
50+
"success": False,
51+
"details": "Forbidden service.",
52+
}
53+
toggle = subprocess.run(
54+
[str(systemctl_path), action, name],
55+
check=False,
56+
)
57+
return {"success": toggle.returncode == 0}
58+
59+
60+
@app.get("/service-is-enabled/{name}")
61+
async def request_service_enabled(
62+
name: Annotated[str, FastAPIPath(title="Name of service to query")],
63+
):
64+
if name not in allowed_services:
65+
return {
66+
"success": False,
67+
"details": "Forbidden service.",
68+
}
69+
toggle = subprocess.run(
70+
[str(systemctl_path), "is-enabled", name],
71+
text=True,
72+
capture_output=True,
73+
check=False,
74+
)
75+
if toggle.stdout.strip() in ("enabled", "disabled"):
76+
return {"success": True, "enabled": toggle.stdout.strip() == "enabled"}
77+
78+
return {"success": False, "details": toggle.stdout.splitlines()[0].strip()}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[Unit]
2+
Description=Mekhenet Host/Admin bridge
3+
Requires=network.target internet-check.service
4+
After=network.target internet-check.service
5+
6+
[Service]
7+
ExecStart=/usr/local/mekhenet-python/bin/uvicorn --uds /run/offspot/mekhenet.sock --app-dir /usr/local/mekhenet-python/lib/python3.13/site-packages/ "mekhenet:app"
8+
9+
RuntimeDirectory=offspot
10+
Type=idle
11+
Restart=always
12+
TimeoutStartSec=10
13+
RestartSec=10
14+
StandardOutput=journal
15+
StandardError=journal
16+
17+
[Install]
18+
WantedBy=docker-compose.service multi-user.target
19+
RequiredBy=docker-compose.service

0 commit comments

Comments
 (0)