Skip to content
This repository was archived by the owner on Jan 7, 2026. It is now read-only.

Commit a215055

Browse files
authored
Merge pull request #6 from Stolas/copilot/add-podman-socket-detection
Add automatic podman socket detection with graceful fallback for Node-RED
2 parents ecca42c + 8979621 commit a215055

3 files changed

Lines changed: 172 additions & 11 deletions

File tree

README.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This project was 99% developed by AI assistants (Gemini and GitHub Copilot). The
1515
* **Security:** Uses `create_secrets.sh` to generate unique, random, 64-character passwords/tokens for sensitive environment variables.
1616
* **External Storage:** Includes logic to mount an **SMB/CIFS** share for Frigate recordings on the host machine.
1717
* **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.
18+
* **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.
1819

1920
## System Requirements
2021

@@ -105,12 +106,10 @@ The script will ask you to choose between:
105106
After the automatic setup, you must manually edit the `secrets.env` file to configure:
106107

107108
* `ZIGBEE_DEVICE_PATH` - Update with the path to your Zigbee adapter (e.g., `/dev/ttyACM0` or `/dev/serial/by-id/...`)
108-
* `PODMAN_SOCKET_PATH` - Update for Node-RED integration. On modern Podman/Leap Micro systems, this is typically:
109-
110-
```bash
111-
PODMAN_SOCKET_PATH=/run/user/$(id -u)/podman/podman.sock
112-
```
113-
109+
* `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:
110+
* Rootless (recommended): `/run/user/$(id -u)/podman/podman.sock`
111+
* Rootful: `/run/podman/podman.sock`
112+
* **Note**: If the socket is not found, Node-RED will still start successfully but without podman/docker integration capabilities.
114113
* Other site-specific variables like `TZ` (timezone), `SMB_SERVER`, `SMB_SHARE`, `SMB_USER` (if using NVR), etc.
115114
* Nginx reverse proxy hostnames: `BASE_DOMAIN`, `GRAFANA_HOSTNAME`, `FRIGATE_HOSTNAME`, `NODERED_HOSTNAME`, `ZIGBEE2MQTT_HOSTNAME`, `COCKPIT_HOSTNAME`, `DOUBLETAKE_HOSTNAME`
116115

secrets.env-example

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
11
# GENERAL HOST CONFIGURATION
22

3-
# Rootful (system service): /run/podman/podman.sock
4-
# Rootless (user service, common on OpenSUSE Micro): /run/user/$(id -u)/podman/podman.sock
5-
6-
PODMAN_SOCKET_PATH=/run/podman/podman.sock
3+
# PODMAN SOCKET PATH (OPTIONAL - Auto-detected if not specified)
4+
#
5+
# The startup script automatically detects the podman socket for Node-RED integration.
6+
# If this variable is not set or the socket is not found, Node-RED will still start
7+
# successfully but without podman/docker integration (limited functionality).
8+
#
9+
# Common socket locations:
10+
# - Rootful (system service): /run/podman/podman.sock
11+
# - Rootless (user service, recommended): /run/user/$(id -u)/podman/podman.sock
12+
#
13+
# Auto-detection priority:
14+
# 1. Path specified here (if set and accessible)
15+
# 2. /run/user/$(id -u)/podman/podman.sock (detected dynamically for current user)
16+
# 3. /run/podman/podman.sock (system-wide socket)
17+
#
18+
# RECOMMENDED: Leave this commented out to use automatic detection for your user:
19+
# PODMAN_SOCKET_PATH=/run/user/$(id -u)/podman/podman.sock
20+
#
21+
# Only uncomment and set a custom path if you have a non-standard setup:
22+
# PODMAN_SOCKET_PATH=/run/podman/podman.sock
723

824
# Zigbee Adapter Path
925

startup.sh

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ VOLUME_LIST=(
3030
)
3131
# Array to track the startup status of each service
3232
declare -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

3437
echo "--- IoT SCADA Stack Management Script (Resilient Raw Podman) ---"
3538
echo "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 ---
50141
FRIGATE_PORT=$(read_var FRIGATE_PORT)
51142
NODERED_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) ---
742860
declare -A SERVICE_CMDS
743861
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"
744862
SERVICE_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"
745863
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"
746864
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"
747865
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"
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
749868
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"
750869
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"
751870
SERVICE_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

Comments
 (0)