site serve and site view: run a site's own preview app in the VM and forward it to the hypervisor #242

Merged
vnprc merged 7 commits from agent/site-preview-unit into master 2026-09-28 15:32:37 +01:00
Member

allod site serve, which replaces allod site preview, runs whatever a site's own flake calls preview, as a user service inside the VM, and the new allod site view on the hypervisor forwards that one port to the owner's browser until Ctrl-C. The part an agent can run is tested against real Nix and systemd; the part that crosses from the hypervisor to the VM has only been checked by its argument lists and is the owner's to run before merging.

  • What changes on a machine: allod site preview is gone, and allod site serve takes its place without starting zola. It takes --port, --stop, --vm and a site id, and the old flags --interface, --base-url, --drafts, --open and the pass-through after -- are gone. Existing zola sites stop previewing until their flake has a preview app; the owner accepted that on the issue. allod site view is new, in every build.
  • What stays identical: deploy, check and config, and what every other command gets from the registry, shown by a test with a malformed port beside a healthy entry.
  • What proves it: Go tests pin every command line allod hands to systemd-run, systemctl, journalctl, nix, git and ssh; a script run in a dev VM starts, finds, fails and stops real previews, 9 steps of 9.
  • If it is wrong: revert this pull request and rebuild. A preview left running is systemctl --user stop 'allod-preview-*' in the VM; it listens on loopback only.
  • Order: allod/inventory#17 first, then this. The VM needs this build before view on the hypervisor can work, because view runs allod site serve <site> in the VM.

Closes allod/tools#240

Risk

R3. The change starts systemd user units, opens SSH connections from the hypervisor, and changes the decoder every registry lookup in allod goes through. The worst credible failures after validation: a preview reported as serving when it is not; a server left running on loopback in a VM; a forward on the hypervisor's loopback that outlives its use until the terminal closes. None touches a secret, a deployed site, or state that cannot be rebuilt. What keeps it at R3 and not lower is that --vm and view cannot be run by an agent.

For the owner to run on the hypervisor

After the VM runs this build and a site has a preview app and a preview_port:

  1. allod site view <site> shows the page in the browser at the printed address.
  2. An edit by the agent reloads the browser without a refresh. Only a WebSocket client has been seen to receive the reload message, never a browser.
  3. Each row of the table under "From the hypervisor" in docs/allod-site-preview.md, checking after each that the hypervisor's port is free and that the VM is in the state the table says. This includes the two questions still unmeasured: whether ssh -N exits when the VM shuts down or the link drops, and how soon.
  4. allod site serve --stop --vm <vm> <site> stops the server from outside.
  5. When nobody is logged in to the VM: start a preview from the hypervisor, wait a minute, and ask whether the unit is still active. Whether a preview survives the last login session ending is not measured, and the documentation says so.

Validation

Run by the overseer on 4807f62, the final commit, which is master merged into the branch:

  • nix build .#checks.x86_64-linux.go-checks -L --no-link --rebuild: exit 0, cmd/allod ok under all four tag combinations.
  • nix flake check: all checks passed!.
  • ALLOD_UNDER_TEST=<built binary> bash tests/site-preview-by-hand.sh: all 9 steps passed, no allod-preview-* unit and no listener left.
  • On the built binary: --stop for a site whose registry port is the string "18650" reports not running, exit 0; with the user manager unreachable it says systemd could not be asked, exit 1; view for a site with no port is refused by name; --vm -x is refused.

Run by the implementing worker and read from its captured output, not repeated by the overseer:

  • A binary whose start reports success without waiting fails 3 of the script's 9 steps, the same three on each of three runs.
  • Reducing the shell quoting to nothing fails TestSiteServeVM, TestSiteViewForwardsThePort and TestSiteViewVMLookup. Moving the address ahead of the question about the unit fails TestSiteServeOutcomes/unit_gone_although_the_port_answered.
  • A worktree lookup that names the worktree's own path fails 2 of the script's 9 steps.
  • systemctl --user is-active --quiet exits 0 for an active unit, 3 for a failed or activating one, 4 for one that is inactive or does not exist, 1 when the manager cannot be reached.

Measured before the code was written, recorded on the issue: nix run of a site's app works inside the unit; zola 0.23.6, live-server 0.11.1 and Vite 7.3.6 each hold one listener on the page port and deliver their reload message through a relay of that single port; systemctl --user works in a non-interactive ssh <vm> <command> and allod is on its path.

