Files
werkator/docs/prs/2026-09-03-PR#18-remote-host-layout.md
T

7.2 KiB

WARNING: This document describes only the change applied in this PR. It may already be outdated once the next PR is merged. Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.

The Problem

tools/remote grew with the Hostsharing Managed Webspace rollout (steps 21 and 23) and encoded that rollout's layout as if it were the only one. The watched repository had to be $WERKATOR_PATH/werkator, the runtime $WERKATOR_PATH/.werkator/werkator, and the systemd unit was spelled out as werkator-werkator.service; a werkdock binary and a bwrap rootfs archive were built and uploaded unconditionally. vm4006, the Docker host that has been running Werkator since long before the script existed, matches none of that: its watched repository is ~/hs.hsadmin.ng (so its unit is werkator-hs.hsadmin.ng.service), its runtime lives in ~/opt/werkator, and it has neither werkdock nor a rootfs because its builds run in Docker. So that host could only be deployed by hand, and had drifted nine releases behind — the question that started this PR was whether the Werkdock and multi-repo work had broken it, which it had not: what was broken was the deployment tooling.

The second problem surfaced while deploying: instance-update stopped the systemd unit and then started the upload. The 66 MB transfer to vm4006 died with scp: Connection closed, leaving the host with no running Werkator and nothing new to start.

Non-Goals

  • instance-start for a Docker host: it places a Hostsharing .htaccess into a doms/<domain>/ docroot, which only exists on a Managed Webspace. vm4006 uses the managed nginx container instead, and keeps its existing units.
  • repo-init for vm4006 — the repository has been cloned and configured there for months; only instance-update was needed.
  • Making the machine configuration of vm4006 current: it still carries the pre-rename gitTally: meta key, which is read by nothing today. Harmless while ConfigVersions.FORMAT_BROKE_IN is empty, a trap on the day it is not.

The Scenarios

Feature: one deployment command for every host layout

Background

  • The env file carries transport values only; the three new keys describe where things are on the host, not what Werkator does.
  • The defaults are exactly the layout instance-install creates, so an env file naming none of them resolves as before.

Scenario#18.01: A host that predates the script can be deployed with it

So that an installation is not condemned to hand-typed scp sequences because it was set up before the tooling existed.

  • Given an env file with WERKATOR_REPO_DIR=hs.hsadmin.ng, WERKATOR_INSTALL_DIR=/home/tallyman/opt and WERKATOR_SANDBOX=docker
  • When tools/remote --env-file .env.vm4006 werkator instance-update runs
  • Then it addresses werkator-hs.hsadmin.ng.service, unpacks into ~/opt, and uploads neither a werkdock binary nor a rootfs archive
Verified by
  • Live on vm4006 (2026-09-03): check-prerequisites reported the Docker daemon instead of running werkdock doctor; the update swapped ~/opt/werkator from v1.0.1 to v1.1.2, the unit came up active, /api/watcher polls without errors, and /api/system still reports real disk figures (diskSource.kind: volume) rather than the quota shape from PR#16.

Scenario#18.02: The webspace hosts are unaffected

So that making the script layout-aware does not break the deployment path that is actually in production.

  • Given .env.mih09 and .env.mih34, neither naming any of the new keys
  • When the layout is resolved
  • Then repository directory, install directory, unit name and sandbox are identical to the hardcoded values they replace
Verified by
  • Resolution measured for both env files (2026-09-03): REPO_DIR=$WERKATOR_PATH/werkator, INSTALL_DIR=$WERKATOR_PATH/.werkator, UNIT=werkator-werkator.service, SANDBOX=bwrap.
  • Live on mih09 (2026-09-03): a full instance-update ran through the bwrap path — werkdock uploaded, werkdock 0.1.0-dev reported after the swap, service active, https://werkator.javagil.de/ answering 200.

Scenario#18.03: A failed transfer does not take the service down

So that a dropped connection costs the upload and nothing else.

  • Given an instance whose service is running
  • When the runtime bundle cannot be transferred
  • Then the service is still running, because the upload happens before the stop, and a partially transferred file is never moved into place
Verified by
  • The failure itself on vm4006 (2026-09-03), which is what this scenario is written from: with the old order, scp: Connection closed left the unit stopped and the host without Werkator.
  • Live on mih09 (2026-09-03): the bundle already on the host was recognised by its sha256 and skipped, so the upload step cost nothing and the stop followed only after it.

The Solution

Three optional env keys replace three hardcoded assumptions. WERKATOR_REPO_DIR and WERKATOR_INSTALL_DIR are resolved absolute-or-relative-to-WERKATOR_PATH, and the unit name is now derived from the repository directory the way SystemdServiceFiles.unitName derives it (basename, every character outside [A-Za-z0-9_.-] replaced by a dash) instead of being spelled out — one rule, in two places, with the script naming the Kotlin function it mirrors. WERKATOR_SANDBOX=docker skips everything bwrap-shaped: no werkdock build, no werkdock upload, no rootfs archive, and check-prerequisites asks the Docker daemon instead of running werkdock doctor. repo-add clones beside the watched repository rather than into WERKATOR_PATH, which is the same directory whenever the default layout is used.

deploy_instance is split into upload_instance_artifacts and swap_instance_runtime, and instance_update calls the first before stopping the unit. Every artifact is uploaded to <name>.part, compared by sha256 with the local file, and only then moved into place; three attempts, and an unchanged artifact is skipped entirely. The checksum is not belt-and-braces: a truncated tarball would unpack into a runtime that starts and misbehaves, which is far worse than the failed transfer it came from.

Open Questions

  • Should instance-start learn the Docker-host shape too? Not answered here. vm4006 keeps its existing units and its managed nginx; the day it needs regenerating, init --systemd on the host is the documented path.

Additional Changes

  • None beyond the feature itself.

Follow-up work discovered while deploying this

  • https://vm4006.hostsharing.net:8443/ is not reachable from outside the host, while https://127.0.0.1:8443/ answers 200 and the werkator-nginx-hs.hsadmin.ng container publishes both ports. Pre-existing and unrelated to this PR — the host's own firewall, not Werkator.
  • The machine configuration on vm4006 still declares gitTally: version: since: "0.9.20"; the current code reads only werkator:, so the file's version claim is silently ignored (werkator.version.since prints empty). It costs nothing while ConfigVersions.FORMAT_BROKE_IN is "", and stops protecting that host the moment plan step 18 sets it.

Prerequisite PRs

  • None; it changes only tools/remote and the documentation.

Follow-up PRs

  • None planned.