Bash · 6320 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 # If the nginx user is different on your system, adjust here
58 NGINX_USER="nginx"
59 if id "$NGINX_USER" >/dev/null 2>&1; then
60 sudo -u "$NGINX_USER" test -r "$RELEASES/$STAMP/index.html" || { echo "✗ nginx user cannot read index.html"; exit 1; }
61 else
62 echo "! Warn: nginx user '$NGINX_USER' not found; skipping readability check"
63 fi
64
65 # ── flip symlink (with rollback trap) ───────────────────────────────────
66 echo "▶ Flip symlink"
67 # Save previous target for potential rollback
68 prev="$(readlink -f "$CURRENT" 2>/dev/null || true)"
69 if [[ -n "$prev" ]]; then
70 echo " Previous release: $prev"
71 fi
72
73 # FIXED: Remove old symlink first, then create new one atomically
74 if [[ -L "$CURRENT" ]]; then
75 sudo rm -f "$CURRENT"
76 fi
77 sudo ln -sfn "$RELEASES/$STAMP" "$CURRENT"
78
79 # Verify the symlink is correct
80 echo "▶ Verify symlink"
81 if [[ ! -L "$CURRENT" ]]; then
82 echo "✗ $CURRENT is not a symlink"
83 exit 1
84 fi
85
86 LINK_TARGET="$(readlink -f "$CURRENT")"
87 echo " Current → $LINK_TARGET"
88
89 # Ensure the symlink points to the right place
90 if [[ "$LINK_TARGET" != "$RELEASES/$STAMP" ]]; then
91 echo "✗ Symlink points to wrong location"
92 echo " Expected: $RELEASES/$STAMP"
93 echo " Got: $LINK_TARGET"
94 exit 1
95 fi
96
97 # Ensure the flipped target is actually valid
98 if ! sudo test -f "$CURRENT/index.html"; then
99 echo "✗ current release missing index.html; rolling back"
100 if [[ -n "${prev:-}" ]] && [[ -d "$prev" ]]; then
101 sudo ln -sfn "$prev" "$CURRENT"
102 fi
103 exit 1
104 fi
105
106 # Setup rollback function
107 rollback() {
108 echo "⚠️ Rolling back symlink to previous release"
109 if [[ -n "${prev:-}" ]] && [[ -d "$prev" ]]; then
110 sudo ln -sfn "$prev" "$CURRENT"
111 echo " Rolled back to: $prev"
112 else
113 echo " No previous release to rollback to"
114 fi
115 }
116 trap 'rollback' ERR
117
118 # ── selinux restore (safe if SELinux is permissive/disabled) ────────────
119 echo "▶ Restore SELinux context"
120 sudo restorecon -Rv "$RELEASES/$STAMP" >/dev/null 2>&1 || true
121 sudo restorecon -v "$CURRENT" >/dev/null 2>&1 || true
122
123 # ── quick health check (non-fatal) ─────────────────────────────────────
124 if command -v curl >/dev/null 2>&1; then
125 echo "▶ Health check: GET https://$SITE/"
126 if curl -fsS -o /dev/null -w " Status: %{http_code}\n" "https://$SITE/" --max-time 5; then
127 echo " ✓ Site responding"
128 else
129 echo " ⚠ Site health check failed (non-fatal)"
130 fi
131 fi
132
133 # ── nginx reload (only if config passes) ────────────────────────────────
134 echo "▶ Test & reload Nginx"
135 if sudo nginx -t 2>/dev/null; then
136 sudo systemctl reload nginx
137 echo " ✓ Nginx reloaded"
138 else
139 echo "✗ nginx -t failed"
140 exit 1
141 fi
142
143 # ── prune old releases ──────────────────────────────────────────────────
144 echo "▶ Prune old releases (keep $KEEP)"
145 # FIXED: Exclude the current release from pruning
146 CURRENT_RELEASE="$(basename "$(readlink -f "$CURRENT")")"
147 OLD_RELEASES=$(sudo bash -c "ls -1dt $RELEASES/* 2>/dev/null | grep -v '$CURRENT_RELEASE' | tail -n +$((KEEP+1))" || true)
148 if [[ -n "$OLD_RELEASES" ]]; then
149 echo "$OLD_RELEASES" | while read -r old_release; do
150 echo " Removing: $(basename "$old_release")"
151 done
152 echo "$OLD_RELEASES" | sudo xargs -r rm -rf
153 else
154 echo " No old releases to prune"
155 fi
156
157 echo "✓ Deployed $STAMP$SITE"
158 echo " Live at: https://$SITE/"