Skip to content
This repository was archived by the owner on Jan 7, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`

Expand Down
24 changes: 20 additions & 4 deletions secrets.env-example
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
# 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 and accessible)
# 2. /run/user/$(id -u)/podman/podman.sock (detected dynamically for current user)
# 3. /run/podman/podman.sock (system-wide socket)
#
# 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

# Zigbee Adapter Path

Expand Down
148 changes: 147 additions & 1 deletion startup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand All @@ -46,6 +49,94 @@ 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 (basic permission check)
if [ ! -r "$socket_path" ]; then
echo " [SKIP] Socket not readable: ${socket_path}"
continue
fi

# 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 ""

# 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)
Expand Down Expand Up @@ -738,14 +829,42 @@ 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
# 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
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"
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"
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)
Expand Down Expand Up @@ -778,6 +897,19 @@ 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)

# 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)
if [ "$SERVICE_NAME" == "frigate" ]; then
mount_smb_share
Expand All @@ -802,6 +934,20 @@ 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)

# 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)

Expand Down
Loading