#!/usr/bin/env bash set -euo pipefail # Deploy a Node-RED node folder to a remote host and restart Node-RED # Usage: ./deploy.sh [remote_user@host|local] # Example: ./deploy.sh adapters/smart-socket/homebus-adapter node-red@192.168.2.104 # # Known deploy targets: # - testing: node-red@192.168.2.104 # - production: node-red@192.168.2.101 # - legacy: pi@192.168.2.133 REMOTE_DEFAULT="node-red@192.168.2.104" REMOTE_NODERED_DIR="~/.node-red" RSYNC_OPTS=( -az --delete --exclude=node_modules --exclude=.git ) # Parse arguments and flags. # Usage: ./deploy.sh [remote_user@host|local] [--restart] RESTART=false POS=() while [[ ${#@} -gt 0 ]]; do case "$1" in -h|--help) cat < [remote_user@host|local] [--restart] This will sync the folder at to the remote staging dir, run npm install for that node inside ~/.node-red, and optionally restart Node-RED when the --restart flag is provided. Examples: $0 presence-detector # deploy without restarting $0 adapters/smart-socket/homebus-adapter --restart $0 adapters/smart-socket/homebus-adapter pi@host $0 adapters/contact-sensor/homekit-adapter local $0 adapters/water-leak-sensor/homebus-adapter --restart pi@host Notes: - SSH access must be available to the remote host (use keys for passwordless login). - The remote user must have sudo rights to run: sudo systemctl restart nodered (only required with --restart) - Known targets: testing -> node-red@192.168.2.104 production -> node-red@192.168.2.101 legacy -> pi@192.168.2.133 EOF exit 0 ;; --restart) RESTART=true shift ;; --*) echo "Unknown option: $1" >&2 exit 3 ;; *) POS+=("$1") shift ;; esac done # Validate positional args if [[ ${#POS[@]} -lt 1 ]]; then echo "Error: missing " >&2 echo "Run '$0 --help' for usage." >&2 exit 1 fi NODE_PATH="${POS[0]}" REMOTE_TARGET="${POS[1]:-$REMOTE_DEFAULT}" LOCAL_DEPLOY=false if [[ "$REMOTE_TARGET" == "local" ]]; then LOCAL_DEPLOY=true fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Resolve local path if [[ -d "$NODE_PATH" ]]; then LOCAL_PATH="$NODE_PATH" elif [[ -d "$PWD/$NODE_PATH" ]]; then LOCAL_PATH="$PWD/$NODE_PATH" elif [[ -d "$SCRIPT_DIR/$NODE_PATH" ]]; then LOCAL_PATH="$SCRIPT_DIR/$NODE_PATH" else LOCAL_PATH="$PWD/$NODE_PATH" fi if [[ ! -d "$LOCAL_PATH" ]]; then echo "Error: local folder '$LOCAL_PATH' does not exist." >&2 echo "Checked: '$NODE_PATH', '$PWD/$NODE_PATH', '$SCRIPT_DIR/$NODE_PATH'" >&2 exit 2 fi # Remote staging directory (copy here first, then npm install from it on the Pi) # NOTE: staged path mirrors the relative adapter path, e.g. "~/incomming/adapters/smart-socket/homebus-adapter" REMOTE_STAGING_DIR="~/incomming" DEST_DIR="${REMOTE_STAGING_DIR%/}/$NODE_PATH" if [[ "$LOCAL_DEPLOY" == "true" ]]; then LOCAL_STAGING_DIR="$HOME/incomming" LOCAL_DEST_DIR="${LOCAL_STAGING_DIR%/}/$NODE_PATH" LOCAL_NODERED_DIR="$HOME/.node-red" echo "Deploying node '$NODE_PATH' locally to ${LOCAL_DEST_DIR} (staging), then installing into ${LOCAL_NODERED_DIR}." mkdir -p "${LOCAL_STAGING_DIR%/}" mkdir -p "$(dirname "${LOCAL_DEST_DIR}")" rm -rf "${LOCAL_DEST_DIR}" mkdir -p "${LOCAL_DEST_DIR}" if command -v rsync >/dev/null 2>&1; then rsync "${RSYNC_OPTS[@]}" "$LOCAL_PATH/" "${LOCAL_DEST_DIR}/" else # Fallback when rsync is not available: copy with tar while excluding large/generated dirs. tar --exclude='.git' --exclude='node_modules' -C "$LOCAL_PATH" -cf - . | tar -C "${LOCAL_DEST_DIR}" -xf - fi ( cd "${LOCAL_NODERED_DIR%/}" && npm install --no-audit --no-fund "${LOCAL_STAGING_DIR%/}/$NODE_PATH" || true ) if [[ "$RESTART" == "true" ]]; then echo "Restarting local Node-RED service (sudo may ask for a password)..." sudo systemctl restart nodered else echo "Skipping Node-RED restart (use --restart to enable)." fi else echo "Deploying node '$NODE_PATH' to ${REMOTE_TARGET}:${DEST_DIR} (staging), then installing into ${REMOTE_NODERED_DIR}. Use --restart to restart Node-RED after install." if ! command -v rsync >/dev/null 2>&1; then echo "Error: rsync is required for remote deploy mode but is not installed locally." >&2 echo "Use '$0 $NODE_PATH local' for local deploy, or install rsync." >&2 exit 4 fi # Ensure staging dir exists on remote ssh "$REMOTE_TARGET" "mkdir -p ${REMOTE_STAGING_DIR%/}" ssh "$REMOTE_TARGET" "mkdir -p ${DEST_DIR%/*}" # Sync node files to staging dir (omit node_modules and .git). Fallback to tar when remote rsync is missing. if ssh "$REMOTE_TARGET" "command -v rsync >/dev/null 2>&1"; then rsync "${RSYNC_OPTS[@]}" -e ssh "$LOCAL_PATH/" "$REMOTE_TARGET:$DEST_DIR/" else ssh "$REMOTE_TARGET" "rm -rf ${DEST_DIR} && mkdir -p ${DEST_DIR}" tar --exclude='.git' --exclude='node_modules' -C "$LOCAL_PATH" -cf - . | ssh "$REMOTE_TARGET" "tar -C ${DEST_DIR} -xf -" fi # Run npm install on the remote (from within ~/.node-red using staged path) ssh "$REMOTE_TARGET" "set -euo pipefail; cd ${REMOTE_NODERED_DIR%/} && npm install --no-audit --no-fund ${REMOTE_STAGING_DIR%/}/$NODE_PATH || true" # Conditionally restart Node-RED (only if --restart was passed) if [[ "$RESTART" == "true" ]]; then echo "Restarting Node-RED on ${REMOTE_TARGET} (sudo may ask for a password)..." ssh "$REMOTE_TARGET" "sudo systemctl restart nodered" else echo "Skipping Node-RED restart (use --restart to enable)." fi fi echo "✅ Deploy finished: $NODE_PATH -> $REMOTE_TARGET"