a manifest cannot ask for a particular port

A preference sounds helpful and fails silently. When the wished-for number is
taken the plugin is handed a different one, the install succeeds, and whatever
was configured against the old number goes on pointing at nothing - with nobody
told. This afternoon that nearly happened: 3011 was only free because fofos-dev
had been stopped twenty minutes earlier, and had it still been running the site
would have been given a random port behind an nginx host still aimed at 3011.

So the number is always drawn fresh and always kept. Always drawn means it is
never something you can assume; always kept means the proxy host is configured
against it exactly once. The failure mode that remains - having to go and read
the number - is one that announces itself, which the other one did not.

isFree went with it; it existed only to test a wish.
This commit is contained in:
2026-09-06 10:58:41 +01:00
parent 518d329c29
commit d9110c373d
3 changed files with 10 additions and 38 deletions
+1 -1
View File
@@ -463,7 +463,7 @@ export async function installPlugin(
*/
const ports: Record<string, number> = {};
for (const declared of plugin.manifest.extraPorts ?? []) {
ports[declared.name] = await allocatePluginPort(appName, declared.name, declared.preferred);
ports[declared.name] = await allocatePluginPort(appName, declared.name);
}
if (Object.keys(ports).length) {
await step(
+7 -15
View File
@@ -436,20 +436,19 @@ export type PluginManifest = {
* process has already had to decide what to bind.
*
* ```ts
* extraPorts: [{ name: 'site' }] // any free port, remembered
* extraPorts: [{ name: 'site', preferred: 3011 }] // that one if it is free, otherwise any
* extraPorts: [{ name: 'site' }] // → PORT_SITE=<n> in the sidecar's environment
* ```
*
* `preferred` is a WISH, not a claim. It exists because a plugin often arrives after the thing that
* connects to it — an nginx proxy host already pointing at 3011, a firewall rule already written — and
* moving that configuration is work the platform can spare somebody. If the number is already given to
* another plugin, or is not free on this machine, the plugin gets a different one and no error: a
* preference that could fail the install would be a claim wearing a softer word.
* A manifest does NOT get to ask for a particular number, and that is deliberate. A preference sounds
* helpful and fails silently: when the wished-for port is taken the plugin is handed a different one,
* the install succeeds, and whatever was configured against the old number keeps pointing at nothing.
* Nobody is told. Drawing a fresh one every time makes the number always something you had to go and
* read, which is the state that keeps the configuration honest.
*
* Binding is still the sidecar's business, including which interface. A port that exists to be reached
* from a container cannot be loopback, and the platform has no way to know which a plugin means.
*/
extraPorts?: { name: string; preferred?: number }[];
extraPorts?: { name: string }[];
/**
* Host binaries this plugin needs. Absent means it is self-sufficient, which offscale and example are.
@@ -1018,13 +1017,6 @@ export function manifestProblems(appName: string, manifest: Partial<PluginManife
// while looking distinct here.
if (seen.has(name)) problems.push(`extraPorts[${i}] repeats the name "${name}"`);
seen.add(name);
const preferred = (entry as { preferred?: unknown }).preferred;
if (preferred !== undefined) {
// Above 1024 because nothing here runs as root, and below 65536 because that is the range.
const ok = typeof preferred === 'number' && Number.isInteger(preferred) && preferred > 1024 && preferred < 65536;
if (!ok) problems.push(`extraPorts[${i}].preferred must be a port number between 1025 and 65535`);
}
}
}
}