Bring deploy/ in line with the renamed app directories and with the domains that are actually configured in the per-app .env files: fitnes fitness.exorecovery.ru 3000 fitness-landing hotel hotel.exorecovery.ru 3001 hotel-landing medcenterphysio physio.exorecovery.ru 3002 physio-landing medcenterstart start.exorecovery.ru 3003 start-landing apps.conf, the README provisioning steps (directories, bare clones, systemd units, the sudoers whitelist) and the exo-deploy usage line all used the old medcenter / medcenterpersonal keys. The README table and app.build.env.example still carried the *_DOMAIN placeholders. Also refresh the tracked .env.example files: real VITE_SITE_URL values instead of the old exodevices.ru sub-paths, and the amo lead tags that match apps.conf. Two lockfile fixes, both of which broke `npm ci` in exo-deploy: - medcenterstart had no lockfile at all after package-lock.json and pnpm-lock.yaml were removed — restore package-lock.json and keep the pnpm one deleted, since the deploy path is npm; - medcenterphysio/package-lock.json had lost "resolved" and "integrity" on 147 of its 236 entries — restore the complete file. Verified: exo-render-nginx renders all four vhosts with no leftover placeholders, and every package-lock.json agrees with its package.json. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
96 lines
3.7 KiB
Bash
Executable File
96 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build one landing from git into a fresh release directory and swap it in.
|
|
# Install as /usr/local/bin/exo-deploy (mode 755); run as the `exo` user.
|
|
#
|
|
# sudo -u exo exo-deploy medcenterphysio # deploys origin/main
|
|
# sudo -u exo exo-deploy medcenterphysio my-branch
|
|
set -Eeuo pipefail
|
|
|
|
APP="${1:?usage: exo-deploy <fitnes|hotel|medcenterphysio|medcenterstart> [git-ref]}"
|
|
REF="${2:-main}"
|
|
BASE="/srv/exo/$APP"
|
|
ENVFILE="/etc/exo/$APP.env"
|
|
BUILDENV="/etc/exo/$APP.build.env"
|
|
KEEP=3
|
|
REL="$BASE/releases/$(date -u +%Y%m%d-%H%M%S)"
|
|
|
|
for f in "$ENVFILE" "$BUILDENV"; do
|
|
[[ -r $f ]] || { echo "FATAL: cannot read $f"; exit 1; }
|
|
done
|
|
[[ -d $BASE/repo ]] || { echo "FATAL: no bare repo at $BASE/repo"; exit 1; }
|
|
|
|
PORT="$(sed -n 's/^PORT=//p' "$ENVFILE")"
|
|
[[ -n $PORT ]] || { echo "FATAL: PORT not set in $ENVFILE"; exit 1; }
|
|
|
|
echo "==> fetching $REF"
|
|
git -C "$BASE/repo" fetch --prune origin '+refs/heads/*:refs/heads/*'
|
|
git -C "$BASE/repo" rev-parse --verify "$REF^{commit}" >/dev/null
|
|
|
|
echo "==> extracting $APP/ into $REL"
|
|
mkdir -p "$REL"
|
|
git -C "$BASE/repo" archive "$REF" "$APP" | tar -x -C "$REL" --strip-components=1
|
|
[[ -f $REL/package.json ]] || { echo "FATAL: $APP/ not found at $REF"; rm -rf "$REL"; exit 1; }
|
|
|
|
# 2.6-3.9 MB of base64-embedded reference HTML that is never served.
|
|
rm -rf "$REL/legacy"
|
|
|
|
# Public build-time vars only. The amoCRM token stays in $ENVFILE, which
|
|
# systemd injects at runtime; dotenv does not override real env vars, so the
|
|
# two never collide.
|
|
cp "$BUILDENV" "$REL/.env"
|
|
|
|
cd "$REL"
|
|
|
|
# devDependencies are REQUIRED here: vite, typescript and tailwindcss all live
|
|
# there and `npm run build` needs them. --omit=dev breaks the build.
|
|
echo "==> npm ci"
|
|
npm ci --no-audit --no-fund
|
|
|
|
echo "==> npm run build"
|
|
npm run build
|
|
|
|
# Fail loudly rather than shipping broken SEO tags or an empty bundle.
|
|
[[ -s dist/client/index.html ]] || { echo "FATAL: dist/client/index.html missing or empty"; exit 1; }
|
|
if grep -q '%VITE_SITE_URL%' dist/client/index.html; then
|
|
echo "FATAL: VITE_SITE_URL was not substituted — check $BUILDENV"; exit 1
|
|
fi
|
|
|
|
# Runtime needs only express/zod/dotenv/tsx: ~130 MB -> ~40 MB per release.
|
|
echo "==> pruning devDependencies"
|
|
npm prune --omit=dev
|
|
|
|
# Precompress for nginx gzip_static. .webp/.woff2 omitted on purpose.
|
|
find dist/client -type f \
|
|
\( -name '*.js' -o -name '*.css' -o -name '*.html' -o -name '*.svg' -o -name '*.json' -o -name '*.xml' -o -name '*.txt' \) \
|
|
-exec gzip -9 -k -f {} +
|
|
|
|
echo "==> swapping $BASE/current -> $REL"
|
|
PREV="$(readlink -f "$BASE/current" 2>/dev/null || true)"
|
|
ln -sfn "$REL" "$BASE/current.tmp"
|
|
mv -Tf "$BASE/current.tmp" "$BASE/current" # single rename(2): atomic
|
|
|
|
sudo systemctl restart "exo@$APP"
|
|
|
|
echo "==> waiting for health on 127.0.0.1:$PORT"
|
|
for _ in $(seq 1 20); do
|
|
if curl -fsS --max-time 2 "http://127.0.0.1:$PORT/api/health" >/dev/null 2>&1; then break; fi
|
|
sleep 1
|
|
done
|
|
HEALTH="$(curl -fsS --max-time 5 "http://127.0.0.1:$PORT/api/health")" || {
|
|
echo "FATAL: health check failed. Rolling back."
|
|
[[ -n $PREV ]] && { ln -sfn "$PREV" "$BASE/current.tmp"; mv -Tf "$BASE/current.tmp" "$BASE/current"; sudo systemctl restart "exo@$APP"; }
|
|
exit 1
|
|
}
|
|
echo " $HEALTH"
|
|
grep -q '"ok":true' <<<"$HEALTH" || { echo "FATAL: health not ok"; exit 1; }
|
|
grep -q '"amo":true' <<<"$HEALTH" || echo " WARNING: amo=false — /api/leads/* will return 503. Check AMO_* in $ENVFILE."
|
|
|
|
# Prune old releases, never the live one.
|
|
CURRENT="$(readlink -f "$BASE/current")"
|
|
ls -1dt "$BASE"/releases/*/ 2>/dev/null | tail -n "+$((KEEP+1))" | while read -r old; do
|
|
[[ "$(readlink -f "$old")" == "$CURRENT" ]] && continue
|
|
rm -rf "$old"
|
|
done
|
|
|
|
echo "==> deployed $APP @ $REF ($(git -C "$BASE/repo" rev-parse --short "$REF")) -> $REL"
|