-
Notifications
You must be signed in to change notification settings - Fork 372
Expand file tree
/
Copy pathdeploy.sh
More file actions
497 lines (435 loc) · 16.3 KB
/
Copy pathdeploy.sh
File metadata and controls
497 lines (435 loc) · 16.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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
#!/bin/bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Deployment start time
DEPLOY_START=$(date +%s)
# Store current directory
ROOT_DIR=$(pwd)
# Defaults (override via env vars on the server if needed)
PM2_APP_ID=${PM2_APP_ID:-4}
# Parse command-line arguments
DEPLOY_FRONTEND=false
DEPLOY_CLIENT_PORTAL=false
DEPLOY_BACKEND=false
DEPLOY_ALL=false
SKIP_GIT_PULL=false
# Show usage
show_usage() {
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --all Deploy all components (default if no options specified)"
echo " --frontend Deploy frontend only"
echo " --client-portal Deploy client portal only"
echo " --backend Deploy backend only"
echo " --skip-git Skip git pull (useful for local testing)"
echo " -h, --help Show this help message"
echo ""
echo "Examples:"
echo " $0 # Deploy all components"
echo " $0 --all # Deploy all components"
echo " $0 --frontend # Deploy frontend only"
echo " $0 --frontend --backend # Deploy frontend and backend"
echo " $0 --backend --skip-git # Deploy backend without git pull"
echo ""
exit 0
}
# Parse arguments
if [ $# -eq 0 ]; then
DEPLOY_ALL=true
else
while [[ $# -gt 0 ]]; do
case $1 in
--all)
DEPLOY_ALL=true
shift
;;
--frontend)
DEPLOY_FRONTEND=true
shift
;;
--client-portal)
DEPLOY_CLIENT_PORTAL=true
shift
;;
--backend)
DEPLOY_BACKEND=true
shift
;;
--skip-git)
SKIP_GIT_PULL=true
shift
;;
-h|--help)
show_usage
;;
*)
echo -e "${RED}Unknown option: $1${NC}"
echo "Use --help for usage information"
exit 1
;;
esac
done
fi
# If --all is specified, enable all deployments
if [ "$DEPLOY_ALL" = true ]; then
DEPLOY_FRONTEND=true
DEPLOY_CLIENT_PORTAL=true
DEPLOY_BACKEND=true
fi
# Display deployment plan
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo -e "${BLUE}Starting Worklenz Deployment Process${NC}"
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo ""
echo -e "${BLUE}Deployment Plan:${NC}"
if [ "$DEPLOY_FRONTEND" = true ]; then
echo -e " ${GREEN}✓${NC} Frontend"
else
echo -e " ${YELLOW}⊘${NC} Frontend (skipped)"
fi
if [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
echo -e " ${GREEN}✓${NC} Client Portal"
else
echo -e " ${YELLOW}⊘${NC} Client Portal (skipped)"
fi
if [ "$DEPLOY_BACKEND" = true ]; then
echo -e " ${GREEN}✓${NC} Backend"
else
echo -e " ${YELLOW}⊘${NC} Backend (skipped)"
fi
if [ "$SKIP_GIT_PULL" = true ]; then
echo -e " ${YELLOW}⊘${NC} Git pull (skipped)"
fi
echo ""
# Function to handle errors
handle_error() {
local component=$1
local exit_code=$2
echo ""
echo -e "${RED}═══ ERROR ═══${NC}"
echo -e "${RED}Error in: ${component}${NC}"
echo -e "${RED}Exit code: ${exit_code}${NC}"
echo -e "${RED}Keeping maintenance mode active for safety${NC}"
echo -e "${RED}═════════════${NC}"
# Don't cleanup - keep maintenance mode active
trap - EXIT
exit 1
}
# Function to disable maintenance mode on successful exit only
cleanup() {
if [ $? -eq 0 ]; then
if [ "${MAINTENANCE_ENABLED:-false}" = true ]; then
echo -e "${YELLOW}Disabling maintenance mode...${NC}"
sudo rm -f /var/www/maintenance-mode
sudo nginx -s reload
echo -e "${GREEN}✅ Maintenance mode disabled${NC}"
fi
fi
}
# Set trap to cleanup only on successful exit
trap cleanup EXIT
MAINTENANCE_ENABLED=false
# Temp files for passing build artifact paths from parallel build jobs
DEPLOY_TMP_DIR="$(mktemp -d -t worklenz-deploy-XXXXXX)"
FRONTEND_BUILD_PATH_FILE="$DEPLOY_TMP_DIR/frontend-build-path"
CLIENT_PORTAL_BUILD_PATH_FILE="$DEPLOY_TMP_DIR/client-portal-build-path"
# 1. Pull latest changes
if [ "$SKIP_GIT_PULL" = false ]; then
echo -e "${YELLOW}▶ Pulling latest changes from git...${NC}"
git pull
if [ $? -ne 0 ]; then
handle_error "Git pull" $?
fi
echo -e "${GREEN}✅ Git pull completed${NC}"
echo ""
else
echo -e "${YELLOW}⊘ Skipping git pull${NC}"
echo ""
fi
# 2. Build Frontend and Client Portal in PARALLEL (build first; swap later)
if [ "$DEPLOY_FRONTEND" = true ] || [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
if [ "$DEPLOY_FRONTEND" = true ] && [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
echo -e "${YELLOW}▶ Building Frontend and Client Portal (parallel)...${NC}"
elif [ "$DEPLOY_FRONTEND" = true ]; then
echo -e "${YELLOW}▶ Building Frontend...${NC}"
else
echo -e "${YELLOW}▶ Building Client Portal...${NC}"
fi
fi
# Frontend build function
build_frontend() {
local start_time=$(date +%s)
cd "$ROOT_DIR/worklenz-frontend" || return 1
echo -e "${BLUE}[Frontend]${NC} Installing dependencies..."
npm ci --prefer-offline --no-audit --progress=false > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}[Frontend]${NC} npm ci failed"
return 1
fi
# Create temporary build directory with timestamp
local temp_build="build-$(date +%s)"
echo -e "${BLUE}[Frontend]${NC} Building to temporary directory: ${temp_build}..."
# Build to temporary directory
VITE_BUILD_OUTDIR="$temp_build" NODE_OPTIONS="--max-old-space-size=4096" npm run build > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}[Frontend]${NC} Build failed"
rm -rf "$temp_build"
return 1
fi
local end_time=$(date +%s)
local duration=$((end_time - start_time))
echo "$ROOT_DIR/worklenz-frontend/$temp_build" > "$FRONTEND_BUILD_PATH_FILE"
echo -e "${GREEN}[Frontend]${NC} ✅ Build completed in ${duration}s (ready to swap)"
return 0
}
# Client Portal build function
build_client_portal() {
local start_time=$(date +%s)
cd "$ROOT_DIR/worklenz-client-portal" || return 1
echo -e "${BLUE}[Client Portal]${NC} Installing dependencies..."
npm ci --prefer-offline --no-audit --progress=false > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}[Client Portal]${NC} npm ci failed"
return 1
fi
# Create temporary build directory with timestamp
local temp_build="dist-$(date +%s)"
echo -e "${BLUE}[Client Portal]${NC} Building to temporary directory: ${temp_build}..."
# Build to temporary directory
VITE_BUILD_OUTDIR="$temp_build" npm run build > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo -e "${RED}[Client Portal]${NC} Build failed"
rm -rf "$temp_build"
return 1
fi
local end_time=$(date +%s)
local duration=$((end_time - start_time))
echo "$ROOT_DIR/worklenz-client-portal/$temp_build" > "$CLIENT_PORTAL_BUILD_PATH_FILE"
echo -e "${GREEN}[Client Portal]${NC} ✅ Build completed in ${duration}s (ready to swap)"
return 0
}
# Run builds in parallel or sequentially based on selection
if [ "$DEPLOY_FRONTEND" = true ] || [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
PARALLEL_START=$(date +%s)
FRONTEND_EXIT=0
CLIENT_PORTAL_EXIT=0
FRONTEND_PID=""
CLIENT_PORTAL_PID=""
# Start builds
if [ "$DEPLOY_FRONTEND" = true ]; then
build_frontend &
FRONTEND_PID=$!
fi
if [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
build_client_portal &
CLIENT_PORTAL_PID=$!
fi
# Wait for builds to complete
if [ -n "$FRONTEND_PID" ]; then
wait $FRONTEND_PID
FRONTEND_EXIT=$?
fi
if [ -n "$CLIENT_PORTAL_PID" ]; then
wait $CLIENT_PORTAL_PID
CLIENT_PORTAL_EXIT=$?
fi
PARALLEL_END=$(date +%s)
PARALLEL_DURATION=$((PARALLEL_END - PARALLEL_START))
# Check if builds succeeded
if [ "$DEPLOY_FRONTEND" = true ] && [ $FRONTEND_EXIT -ne 0 ]; then
handle_error "Frontend build" $FRONTEND_EXIT
fi
if [ "$DEPLOY_CLIENT_PORTAL" = true ] && [ $CLIENT_PORTAL_EXIT -ne 0 ]; then
handle_error "Client Portal build" $CLIENT_PORTAL_EXIT
fi
# Success message
if [ "$DEPLOY_FRONTEND" = true ] && [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
echo -e "${GREEN}✅ Frontend and Client Portal built successfully in ${PARALLEL_DURATION}s${NC}"
elif [ "$DEPLOY_FRONTEND" = true ]; then
echo -e "${GREEN}✅ Frontend built successfully in ${PARALLEL_DURATION}s${NC}"
else
echo -e "${GREEN}✅ Client Portal built successfully in ${PARALLEL_DURATION}s${NC}"
fi
echo ""
fi
# 4. Verify Apple Sign-In key file (if Apple login is enabled)
if [ -n "$APPLE_PRIVATE_KEY_PATH" ] || grep -q "APPLE_CLIENT_ID" "$ROOT_DIR/worklenz-backend/.env" 2>/dev/null; then
echo -e "${YELLOW}▶ Checking Apple Sign-In key file...${NC}"
KEY_PATH=$(grep "APPLE_PRIVATE_KEY_PATH" "$ROOT_DIR/worklenz-backend/.env" 2>/dev/null | cut -d '=' -f2 | tr -d '"' | tr -d "'" | xargs)
if [ -n "$KEY_PATH" ]; then
# Resolve relative path
if [[ "$KEY_PATH" == ./* ]]; then
FULL_KEY_PATH="$ROOT_DIR/worklenz-backend/${KEY_PATH#./}"
else
FULL_KEY_PATH="$KEY_PATH"
fi
if [ -f "$FULL_KEY_PATH" ]; then
echo -e "${GREEN}✅ Apple Sign-In key file found${NC}"
else
echo -e "${RED}⚠️ WARNING: Apple Sign-In key file not found at: $FULL_KEY_PATH${NC}"
echo -e "${YELLOW} Apple Sign-In will not work until the key file is uploaded.${NC}"
echo -e "${YELLOW} See docs/APPLE_P8_KEY_MANAGEMENT.md for instructions.${NC}"
fi
fi
echo ""
fi
# 5. Build Backend
if [ "$DEPLOY_BACKEND" = true ]; then
echo -e "${YELLOW}▶ Building Backend application...${NC}"
BACKEND_START=$(date +%s)
cd "$ROOT_DIR/worklenz-backend" || handle_error "Backend directory not found" 1
echo -e "${BLUE}[Backend]${NC} Installing dependencies..."
npm ci --prefer-offline --no-audit --progress=false > /dev/null 2>&1
if [ $? -ne 0 ]; then
handle_error "Backend npm ci" $?
fi
echo -e "${BLUE}[Backend]${NC} Building application..."
npm run build > /dev/null 2>&1
if [ $? -ne 0 ]; then
handle_error "Backend build" $?
fi
BACKEND_END=$(date +%s)
BACKEND_DURATION=$((BACKEND_END - BACKEND_START))
echo -e "${GREEN}✅ Backend built successfully in ${BACKEND_DURATION}s${NC}"
echo ""
else
BACKEND_DURATION=0
fi
# 6. Restart Backend with PM2
if [ "$DEPLOY_BACKEND" = true ]; then
echo -e "${YELLOW}▶ Restarting Backend service with PM2...${NC}"
pm2 restart "$PM2_APP_ID" --update-env > /dev/null 2>&1
if [ $? -ne 0 ]; then
handle_error "PM2 restart" $?
fi
# Wait for service to stabilize
sleep 5
# 7. Health Check
echo -e "${YELLOW}▶ Performing health check...${NC}"
HEALTH_CHECK_ATTEMPTS=0
MAX_HEALTH_CHECKS=10
while [ $HEALTH_CHECK_ATTEMPTS -lt $MAX_HEALTH_CHECKS ]; do
if pm2 describe "$PM2_APP_ID" 2>&1 | grep -q "online"; then
echo -e "${GREEN}✅ Backend service is running${NC}"
break
fi
HEALTH_CHECK_ATTEMPTS=$((HEALTH_CHECK_ATTEMPTS + 1))
if [ $HEALTH_CHECK_ATTEMPTS -lt $MAX_HEALTH_CHECKS ]; then
echo -e "${YELLOW}Waiting for service to come online (attempt ${HEALTH_CHECK_ATTEMPTS}/${MAX_HEALTH_CHECKS})...${NC}"
sleep 2
else
echo -e "${RED}Health check failed - service not responding${NC}"
pm2 logs "$PM2_APP_ID" --lines 20
handle_error "Health check" 1
fi
done
echo ""
fi
# 7. Brief maintenance mode and atomic swap (frontend/client portal)
if [ "$DEPLOY_FRONTEND" = true ] || [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
echo -e "${YELLOW}▶ Enabling maintenance mode (brief) for final swap...${NC}"
sudo touch /var/www/maintenance-mode
sudo nginx -s reload
if [ $? -ne 0 ]; then
handle_error "Enable maintenance mode" $?
fi
MAINTENANCE_ENABLED=true
echo ""
if [ "$DEPLOY_FRONTEND" = true ]; then
if [ ! -f "$FRONTEND_BUILD_PATH_FILE" ]; then
handle_error "Frontend build artifact missing" 1
fi
FRONTEND_BUILD_PATH="$(cat "$FRONTEND_BUILD_PATH_FILE")"
if [ ! -d "$FRONTEND_BUILD_PATH" ]; then
handle_error "Frontend build artifact not found" 1
fi
echo -e "${YELLOW}▶ Swapping Frontend build...${NC}"
cd "$ROOT_DIR/worklenz-frontend" || handle_error "Frontend directory not found" 1
if [ -d "build" ] && [ ! -L "build" ]; then
mv build "build-backup-$(date +%s)" 2>/dev/null || true
elif [ -L "build" ]; then
rm -f build
fi
mv "$FRONTEND_BUILD_PATH" build
ls -t | grep "^build-backup-" | tail -n +3 | xargs -r rm -rf
echo -e "${GREEN}✅ Frontend deployed${NC}"
echo ""
fi
if [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
if [ ! -f "$CLIENT_PORTAL_BUILD_PATH_FILE" ]; then
handle_error "Client Portal build artifact missing" 1
fi
CLIENT_PORTAL_BUILD_PATH="$(cat "$CLIENT_PORTAL_BUILD_PATH_FILE")"
if [ ! -d "$CLIENT_PORTAL_BUILD_PATH" ]; then
handle_error "Client Portal build artifact not found" 1
fi
echo -e "${YELLOW}▶ Swapping Client Portal build...${NC}"
cd "$ROOT_DIR/worklenz-client-portal" || handle_error "Client Portal directory not found" 1
if [ -d "dist" ] && [ ! -L "dist" ]; then
mv dist "dist-backup-$(date +%s)" 2>/dev/null || true
elif [ -L "dist" ]; then
rm -f dist
fi
mv "$CLIENT_PORTAL_BUILD_PATH" dist
ls -t | grep "^dist-backup-" | tail -n +3 | xargs -r rm -rf
echo -e "${GREEN}✅ Client Portal deployed${NC}"
echo ""
fi
echo -e "${YELLOW}Disabling maintenance mode...${NC}"
sudo rm -f /var/www/maintenance-mode
sudo nginx -s reload
if [ $? -ne 0 ]; then
handle_error "Disable maintenance mode" $?
fi
MAINTENANCE_ENABLED=false
echo -e "${GREEN}✅ Maintenance mode disabled${NC}"
echo ""
fi
rm -rf "$DEPLOY_TMP_DIR" 2>/dev/null || true
# Calculate total deployment time
DEPLOY_END=$(date +%s)
TOTAL_DURATION=$((DEPLOY_END - DEPLOY_START))
MINUTES=$((TOTAL_DURATION / 60))
SECONDS=$((TOTAL_DURATION % 60))
# Return to root directory
cd "$ROOT_DIR"
# Success summary
echo ""
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo -e "${GREEN}🚀 Deployment Completed Successfully!${NC}"
echo -e "${GREEN}════════════════════════════════════════${NC}"
echo ""
echo -e "${BLUE}📊 Build Time Summary:${NC}"
if [ "$DEPLOY_FRONTEND" = true ] || [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
if [ "$DEPLOY_FRONTEND" = true ] && [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
echo -e " Frontend + Client Portal: ${PARALLEL_DURATION}s (parallel)"
elif [ "$DEPLOY_FRONTEND" = true ]; then
echo -e " Frontend: ${PARALLEL_DURATION}s"
else
echo -e " Client Portal: ${PARALLEL_DURATION}s"
fi
fi
if [ "$DEPLOY_BACKEND" = true ]; then
echo -e " Backend: ${BACKEND_DURATION}s"
fi
echo -e " Total deployment: ${MINUTES}m ${SECONDS}s"
echo ""
if [ "$SKIP_GIT_PULL" = false ]; then
echo -e "${GREEN}✅ Git pull completed${NC}"
fi
if [ "$DEPLOY_FRONTEND" = true ]; then
echo -e "${GREEN}✅ Frontend built and deployed${NC}"
fi
if [ "$DEPLOY_CLIENT_PORTAL" = true ]; then
echo -e "${GREEN}✅ Client Portal built and deployed${NC}"
fi
if [ "$DEPLOY_BACKEND" = true ]; then
echo -e "${GREEN}✅ Backend built and restarted${NC}"
echo -e "${GREEN}✅ Health check passed${NC}"
fi
echo -e "${GREEN}════════════════════════════════════════${NC}"