Skip to content

Commit 48dee96

Browse files
feat(docs): configuration management system
changes: - file: __init__.py area: core added: [_inject_firstboot, build, build_image, _unmount_image, __init__, _set_hostname, +7 more] - file: test_e2e.py area: test added: [TestHostHealth, TestOtaAPI, TestWebSocketConnection, TestMetricsEndpoint, TestAuditAPI, shutil_which, +4 more] new_tests: 14 testing: new_tests: 14 scenarios: - info_endpoint - audit_log_empty - list_ota_jobs - enable_disable_rule - list_devices_empty - websocket_connect - alerts_status - websocket_ping_pong - metrics_endpoint - get_nonexistent_job # +4 more stats: lines: "+879/-173 (net +706)" files: 9 complexity: "Large structural change (normalized)"
1 parent 9d9beb7 commit 48dee96

13 files changed

Lines changed: 902 additions & 176 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
## [0.1.31] - 2026-02-24
2+
3+
### Summary
4+
5+
feat(docs): configuration management system
6+
7+
### Test
8+
9+
- update tests/test_e2e.py
10+
11+
### Other
12+
13+
- update Dockerfile.client
14+
- config: update dashboards.yml
15+
- update deploy/grafana/provisioning/dashboards/json/meshpi-overview.json
16+
- config: update datasources.yml
17+
- config: update prometheus.yml
18+
- update meshpi/image/__init__.py
19+
20+
121
## [0.1.30] - 2026-02-24
222

323
### Summary

Dockerfile

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,62 @@
1-
# ─────────────────────────────────────────────────────────────────
2-
# MeshPi HOST image
3-
# Runs: meshpi host (FastAPI + WebSocket + mDNS advertisement)
4-
#
5-
# Build: docker build -t meshpi-host -f docker/host/Dockerfile .
6-
# Run: docker run -p 7422:7422 meshpi-host
7-
# ─────────────────────────────────────────────────────────────────
8-
FROM python:3.12-slim-bookworm AS builder
1+
"""
2+
MeshPi Host Docker Image
93
10-
WORKDIR /build
11-
COPY pyproject.toml README.md LICENSE ./
12-
COPY meshpi/ ./meshpi/
4+
Usage:
5+
docker build -t meshpi-host .
6+
docker run -p 7422:7422 meshpi-host
137
14-
RUN pip install --no-cache-dir build && \
15-
python -m build --wheel && \
16-
ls dist/
8+
Environment Variables:
9+
MESHPI_PORT - Host port (default: 7422)
10+
MESHPI_BIND - Bind address (default: 0.0.0.0)
11+
MESHPI_CONFIG_DIR - Config directory (default: /app/config)
12+
"""
1713

14+
FROM python:3.11-slim
1815

19-
FROM python:3.12-slim-bookworm
20-
21-
LABEL maintainer="Softreck <info@softreck.dev>"
22-
LABEL description="MeshPi Host — encrypted RPi fleet configuration server"
16+
LABEL maintainer="MeshPi"
2317
LABEL version="0.2.0"
24-
LABEL license="Apache-2.0"
18+
LABEL description="MeshPi Host Service for Raspberry Pi Fleet Management"
19+
20+
# Set environment variables
21+
ENV PYTHONUNBUFFERED=1
22+
ENV PYTHONDONTWRITEBYTECODE=1
23+
ENV MESHPI_PORT=7422
24+
ENV MESHPI_BIND=0.0.0.0
25+
ENV MESHPI_CONFIG_DIR=/app/config
2526