Not run: --vm and view against a real VM. This VM's key is not authorized to log in to the VM itself.

Review

One cold pass by gpt-5.6-sol through codex exec, reasoning effort high, on 6bdfffd, read-only. Eleven findings; each was checked against the source. One fix round, 260e719. The rename that followed, f0b415b, and the merge of master, 4807f62, change no behavior of these commands and were not reviewed again.

Finding Outcome
A malformed registry port blocks --stop and defeats --port Fixed and pinned, for preview and view
The path check runs before --stop Fixed: moved to the start
The wait reports serving without asking about the unit Fixed and pinned: after a connection succeeds the unit must still be running
Every non-zero is-active reads as not running Fixed and pinned: only the measured code for an absent unit does
The script's first step can pass against a binary that does not wait Fixed: the fixture stays silent for 10 seconds, the fetch waits 5 at most
list-units ... || true hides a failed probe Fixed
The documentation says a server that moves ports is reported as serving Fixed
Help says exit 1 means the unit has stopped Fixed
Checkout paths with spaces or quotes are refused, not quoted Kept. The path goes into Nix source and a flake reference; the registry validation forbids whitespace in a checkout
Paths are compared without resolving symbolic links Kept. The miss is loud: the site has no port and is refused by name
view prints the address before ssh has bound the port Kept. It is the issue's order, and ssh reports a taken port itself

The reviewer's list of comments that restate code was applied in the same round.

Known limits

  • Two registry ids that differ only in characters the slug replaces, such as acme/site and acme-site, share a unit name.
  • If something that is not the preview already holds the site's port, the start is reported as serving. Telling the two apart needs a probe before the start, which the issue rules out.
  • nix run writes a flake.lock into a site checkout that has none.
  • The Vite example runs npm install at every start and writes about 21 MB of node_modules/ into the checkout.

Size

The issue's numbers were called an estimate by the owner on the issue. Against them:

Part Lines Issue's estimate
Go, tests excluded: site_serve.go 256, site_view.go 81, registry.go 51 added 388 250
Go tests: site_serve_test.go 455, site_view_test.go 215 670 300
docs/allod-site-preview.md 114 150
tests/site-preview-by-hand.sh 329 none

The Go above the estimate is the worktree lookup and the refusals the issue did not count: a missing app, a malformed registry port, a path nix cannot be given, a user manager that cannot be asked, and the unit asked about again after its port answers. Each has a test that pins it.

Departures from the issue's text

All are recorded on the issue.

  • An active unit goes through the same bounded wait as a fresh start. Still starting exits 3, failure exits 1, the wait is 60 seconds.
  • A site found inside a git worktree previews under the id, port and unit name of the repository the worktree belongs to, by one local git rev-parse. The check for the app therefore runs only when a start is about to happen.
  • --stop asks whether the unit is there before stopping it: systemctl --user stop exits 5 for a unit that is not loaded.
  • The contract gained one line: the app fails when its port is taken and does not move to another.
  • The command is allod site serve, not allod site preview, at the owner's request on this pull request. The noun preview stays for the flake app, preview_port, the ALLOD_PREVIEW_* variables and the unit name. allod site preview is an unknown command, pinned by TestSiteServeOldNameIsUnknown.
