-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathdeploy.sh
More file actions
executable file
·220 lines (184 loc) · 7.3 KB
/
deploy.sh
File metadata and controls
executable file
·220 lines (184 loc) · 7.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#!/usr/bin/env bash
# deploy.sh — Orchestrates a full deployment: build artifacts, sync configs
# to VPS, and auto-restart services that need it.
#
# Usage:
# scripts/deploy.sh # Deploy everything (all claws)
# scripts/deploy.sh --instance <claw> # Deploy one claw only
# scripts/deploy.sh --force # Overwrite VPS configs
# scripts/deploy.sh --no-restart # Skip auto-restart (print warning instead)
# scripts/deploy.sh -n | --dry-run # Preview only (no transfers, no restart)
# scripts/deploy.sh -y | --yes # Skip confirmation prompt
#
# Steps:
# 1. npm run pre-deploy — build deployment artifacts
# 2. sync-deploy.sh — push stack artifacts + configs to VPS
# 3. Auto-restart — recreate services listed in .restart-required
#
# Workspace sync is separate — use sync-workspaces.sh directly.
# Does NOT handle fresh deploys (use sync-deploy.sh --fresh) or tagging
# (use tag-deploy.sh after verification).
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
source "$SCRIPT_DIR/lib/colors.sh"
# ── Parse args ────────────────────────────────────────────────────────────────
INSTANCE=""
FORCE=false
NO_RESTART=false
DRY_RUN=false
YES=false
while [[ $# -gt 0 ]]; do
case "$1" in
--instance) INSTANCE="$2"; shift 2 ;;
--force) FORCE=true; shift ;;
--no-restart) NO_RESTART=true; shift ;;
-n|--dry-run) DRY_RUN=true; shift ;;
-y|--yes) YES=true; shift ;;
--help|-h)
sed -n '2,/^[^#]/{ /^#/s/^# \?//p; }' "$0"
exit 0
;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
# ── Confirmation ──────────────────────────────────────────────────────────────
if ! $DRY_RUN && ! $YES; then
echo ""
echo -e "\033[1mDeploy overview:\033[0m"
if [ -n "$INSTANCE" ]; then
echo " Instance: ${INSTANCE}"
else
echo " Instance: all claws"
fi
echo " Steps: pre-deploy → sync configs → auto-restart"
$FORCE && echo " Force: yes (overwrite VPS configs)"
$NO_RESTART && echo " Restart: skip (manual restart required if needed)"
echo ""
read -r -p "Continue? [Y/n] " reply
case "${reply:-Y}" in
[yY]|[yY][eE][sS]|"") ;;
*) echo "Aborted."; exit 0 ;;
esac
fi
# ── Step 1: Build deployment artifacts ────────────────────────────────────────
header "Step 1/3: Build deployment artifacts"
if $DRY_RUN; then
info "Dry run — running pre-deploy:dry"
npm run --prefix "$REPO_ROOT" pre-deploy:dry
else
npm run --prefix "$REPO_ROOT" pre-deploy
fi
success "Artifacts built"
# ── Step 2: Sync deploy artifacts to VPS ──────────────────────────────────────
header "Step 2/3: Sync configs to VPS"
SYNC_ARGS=()
if [ -n "$INSTANCE" ]; then
SYNC_ARGS+=(--instance "$INSTANCE")
else
SYNC_ARGS+=(--all)
fi
$FORCE && SYNC_ARGS+=(--force)
$DRY_RUN && SYNC_ARGS+=(--dry-run)
sync_exit=0
"$SCRIPT_DIR/sync-deploy.sh" "${SYNC_ARGS[@]}" || sync_exit=$?
if [ "$sync_exit" -ne 0 ]; then
echo ""
err "Config sync failed (exit code ${sync_exit})"
echo ""
success "[1] pre-deploy — artifacts built in .deploy/"
err "[2] sync-deploy — config drift detected or sync error (see above)"
echo " [3] auto-restart — skipped"
echo ""
warn "To resolve: review openclaw/<claw>/openclaw.live-version.jsonc"
warn " then re-run with --force to overwrite, or merge changes into openclaw.jsonc"
exit "$sync_exit"
fi
success "Config sync complete"
# ── Step 3: Auto-restart if needed ────────────────────────────────────────────
header "Step 3/3: Service restart"
DEPLOY_DIR="${REPO_ROOT}/.deploy"
RESTART_FILE="${DEPLOY_DIR}/.restart-required"
if $DRY_RUN; then
info "Dry run — skipping restart check"
elif [ ! -f "$RESTART_FILE" ]; then
info "No restart required — all config changes are hot-reloadable"
else
source "$SCRIPT_DIR/lib/source-config.sh"
source "$SCRIPT_DIR/lib/ssh.sh"
PROJECT_NAME="${STACK__STACK__PROJECT_NAME:-openclaw-stack}"
# Parse .restart-required (format: "instance:key1,key2\n...")
restart_instances=""
all_restart_keys=""
services=""
while IFS=: read -r inst keys; do
[ -z "$inst" ] && continue
# If --instance is set, only restart that instance
if [ -n "$INSTANCE" ] && [ "$inst" != "$INSTANCE" ]; then
continue
fi
restart_instances="${restart_instances:+${restart_instances} }${inst}"
service="${PROJECT_NAME}-openclaw-${inst}"
services="${services:+${services} }${service}"
for k in $(echo "$keys" | tr ',' ' '); do
case ",$all_restart_keys," in
*",$k,"*) ;;
*) all_restart_keys="${all_restart_keys:+${all_restart_keys}, }${k}" ;;
esac
done
done < "$RESTART_FILE"
if [ -z "$services" ]; then
info "No restart required for selected instance(s)"
elif $NO_RESTART; then
warn "Restart required for: ${restart_instances}"
warn " Changed keys: ${all_restart_keys}"
warn " Skipped (--no-restart). Run manually:"
warn " sudo -u openclaw bash -c 'cd ${INSTALL_DIR} && docker compose up -d --force-recreate ${services}'"
else
info "Restarting services: ${services}"
info " Changed keys: ${all_restart_keys}"
${SSH_CMD} "${VPS}" "sudo -u openclaw bash -c 'cd ${INSTALL_DIR} && docker compose up -d --force-recreate ${services}'"
# Wait for health checks
info "Waiting for services to become healthy..."
TIMEOUT=300
ELAPSED=0
all_healthy=false
while ! $all_healthy; do
all_healthy=true
for service in $services; do
STATUS=$(${SSH_CMD} "${VPS}" \
"sudo docker inspect -f '{{.State.Health.Status}}' ${service} 2>/dev/null" 2>/dev/null || echo "unknown")
if [ "$STATUS" != "healthy" ]; then
all_healthy=false
break
fi
done
if $all_healthy; then
break
fi
if [ "$ELAPSED" -ge "$TIMEOUT" ]; then
warn "Services not healthy after ${TIMEOUT}s (last status: ${STATUS})"
warn "Check logs: ssh into VPS and run: sudo docker logs <service>"
# Don't clean up .restart-required on timeout so user can retry
exit 1
fi
sleep 5
ELAPSED=$((ELAPSED + 5))
printf ' Waiting... (%ds)\n' "$ELAPSED"
done
success "All services healthy"
# Clean up .restart-required after successful restart
rm -f "$RESTART_FILE"
fi
fi
# ── Summary ───────────────────────────────────────────────────────────────────
echo ""
if $DRY_RUN; then
echo "Dry run complete — no changes made."
else
success "Deploy complete"
echo ""
info "Next steps:"
info " 1. Verify: run § 7.1 checks"
info " 2. Tag: scripts/tag-deploy.sh \"description of changes\""
fi