feat: remote commands, systemd units, process observability, broker auth split

- Command intake (reboot/shutdown) on infoscreen/{uuid}/commands with ack lifecycle
- MQTT_USER/MQTT_PASSWORD_BROKER split from identity vars; configure_mqtt_security() updated
- infoscreen-simclient.service: Type=notify, WatchdogSec=60, Restart=on-failure
- infoscreen-notify-failure@.service + script: retained MQTT alert when systemd gives up (Gap 3)
- _sd_notify() watchdog keepalive in simclient main loop (Gap 1)
- broker_connection block in health payload: reconnect_count, last_disconnect_at (Gap 2)
- COMMAND_MOCK_REBOOT_IMMEDIATE_COMPLETE canary flag with safety guard
- SERVER_TEAM_ACTIONS.md: server-side integration action items
- Docs: README, CHANGELOG, src/README, copilot-instructions updated
- 43 tests passing
This commit is contained in:
RobbStarkAustria
2026-04-05 08:36:50 +02:00
parent 82f43f75ba
commit 0cd0d95612
28 changed files with 2487 additions and 36 deletions

View File

@@ -36,6 +36,31 @@ fi
BROKER="${MQTT_BROKER:-localhost}"
PORT="${MQTT_PORT:-1883}"
MQTT_USERNAME="${MQTT_USERNAME:-}"
MQTT_PASSWORD="${MQTT_PASSWORD:-}"
MQTT_TLS_ENABLED="${MQTT_TLS_ENABLED:-0}"
MQTT_TLS_CA_CERT="${MQTT_TLS_CA_CERT:-}"
MQTT_TLS_CERT="${MQTT_TLS_CERT:-}"
MQTT_TLS_KEY="${MQTT_TLS_KEY:-}"
MQTT_TLS_INSECURE="${MQTT_TLS_INSECURE:-0}"
MQTT_AUTH_ARGS=()
MQTT_TLS_ARGS=()
if [[ -n "$MQTT_USERNAME" ]]; then
MQTT_AUTH_ARGS+=( -u "$MQTT_USERNAME" )
fi
if [[ -n "$MQTT_PASSWORD" ]]; then
MQTT_AUTH_ARGS+=( -P "$MQTT_PASSWORD" )
fi
if [[ "$MQTT_TLS_ENABLED" == "1" || "$MQTT_TLS_ENABLED" == "true" || "$MQTT_TLS_ENABLED" == "yes" ]]; then
[[ -n "$MQTT_TLS_CA_CERT" ]] && MQTT_TLS_ARGS+=( --cafile "$MQTT_TLS_CA_CERT" )
[[ -n "$MQTT_TLS_CERT" ]] && MQTT_TLS_ARGS+=( --cert "$MQTT_TLS_CERT" )
[[ -n "$MQTT_TLS_KEY" ]] && MQTT_TLS_ARGS+=( --key "$MQTT_TLS_KEY" )
if [[ "$MQTT_TLS_INSECURE" == "1" || "$MQTT_TLS_INSECURE" == "true" || "$MQTT_TLS_INSECURE" == "yes" ]]; then
MQTT_TLS_ARGS+=( --insecure )
fi
fi
# ── Read runtime IDs ─────────────────────────────────────────────────────────
GROUP_ID_FILE="$PROJECT_ROOT/src/config/last_group_id.txt"
@@ -107,7 +132,7 @@ EOF
echo -e "${YELLOW}Publishing to: $topic${NC}"
echo "$payload" | python3 -m json.tool 2>/dev/null || echo "$payload"
echo ""
mosquitto_pub -h "$BROKER" -p "$PORT" -t "$topic" -q 1 --retain -m "$payload"
mosquitto_pub -h "$BROKER" -p "$PORT" "${MQTT_AUTH_ARGS[@]}" "${MQTT_TLS_ARGS[@]}" -t "$topic" -q 1 --retain -m "$payload"
echo -e "${GREEN}Published (retained, QoS 1)${NC}"
echo "intent_id: $intent_id"
}
@@ -119,7 +144,7 @@ clear_intent() {
echo -e "${RED}No group_id found.${NC}"
return 1
fi
mosquitto_pub -h "$BROKER" -p "$PORT" -t "$topic" -q 1 --retain --null-message
mosquitto_pub -h "$BROKER" -p "$PORT" "${MQTT_AUTH_ARGS[@]}" "${MQTT_TLS_ARGS[@]}" -t "$topic" -q 1 --retain --null-message
echo -e "${GREEN}Retained intent cleared from broker${NC}"
}
@@ -167,7 +192,7 @@ subscribe_power_state() {
echo -e "${YELLOW}Subscribing to: $topic${NC}"
echo "(Ctrl-C to stop)"
echo ""
mosquitto_sub -h "$BROKER" -p "$PORT" -t "$topic" | \
mosquitto_sub -h "$BROKER" -p "$PORT" "${MQTT_AUTH_ARGS[@]}" "${MQTT_TLS_ARGS[@]}" -t "$topic" | \
python3 -c "
import sys, json
for line in sys.stdin:
@@ -216,7 +241,7 @@ while true; do
echo -e "${RED}No group_id.${NC}"
else
TOPIC="$(group_topic)"
mosquitto_pub -h "$BROKER" -p "$PORT" -t "$TOPIC" -q 1 --retain \
mosquitto_pub -h "$BROKER" -p "$PORT" "${MQTT_AUTH_ARGS[@]}" "${MQTT_TLS_ARGS[@]}" -t "$TOPIC" -q 1 --retain \
-m '{"schema_version":"1.0","desired_state":"on"}'
echo -e "${YELLOW}⚠ Malformed intent published - client must reject with 'missing required field' in log${NC}"
fi