site preview: support sites not built with zola, and start a preview in a VM from the hypervisor #234

Closed
opened 2026-09-27 05:50:10 +01:00 by vnprc-agent · 3 comments
Contributor

Make allod site preview work for a site that is not built with zola, and let the hypervisor start a preview inside a dev VM and reach it in one command.

Primary goals:

  • Any generator — a site repository whose default package is built by something other than zola gets a working preview, where today it gets a zola error.
  • No change for zola sites — a zola site previews exactly as it does now, with no edit to its repository.
  • Live reload where the generator has it — a site that has a development server uses it; a site that has none is built and served as static files.
  • One command from the hypervisor — allod site preview --vm <name> starts the preview in the VM and forwards every port the browser needs, and stopping it stops both.
  • Nothing left running — ending the command, or losing the connection, leaves no server holding the port in the VM and no forward holding it on the hypervisor.

Current state and specifics

preview always runs zola. cmd/allod/site_preview.go:40 builds the one command line it can run: nix shell --inputs-from <root> nixpkgs#zola --command zola --root <root> serve. In a site repository with no config.toml it exits with zola's own message:

Error: config.toml not found in current directory or ancestors

The comment at cmd/allod/site_preview.go:14 records the hardcoding as deliberate and names its own trigger: a second generator appearing in a site repository. Two have appeared, a bundled JavaScript application and a single hand-written HTML page. Both publish correctly with allod site deploy, which builds the repository's default flake package and never asks what produced the files.

The flags at cmd/allod/site_preview.go:58 and :64 mirror zola's own. --port and --interface mean something to any server; --base-url and --drafts are zola's alone.

How a preview is chosen

In this order, stopping at the first that applies:

Condition at the site root Preview runs
The flake exposes an app named preview that app, with nix run
config.toml exists zola, as today
Neither the default package is built and its output served as static files by allod itself

The third row needs no generator and no extra dependency: the files are already what deploy would publish. It has no live reload, which is acceptable for a site with no development server to reload from.

A preview app needs to be told where to listen. The default is two environment variables set by allod, one for the port and one for the interface, so the app's own command line stays its own. --base-url and --drafts keep working for zola and are refused, by name, for the other two rows.

Measured on a dev VM

Zola 0.21.0, started by allod site preview --port 1188 with standard input closed and no terminal attached:

Observation Result
Starts and serves without a terminal yes, HTTP 200 on the loopback address
Rebuilds on a changed file yes for an edit; no for a bare touch
Ports it listens on two: the page port, and 1024 for live reload. The page loads /livereload.js?port=1024
Signal sent to the allod process alone allod exits, zola keeps running and keeps the port
What localhost resolves to ::1 only

Three consequences for the design:

  • Forwarding the page port alone carries the page and not the reload signal. The forward has to include every port the server opened, which for zola is two.
  • allod does not take its child down with it. A session that ends without a terminal hangup leaves the server behind, and the next preview cannot bind.
  • A development server that binds localhost by default listens on IPv6 loopback only, and a forward aimed at 127.0.0.1 misses it. The interface the tool passes is the literal 127.0.0.1.

Starting it from the hypervisor

Dev VMs cannot open a connection to the hypervisor, so the forward is opened from the hypervisor side. allod site preview --vm <name>, run there inside a site checkout, connects to the VM, runs allod site preview in the same checkout path, and forwards the ports over that one connection.

Two properties of the connection are load-bearing:

  • It is not shared with any other session. Where the hypervisor's SSH configuration multiplexes connections, a forward added to a shared connection outlives the command that asked for it, and the port stays taken until the shared connection closes.
  • It allocates a terminal on the VM side, so an interrupt on the hypervisor reaches the server and its whole process group.

The VM's firewall stays closed. Nothing listens on any address but loopback, on either machine.

Validation

  • A fixture site of each of the three kinds previews, and the two non-zola kinds refuse --drafts with a message that names the flag.
  • After allod site preview is interrupted, and after its parent is killed, no child process remains and the port is free.
  • Manual, on a hypervisor with a dev VM: --vm serves the page, a saved edit in the VM reloads the browser without a refresh, and after an interrupt both machines show the ports free. Repeated with the network connection dropped instead of interrupted.

Scope

In scope: generator selection in preview, the static fallback, process cleanup, --vm, and the help text and tests for each.

Out of scope: deploy, check and config, which are already independent of the generator. Moving site repositories onto a shared site builder is separate work. Choosing a free port automatically when the default is taken is a follow-up; the first version refuses with a message that names the port.

