Update host dir and release process
This commit is contained in:
1 parent
42287395fe
commit
2ce1fc073d
2 files changed
+118
-20
No files matched your search
+18
-5
@@ -17,6 +17,13 @@
|
||||
# /assets/, /sw.js and /manifest.json unresolvable once they needed their
|
||||
# own location blocks.
|
||||
#
|
||||
# It also no longer points into the repo. deploy.sh publishes the built
|
||||
# client to /var/www/ss/releases/<timestamp> and atomically repoints the
|
||||
# /var/www/ss/current symlink at it. Pointing nginx at that stable path
|
||||
# means a deploy is one rename -- nginx resolves the symlink per request,
|
||||
# so no reload is needed and no request sees a half-copied directory.
|
||||
# Rollback is the same rename pointing at an older release.
|
||||
#
|
||||
# 3. proxyWebsockets on /socket. recommendedProxySettings does NOT add the
|
||||
# Upgrade/Connection headers, so Socket.IO has been silently falling back
|
||||
# to long-polling -- that's the live note-sync path running degraded.
|
||||
@@ -45,7 +52,8 @@ services.nginx = {
|
||||
virtualHosts."192.168.1.7" = {
|
||||
# serverAliases = [ "notes.example.com" ]; # if the tunnel forwards the public host
|
||||
|
||||
root = "REPO_PATH/client/dist";
|
||||
# Published by deploy.sh; a symlink to the live release. Not in the repo.
|
||||
root = "/var/www/ss/current";
|
||||
|
||||
locations."/" = {
|
||||
tryFiles = "$uri $uri/ /index.html";
|
||||
@@ -84,7 +92,12 @@ services.nginx = {
|
||||
};
|
||||
};
|
||||
|
||||
# Note on file access: nginx runs as its own user, so REPO_PATH/client/dist must
|
||||
# be readable by it -- every parent directory needs +x. If the repo lives under
|
||||
# /home/mab this usually means loosening the home directory's permissions, or
|
||||
# putting the clone somewhere like /srv instead.
|
||||
# Note on file access: nginx runs as its own user and needs read access to the
|
||||
# web root, with +x on every parent directory. Serving from /var/www/ss is what
|
||||
# makes that a non-issue -- the one-time `chmod 755 /var/www /var/www/ss` in
|
||||
# deploy.sh is the whole of it. This is the reason the root is not the repo
|
||||
# clone: a clone under /home/mab would require loosening the permissions on the
|
||||
# home directory itself.
|
||||
#
|
||||
# Uploads are NOT here. Express serves them from REPO_PATH/staticFiles at
|
||||
# /api/static via the proxy below, so they are untouched by deploys.
|
||||
@@ -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
|
||||
# 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
|
||||
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
|
||||
Reference in new issue
Block a user