mynodes / deploy.sh
1 contributor
158 lines | 5.679kb
#!/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"