site preview: choose ports automatically and tie both machines to one connection that ends by itself #238

Closed
opened 2026-09-27 17:48:49 +01:00 by allod-agent · 2 comments
Member

Make allod site preview choose its own ports and tie everything it starts, on both machines, to one connection that ends by itself when either side goes away, so two previews never collide and no way of stopping one leaves a process, a forward, or a file behind.

Primary goals:

  • No fixed ports — without --port, preview takes a port that is free, prints the address to open, and two previews running at once do not collide. --port <n> stays exact and is refused when taken.
  • No port is special — allod stops knowing that one version of one generator opens port 1024. A server that needs more than the page port says so with --forward.
  • The connection is the lease — a preview started with --vm runs in the VM only while it keeps hearing from the command that asked for it. Silence, or the connection closing, stops it within a bounded time with no help from the VM's SSH configuration.
  • Stop from either side — ending the command on the hypervisor, or ending the preview in the VM, ends both, and frees every port on both machines.
  • Nothing to clean up — no state file, socket file, lock, registry, background service, or shared connection. Interrupt, kill, a closed terminal, a dropped network and a power cut all take the same path, and none of them needs a later repair step.
  • Suspend behaves — Ctrl-Z does what a person expects or is refused in words, never a terminal that stops answering.

Current state and specifics

allod/tools#236 is the starting point. What it already has, and this keeps: the server runs in its own process group; an interrupt, a terminate or a hangup takes the whole group down; the kernel signals the server if allod is killed; --vm opens its own connection and never a shared one.

What it leaves open:

Behaviour today Why it is a problem
The page port defaults to 1111 and a taken port is refused A second preview needs a hand-picked --port
A zola site answers --ports with the page port and 1024 1024 is what zola 0.21 opens; zola 0.23.6 was measured serving reload on the page port. Two zola previews at once both want 1024, and --vm refuses when it is taken on the hypervisor even for a site that does not use it
--vm asks the VM for ports over one connection, then opens a second with the forwards The ports can be taken between the two
The VM side stops when its terminal hangs up After a network drop that only happens once the VM's SSH daemon notices, and nothing in the workspace configures it to look (ClientAliveInterval is unset)
Ctrl-Z is ignored while a preview runs It is a workaround for the terminal getting stuck, not job control

The design

Three rules, each of which removes a cleanup task by never creating the thing that would need cleaning.

1. The kernel hands out ports; binding is the only lock. A port is free when binding it succeeds and at no other time, so nothing else is consulted: no reserved range, no registry, no file recording who has what. Without --port, preview tries 1111 and, when that is taken, asks the kernel for a free port by binding port 0. Under --vm the browser and the server must agree on the number, because a generator writes its own address into the pages it serves, so the hypervisor picks a port free there and the VM side must bind the same one. When the VM side cannot, it exits with a code that means exactly that, and the hypervisor picks again, up to a small fixed number of tries. The gap between choosing a port and the server binding it cannot be closed for a server allod does not control; it is made harmless by failing loudly and trying again.

2. One connection, and it is the lease. --vm opens a single SSH connection with the forwards on it, runs allod site preview at the far end without a terminal, and keeps that command's standard input. The near side writes a short line on it every few seconds. The far side stops its server group when standard input closes or when three lines in a row are missed. The server's own standard input is /dev/null. An interrupt on the hypervisor closes the pipe and waits, bounded, for the far side to finish. Killing the near allod outright ends its ssh by the parent-death signal, which closes the pipe. Ending the preview in the VM ends the remote command, which ends ssh, which ends the forwards. There is no second connection and nothing to reconcile.

This departs from allod/tools#234, which found that the tool must allocate a terminal on the VM side. That was measured with an allod that did not take its server down; with the group cleanup in allod/tools#236 and end-of-input as the hangup, the terminal is no longer what carries the stop. This is the exploratory part and is settled by measurement, not by reading: the manual runs below decide it, and if end-of-input does not arrive when the client dies, the terminal stays and only the heartbeat is added.

3. Extra ports are declared, never guessed. --ports answers the page port alone. --forward names anything else. The 1024 constant is removed when the site repositories pin a zola that serves reload on the page port; until that is true of every site repository, it stays, and the forward for it becomes optional: taken on the hypervisor, it is skipped with a warning that live reload will need a manual refresh.

Suspend: under --vm a suspended command stops sending, the lease runs out and the preview stops, which the help text says. Run directly, preview gets job control proper: notice the server stopped, hand the terminal back, stop, and reverse it on continue.

Order of work

