|
Bogdan Timofte
authored
2 weeks ago
|
1
|
#!/usr/bin/env bash
|
|
|
2
|
set -euo pipefail
|
|
|
3
|
|
|
|
4
|
# Deploy a Node-RED node folder to a remote host and restart Node-RED
|
|
|
5
|
# Usage: ./deploy.sh <node-folder-path> [remote_user@host|local]
|
|
|
6
|
# Example: ./deploy.sh adapters/smart-socket/homebus-adapter node-red@192.168.2.104
|
|
|
7
|
#
|
|
|
8
|
# Known deploy targets:
|
|
|
9
|
# - testing: node-red@192.168.2.104
|
|
|
10
|
# - production: node-red@192.168.2.101
|
|
|
11
|
# - legacy: pi@192.168.2.133
|
|
|
12
|
|
|
|
13
|
REMOTE_DEFAULT="node-red@192.168.2.104"
|
|
|
14
|
REMOTE_NODERED_DIR="~/.node-red"
|
|
|
15
|
RSYNC_OPTS=( -az --delete --exclude=node_modules --exclude=.git )
|
|
|
16
|
|
|
|
17
|
# Parse arguments and flags.
|
|
|
18
|
# Usage: ./deploy.sh <node-folder-path> [remote_user@host|local] [--restart]
|
|
|
19
|
RESTART=false
|
|
|
20
|
POS=()
|
|
|
21
|
|
|
|
22
|
while [[ ${#@} -gt 0 ]]; do
|
|
|
23
|
case "$1" in
|
|
|
24
|
-h|--help)
|
|
|
25
|
cat <<EOF
|
|
|
26
|
Usage: $0 <node-folder-path> [remote_user@host|local] [--restart]
|
|
|
27
|
|
|
|
28
|
This will sync the folder at <node-folder-path> to the remote staging dir,
|
|
|
29
|
run npm install for that node inside ~/.node-red, and optionally restart Node-RED
|
|
|
30
|
when the --restart flag is provided.
|
|
|
31
|
|
|
|
32
|
Examples:
|
|
|
33
|
$0 presence-detector # deploy without restarting
|
|
|
34
|
$0 adapters/smart-socket/homebus-adapter --restart
|
|
|
35
|
$0 adapters/smart-socket/homebus-adapter pi@host
|
|
|
36
|
$0 adapters/contact-sensor/homekit-adapter local
|
|
|
37
|
$0 adapters/water-leak-sensor/homebus-adapter --restart pi@host
|
|
|
38
|
|
|
|
39
|
Notes:
|
|
|
40
|
- SSH access must be available to the remote host (use keys for passwordless login).
|
|
|
41
|
- The remote user must have sudo rights to run: sudo systemctl restart nodered (only required with --restart)
|
|
|
42
|
- Known targets:
|
|
|
43
|
testing -> node-red@192.168.2.104
|
|
|
44
|
production -> node-red@192.168.2.101
|
|
|
45
|
legacy -> pi@192.168.2.133
|
|
|
46
|
EOF
|
|
|
47
|
exit 0
|
|
|
48
|
;;
|
|
|
49
|
--restart)
|
|
|
50
|
RESTART=true
|
|
|
51
|
shift
|
|
|
52
|
;;
|
|
|
53
|
--*)
|
|
|
54
|
echo "Unknown option: $1" >&2
|
|
|
55
|
exit 3
|
|
|
56
|
;;
|
|
|
57
|
*)
|
|
|
58
|
POS+=("$1")
|
|
|
59
|
shift
|
|
|
60
|
;;
|
|
|
61
|
esac
|
|
|
62
|
done
|
|
|
63
|
|
|
|
64
|
# Validate positional args
|
|
|
65
|
if [[ ${#POS[@]} -lt 1 ]]; then
|
|
|
66
|
echo "Error: missing <node-folder-path>" >&2
|
|
|
67
|
echo "Run '$0 --help' for usage." >&2
|
|
|
68
|
exit 1
|
|
|
69
|
fi
|
|
|
70
|
|
|
|
71
|
NODE_PATH="${POS[0]}"
|
|
|
72
|
REMOTE_TARGET="${POS[1]:-$REMOTE_DEFAULT}"
|
|
|
73
|
LOCAL_DEPLOY=false
|
|
|
74
|
if [[ "$REMOTE_TARGET" == "local" ]]; then
|
|
|
75
|
LOCAL_DEPLOY=true
|
|
|
76
|
fi
|
|
|
77
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
78
|
|
|
|
79
|
# Resolve local path
|
|
|
80
|
if [[ -d "$NODE_PATH" ]]; then
|
|
|
81
|
LOCAL_PATH="$NODE_PATH"
|
|
|
82
|
elif [[ -d "$PWD/$NODE_PATH" ]]; then
|
|
|
83
|
LOCAL_PATH="$PWD/$NODE_PATH"
|
|
|
84
|
elif [[ -d "$SCRIPT_DIR/$NODE_PATH" ]]; then
|
|
|
85
|
LOCAL_PATH="$SCRIPT_DIR/$NODE_PATH"
|
|
|
86
|
else
|
|
|
87
|
LOCAL_PATH="$PWD/$NODE_PATH"
|
|
|
88
|
fi
|
|
|
89
|
if [[ ! -d "$LOCAL_PATH" ]]; then
|
|
|
90
|
echo "Error: local folder '$LOCAL_PATH' does not exist." >&2
|
|
|
91
|
echo "Checked: '$NODE_PATH', '$PWD/$NODE_PATH', '$SCRIPT_DIR/$NODE_PATH'" >&2
|
|
|
92
|
exit 2
|
|
|
93
|
fi
|
|
|
94
|
|
|
|
95
|
# Remote staging directory (copy here first, then npm install from it on the Pi)
|
|
|
96
|
# NOTE: staged path mirrors the relative adapter path, e.g. "~/incomming/adapters/smart-socket/homebus-adapter"
|
|
|
97
|
REMOTE_STAGING_DIR="~/incomming"
|
|
|
98
|
|
|
|
99
|
DEST_DIR="${REMOTE_STAGING_DIR%/}/$NODE_PATH"
|
|
|
100
|
|
|
|
101
|
if [[ "$LOCAL_DEPLOY" == "true" ]]; then
|
|
|
102
|
LOCAL_STAGING_DIR="$HOME/incomming"
|
|
|
103
|
LOCAL_DEST_DIR="${LOCAL_STAGING_DIR%/}/$NODE_PATH"
|
|
|
104
|
LOCAL_NODERED_DIR="$HOME/.node-red"
|
|
|
105
|
|
|
|
106
|
echo "Deploying node '$NODE_PATH' locally to ${LOCAL_DEST_DIR} (staging), then installing into ${LOCAL_NODERED_DIR}."
|
|
|
107
|
|
|
|
108
|
mkdir -p "${LOCAL_STAGING_DIR%/}"
|
|
|
109
|
mkdir -p "$(dirname "${LOCAL_DEST_DIR}")"
|
|
|
110
|
rm -rf "${LOCAL_DEST_DIR}"
|
|
|
111
|
mkdir -p "${LOCAL_DEST_DIR}"
|
|
|
112
|
if command -v rsync >/dev/null 2>&1; then
|
|
|
113
|
rsync "${RSYNC_OPTS[@]}" "$LOCAL_PATH/" "${LOCAL_DEST_DIR}/"
|
|
|
114
|
else
|
|
|
115
|
# Fallback when rsync is not available: copy with tar while excluding large/generated dirs.
|
|
|
116
|
tar --exclude='.git' --exclude='node_modules' -C "$LOCAL_PATH" -cf - . | tar -C "${LOCAL_DEST_DIR}" -xf -
|
|
|
117
|
fi
|
|
|
118
|
( cd "${LOCAL_NODERED_DIR%/}" && npm install --no-audit --no-fund "${LOCAL_STAGING_DIR%/}/$NODE_PATH" || true )
|
|
|
119
|
|
|
|
120
|
if [[ "$RESTART" == "true" ]]; then
|
|
|
121
|
echo "Restarting local Node-RED service (sudo may ask for a password)..."
|
|
|
122
|
sudo systemctl restart nodered
|
|
|
123
|
else
|
|
|
124
|
echo "Skipping Node-RED restart (use --restart to enable)."
|
|
|
125
|
fi
|
|
|
126
|
else
|
|
|
127
|
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."
|
|
|
128
|
if ! command -v rsync >/dev/null 2>&1; then
|
|
|
129
|
echo "Error: rsync is required for remote deploy mode but is not installed locally." >&2
|
|
|
130
|
echo "Use '$0 $NODE_PATH local' for local deploy, or install rsync." >&2
|
|
|
131
|
exit 4
|
|
|
132
|
fi
|
|
|
133
|
|
|
|
134
|
# Ensure staging dir exists on remote
|
|
|
135
|
ssh "$REMOTE_TARGET" "mkdir -p ${REMOTE_STAGING_DIR%/}"
|
|
|
136
|
ssh "$REMOTE_TARGET" "mkdir -p ${DEST_DIR%/*}"
|
|
|
137
|
|
|
|
138
|
# Sync node files to staging dir (omit node_modules and .git). Fallback to tar when remote rsync is missing.
|
|
|
139
|
if ssh "$REMOTE_TARGET" "command -v rsync >/dev/null 2>&1"; then
|
|
|
140
|
rsync "${RSYNC_OPTS[@]}" -e ssh "$LOCAL_PATH/" "$REMOTE_TARGET:$DEST_DIR/"
|
|
|
141
|
else
|
|
|
142
|
ssh "$REMOTE_TARGET" "rm -rf ${DEST_DIR} && mkdir -p ${DEST_DIR}"
|
|
|
143
|
tar --exclude='.git' --exclude='node_modules' -C "$LOCAL_PATH" -cf - . | ssh "$REMOTE_TARGET" "tar -C ${DEST_DIR} -xf -"
|
|
|
144
|
fi
|
|
|
145
|
|
|
|
146
|
# Run npm install on the remote (from within ~/.node-red using staged path)
|
|
|
147
|
ssh "$REMOTE_TARGET" "set -euo pipefail; cd ${REMOTE_NODERED_DIR%/} && npm install --no-audit --no-fund ${REMOTE_STAGING_DIR%/}/$NODE_PATH || true"
|
|
|
148
|
|
|
|
149
|
# Conditionally restart Node-RED (only if --restart was passed)
|
|
|
150
|
if [[ "$RESTART" == "true" ]]; then
|
|
|
151
|
echo "Restarting Node-RED on ${REMOTE_TARGET} (sudo may ask for a password)..."
|
|
|
152
|
ssh "$REMOTE_TARGET" "sudo systemctl restart nodered"
|
|
|
153
|
else
|
|
|
154
|
echo "Skipping Node-RED restart (use --restart to enable)."
|
|
|
155
|
fi
|
|
|
156
|
fi
|
|
|
157
|
|
|
|
158
|
echo "✅ Deploy finished: $NODE_PATH -> $REMOTE_TARGET"
|