Bash · 6259 bytes Raw Blame History
1 #!/usr/bin/env bash
2 set -Eeuo pipefail
3 umask 022
4
5 # ── config ──────────────────────────────────────────────────────────────
6 SITE="cob.musicsian.com"
7 WEB_ROOT="/var/www/$SITE"
8 RELEASES="$WEB_ROOT/releases"
9 CURRENT="$WEB_ROOT/current"
10 STAMP="${1:-$(date +%Y-%m-%d-%H%M%S)}" # you can pass a stamp manually if you want
11 KEEP="${KEEP:-10}" # how many releases to keep
12 PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
13 STAGE="${STAGE_DIR:-$HOME/builds/$SITE/$STAMP}"
14
15 # ── ensure dirs exist ───────────────────────────────────────────────────
16 echo "▶ Ensure web root"
17 sudo install -d -m 0755 "$WEB_ROOT"
18 sudo install -d -m 0755 "$RELEASES"
19
20 # ── stage ───────────────────────────────────────────────────────────────
21 echo "▶ Stage files → $STAGE"
22 mkdir -p "$STAGE"
23 # Only ship the static site files we care about
24 rsync -az --delete \
25 --include '/index.html' \
26 --include '/favicon.ico' \
27 --include '/css/***' \
28 --include '/js/***' \
29 --exclude '*' \
30 "$PROJECT_DIR"/ "$STAGE"/
31
32 echo "▶ Verify staged content"
33 ls -l "$STAGE"
34 test -f "$STAGE/index.html" || { echo "✗ index.html missing in stage"; exit 1; }
35 test -d "$STAGE/css" || { echo "✗ css/ missing in stage"; exit 1; }
36 test -d "$STAGE/js" || { echo "✗ js/ missing in stage"; exit 1; }
37
38 # ── publish ─────────────────────────────────────────────────────────────
39 echo "▶ Publish → $RELEASES/$STAMP"
40 sudo rsync -az --delete "$STAGE"/ "$RELEASES/$STAMP"/
41
42 echo "▶ Verify release contents"
43 sudo test -f "$RELEASES/$STAMP/index.html" || { echo "✗ index.html missing in release"; exit 1; }
44 sudo test -d "$RELEASES/$STAMP/css" || { echo "✗ css/ missing in release"; exit 1; }
45 sudo test -d "$RELEASES/$STAMP/js" || { echo "✗ js/ missing in release"; exit 1; }
46
47 # show release contents for debugging
48 echo "▶ Release contents:"
49 sudo ls -la "$RELEASES/$STAMP"
50
51 # ── harden perms & preflight readability (as nginx user) ────────────────
52 echo "▶ Fix ownership/permissions and preflight readability"
53 sudo chown -R root:root "$RELEASES/$STAMP"
54 sudo find "$RELEASES/$STAMP" -type d -exec chmod 0755 {} +
55 sudo find "$RELEASES/$STAMP" -type f -exec chmod 0644 {} +
56
57 NGINX_USER="nginx"
58 if id "$NGINX_USER" >/dev/null 2>&1; then
59 sudo -u "$NGINX_USER" test -r "$RELEASES/$STAMP/index.html" || { echo "✗ nginx user cannot read index.html"; exit 1; }
60 else
61 echo "! Warn: nginx user '$NGINX_USER' not found; skipping readability check"
62 fi
63
64 # ── flip symlink (with rollback trap) ───────────────────────────────────
65 echo "▶ Flip symlink"
66 # Save previous target for potential rollback
67 prev="$(readlink -f "$CURRENT" 2>/dev/null || true)"
68 if [[ -n "$prev" ]]; then
69 echo " Previous release: $prev"
70 fi
71
72 # FIXED: Remove old symlink first, then create new one atomically
73 if [[ -L "$CURRENT" ]]; then
74 sudo rm -f "$CURRENT"
75 fi
76 sudo ln -sfn "$RELEASES/$STAMP" "$CURRENT"
77
78 # Verify the symlink is correct
79 echo "▶ Verify symlink"
80 if [[ ! -L "$CURRENT" ]]; then
81 echo "✗ $CURRENT is not a symlink"
82 exit 1
83 fi
84
85 LINK_TARGET="$(readlink -f "$CURRENT")"
86 echo " Current → $LINK_TARGET"
87
88 # Ensure the symlink points to the right place
89 if [[ "$LINK_TARGET" != "$RELEASES/$STAMP" ]]; then
90 echo "✗ Symlink points to wrong location"
91 echo " Expected: $RELEASES/$STAMP"
92 echo " Got: $LINK_TARGET"
93 exit 1
94 fi
95
96 # Ensure the flipped target is actually valid
97 if ! sudo test -f "$CURRENT/index.html"; then
98 echo "✗ current release missing index.html; rolling back"
99 if [[ -n "${prev:-}" ]] && [[ -d "$prev" ]]; then
100 sudo ln -sfn "$prev" "$CURRENT"
101 fi
102 exit 1
103 fi
104
105 # Setup rollback function
106 rollback() {
107 echo "⚠️ Rolling back symlink to previous release"
108 if [[ -n "${prev:-}" ]] && [[ -d "$prev" ]]; then
109 sudo ln -sfn "$prev" "$CURRENT"
110 echo " Rolled back to: $prev"
111 else
112 echo " No previous release to rollback to"
113 fi
114 }
115 trap 'rollback' ERR
116
117 # ── selinux restore (safe if SELinux is permissive/disabled) ────────────
118 echo "▶ Restore SELinux context"
119 sudo restorecon -Rv "$RELEASES/$STAMP" >/dev/null 2>&1 || true
120 sudo restorecon -v "$CURRENT" >/dev/null 2>&1 || true
121
122 # ── quick health check (non-fatal) ─────────────────────────────────────
123 if command -v curl >/dev/null 2>&1; then
124 echo "▶ Health check: GET https://$SITE/"
125 if curl -fsS -o /dev/null -w " Status: %{http_code}\n" "https://$SITE/" --max-time 5; then
126 echo " ✓ Site responding"
127 else
128 echo " ⚠ Site health check failed (non-fatal)"
129 fi
130 fi
131
132 # ── nginx reload (only if config passes) ────────────────────────────────
133 echo "▶ Test & reload Nginx"
134 if sudo nginx -t 2>/dev/null; then
135 sudo systemctl reload nginx
136 echo " ✓ Nginx reloaded"
137 else
138 echo "✗ nginx -t failed"
139 exit 1
140 fi
141
142 # ── prune old releases ──────────────────────────────────────────────────
143 echo "▶ Prune old releases (keep $KEEP)"
144 # FIXED: Exclude the current release from pruning
145 CURRENT_RELEASE="$(basename "$(readlink -f "$CURRENT")")"
146 OLD_RELEASES=$(sudo bash -c "ls -1dt $RELEASES/* 2>/dev/null | grep -v '$CURRENT_RELEASE' | tail -n +$((KEEP+1))" || true)
147 if [[ -n "$OLD_RELEASES" ]]; then
148 echo "$OLD_RELEASES" | while read -r old_release; do
149 echo " Removing: $(basename "$old_release")"
150 done
151 echo "$OLD_RELEASES" | sudo xargs -r rm -rf
152 else
153 echo " No old releases to prune"
154 fi
155
156 echo "✓ Deployed $STAMP$SITE"
157 echo " Live at: https://$SITE/"