Make `allod site preview` work for a site that is not built with zola, and let the hypervisor start a preview inside a dev VM and reach it in one command. Primary goals: - **Any generator** — a site repository whose default package is built by something other than zola gets a working preview, where today it gets a zola error. - **No change for zola sites** — a zola site previews exactly as it does now, with no edit to its repository. - **Live reload where the generator has it** — a site that has a development server uses it; a site that has none is built and served as static files. - **One command from the hypervisor** — `allod site preview --vm <name>` starts the preview in the VM and forwards every port the browser needs, and stopping it stops both. - **Nothing left running** — ending the command, or losing the connection, leaves no server holding the port in the VM and no forward holding it on the hypervisor. ### Current state and specifics `preview` always runs zola. `cmd/allod/site_preview.go:40` builds the one command line it can run: `nix shell --inputs-from <root> nixpkgs#zola --command zola --root <root> serve`. In a site repository with no `config.toml` it exits with zola's own message: ``` Error: config.toml not found in current directory or ancestors ``` The comment at `cmd/allod/site_preview.go:14` records the hardcoding as deliberate and names its own trigger: a second generator appearing in a site repository. Two have appeared, a bundled JavaScript application and a single hand-written HTML page. Both publish correctly with `allod site deploy`, which builds the repository's default flake package and never asks what produced the files. The flags at `cmd/allod/site_preview.go:58` and `:64` mirror zola's own. `--port` and `--interface` mean something to any server; `--base-url` and `--drafts` are zola's alone. ### How a preview is chosen In this order, stopping at the first that applies: | Condition at the site root | Preview runs | | --- | --- | | The flake exposes an app named `preview` | that app, with `nix run` | | `config.toml` exists | zola, as today | | Neither | the default package is built and its output served as static files by `allod` itself | The third row needs no generator and no extra dependency: the files are already what deploy would publish. It has no live reload, which is acceptable for a site with no development server to reload from. A `preview` app needs to be told where to listen. The default is two environment variables set by `allod`, one for the port and one for the interface, so the app's own command line stays its own. `--base-url` and `--drafts` keep working for zola and are refused, by name, for the other two rows. ### Measured on a dev VM Zola 0.21.0, started by `allod site preview --port 1188` with standard input closed and no terminal attached: | Observation | Result | | --- | --- | | Starts and serves without a terminal | yes, HTTP 200 on the loopback address | | Rebuilds on a changed file | yes for an edit; no for a bare `touch` | | Ports it listens on | two: the page port, and `1024` for live reload. The page loads `/livereload.js?port=1024` | | Signal sent to the `allod` process alone | `allod` exits, zola keeps running and keeps the port | | What `localhost` resolves to | `::1` only | Three consequences for the design: - Forwarding the page port alone carries the page and not the reload signal. The forward has to include every port the server opened, which for zola is two. - `allod` does not take its child down with it. A session that ends without a terminal hangup leaves the server behind, and the next preview cannot bind. - A development server that binds `localhost` by default listens on IPv6 loopback only, and a forward aimed at `127.0.0.1` misses it. The interface the tool passes is the literal `127.0.0.1`. ### Starting it from the hypervisor Dev VMs cannot open a connection to the hypervisor, so the forward is opened from the hypervisor side. `allod site preview --vm <name>`, run there inside a site checkout, connects to the VM, runs `allod site preview` in the same checkout path, and forwards the ports over that one connection. Two properties of the connection are load-bearing: - It is not shared with any other session. Where the hypervisor's SSH configuration multiplexes connections, a forward added to a shared connection outlives the command that asked for it, and the port stays taken until the shared connection closes. - It allocates a terminal on the VM side, so an interrupt on the hypervisor reaches the server and its whole process group. The VM's firewall stays closed. Nothing listens on any address but loopback, on either machine. ### Validation - A fixture site of each of the three kinds previews, and the two non-zola kinds refuse `--drafts` with a message that names the flag. - After `allod site preview` is interrupted, and after its parent is killed, no child process remains and the port is free. - Manual, on a hypervisor with a dev VM: `--vm` serves the page, a saved edit in the VM reloads the browser without a refresh, and after an interrupt both machines show the ports free. Repeated with the network connection dropped instead of interrupted. ### Scope In scope: generator selection in `preview`, the static fallback, process cleanup, `--vm`, and the help text and tests for each. Out of scope: `deploy`, `check` and `config`, which are already independent of the generator. Moving site repositories onto a shared site builder is separate work. Choosing a free port automatically when the default is taken is a follow-up; the first version refuses with a message that names the port.
Author
Contributor

Results of the manual test, run from a hypervisor against a dev VM with a zola site. Each run started allod site preview in the VM over SSH with both ports forwarded, then was interrupted with Ctrl-C.

Run Terminal allocated Connection shared Server left running in the VM Ports left held on the hypervisor
1 no no yes no
2 yes (-t) no no no
3 no yes yes yes, both ports, on IPv4 and IPv6 loopback

In every run the tools were found on the non-interactive session's PATH, the site built and served, and a saved edit in the VM triggered a rebuild.

