Update host dir and release process

This commit is contained in:
Max committed 2026-10-28 16:30:36 -07:00
1 parent 42287395fe
commit 2ce1fc073d
2 files changed
+119 -21

No files matched your search

+101 -16
View File
@@ -13,24 +13,72 @@
# environment.systemPackages or the user's profile -- NOT only inside a
# nix-shell, or this will fail at the install step)
# - pm2 is already managing NoteServer (pm2 resurrect on boot)
# - nginx serves client/dist with an SPA fallback to index.html
# - nginx serves $WEBROOT/current with an SPA fallback to index.html
#
# nginx is NOT touched here. On NixOS it is declarative -- see configs/.
#
# --- the web root --------------------------------------------------------
#
# The built client is published to /var/www/ss, NOT served out of the repo.
# nginx runs as its own user, so serving from a clone under /home/mab meant
# loosening the home directory's permissions for every parent dir. /var/www is
# the conventional place for this and sidesteps the whole problem.
#
# Layout, which is the standard symlink-swap pattern:
#
# /var/www/ss/
# releases/2026-10-28_1147/ <- one directory per deploy
# releases/2026-10-28_0930/
# current -> releases/2026-10-28_1147
#
# nginx points at the stable path $WEBROOT/current. Publishing is a rename of
# that symlink, which is a single atomic syscall: no request ever sees a
# half-copied directory, and no window exists where the root is missing.
# Rolling back is the same rename pointing at the previous release.
#
# Only the *client* moves. The API server still runs from the repo under pm2,
# and user uploads are still served by Express from $REPO/staticFiles at
# /api/static -- deliberately, so deploys never touch user data.
#
# ONE-TIME SETUP on a new server (needs root):
#
# sudo mkdir -p /var/www/ss/releases
# sudo chown -R "$USER" /var/www/ss
# sudo chmod 755 /var/www /var/www/ss
#
# Override the location for a staging box with: WEBROOT=/var/www/ss-staging ./deploy.sh
#
set -euo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$REPO"
WEBROOT="${WEBROOT:-/var/www/ss}"
RELEASE="$WEBROOT/releases/$(date +%Y-%m-%d_%H%M%S)"
# Number of old releases to keep around for rollback.
KEEP_RELEASES=5
step(){ printf '\n\033[32m::--:: %s\033[0m\n' "$1"; }
# --- sanity checks before changing anything ------------------------------
for tool in git pnpm pm2; do
for tool in git pnpm pm2 rsync; do
command -v "$tool" >/dev/null || { echo "$tool is not on PATH" >&2; exit 1; }
done
# Fail here rather than after a three-minute build. A missing web root is
# almost always a server that never had the one-time setup above run on it.
if [ ! -d "$WEBROOT/releases" ]; then
echo "$WEBROOT/releases does not exist -- run the one-time setup at the top of this script." >&2
exit 1
fi
if [ ! -w "$WEBROOT/releases" ]; then
echo "$WEBROOT/releases is not writable by $(whoami) -- check the chown in the one-time setup." >&2
exit 1
fi
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
echo "Working tree has local modifications. Commit or stash them first:" >&2
git status --short >&2
@@ -54,23 +102,54 @@ pnpm install --frozen-lockfile --dir client
# --- build ---------------------------------------------------------------
# dist is gitignored, so the server builds it. Built into a temp dir first so a
# failed build leaves the currently-served files untouched.
# dist is gitignored, so the server builds it. The build stays inside the repo
# and is only copied to $WEBROOT once it has been checked, so a failed build
# cannot touch what is currently being served.
step "Building client..."
rm -rf client/dist.new
pnpm --dir client exec vite build --outDir dist.new --emptyOutDir
rm -rf client/dist
pnpm --dir client exec vite build
if [ ! -f client/dist.new/index.html ] || [ ! -f client/dist.new/sw.js ]; then
echo "Build output looks wrong (missing index.html or sw.js) -- keeping the previous build." >&2
rm -rf client/dist.new
exit 1
fi
# manifest.json is checked in under client/public and copied verbatim by Vite;
# if it is missing, the PWA installs without icons, scope or a share target.
for required in index.html sw.js manifest.json; do
if [ ! -f "client/dist/$required" ]; then
echo "Build output looks wrong (missing $required) -- $WEBROOT left untouched." >&2
exit 1
fi
done
step "Swapping in the new build..."
rm -rf client/dist.old
[ -d client/dist ] && mv client/dist client/dist.old
mv client/dist.new client/dist
rm -rf client/dist.old
# --- publish -------------------------------------------------------------
step "Publishing to $RELEASE..."
mkdir -p "$RELEASE"
# Trailing slash on the source: copy the *contents* of dist into the release
# dir. --delete is harmless on a fresh dir but keeps a re-run of the same
# timestamp honest.
rsync -a --delete client/dist/ "$RELEASE/"
# nginx reads these as its own user, so they must be world-readable.
chmod -R a+rX "$RELEASE"
step "Pointing $WEBROOT/current at the new release..."
# Two steps because `ln -sfn` over an existing symlink unlinks before relinking,
# which leaves a brief window with no web root at all. Creating a scratch
# symlink and renaming it over the old one is a single atomic rename(2).
ln -sfn "$RELEASE" "$WEBROOT/current.new"
mv -T "$WEBROOT/current.new" "$WEBROOT/current"
# --- prune ---------------------------------------------------------------
# Keep the most recent $KEEP_RELEASES for rollback, drop the rest. Never remove
# whatever `current` actually resolves to, however old it is.
live="$(readlink -f "$WEBROOT/current")"
ls -1d "$WEBROOT"/releases/*/ 2>/dev/null \
| sort -r \
| tail -n +$((KEEP_RELEASES + 1)) \
| while read -r stale; do
[ "$(readlink -f "$stale")" = "$live" ] && continue
echo " pruning $(basename "$stale")"
rm -rf "$stale"
done
# --- restart -------------------------------------------------------------
@@ -83,4 +162,10 @@ pm2 reload "$REPO/server/ecosystem.config.js" --env production --update-env
pm2 save
step "Done."
echo " serving $(readlink -f "$WEBROOT/current")"
echo
echo " To roll back, repoint the symlink at the previous release:"
echo " ls $WEBROOT/releases"
echo " ln -sfn $WEBROOT/releases/<older> $WEBROOT/current.new && mv -T $WEBROOT/current.new $WEBROOT/current"
echo
pm2 list