the development server gets av-club too

The cropper needs an encoder, and only the build was told where one is. That made development the one
place the old behaviour survived: the framing approved in the admin, the picture dev showed, and the
file that shipped would have been three different things — which is the class of bug this whole change
exists to remove.

The port is ephemeral by design, so it is read at start rather than written to a file. av-club moving
leaves dev pointing at a port that has gone; restarting dev from the panel fixes it, and startServer
already deletes and re-creates precisely so a changed port is picked up.

/images/ gets a year and immutable in the nginx config. Earned rather than assumed: the filename is a
hash of the original, the framing, the slot and the width, so the bytes at a URL cannot legitimately
change. That is the difference from /derived/, which is rewritten in place and therefore capped at
thirty days.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-31 01:16:42 +01:00
co-authored by Claude Opus 5
parent 671097877e
commit 72edabdc8f
2 changed files with 41 additions and 1 deletions
+27
View File
@@ -6,6 +6,8 @@ import { randomBytes } from 'node:crypto';
import { encryptSecret, decryptSecret } from 'officerdb/crypto';
import { readDatabases } from './databases';
import { closeDevPort, openDevPort } from './firewall';
import { sidecarPort } from '@@/sidecar-registry';
import { getOwnerUser } from 'officerdb';
/**
* The site's DEVELOPMENT server, under pm2.
@@ -300,6 +302,31 @@ export async function startServer(which: ServerName): Promise<{ ok: boolean; out
POSTGRES_URL_DEV: url.toString(),
};
/*
How the development server reaches av-club — the same two values the BUILD gets, for the same
reason and by the same route.
The site crops each placement to the exact shape and size its slot renders at, rather than shipping
a whole photograph and asking CSS to hide most of it. Given only to the build, that would make
development the one place the old behaviour survived: the crop you approved in the admin, the
picture dev showed you, and the file that actually shipped would be three different things. That is
precisely the class of bug this whole change exists to remove, so dev runs the same path.
The port is EPHEMERAL by design — av-club binds whatever is free and reports it — so this is read
at start rather than written into a file. A restart of av-club therefore leaves dev pointing at a
port that has moved, and the fix is to restart dev from the panel, which is where its environment
is assembled anyway. Worth knowing rather than worth engineering around: `startServer` deletes and
re-creates the process precisely so a changed port is picked up.
*/
const avClubPort = sidecarPort('av-club');
// av-club resolves every path against a user's own media directory, so it has to be told who this is
// working for. The owner, because that is whose root location this plugin administers.
const owner = await getOwnerUser();
if (avClubPort && owner?.email) {
env.AV_CLUB_URL = `http://127.0.0.1:${avClubPort}`;
env.AV_CLUB_USER = owner.email;
}
/*
Open the firewall to the proxy BEFORE starting, so the port is reachable the moment it binds.
+14 -1
View File
@@ -209,7 +209,11 @@ export async function buildSite(secrets: {
* compression, and no cache headers at all, so every asset carried a TTL of zero and a returning
* visitor re-downloaded the whole site.
*
* ── Three lifetimes, because there are three kinds of file ──
* ── Four lifetimes, because there are four kinds of file ──
*
* `images/` are placements cropped to their slot, named by a hash of everything that decides their
* bytes. A year, immutable, and earned: the file at that URL cannot change, because a change produces
* a different name. This is exactly what `derived/` cannot have.
*
* `derived/` renditions are rebuilt IN PLACE — av-club replaces the contents of `1280.webp` rather
* than writing a new name. So they cannot be `immutable`, however much they look like they could be:
@@ -258,6 +262,15 @@ const NGINX_CONF = `server {
add_header Cache-Control "public";
}
# Placements cropped to their slot. A year and immutable, and this one is EARNED rather than assumed:
# the filename is a hash of the original, the framing, the slot and the width, so the bytes at a given
# URL can never legitimately change. A re-crop is a different name, and the HTML that carries it is
# revalidated every visit. This is the difference from /derived/ above, which is rewritten in place.
location ^~ /images/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ^~ /media/ {
expires 30d;
add_header Cache-Control "public";