26-
# Runtime dependencies only
27+
# Install system dependencies
2728
RUN apt-get update && apt-get install -y --no-install-recommends \
28-
iputils-ping \
29+
curl \
2930
avahi-daemon \
3031
avahi-utils \
3132
libnss-mdns \
32-
dbus \
3333
&& rm -rf /var/lib/apt/lists/*
3434

35+
# Create app directory
3536
WORKDIR /app
3637

37-
# Copy built wheel from builder stage
38-
COPY --from=builder /build/dist/*.whl /tmp/
39-
RUN pip install --no-cache-dir /tmp/*.whl "meshpi[llm]" 2>/dev/null || \
40-
pip install --no-cache-dir /tmp/*.whl && \
41-
rm /tmp/*.whl
38+
# Create non-root user
39+
RUN useradd -m -u 1000 meshpi && \
40+
mkdir -p /app/config /app/data && \
41+
chown -R meshpi:meshpi /app
4242

43-
# Create meshpi config directory
44-
RUN mkdir -p /root/.meshpi && chmod 700 /root/.meshpi
43+
# Install Python dependencies
44+
COPY pyproject.toml .
45+
RUN pip install --no-cache-dir -e . && \
46+
pip install --no-cache-dir prometheus-client pyyaml
4547

46-
# Copy entrypoint
47-
COPY docker/host/entrypoint.sh /entrypoint.sh
48-
RUN chmod +x /entrypoint.sh
48+
# Copy application code
49+
COPY --chown=meshpi:meshpi . .
4950

50-
# Config volume — mount your config.env here
51-
VOLUME ["/root/.meshpi"]
52-
53-
# Default environment
54-
ENV MESHPI_PORT=7422
55-
ENV MESHPI_BIND=0.0.0.0
56-
ENV PYTHONUNBUFFERED=1
51+
# Switch to non-root user
52+
USER meshpi
5753

54+
# Expose ports
5855
EXPOSE 7422
5956

60-
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
61-
CMD python -c "import httpx; httpx.get('http://localhost:7422/health', timeout=4).raise_for_status()"
57+
# Health check
58+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
59+
CMD curl -f http://localhost:7422/health || exit 1
6260

63-
ENTRYPOINT ["/entrypoint.sh"]
64-
CMD ["host"]
61+
# Run the host service
62+
CMD ["python", "-m", "meshpi.host"]

Dockerfile.client

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
"""
2+
MeshPi Client Docker Image
3+
4+
Simulates a Raspberry Pi client for E2E testing.
5+
"""
6+
7+
FROM python:3.11-slim
8+
9+
LABEL maintainer="MeshPi"
10+
LABEL description="MeshPi Client for E2E Testing"
11+
12+
ENV PYTHONUNBUFFERED=1
13+
ENV PYTHONDONTWRITEBYTECODE=1
14+
15+
WORKDIR /app
16+
17+
# Install dependencies
18+
COPY pyproject.toml .
19+
RUN pip install --no-cache-dir -e . && \
20+
pip install --no-cache-dir websockets pytest pytest-asyncio httpx
21+
22+
# Copy application code
23+
COPY . .
24+
25+
# Create client entrypoint script
26+
RUN echo '#!/bin/bash\n\
27+
set -e\n\
28+
echo "Starting MeshPi client $DEVICE_ID..."\n\
29+
echo "Connecting to $MESHPI_HOST_IP:$MESHPI_HOST_PORT"\n\
30+
python -c "\n\
31+
import asyncio\n\
32+
import json\n\
33+
import os\n\
34+
import websockets\n\
35+
\n\
36+
async def run_client():\n\
37+
host = os.environ.get(\"MESHPI_HOST_IP\", \"localhost\")\n\
38+
port = int(os.environ.get(\"MESHPI_HOST_PORT\", \"7422\"))\n\
39+
device_id = os.environ.get(\"DEVICE_ID\", \"rpi-test-001\")\n\
40+
\n\
41+
uri = f\"ws://{host}:{port}/ws/{device_id}\"\n\
42+
print(f\"Connecting to {uri}\")\n\
43+
\n\
44+
async with websockets.connect(uri) as ws:\n\
45+
# Send hello\n\
46+
await ws.send(json.dumps({\"type\": \"hello\", \"device_id\": device_id, \"address\": \"docker\"}))\n\
47+
print(\"Sent hello\")\n\
48+
\n\
49+
# Simulate diagnostics\n\
50+
import random\n\
51+
while True:\n\
52+
msg = await asyncio.wait_for(ws.recv(), timeout=60)\n\
53+
data = json.loads(msg)\n\
54+
print(f\"Received: {data.get(\\\"type\\\")}\")\n\
55+
\n\
56+
if data.get(\"type\") == \"welcome\":\n\
57+
# Start sending diagnostics\n\
58+
while True:\n\
59+
diag = {\n\
60+
\"type\": \"diagnostics\",\n\
61+
\"seq\": int(asyncio.get_event_loop().time()),\n\
62+
\"data\": {\n\
63+
\"cpu\": {\"load_1m\": round(random.uniform(0.5, 2.0), 2)},\n\
64+
\"memory\": {\"used_percent\": round(random.uniform(40, 80), 1)},\n\
65+
\"temperature\": {\"cpu_gpu\": round(random.uniform(40, 65), 1)},\n\
66+
\"wifi\": {\"signal\": random.randint(-80, -40)}\n\
67+
}\n\
68+
}\n\
69+
await ws.send(json.dumps(diag))\n\
70+
await asyncio.sleep(10)\n\
71+
\n\
72+
elif data.get(\"type\") == \"command\":\n\
73+
# Respond to commands\n\
74+
result = {\"success\": True, \"output\": \"Command executed\"}\n\
75+
await ws.send(json.dumps({\n\
76+
\"type\": \"command_result\",\n\
77+
\"command_id\": data.get(\"command_id\"),\n\
78+
\"result\": result\n\
79+
}))\n\
80+
\n\
81+
asyncio.run(run_client())\n\
82+
"\n\
83+
' > /app/client_entrypoint.sh && chmod +x /app/client_entrypoint.sh
84+
85+
# Run the client
86+
CMD ["/app/client_entrypoint.sh"]

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.30
1+
0.1.31
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
apiVersion: 1
2+
3+
providers:
4+
- name: 'MeshPi'
5+
orgId: 1
6+
folder: ''
7+
type: file
8+
disableDeletion: false
9+
updateIntervalSeconds: 30
10+
options:
11+
path: /etc/grafana/provisioning/dashboards/json

deploy/grafana/provisioning/dashboards/json/meshpi-overview.json

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
apiVersion: 1
2+
3+
datasources:
4+
- name: Prometheus
5+
type: prometheus
6+
access: proxy
7+
url: http://prometheus:9090
8+
isDefault: true
9+
editable: false

deploy/prometheus.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
global:
2+
scrape_interval: 15s
3+
evaluation_interval: 15s
4+
external_labels:
5+
monitor: 'meshpi-monitor'
6+
7+
scrape_configs:
8+
# MeshPi Host metrics
9+
- job_name: 'meshpi-host'
10+
static_configs:
11+
- targets: ['meshpi-host:7422']
12+
metrics_path: '/metrics'
13+
14+
# Prometheus self-monitoring
15+
- job_name: 'prometheus'
16+
static_configs:
17+
- targets: ['localhost:9090']

0 commit comments

Comments
 (0)