Each step lands by itself and leaves the command working.

  1. Automatic port choice for a preview run directly. Smallest, no dependence on the rest.
  2. The lease over standard input for --vm, replacing the terminal and the second connection, with the retry on a taken port.
  3. Job control for a preview run directly.
  4. Removal of 1024, on the trigger stated in rule 3.

Step 2 starts once allod/tools#236 has merged and its manual --vm runs have been done, so there is a measured baseline to compare against.

Validation

  • Two previews of two fixture sites start one after the other with no --port and both serve.
  • With real processes: after the near side is interrupted, terminated, killed, and suspended past the lease, and after the far side is killed, nothing either side started is running and every port binds again. The lease timeout is shown by a far side whose input stays open and silent.
  • Manual, from a hypervisor with a dev VM, since no agent can run it: start with --vm; stop it from the hypervisor; start again and stop it from inside the VM; start again and drop the network between the machines. After each, both machines show no preview process and no port held, with nothing run to tidy up.

Scope

In scope: cmd/allod/site_preview*.go, the help text, the README section, and their tests.

Out of scope, with the condition that would bring each in:

  • Setting ClientAliveInterval on dev VMs. The lease makes preview independent of it. It becomes its own change if other long-running remote commands show the same leftover.
  • Surviving a kill of the far allod when the server has started children of its own that it does not take down: the kernel signals only the direct child. Closing that needs the server in a process namespace or control group that dies as a whole, which adds a dependency; it comes in if a real preview app is seen leaving a child behind.
  • Handing a preview app an already-bound socket so the choose-then-bind gap closes entirely. It comes in if the retry in rule 1 is seen to fail in practice.
  • A stop command. The running process is the handle; a command to find and stop previews would need the state file this design exists to avoid.
