Files
exodevices/deploy/bin/exo-deploy
T
Yuriy PanovandClaude Opus 5 51f39c9b88 deploy: follow the landing rename and the exorecovery.ru domains
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>
2026-09-06 23:36:17 +06:00

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"