Update host dir and release process
This commit is contained in:
1 parent
42287395fe
commit
2ce1fc073d
2 files changed
+117
-19
No files matched your search
+18
-5
@@ -17,6 +17,13 @@
|
|||||||
# /assets/, /sw.js and /manifest.json unresolvable once they needed their
|
# /assets/, /sw.js and /manifest.json unresolvable once they needed their
|
||||||
# own location blocks.
|
# 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
|
# 3. proxyWebsockets on /socket. recommendedProxySettings does NOT add the
|
||||||
# Upgrade/Connection headers, so Socket.IO has been silently falling back
|
# Upgrade/Connection headers, so Socket.IO has been silently falling back
|
||||||
# to long-polling -- that's the live note-sync path running degraded.
|
# to long-polling -- that's the live note-sync path running degraded.
|
||||||
@@ -45,7 +52,8 @@ services.nginx = {
|
|||||||
virtualHosts."192.168.1.7" = {
|
virtualHosts."192.168.1.7" = {
|
||||||
# serverAliases = [ "notes.example.com" ]; # if the tunnel forwards the public host
|
# 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."/" = {
|
locations."/" = {
|
||||||
tryFiles = "$uri $uri/ /index.html";
|
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
|
# Note on file access: nginx runs as its own user and needs read access to the
|
||||||
# be readable by it -- every parent directory needs +x. If the repo lives under
|
# web root, with +x on every parent directory. Serving from /var/www/ss is what
|
||||||
# /home/mab this usually means loosening the home directory's permissions, or
|
# makes that a non-issue -- the one-time `chmod 755 /var/www /var/www/ss` in
|
||||||
# putting the clone somewhere like /srv instead.
|
# 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
|
# environment.systemPackages or the user's profile -- NOT only inside a
|
||||||
# nix-shell, or this will fail at the install step)
|
# nix-shell, or this will fail at the install step)
|
||||||
# - pm2 is already managing NoteServer (pm2 resurrect on boot)
|
# - 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/.
|
# 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
|
set -euo pipefail
|
||||||
|
|
||||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
cd "$REPO"
|
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"; }
|
step(){ printf '\n\033[32m::--:: %s\033[0m\n' "$1"; }
|
||||||
|
|
||||||
# --- sanity checks before changing anything ------------------------------
|
# --- 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; }
|
command -v "$tool" >/dev/null || { echo "$tool is not on PATH" >&2; exit 1; }
|
||||||
done
|
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
|
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
|
||||||
echo "Working tree has local modifications. Commit or stash them first:" >&2
|
echo "Working tree has local modifications. Commit or stash them first:" >&2
|
||||||
git status --short >&2
|
git status --short >&2
|
||||||
@@ -54,23 +102,54 @@ pnpm install --frozen-lockfile --dir client
|
|||||||
|
|
||||||
# --- build ---------------------------------------------------------------
|
# --- build ---------------------------------------------------------------
|
||||||
|
|
||||||
# dist is gitignored, so the server builds it. Built into a temp dir first so a
|
# dist is gitignored, so the server builds it. The build stays inside the repo
|
||||||
# failed build leaves the currently-served files untouched.
|
# 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..."
|
step "Building client..."
|
||||||
rm -rf client/dist.new
|
rm -rf client/dist
|
||||||
pnpm --dir client exec vite build --outDir dist.new --emptyOutDir
|
pnpm --dir client exec vite build
|
||||||
|
|
||||||
if [ ! -f client/dist.new/index.html ] || [ ! -f client/dist.new/sw.js ]; then
|
# manifest.json is checked in under client/public and copied verbatim by Vite;
|
||||||
echo "Build output looks wrong (missing index.html or sw.js) -- keeping the previous build." >&2
|
# if it is missing, the PWA installs without icons, scope or a share target.
|
||||||
rm -rf client/dist.new
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
step "Swapping in the new build..."
|
# --- publish -------------------------------------------------------------
|
||||||
rm -rf client/dist.old
|
|
||||||
[ -d client/dist ] && mv client/dist client/dist.old
|
step "Publishing to $RELEASE..."
|
||||||
mv client/dist.new client/dist
|
mkdir -p "$RELEASE"
|
||||||
rm -rf client/dist.old
|
# 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 -------------------------------------------------------------
|
# --- restart -------------------------------------------------------------
|
||||||
|
|
||||||
@@ -83,4 +162,10 @@ pm2 reload "$REPO/server/ecosystem.config.js" --env production --update-env
|
|||||||
pm2 save
|
pm2 save
|
||||||
|
|
||||||
step "Done."
|
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
|
pm2 list
|
||||||
Reference in new issue
Block a user