Make `allod site preview` choose its own ports and tie everything it starts, on both machines, to one connection that ends by itself when either side goes away, so two previews never collide and no way of stopping one leaves a process, a forward, or a file behind. Primary goals: - **No fixed ports** — without `--port`, preview takes a port that is free, prints the address to open, and two previews running at once do not collide. `--port <n>` stays exact and is refused when taken. - **No port is special** — `allod` stops knowing that one version of one generator opens port `1024`. A server that needs more than the page port says so with `--forward`. - **The connection is the lease** — a preview started with `--vm` runs in the VM only while it keeps hearing from the command that asked for it. Silence, or the connection closing, stops it within a bounded time with no help from the VM's SSH configuration. - **Stop from either side** — ending the command on the hypervisor, or ending the preview in the VM, ends both, and frees every port on both machines. - **Nothing to clean up** — no state file, socket file, lock, registry, background service, or shared connection. Interrupt, kill, a closed terminal, a dropped network and a power cut all take the same path, and none of them needs a later repair step. - **Suspend behaves** — Ctrl-Z does what a person expects or is refused in words, never a terminal that stops answering. ### Current state and specifics allod/tools#236 is the starting point. What it already has, and this keeps: the server runs in its own process group; an interrupt, a terminate or a hangup takes the whole group down; the kernel signals the server if `allod` is killed; `--vm` opens its own connection and never a shared one. What it leaves open: | Behaviour today | Why it is a problem | | --- | --- | | The page port defaults to `1111` and a taken port is refused | A second preview needs a hand-picked `--port` | | A zola site answers `--ports` with the page port and `1024` | `1024` is what zola 0.21 opens; zola 0.23.6 was measured serving reload on the page port. Two zola previews at once both want `1024`, and `--vm` refuses when it is taken on the hypervisor even for a site that does not use it | | `--vm` asks the VM for ports over one connection, then opens a second with the forwards | The ports can be taken between the two | | The VM side stops when its terminal hangs up | After a network drop that only happens once the VM's SSH daemon notices, and nothing in the workspace configures it to look (`ClientAliveInterval` is unset) | | Ctrl-Z is ignored while a preview runs | It is a workaround for the terminal getting stuck, not job control | ### The design Three rules, each of which removes a cleanup task by never creating the thing that would need cleaning. **1. The kernel hands out ports; binding is the only lock.** A port is free when binding it succeeds and at no other time, so nothing else is consulted: no reserved range, no registry, no file recording who has what. Without `--port`, preview tries `1111` and, when that is taken, asks the kernel for a free port by binding port 0. Under `--vm` the browser and the server must agree on the number, because a generator writes its own address into the pages it serves, so the hypervisor picks a port free there and the VM side must bind the same one. When the VM side cannot, it exits with a code that means exactly that, and the hypervisor picks again, up to a small fixed number of tries. The gap between choosing a port and the server binding it cannot be closed for a server `allod` does not control; it is made harmless by failing loudly and trying again. **2. One connection, and it is the lease.** `--vm` opens a single SSH connection with the forwards on it, runs `allod site preview` at the far end without a terminal, and keeps that command's standard input. The near side writes a short line on it every few seconds. The far side stops its server group when standard input closes or when three lines in a row are missed. The server's own standard input is `/dev/null`. An interrupt on the hypervisor closes the pipe and waits, bounded, for the far side to finish. Killing the near `allod` outright ends its `ssh` by the parent-death signal, which closes the pipe. Ending the preview in the VM ends the remote command, which ends `ssh`, which ends the forwards. There is no second connection and nothing to reconcile. This departs from allod/tools#234, which found that the tool must allocate a terminal on the VM side. That was measured with an `allod` that did not take its server down; with the group cleanup in allod/tools#236 and end-of-input as the hangup, the terminal is no longer what carries the stop. **This is the exploratory part and is settled by measurement, not by reading:** the manual runs below decide it, and if end-of-input does not arrive when the client dies, the terminal stays and only the heartbeat is added. **3. Extra ports are declared, never guessed.** `--ports` answers the page port alone. `--forward` names anything else. The `1024` constant is removed when the site repositories pin a zola that serves reload on the page port; until that is true of every site repository, it stays, and the forward for it becomes optional: taken on the hypervisor, it is skipped with a warning that live reload will need a manual refresh. Suspend: under `--vm` a suspended command stops sending, the lease runs out and the preview stops, which the help text says. Run directly, preview gets job control proper: notice the server stopped, hand the terminal back, stop, and reverse it on continue. ### Order of work Each step lands by itself and leaves the command working. 1. Automatic port choice for a preview run directly. Smallest, no dependence on the rest. 2. The lease over standard input for `--vm`, replacing the terminal and the second connection, with the retry on a taken port. 3. Job control for a preview run directly. 4. Removal of `1024`, on the trigger stated in rule 3. Step 2 starts once allod/tools#236 has merged and its manual `--vm` runs have been done, so there is a measured baseline to compare against. ### Validation - Two previews of two fixture sites start one after the other with no `--port` and both serve. - With real processes: after the near side is interrupted, terminated, killed, and suspended past the lease, and after the far side is killed, nothing either side started is running and every port binds again. The lease timeout is shown by a far side whose input stays open and silent. - Manual, from a hypervisor with a dev VM, since no agent can run it: start with `--vm`; stop it from the hypervisor; start again and stop it from inside the VM; start again and drop the network between the machines. After each, both machines show no preview process and no port held, with nothing run to tidy up. ### Scope In scope: `cmd/allod/site_preview*.go`, the help text, the README section, and their tests. Out of scope, with the condition that would bring each in: - Setting `ClientAliveInterval` on dev VMs. The lease makes preview independent of it. It becomes its own change if other long-running remote commands show the same leftover. - Surviving a kill of the far `allod` when the server has started children of its own that it does not take down: the kernel signals only the direct child. Closing that needs the server in a process namespace or control group that dies as a whole, which adds a dependency; it comes in if a real preview app is seen leaving a child behind. - Handing a preview app an already-bound socket so the choose-then-bind gap closes entirely. It comes in if the retry in rule 1 is seen to fail in practice. - A `stop` command. The running process is the handle; a command to find and stop previews would need the state file this design exists to avoid.
Author
Member

Closed as superseded by allod/tools#240, before any of it was built.

This issue proposed automatic port choice, a heartbeat over standard input so the VM side stops when the hypervisor goes quiet, and job control for Ctrl-Z. All three were answers to problems created by one decision: that the command on the hypervisor owns the server in the VM. allod/tools#240 drops that decision. The agent owns the server as a user service in the VM, the hypervisor only forwards a port, and each site's port is written once in the repository registry, so there is no lifetime to tie together and no port to negotiate.

One idea from here survives: ports are never guessed and never probed. It survives as a registry field, not as code.

Closed as superseded by allod/tools#240, before any of it was built. This issue proposed automatic port choice, a heartbeat over standard input so the VM side stops when the hypervisor goes quiet, and job control for Ctrl-Z. All three were answers to problems created by one decision: that the command on the hypervisor owns the server in the VM. allod/tools#240 drops that decision. The agent owns the server as a user service in the VM, the hypervisor only forwards a port, and each site's port is written once in the repository registry, so there is no lifetime to tie together and no port to negotiate. One idea from here survives: ports are never guessed and never probed. It survives as a registry field, not as code.
Author
Member

Closed as not planned.

Closed as not planned.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/tools#238
No description provided.