@@ -30,6 +30,9 @@ VOLUME_LIST=(
3030)
3131# Array to track the startup status of each service
3232declare -A SERVICE_STATUS
33+ # Global variables for podman socket detection (populated by detect_podman_socket function)
34+ DETECTED_PODMAN_SOCKET=" "
35+ PODMAN_SOCKET_AVAILABLE=" false"
3336
3437echo " --- IoT SCADA Stack Management Script (Resilient Raw Podman) ---"
3538echo " Using environment file: ${ENV_FILE} "
@@ -46,6 +49,94 @@ read_var() {
4649 grep " ^${1} =" " ${ENV_FILE} " | cut -d' =' -f2- | tr -d ' [:space:]'
4750}
4851
52+ # ----------------------------------------------------------------------
53+ # --- PODMAN SOCKET DETECTION AND VALIDATION ---
54+ # ----------------------------------------------------------------------
55+
56+ # --- Function to detect and validate the podman socket for the current user ---
57+ # This function implements resilient socket detection to prevent Node-RED from crashing
58+ # if the socket is missing or inaccessible.
59+ detect_podman_socket () {
60+ echo " "
61+ echo " --- Podman Socket Detection ---"
62+
63+ # Read the PODMAN_SOCKET_PATH from secrets.env if it exists
64+ local configured_socket=$( read_var PODMAN_SOCKET_PATH 2> /dev/null || echo " " )
65+
66+ # Common socket paths to check (in priority order)
67+ # 1. User-provided path from secrets.env
68+ # 2. Rootless socket for current user (most common on modern systems)
69+ # 3. Rootful system socket (less common for home setups)
70+ local socket_candidates=()
71+
72+ if [ -n " $configured_socket " ]; then
73+ socket_candidates+=(" $configured_socket " )
74+ fi
75+
76+ # Dynamically detect rootless socket path for current user
77+ local current_user_uid=$( id -u)
78+ local rootless_socket=" /run/user/${current_user_uid} /podman/podman.sock"
79+ socket_candidates+=(" $rootless_socket " )
80+
81+ # Add common rootful socket path
82+ socket_candidates+=(" /run/podman/podman.sock" )
83+
84+ echo " Searching for podman socket in the following locations:"
85+ for candidate in " ${socket_candidates[@]} " ; do
86+ echo " - ${candidate} "
87+ done
88+ echo " "
89+
90+ # Try each candidate socket and validate it
91+ for socket_path in " ${socket_candidates[@]} " ; do
92+ # Check if the socket file exists
93+ if [ ! -e " $socket_path " ]; then
94+ echo " [SKIP] Socket not found: ${socket_path} "
95+ continue
96+ fi
97+
98+ # Check if it's actually a socket file (not a regular file or directory)
99+ if [ ! -S " $socket_path " ]; then
100+ echo " [SKIP] Not a socket: ${socket_path} "
101+ continue
102+ fi
103+
104+ # Check if the socket is readable (basic permission check)
105+ if [ ! -r " $socket_path " ]; then
106+ echo " [SKIP] Socket not readable: ${socket_path} "
107+ continue
108+ fi
109+
110+ # More robust permission check: try to stat the socket
111+ # This catches cases where -r passes but actual access fails
112+ if ! stat " $socket_path " > /dev/null 2>&1 ; then
113+ echo " [SKIP] Socket not accessible (permission denied): ${socket_path} "
114+ continue
115+ fi
116+
117+ # Socket exists, is a socket type, and is accessible - this is our winner!
118+ echo " [SUCCESS] Found valid podman socket: ${socket_path} "
119+ echo " "
120+
121+ # Export the detected socket path for use by other functions
122+ DETECTED_PODMAN_SOCKET=" $socket_path "
123+ PODMAN_SOCKET_AVAILABLE=" true"
124+ return 0
125+ done
126+
127+ # No valid socket found
128+ echo " [WARNING] No valid podman socket detected."
129+ echo " Node-RED will start WITHOUT podman/docker integration."
130+ echo " To enable podman integration, ensure the podman socket is available:"
131+ echo " - For rootless: systemctl --user enable --now podman.socket"
132+ echo " - For rootful: systemctl enable --now podman.socket"
133+ echo " "
134+
135+ DETECTED_PODMAN_SOCKET=" "
136+ PODMAN_SOCKET_AVAILABLE=" false"
137+ return 1
138+ }
139+
49140# --- Read variables for services and SMB mount ---
50141FRIGATE_PORT=$( read_var FRIGATE_PORT)
51142NODERED_PORT=$( read_var NODERED_PORT)
@@ -738,14 +829,42 @@ run_service() {
738829 fi
739830}
740831
832+ # ----------------------------------------------------------------------
833+ # --- DYNAMIC SERVICE COMMAND BUILDER ---
834+ # ----------------------------------------------------------------------
835+
836+ # --- Function to build Node-RED command with or without socket mounting ---
837+ # This function ensures Node-RED starts successfully even if the podman socket is missing.
838+ # Decision path:
839+ # 1. If socket detected and available -> mount it for full Docker/Podman integration
840+ # 2. If socket missing or not usable -> start Node-RED without socket (limited functionality but no crash)
841+ build_nodered_command () {
842+ 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"
843+
844+ # If podman socket is available, add socket mounting and DOCKER_HOST environment variable
845+ # Use strict validation to prevent empty or invalid socket paths from being used
846+ if [ " $PODMAN_SOCKET_AVAILABLE " == " true" ] && [ -n " $DETECTED_PODMAN_SOCKET " ] && [ -e " $DETECTED_PODMAN_SOCKET " ]; then
847+ echo " [INFO] Node-RED will start with Podman socket integration: ${DETECTED_PODMAN_SOCKET} " >&2
848+ base_cmd=" ${base_cmd} -e DOCKER_HOST=unix:///var/run/docker.sock -v ${DETECTED_PODMAN_SOCKET} :/var/run/docker.sock:ro"
849+ else
850+ echo " [INFO] Node-RED will start WITHOUT Podman socket integration (limited functionality)" >&2
851+ fi
852+
853+ # Add the container image at the end
854+ base_cmd=" ${base_cmd} docker.io/nodered/node-red:latest"
855+
856+ echo " $base_cmd "
857+ }
858+
741859# --- Service Definitions (For run_service and manual starts) ---
742860declare -A SERVICE_CMDS
743861SERVICE_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"
744862SERVICE_CMDS[influxdb]=" podman run -d --name influxdb --restart unless-stopped --network ${NETWORK_NAME} -p 8086:8086 -v influxdb_data:/var/lib/influxdb2 -e DOCKER_INFLUXDB_INIT_MODE=setup -e DOCKER_INFLUXDB_INIT_USERNAME=${INFLUXDB_ADMIN_USER} -e DOCKER_INFLUXDB_INIT_PASSWORD=${INFLUXDB_ADMIN_PASSWORD} -e DOCKER_INFLUXDB_INIT_ORG=${INFLUXDB_ORG} -e DOCKER_INFLUXDB_INIT_BUCKET=${INFLUXDB_BUCKET} -e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=${INFLUXDB_ADMIN_TOKEN} -e TZ=${TZ} docker.io/influxdb:2.7"
745863SERVICE_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"
746864SERVICE_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"
747865SERVICE_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"
748- 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"
866+ # Note: Node-RED command is built dynamically by build_nodered_command() based on socket availability
867+ SERVICE_CMDS[nodered]=" " # Will be populated dynamically during startup
749868SERVICE_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"
750869SERVICE_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"
751870SERVICE_NAMES=(mosquitto influxdb zigbee2mqtt frigate grafana nodered nginx doubletake)
@@ -778,6 +897,19 @@ start_manual_service() {
778897
779898 # Ensure the network is up (critical prerequisite)
780899 podman network exists " ${NETWORK_NAME} " || podman network create " ${NETWORK_NAME} "
900+
901+ # If starting Node-RED manually, detect socket and build command dynamically
902+ if [ " $SERVICE_NAME " == " nodered" ]; then
903+ detect_podman_socket
904+ SERVICE_CMDS[nodered]=$( build_nodered_command)
905+
906+ # Fallback: If command building somehow failed, use a minimal safe command
907+ if [ -z " ${SERVICE_CMDS[nodered]} " ]; then
908+ echo " WARNING: Failed to build Node-RED command dynamically. Using fallback command without socket."
909+ 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"
910+ fi
911+ fi
912+
781913 # Only mount SMB if the service needs it (i.e., frigate)
782914 if [ " $SERVICE_NAME " == " frigate" ]; then
783915 mount_smb_share
@@ -802,6 +934,20 @@ setup_system() {
802934 # Check for first run and handle configuration
803935 check_first_run
804936
937+ # Detect and validate podman socket before starting services
938+ # This prevents Node-RED from crashing if socket is missing
939+ detect_podman_socket
940+
941+ # Build Node-RED command dynamically based on socket availability
942+ # This must be done after socket detection and before services start
943+ SERVICE_CMDS[nodered]=$( build_nodered_command)
944+
945+ # Fallback: If command building somehow failed, use a minimal safe command
946+ if [ -z " ${SERVICE_CMDS[nodered]} " ]; then
947+ echo " WARNING: Failed to build Node-RED command dynamically. Using fallback command without socket."
948+ 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"
949+ fi
950+
805951 # Get the stack configuration
806952 local stack_type=$( read_stack_config)
807953
0 commit comments