`allod site serve`, which replaces `allod site preview`, runs whatever a site's own flake calls `preview`, as a user service inside the VM, and the new `allod site view` on the hypervisor forwards that one port to the owner's browser until Ctrl-C. The part an agent can run is tested against real Nix and systemd; the part that crosses from the hypervisor to the VM has only been checked by its argument lists and is the owner's to run before merging. - **What changes on a machine:** `allod site preview` is gone, and `allod site serve` takes its place without starting zola. It takes `--port`, `--stop`, `--vm` and a site id, and the old flags `--interface`, `--base-url`, `--drafts`, `--open` and the pass-through after `--` are gone. Existing zola sites stop previewing until their flake has a `preview` app; the owner accepted that on the issue. `allod site view` is new, in every build. - **What stays identical:** `deploy`, `check` and `config`, and what every other command gets from the registry, shown by a test with a malformed port beside a healthy entry. - **What proves it:** Go tests pin every command line `allod` hands to `systemd-run`, `systemctl`, `journalctl`, `nix`, `git` and `ssh`; a script run in a dev VM starts, finds, fails and stops real previews, 9 steps of 9. - **If it is wrong:** revert this pull request and rebuild. A preview left running is `systemctl --user stop 'allod-preview-*'` in the VM; it listens on loopback only. - **Order:** allod/inventory#17 first, then this. The VM needs this build before `view` on the hypervisor can work, because `view` runs `allod site serve <site>` in the VM. Closes allod/tools#240 ## Risk R3. The change starts systemd user units, opens SSH connections from the hypervisor, and changes the decoder every registry lookup in `allod` goes through. The worst credible failures after validation: a preview reported as serving when it is not; a server left running on loopback in a VM; a forward on the hypervisor's loopback that outlives its use until the terminal closes. None touches a secret, a deployed site, or state that cannot be rebuilt. What keeps it at R3 and not lower is that `--vm` and `view` cannot be run by an agent. ## For the owner to run on the hypervisor After the VM runs this build and a site has a `preview` app and a `preview_port`: 1. `allod site view <site>` shows the page in the browser at the printed address. 2. An edit by the agent reloads the browser without a refresh. Only a WebSocket client has been seen to receive the reload message, never a browser. 3. Each row of the table under "From the hypervisor" in `docs/allod-site-preview.md`, checking after each that the hypervisor's port is free and that the VM is in the state the table says. This includes the two questions still unmeasured: whether `ssh -N` exits when the VM shuts down or the link drops, and how soon. 4. `allod site serve --stop --vm <vm> <site>` stops the server from outside. 5. When nobody is logged in to the VM: start a preview from the hypervisor, wait a minute, and ask whether the unit is still active. Whether a preview survives the last login session ending is not measured, and the documentation says so. ## Validation Run by the overseer on `4807f62`, the final commit, which is `master` merged into the branch: - `nix build .#checks.x86_64-linux.go-checks -L --no-link --rebuild`: exit 0, `cmd/allod` `ok` under all four tag combinations. - `nix flake check`: `all checks passed!`. - `ALLOD_UNDER_TEST=<built binary> bash tests/site-preview-by-hand.sh`: `all 9 steps passed`, no `allod-preview-*` unit and no listener left. - On the built binary: `--stop` for a site whose registry port is the string `"18650"` reports not running, exit 0; with the user manager unreachable it says systemd could not be asked, exit 1; `view` for a site with no port is refused by name; `--vm -x` is refused. Run by the implementing worker and read from its captured output, not repeated by the overseer: - A binary whose start reports success without waiting fails 3 of the script's 9 steps, the same three on each of three runs. - Reducing the shell quoting to nothing fails `TestSiteServeVM`, `TestSiteViewForwardsThePort` and `TestSiteViewVMLookup`. Moving the address ahead of the question about the unit fails `TestSiteServeOutcomes/unit_gone_although_the_port_answered`. - A worktree lookup that names the worktree's own path fails 2 of the script's 9 steps. - `systemctl --user is-active --quiet` exits 0 for an active unit, 3 for a failed or activating one, 4 for one that is inactive or does not exist, 1 when the manager cannot be reached. Measured before the code was written, recorded on the issue: `nix run` of a site's app works inside the unit; zola 0.23.6, live-server 0.11.1 and Vite 7.3.6 each hold one listener on the page port and deliver their reload message through a relay of that single port; `systemctl --user` works in a non-interactive `ssh <vm> <command>` and `allod` is on its path. Not run: `--vm` and `view` against a real VM. This VM's key is not authorized to log in to the VM itself. ## Review One cold pass by `gpt-5.6-sol` through `codex exec`, reasoning effort `high`, on `6bdfffd`, read-only. Eleven findings; each was checked against the source. One fix round, `260e719`. The rename that followed, `f0b415b`, and the merge of `master`, `4807f62`, change no behavior of these commands and were not reviewed again. | Finding | Outcome | | --- | --- | | A malformed registry port blocks `--stop` and defeats `--port` | Fixed and pinned, for `preview` and `view` | | The path check runs before `--stop` | Fixed: moved to the start | | The wait reports serving without asking about the unit | Fixed and pinned: after a connection succeeds the unit must still be running | | Every non-zero `is-active` reads as not running | Fixed and pinned: only the measured code for an absent unit does | | The script's first step can pass against a binary that does not wait | Fixed: the fixture stays silent for 10 seconds, the fetch waits 5 at most | | `list-units ... \|\| true` hides a failed probe | Fixed | | The documentation says a server that moves ports is reported as serving | Fixed | | Help says exit 1 means the unit has stopped | Fixed | | Checkout paths with spaces or quotes are refused, not quoted | Kept. The path goes into Nix source and a flake reference; the registry validation forbids whitespace in a checkout | | Paths are compared without resolving symbolic links | Kept. The miss is loud: the site has no port and is refused by name | | `view` prints the address before `ssh` has bound the port | Kept. It is the issue's order, and `ssh` reports a taken port itself | The reviewer's list of comments that restate code was applied in the same round. ## Known limits - Two registry ids that differ only in characters the slug replaces, such as `acme/site` and `acme-site`, share a unit name. - If something that is not the preview already holds the site's port, the start is reported as serving. Telling the two apart needs a probe before the start, which the issue rules out. - `nix run` writes a `flake.lock` into a site checkout that has none. - The Vite example runs `npm install` at every start and writes about 21 MB of `node_modules/` into the checkout. ## Size The issue's numbers were called an estimate by the owner on the issue. Against them: | Part | Lines | Issue's estimate | | --- | --- | --- | | Go, tests excluded: `site_serve.go` 256, `site_view.go` 81, `registry.go` 51 added | 388 | 250 | | Go tests: `site_serve_test.go` 455, `site_view_test.go` 215 | 670 | 300 | | `docs/allod-site-preview.md` | 114 | 150 | | `tests/site-preview-by-hand.sh` | 329 | none | The Go above the estimate is the worktree lookup and the refusals the issue did not count: a missing app, a malformed registry port, a path nix cannot be given, a user manager that cannot be asked, and the unit asked about again after its port answers. Each has a test that pins it. ## Departures from the issue's text All are recorded on the issue. - An active unit goes through the same bounded wait as a fresh start. Still starting exits 3, failure exits 1, the wait is 60 seconds. - A site found inside a git worktree previews under the id, port and unit name of the repository the worktree belongs to, by one local `git rev-parse`. The check for the app therefore runs only when a start is about to happen. - `--stop` asks whether the unit is there before stopping it: `systemctl --user stop` exits 5 for a unit that is not loaded. - The contract gained one line: the app fails when its port is taken and does not move to another. - The command is `allod site serve`, not `allod site preview`, at the owner's request on this pull request. The noun `preview` stays for the flake app, `preview_port`, the `ALLOD_PREVIEW_*` variables and the unit name. `allod site preview` is an unknown command, pinned by `TestSiteServeOldNameIsUnknown`.
'allod site preview [--port <n>] [--stop] [<site>]' hands the site's own
'preview' flake app to the user systemd manager as the transient unit
allod-preview-<slug>, waits up to 60 seconds for its port to answer, and
exits. It knows no generator: the flake decides what serves the site, and a
flake without 'apps.<system>.preview' is refused by one 'nix eval'.

