1 contributor
#!/usr/bin/env bash
set -euo pipefail
# Deploy a Node-RED node folder to a remote host and restart Node-RED
# Usage: ./deploy.sh <node-folder-path> [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 <node-folder-path> [remote_user@host|local] [--restart]
RESTART=false
POS=()
while [[ ${#@} -gt 0 ]]; do
case "$1" in
-h|--help)
cat <<EOF
Usage: $0 <node-folder-path> [remote_user@host|local] [--restart]
This will sync the folder at <node-folder-path> 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 <node-folder-path>" >&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"