From 8761c98b502a0e440793be03c41fafa6b892bb8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:15:43 +0000 Subject: [PATCH 1/6] Initial plan From 758f51e6c24cde47d1e19300e3176982432fdcb4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:21:24 +0000 Subject: [PATCH 2/6] Add automatic podman socket detection and resilient Node-RED startup Co-authored-by: Stolas <610753+Stolas@users.noreply.github.com> --- startup.sh | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/startup.sh b/startup.sh index c278136..734b72d 100755 --- a/startup.sh +++ b/startup.sh @@ -46,6 +46,87 @@ read_var() { grep "^${1}=" "${ENV_FILE}" | cut -d'=' -f2- | tr -d '[:space:]' } +# ---------------------------------------------------------------------- +# --- PODMAN SOCKET DETECTION AND VALIDATION --- +# ---------------------------------------------------------------------- + +# --- Function to detect and validate the podman socket for the current user --- +# This function implements resilient socket detection to prevent Node-RED from crashing +# if the socket is missing or inaccessible. +detect_podman_socket() { + echo "" + echo "--- Podman Socket Detection ---" + + # Read the PODMAN_SOCKET_PATH from secrets.env if it exists + local configured_socket=$(read_var PODMAN_SOCKET_PATH 2>/dev/null || echo "") + + # Common socket paths to check (in priority order) + # 1. User-provided path from secrets.env + # 2. Rootless socket for current user (most common on modern systems) + # 3. Rootful system socket (less common for home setups) + local socket_candidates=() + + if [ -n "$configured_socket" ]; then + socket_candidates+=("$configured_socket") + fi + + # Dynamically detect rootless socket path for current user + local current_user_uid=$(id -u) + local rootless_socket="/run/user/${current_user_uid}/podman/podman.sock" + socket_candidates+=("$rootless_socket") + + # Add common rootful socket path + socket_candidates+=("/run/podman/podman.sock") + + echo "Searching for podman socket in the following locations:" + for candidate in "${socket_candidates[@]}"; do + echo " - ${candidate}" + done + echo "" + + # Try each candidate socket and validate it + for socket_path in "${socket_candidates[@]}"; do + # Check if the socket file exists + if [ ! -e "$socket_path" ]; then + echo " [SKIP] Socket not found: ${socket_path}" + continue + fi + + # Check if it's actually a socket file (not a regular file or directory) + if [ ! -S "$socket_path" ]; then + echo " [SKIP] Not a socket: ${socket_path}" + continue + fi + + # Check if the socket is readable + if [ ! -r "$socket_path" ]; then + echo " [SKIP] Socket not readable: ${socket_path}" + continue + fi + + # Socket exists, is a socket type, and is readable - this is our winner! + echo " [SUCCESS] Found valid podman socket: ${socket_path}" + echo "" + + # Export the detected socket path for use by other functions + DETECTED_PODMAN_SOCKET="$socket_path" + PODMAN_SOCKET_AVAILABLE="true" + return 0 + done + + # No valid socket found + echo " [WARNING] No valid podman socket detected." + echo " Node-RED will start WITHOUT podman/docker integration." + echo " To enable podman integration, ensure the podman socket is available:" + echo " - For rootless: systemctl --user enable --now podman.socket" + echo " - For rootful: systemctl enable --now podman.socket" + echo "" + + DETECTED_PODMAN_SOCKET="" + PODMAN_SOCKET_AVAILABLE="false" + return 1 +} + # --- Read variables for services and SMB mount --- FRIGATE_PORT=$(read_var FRIGATE_PORT) NODERED_PORT=$(read_var NODERED_PORT) @@ -738,6 +819,32 @@ run_service() { fi } +# ---------------------------------------------------------------------- +# --- DYNAMIC SERVICE COMMAND BUILDER --- +# ---------------------------------------------------------------------- + +# --- Function to build Node-RED command with or without socket mounting --- +# This function ensures Node-RED starts successfully even if the podman socket is missing. +# Decision path: +# 1. If socket detected and available -> mount it for full Docker/Podman integration +# 2. If socket missing or not usable -> start Node-RED without socket (limited functionality but no crash) +build_nodered_command() { + local base_cmd="podman run -d --name nodered --restart unless-stopped --network ${NETWORK_NAME} -p ${NODERED_PORT}:1880 -e TZ=${TZ} -v nodered_data:/data --security-opt label=disable --user root" + + # If podman socket is available, add socket mounting and DOCKER_HOST environment variable + if [ "$PODMAN_SOCKET_AVAILABLE" == "true" ] && [ -n "$DETECTED_PODMAN_SOCKET" ]; then + echo " [INFO] Node-RED will start with Podman socket integration: ${DETECTED_PODMAN_SOCKET}" >&2 + base_cmd="${base_cmd} -e DOCKER_HOST=unix:///var/run/docker.sock -v ${DETECTED_PODMAN_SOCKET}:/var/run/docker.sock:ro" + else + echo " [INFO] Node-RED will start WITHOUT Podman socket integration (limited functionality)" >&2 + fi + + # Add the container image at the end + base_cmd="${base_cmd} docker.io/nodered/node-red:latest" + + echo "$base_cmd" +} + # --- Service Definitions (For run_service and manual starts) --- declare -A SERVICE_CMDS SERVICE_CMDS[mosquitto]="podman run -d --name mosquitto --restart unless-stopped --network ${NETWORK_NAME} -p 1883:1883 -p 9001:9001 -v mosquitto_data:/mosquitto/data -v ./mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf:ro docker.io/eclipse-mosquitto:latest" @@ -745,7 +852,8 @@ SERVICE_CMDS[influxdb]="podman run -d --name influxdb --restart unless-stopped - SERVICE_CMDS[zigbee2mqtt]="podman run -d --name zigbee2mqtt --restart unless-stopped --network ${NETWORK_NAME} -p 8080:8080 -e MQTT_SERVER=mqtt://mosquitto -e MQTT_USER=${MQTT_USER} -e MQTT_PASSWORD=${MQTT_PASSWORD} -e TZ=${TZ} -v z2m_data:/app/data --device ${ZIGBEE_DEVICE_PATH}:/dev/zigbee --cap-add NET_ADMIN --cap-add SYS_ADMIN docker.io/koenkk/zigbee2mqtt:latest" SERVICE_CMDS[frigate]="podman run -d --name frigate --restart unless-stopped --network ${NETWORK_NAME} --privileged -e TZ=${TZ} -p ${FRIGATE_PORT}:5000/tcp -p 1935:1935 -v ${FRIGATE_RECORDINGS_HOST_PATH}:/media/frigate:rw -v ./frigate_config.yml:/config/config.yml:ro -v /etc/localtime:/etc/localtime:ro --shm-size 256m ghcr.io/blakeblackshear/frigate:stable" SERVICE_CMDS[grafana]="podman run -d --name grafana --restart unless-stopped --network ${NETWORK_NAME} -p 3000:3000 -v grafana_data:/var/lib/grafana -e GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USER} -e GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD} -e GF_SECURITY_SECRET_KEY=${GRAFANA_SECRET_KEY} docker.io/grafana/grafana:latest" -SERVICE_CMDS[nodered]="podman run -d --name nodered --restart unless-stopped --network ${NETWORK_NAME} -p ${NODERED_PORT}:1880 -e TZ=${TZ} -e DOCKER_HOST=unix:///var/run/docker.sock -v nodered_data:/data -v ${PODMAN_SOCKET_PATH}:/var/run/docker.sock:ro --security-opt label=disable --user root docker.io/nodered/node-red:latest" +# Note: Node-RED command is built dynamically by build_nodered_command() based on socket availability +SERVICE_CMDS[nodered]="" # Will be populated dynamically during startup SERVICE_CMDS[nginx]="podman run -d --name nginx --restart unless-stopped --network ${NETWORK_NAME} --add-host=host.containers.internal:host-gateway -p 80:80 --security-opt label=disable -v ${PWD}/nginx/nginx.conf:/etc/nginx/nginx.conf:ro -v nginx_cache:/var/cache/nginx docker.io/library/nginx:alpine" SERVICE_CMDS[doubletake]="podman run -d --name doubletake --restart unless-stopped --network ${NETWORK_NAME} -p 3001:3000 -v doubletake_data:/.storage -e TZ=${TZ} docker.io/jakowenko/double-take:latest" SERVICE_NAMES=(mosquitto influxdb zigbee2mqtt frigate grafana nodered nginx doubletake) @@ -778,6 +886,13 @@ start_manual_service() { # Ensure the network is up (critical prerequisite) podman network exists "${NETWORK_NAME}" || podman network create "${NETWORK_NAME}" + + # If starting Node-RED manually, detect socket and build command dynamically + if [ "$SERVICE_NAME" == "nodered" ]; then + detect_podman_socket + SERVICE_CMDS[nodered]=$(build_nodered_command) + fi + # Only mount SMB if the service needs it (i.e., frigate) if [ "$SERVICE_NAME" == "frigate" ]; then mount_smb_share @@ -802,6 +917,14 @@ setup_system() { # Check for first run and handle configuration check_first_run + # Detect and validate podman socket before starting services + # This prevents Node-RED from crashing if socket is missing + detect_podman_socket + + # Build Node-RED command dynamically based on socket availability + # This must be done after socket detection and before services start + SERVICE_CMDS[nodered]=$(build_nodered_command) + # Get the stack configuration local stack_type=$(read_stack_config) From 73519a9d362cabb6ed84c04b607dbad67bf499b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:23:30 +0000 Subject: [PATCH 3/6] Address code review feedback - add global variable declarations and error handling Co-authored-by: Stolas <610753+Stolas@users.noreply.github.com> --- startup.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/startup.sh b/startup.sh index 734b72d..bd08aed 100755 --- a/startup.sh +++ b/startup.sh @@ -30,6 +30,9 @@ VOLUME_LIST=( ) # Array to track the startup status of each service declare -A SERVICE_STATUS +# Global variables for podman socket detection (populated by detect_podman_socket function) +DETECTED_PODMAN_SOCKET="" +PODMAN_SOCKET_AVAILABLE="false" echo "--- IoT SCADA Stack Management Script (Resilient Raw Podman) ---" echo "Using environment file: ${ENV_FILE}" @@ -891,6 +894,12 @@ start_manual_service() { if [ "$SERVICE_NAME" == "nodered" ]; then detect_podman_socket SERVICE_CMDS[nodered]=$(build_nodered_command) + + # Fallback: If command building somehow failed, use a minimal safe command + if [ -z "${SERVICE_CMDS[nodered]}" ]; then + echo "WARNING: Failed to build Node-RED command dynamically. Using fallback command without socket." + SERVICE_CMDS[nodered]="podman run -d --name nodered --restart unless-stopped --network ${NETWORK_NAME} -p ${NODERED_PORT}:1880 -e TZ=${TZ} -v nodered_data:/data --security-opt label=disable --user root docker.io/nodered/node-red:latest" + fi fi # Only mount SMB if the service needs it (i.e., frigate) @@ -925,6 +934,12 @@ setup_system() { # This must be done after socket detection and before services start SERVICE_CMDS[nodered]=$(build_nodered_command) + # Fallback: If command building somehow failed, use a minimal safe command + if [ -z "${SERVICE_CMDS[nodered]}" ]; then + echo "WARNING: Failed to build Node-RED command dynamically. Using fallback command without socket." + SERVICE_CMDS[nodered]="podman run -d --name nodered --restart unless-stopped --network ${NETWORK_NAME} -p ${NODERED_PORT}:1880 -e TZ=${TZ} -v nodered_data:/data --security-opt label=disable --user root docker.io/nodered/node-red:latest" + fi + # Get the stack configuration local stack_type=$(read_stack_config) From c14b3c855d0f437c2ee6b173bf7a7650ffd50e51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:50:02 +0000 Subject: [PATCH 4/6] Update documentation and secrets file to reflect automatic socket detection Co-authored-by: Stolas <610753+Stolas@users.noreply.github.com> --- README.md | 11 +++++------ secrets.env-example | 22 ++++++++++++++++++---- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1e1d737..de11ccc 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This project was 99% developed by AI assistants (Gemini and GitHub Copilot). The * **Security:** Uses `create_secrets.sh` to generate unique, random, 64-character passwords/tokens for sensitive environment variables. * **External Storage:** Includes logic to mount an **SMB/CIFS** share for Frigate recordings on the host machine. * **Resilience:** The `startup.sh` script continues running even if individual service starts fail, providing a complete status report. Nginx automatically adapts to only proxy running services. +* **Automatic Podman Socket Detection:** Node-RED automatically detects and uses the podman socket for docker/container integration. If the socket is unavailable, Node-RED starts in standalone mode without crashing. ## System Requirements @@ -105,12 +106,10 @@ The script will ask you to choose between: After the automatic setup, you must manually edit the `secrets.env` file to configure: * `ZIGBEE_DEVICE_PATH` - Update with the path to your Zigbee adapter (e.g., `/dev/ttyACM0` or `/dev/serial/by-id/...`) -* `PODMAN_SOCKET_PATH` - Update for Node-RED integration. On modern Podman/Leap Micro systems, this is typically: - -```bash -PODMAN_SOCKET_PATH=/run/user/$(id -u)/podman/podman.sock -``` - +* `PODMAN_SOCKET_PATH` - **OPTIONAL** for Node-RED integration. The startup script automatically detects the podman socket for the current user. If you need to specify a custom path, uncomment and update this variable. Common paths: + * Rootless (recommended): `/run/user/$(id -u)/podman/podman.sock` + * Rootful: `/run/podman/podman.sock` + * **Note**: If the socket is not found, Node-RED will still start successfully but without podman/docker integration capabilities. * Other site-specific variables like `TZ` (timezone), `SMB_SERVER`, `SMB_SHARE`, `SMB_USER` (if using NVR), etc. * Nginx reverse proxy hostnames: `BASE_DOMAIN`, `GRAFANA_HOSTNAME`, `FRIGATE_HOSTNAME`, `NODERED_HOSTNAME`, `ZIGBEE2MQTT_HOSTNAME`, `COCKPIT_HOSTNAME`, `DOUBLETAKE_HOSTNAME` diff --git a/secrets.env-example b/secrets.env-example index afb0903..3800e07 100644 --- a/secrets.env-example +++ b/secrets.env-example @@ -1,9 +1,23 @@ # GENERAL HOST CONFIGURATION -# Rootful (system service): /run/podman/podman.sock -# Rootless (user service, common on OpenSUSE Micro): /run/user/$(id -u)/podman/podman.sock - -PODMAN_SOCKET_PATH=/run/podman/podman.sock +# PODMAN SOCKET PATH (OPTIONAL - Auto-detected if not specified) +# +# The startup script automatically detects the podman socket for Node-RED integration. +# If this variable is not set or the socket is not found, Node-RED will still start +# successfully but without podman/docker integration (limited functionality). +# +# Common socket locations: +# - Rootful (system service): /run/podman/podman.sock +# - Rootless (user service, recommended): /run/user/$(id -u)/podman/podman.sock +# +# Auto-detection priority: +# 1. Path specified here (if set) +# 2. /run/user/$(id -u)/podman/podman.sock (detected dynamically) +# 3. /run/podman/podman.sock (system-wide socket) +# +# You can leave this commented out to use automatic detection, or specify a custom path: +# PODMAN_SOCKET_PATH=/run/podman/podman.sock +PODMAN_SOCKET_PATH=/run/user/1000/podman/podman.sock # Zigbee Adapter Path From 3ad0ffa3cd0afde715ffb31e73ba44c60100891a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:11:54 +0000 Subject: [PATCH 5/6] Fix socket detection to properly handle permission denied errors Co-authored-by: Stolas <610753+Stolas@users.noreply.github.com> --- secrets.env-example | 10 ++++++---- startup.sh | 11 +++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/secrets.env-example b/secrets.env-example index 3800e07..1f9fd4b 100644 --- a/secrets.env-example +++ b/secrets.env-example @@ -11,13 +11,15 @@ # - Rootless (user service, recommended): /run/user/$(id -u)/podman/podman.sock # # Auto-detection priority: -# 1. Path specified here (if set) -# 2. /run/user/$(id -u)/podman/podman.sock (detected dynamically) +# 1. Path specified here (if set and accessible) +# 2. /run/user/$(id -u)/podman/podman.sock (detected dynamically for current user) # 3. /run/podman/podman.sock (system-wide socket) # -# You can leave this commented out to use automatic detection, or specify a custom path: +# RECOMMENDED: Leave this commented out to use automatic detection for your user: +# PODMAN_SOCKET_PATH=/run/user/$(id -u)/podman/podman.sock +# +# Only uncomment and set a custom path if you have a non-standard setup: # PODMAN_SOCKET_PATH=/run/podman/podman.sock -PODMAN_SOCKET_PATH=/run/user/1000/podman/podman.sock # Zigbee Adapter Path diff --git a/startup.sh b/startup.sh index bd08aed..385bad3 100755 --- a/startup.sh +++ b/startup.sh @@ -101,13 +101,20 @@ detect_podman_socket() { continue fi - # Check if the socket is readable + # Check if the socket is readable (basic permission check) if [ ! -r "$socket_path" ]; then echo " [SKIP] Socket not readable: ${socket_path}" continue fi - # Socket exists, is a socket type, and is readable - this is our winner! + # More robust permission check: try to stat the socket + # This catches cases where -r passes but actual access fails + if ! stat "$socket_path" >/dev/null 2>&1; then + echo " [SKIP] Socket not accessible (permission denied): ${socket_path}" + continue + fi + + # Socket exists, is a socket type, and is accessible - this is our winner! echo " [SUCCESS] Found valid podman socket: ${socket_path}" echo "" From 89796212280ec19d6ccb0b5cfd5a540ffb2621c1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 24 Nov 2025 18:54:51 +0000 Subject: [PATCH 6/6] Add extra validation to prevent empty socket mount in Node-RED command Co-authored-by: Stolas <610753+Stolas@users.noreply.github.com> --- startup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/startup.sh b/startup.sh index 385bad3..09e4c99 100755 --- a/startup.sh +++ b/startup.sh @@ -842,7 +842,8 @@ build_nodered_command() { local base_cmd="podman run -d --name nodered --restart unless-stopped --network ${NETWORK_NAME} -p ${NODERED_PORT}:1880 -e TZ=${TZ} -v nodered_data:/data --security-opt label=disable --user root" # If podman socket is available, add socket mounting and DOCKER_HOST environment variable - if [ "$PODMAN_SOCKET_AVAILABLE" == "true" ] && [ -n "$DETECTED_PODMAN_SOCKET" ]; then + # Use strict validation to prevent empty or invalid socket paths from being used + if [ "$PODMAN_SOCKET_AVAILABLE" == "true" ] && [ -n "$DETECTED_PODMAN_SOCKET" ] && [ -e "$DETECTED_PODMAN_SOCKET" ]; then echo " [INFO] Node-RED will start with Podman socket integration: ${DETECTED_PODMAN_SOCKET}" >&2 base_cmd="${base_cmd} -e DOCKER_HOST=unix:///var/run/docker.sock -v ${DETECTED_PODMAN_SOCKET}:/var/run/docker.sock:ro" else