The zola-specific flags, the pass-through after '--', and the file's
paragraphs about zola are gone. The port comes from the new optional
'preview_port' field of a repository's registry entry, which '--port'
overrides; registry.go grows registryEntries so the lookup works by id and
by checkout directory alike.

Exit 0 prints the address, whether this run started the server or found one
up; exit 1 shows the unit's last 20 log lines when it is no longer running;
exit 3 says it is still starting and names the command that follows its log.

Validated by Go tests at the exec boundary, which pin each argument list and
each refusal without a systemd, a nix daemon or a sleep, and by
tests/site-preview-by-hand.sh, run by hand in a dev VM against real Nix and
real systemd. Each validator was shown to fail: a binary whose start reports
success without waiting turns two steps of the script red, and shortening the
systemd-run argument list turns the Go tests red.

'allod site view', '--vm' and docs/allod-site-preview.md wait on measurements
only the hypervisor can make; the refusal already points at that file.

Refs allod/tools#240
Two defects the overseer reproduced on the previous commit.

A repository entry whose 'preview_port' held a string, a fraction or null
failed json.Unmarshal for the whole registry, so registryEntries returned
nothing and every lookup in the file missed — including the ones commands
that have nothing to do with sites make. The field is now decoded late, from
json.RawMessage, and one that is present and outside 1024-65535 is refused by
name. An absent or null one still means "no port". Measured: a sibling entry
resolves while the mistyped site is refused as

    allod: preview_port "18650" for fixture/site in <registry> is not a whole
    number from 1024 to 65535