What this settles:

  • The tool must allocate a terminal on the VM side. Without one, an interrupt ends the local command and leaves the server running in the VM, holding its port.
  • The tool must open its own connection and never a shared one. On a shared connection the forwards outlive the command that asked for them.
  • The tool must never close a shared connection to clean up. The usual way to release forwards stuck on a shared connection closes every session riding it. In this test that ended an unrelated long-running terminal session on the same VM. Cleanup has to be something the tool never needs.

One log line worth keeping: after the server was stopped, the VM's SSH daemon recorded connect_to 127.0.0.1 port 1024: failed four times in three seconds. That is the browser retrying the live reload port through a forward that no longer had anything behind it, and it confirms that zola's reload channel travels on the second port.

Not tested: a dropped network connection in place of an interrupt, and whether the browser reloaded without a manual refresh.

Results of the manual test, run from a hypervisor against a dev VM with a zola site. Each run started `allod site preview` in the VM over SSH with both ports forwarded, then was interrupted with Ctrl-C. | Run | Terminal allocated | Connection shared | Server left running in the VM | Ports left held on the hypervisor | | --- | --- | --- | --- | --- | | 1 | no | no | **yes** | no | | 2 | yes (`-t`) | no | no | no | | 3 | no | yes | **yes** | **yes**, both ports, on IPv4 and IPv6 loopback | In every run the tools were found on the non-interactive session's PATH, the site built and served, and a saved edit in the VM triggered a rebuild. What this settles: - **The tool must allocate a terminal on the VM side.** Without one, an interrupt ends the local command and leaves the server running in the VM, holding its port. - **The tool must open its own connection and never a shared one.** On a shared connection the forwards outlive the command that asked for them. - **The tool must never close a shared connection to clean up.** The usual way to release forwards stuck on a shared connection closes every session riding it. In this test that ended an unrelated long-running terminal session on the same VM. Cleanup has to be something the tool never needs. One log line worth keeping: after the server was stopped, the VM's SSH daemon recorded `connect_to 127.0.0.1 port 1024: failed` four times in three seconds. That is the browser retrying the live reload port through a forward that no longer had anything behind it, and it confirms that zola's reload channel travels on the second port. Not tested: a dropped network connection in place of an interrupt, and whether the browser reloaded without a manual refresh.
Member

Closed unfinished and replaced by allod/tools#240. This issue's design is not being carried forward; it is left as written so the record shows what was tried.

What this issue asked for, and what happened

It asked for three ways to serve a site chosen by what the repository contains, zola kept exactly as it was, cleanup of the server process, and --vm to start a preview in a VM and forward its ports. allod/tools#236 built all of it in about 2,200 lines, passed its tests and a cold review, and was closed unmerged when the owner read it. The reasons are recorded on that pull request.

What was wrong with the design here

  • It prescribed mechanisms, not the need. The table of three rows was a solution. The need was one sentence: see the site an agent is editing, in a browser on the hypervisor.
  • "No change for zola sites" kept zola inside the tool. That goal is why the tool held zola's flags and zola 0.21's second port while trying to stop caring about generators.
  • The static row has no live reload, which is the thing a preview is for.
  • It put the owner's command in charge of the server. The agent does the work in the VM and needs the server for its own checks; the hypervisor only needs a port. Tying the two together is what required a terminal on the VM side and detection of a dead connection.

What stays true

The measurements in this issue and its comment stand and are carried into allod/tools#240: zola 0.21 listens on a second port, localhost resolves to ::1 only, a server signalled through its parent alone keeps running, and a forward on a shared connection outlives the command that asked for it.

Closed unfinished and replaced by allod/tools#240. This issue's design is not being carried forward; it is left as written so the record shows what was tried. ### What this issue asked for, and what happened It asked for three ways to serve a site chosen by what the repository contains, zola kept exactly as it was, cleanup of the server process, and `--vm` to start a preview in a VM and forward its ports. allod/tools#236 built all of it in about 2,200 lines, passed its tests and a cold review, and was closed unmerged when the owner read it. The reasons are recorded on that pull request. ### What was wrong with the design here - **It prescribed mechanisms, not the need.** The table of three rows was a solution. The need was one sentence: see the site an agent is editing, in a browser on the hypervisor. - **"No change for zola sites" kept zola inside the tool.** That goal is why the tool held zola's flags and zola 0.21's second port while trying to stop caring about generators. - **The static row has no live reload,** which is the thing a preview is for. - **It put the owner's command in charge of the server.** The agent does the work in the VM and needs the server for its own checks; the hypervisor only needs a port. Tying the two together is what required a terminal on the VM side and detection of a dead connection. ### What stays true The measurements in this issue and its comment stand and are carried into allod/tools#240: zola 0.21 listens on a second port, `localhost` resolves to `::1` only, a server signalled through its parent alone keeps running, and a forward on a shared connection outlives the command that asked for it.
Member

Closed as not planned.

Closed as not planned.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#234
No description provided.