A checkout path is interpolated into Nix source, inside a quoted string, and
again into '<root>#preview', where '#' and '?' have their own meaning; 'nix
eval --expr' cannot take it as an argument instead. One check now refuses a
path holding anything but letters, digits, '/', '.', '_', '+' and '-', naming
the path and the character, before any command runs.

previewPortValue in registry.go is the one definition of the range, so
'--port' and a registry value are held to it in the same place.

Refs allod/tools#240
docs/allod-site-preview.md, 88 lines of the 150 the issue gives the
documentation: what a preview is, the contract a site repository's flake meets,
the three worked examples, and the command's use in the VM. Every example is a
flake that was run under the issue's own systemd-run line, and every command in
it has been run as written.

The contract gains the line the measurements added: the app must fail when its
port is taken and must never move to another one, because the wait only ever
watches the port it handed over. Each example carries what its owner has to
know and nothing else: zola needs a nixpkgs pin at 0.23 or later, since an
older zola's reload channel is on a second port that one forwarded port cannot
reach; the Vite app installs Vite from npm at every start, which puts about
21 MB of node_modules in the checkout and wants the network on a cold cache,
and its --strictPort is what makes it fail rather than move.

README: 'preview' no longer starts 'zola serve' from a site's locked generator,
so the sentence that said so now says what it does, and the documentation list
names the new file.

'allod site view', '--vm' and the hypervisor are not documented yet; they wait
on measurements only the hypervisor can make.

Refs allod/tools#240
Three things the owner's measurements unblocked.

A site edited in a git worktree now previews as that site. When the directory
walked up to is in no registry entry, one local 'git rev-parse
--path-format=absolute --git-common-dir' names the repository it belongs to,
and that entry supplies the id, the port and the unit name while the working
directory and the flake stay the worktree's. The app check moved inside the
start, after the unit is found inactive: a preview serving a worktree answers
to the same id as a shared checkout that has no preview app, and evaluating
that checkout would refuse a preview that is running.

'allod site preview --vm <name> <site>' resolves nothing locally and runs the
same command in that VM over a connection of its own. ssh hands the words
after the host to a shell there, so the site id and the port are quoted: a site
id with a space or a single quote arrives as one argument.

'allod site view [--vm <name>] [--port <n>] <site>' runs on the hypervisor, in
every build. It reads the port from this machine's registry and the VM from
vm-specs.json beside it, starts the preview in that VM, prints the address
once, and replaces itself with the ssh that forwards the port. Nothing is
watched, nothing is shared, and no local port is probed. A remote start that
fails stops it with that command's own exit code.

The documentation gains the hypervisor half, the worktree rule, and the note
that what becomes of the unit when the last login session in the VM ends is
not measured. The README names view beside preview.

Refs allod/tools#240
Eight defects, and the comments the reviewer named.

A registry port nothing can use no longer blocks what does not need it: the
port is read after the stop branch and only when '--port' did not supply one.
The checkout-path check moved with it, into the start, which is the only place
the path reaches nix. Both are pinned, in preview and in view.

The wait now asks about the unit after a connection succeeds, not before: an
app that accepts one connection and exits, or another program holding the
port, was reported with exit 0 and an address. A connection is good news only
while the unit is still up.

'systemctl --user is-active --quiet' is read by the codes measured for it: 0
active, 3 loaded but not active, 4 inactive or no such unit. Only 4 means not
running; 3 counts as running, so a unit still coming up is stopped rather than
reported absent, and any other code - 1 when the user manager cannot be
reached - now says systemd could not be asked and exits 1, on both paths.
Before, an unreachable manager made '--stop' print "is not running" and exit 0.

In the by-hand script the fixture now stays silent for ten seconds against a
fetch that can wait five, so the first step fails whenever the address was
printed before the port answered; the sabotage was run three times to show it.
The 'list-units' probe lost its '|| true', so a probe that cannot run fails
its step instead of reporting nothing.

The documentation said a server that moves to another port would be reported
as serving; it is reported as still starting, or as serving only if something
else holds the assigned port. Help and documentation now say that exit 1 is
every refusal and every failed command, not only a unit that stopped.

sitePreviewPoll is a constant, sitePreviewNames is folded into its refusal, and
the duplicated test rows are one each.

Refs allod/tools#240
Author
Member

Requested by the owner: rename the command allod site preview to allod site serve.

The reason given: preview names what the owner gets, not what the command does, and it reads like something that shows a page, which is view's job. serve is the verb the site tools use for this (zola serve, hugo serve) and says a server is running and listening.

Only the command word changes. --stop stays a flag of it. The noun stays preview wherever it names the thing being served: the flake app apps.<system>.preview, the registry field preview_port, ALLOD_PREVIEW_PORT and ALLOD_PREVIEW_INTERFACE, and the unit name allod-preview-<slug>. allod/inventory#17 is unaffected.

Requested by the owner: rename the command `allod site preview` to `allod site serve`. The reason given: `preview` names what the owner gets, not what the command does, and it reads like something that shows a page, which is `view`'s job. `serve` is the verb the site tools use for this (`zola serve`, `hugo serve`) and says a server is running and listening. Only the command word changes. `--stop` stays a flag of it. The noun stays `preview` wherever it names the thing being served: the flake app `apps.<system>.preview`, the registry field `preview_port`, `ALLOD_PREVIEW_PORT` and `ALLOD_PREVIEW_INTERFACE`, and the unit name `allod-preview-<slug>`. allod/inventory#17 is unaffected.
allod-agent changed title from site preview: run the site's own preview app as a user unit, and site view to forward it to site serve and site view: run a site's own preview app in the VM and forward it to the hypervisor 2026-09-28 15:10:11 +01:00
The owner's rename, asked for on pull request 242. Only the command word moves:
'allod site serve' starts the server in the VM, 'allod site view' brings it to
the hypervisor's browser. No behavior changes.

The noun stays wherever it names the thing served: the flake app
apps.<system>.preview and 'nix run <root>#preview', the registry field
preview_port, ALLOD_PREVIEW_PORT and ALLOD_PREVIEW_INTERFACE, the unit
allod-preview-<slug>, and the two file names that carry it.

site_preview.go and site_preview_test.go are site_serve.go and
site_serve_test.go, and the function the command runs is siteServe. The helpers
keep their sitePreview prefix: each names the preview, not the command - its
unit, its slug, its app expression, the wait for its port.

'preview' is not an alias. It is now an unknown site command, like any other
word, and a test pins that.

Refs allod/tools#240
Author
Member

master was merged into this branch, at the owner's request to bring it up to date. The branch is a merge and not a rebase because agent/* branches take new commits only.

Two things needed a hand:

  • README.md: both sides changed the cmd/allod/ line of the layout. The result names check, from master, and site serve and view, from this branch.
  • cmd/allod/main_test.go gained the test helper equalArgs. master's new check_test.go uses it, and its only definition was in site_preview_test.go, which this branch replaces. Git merged the two without a conflict and the Go checks then failed with undefined: equalArgs.

Run on the merged tree: the Go checks pass under all four tag combinations, nix flake check prints all checks passed!, and tests/site-preview-by-hand.sh passes 9 of 9. allod lists both check and site.

`master` was merged into this branch, at the owner's request to bring it up to date. The branch is a merge and not a rebase because `agent/*` branches take new commits only. Two things needed a hand: - `README.md`: both sides changed the `cmd/allod/` line of the layout. The result names `check`, from `master`, and `site serve and view`, from this branch. - `cmd/allod/main_test.go` gained the test helper `equalArgs`. `master`'s new `check_test.go` uses it, and its only definition was in `site_preview_test.go`, which this branch replaces. Git merged the two without a conflict and the Go checks then failed with `undefined: equalArgs`. Run on the merged tree: the Go checks pass under all four tag combinations, `nix flake check` prints `all checks passed!`, and `tests/site-preview-by-hand.sh` passes 9 of 9. `allod` lists both `check` and `site`.
README.md: both sides changed the cmd/allod line of the layout; the result
names check, from master, and site serve and view, from this branch.

cmd/allod/main_test.go: equalArgs moves here. master's check_test.go uses
it, and its only definition was in site_preview_test.go, which this branch
replaces.

Refs allod/tools#240
vnprc approved these changes 2026-09-28 15:32:31 +01:00
vnprc merged commit 4807f62fb4 into master 2026-09-28 15:32:37 +01:00
vnprc deleted branch agent/site-preview-unit 2026-09-28 15:32:37 +01:00
Sign in to join this conversation.
No description provided.