ermahgerd is designed to be rebuilt. make destroy removes the default compact
Lima appliance and its platform state; make up recreates it, its Local
Development organization, curated applications, and trusted local Worker HTTPS
from source. make multi-destroy and make multi-up do the same for the
optional six-node profile. Treat either profile as a reproducible local
environment, not as a backup target or high-availability service.
Provisioning and health
make site # converge the running compact appliance
make status # show its Lima and systemd state
make ssh # inspect the all-in-one node
make test # verify the bundled Worker through public ingressUse make multi-site, make multi-status, and make multi-ssh for the
six-node profile.
Public single-node profile
The checked-in latitude inventory applies the compact topology to an ordinary
Ubuntu 24.04 or 26.x server. It uses the same roles as Lima; only inventory,
public origins, firewall exposure, and certificate policy differ. Set
LATITUDE_HOST, LATITUDE_SSH_USER, or LATITUDE_SSH_KEY to override the
reference host, user, and key.
Once the apex, portal, *.workers, and *.r2 A records resolve to the host,
bootstrap the complete installation with:
make latitude-upThat target generates untracked per-installation secrets, converges Ansible,
deploys the production example set through Wrangler over an SSH tunnel, obtains
trusted certificates with Certbot HTTP-01, reconverges secure origins and
cookies, and verifies the applications over HTTPS. The public profile seeds the
photo publishing workflow and Nimbus documentation site; the local-only sum,
order pipeline, and Durable Object counter remain available in the disposable
Lima profiles. Re-running the target is safe.
Use focused lifecycle targets afterward:
make latitude-site # converge services without deploying applications
make latitude-harden # apply SSH, firewall, kernel, and fail2ban policy
make latitude-deploy # redeploy curated applications through Wrangler
make latitude-check # verify existing applications over public HTTPS
make latitude-status # report failed systemd units
make latitude-ssh # open an operator shell
make latitude-credentials # display the locally held bootstrap identityThe generated secret inventory and bootstrap credentials live under
.cache/latitude/ with owner-only permissions and are deliberately excluded
from Git. Back them up in a real secret manager before treating the server as
durable infrastructure; losing them and generating replacements does not rotate
an existing installation safely.
The public listener serves the landing page, tenant console, status page, Workers, Access, and tenant R2 endpoints. The administrator portal is intentionally absent from public ingress. Give it a private listener reached through VPN or another operator-only network rather than publishing it beside tenant traffic.
The host hardening role denies unsolicited inbound and routed traffic by
default, exposes only SSH, HTTP, HTTPS, and the WireGuard underlay, and admits
platform service ports only on wg0. SSH accepts public-key authentication for
the declared operator account, permits local forwarding for the Wrangler SSH
tunnel, and disables root, password, agent, remote, X11, and tunnel forwarding.
UFW rate-limits new SSH connections. Fail2ban watches the systemd SSH journal in
aggressive mode, bans five failures within ten minutes for an hour, and
increases repeat-offender bans up to one week. Loopback and the platform
WireGuard subnet are excluded from bans.
make up is the default full readiness path: it provisions the compact node,
deploys the sample Worker and examples, verifies stored metrics, logs, and
traces, then enables trusted local Worker HTTPS. make multi-up follows the
same path for the six-node profile. The matching site target does not deploy
Worker code and preserves applied Worker routes.
Platform → Overview, Fleet, and Events & logs are the supported operator views for service posture, host pressure, and lifecycle evidence. The control plane only reports ready when its required identity, quota, Access, data-service, proxy, and runtime-pool dependencies are ready too.
Upgrading workerd
The runtime version and Linux arm64/amd64 archive SHA-256 checksums are pinned in
ansible/inventory/group_vars/all.yml. Update them together, run make checks,
then apply the full make site. Each Worker pool, storage, and queue unit uses
the versioned executable path, so changing the pin updates its unit and restarts
the service; updating only the /usr/local/bin/workerd symlink would not replace
an already-running process.
Keep compatibility dates unchanged during a runtime upgrade unless intentionally
adopting new API behavior. Existing Worker code, storage data, and durable queue
state remain on disk. Verify the upgrade with make storage-check,
make queue-check, make isolation-check, and make load-update. These exercise
the sample resources, including queue retry/restart recovery and Worker
deployments under traffic.
Runtime upgrades currently restart services and can interrupt requests. They are maintenance operations, distinct from Worker code deployments through Wrangler; the appliance does not yet provide rolling runtime-binary upgrades. Schedule a quiet period and do not treat a successful Worker rollout test as proof of a zero-downtime runtime upgrade.
Data and lifecycle
make down preserves the compact node disk and all platform state; make destroy removes it. make multi-down and make multi-destroy apply to the
six-node profile. A fresh up or multi-up recreates identity, quotas, Access,
Worker deployment history, storage catalogs, queues, databases, key-value
namespaces, models, and vector indexes as a new appliance.
State belongs to the service that owns the capability. The compact profile
places every service’s state on single-01; the six-node profile places Worker
releases and organization metadata on control-01, objects and asset manifests
on data-01, and queues, key-value namespaces, databases, and vector indexes on
data-02. Those are placement choices, not replicas. The appliance has no
built-in backup, restore, replication, or failover workflow; protect Lima disks
separately only when you deliberately need to retain local evaluation state.
Both profiles run two fixed native Durable Object shard members. They live on
single-01 in the compact profile and data-02 in the six-node profile. Their
encrypted deployment journals and native SQLite namespaces live under
/var/lib/ermahgerd-durable; destroying that VM intentionally removes the
state. Adding a member on either fleet data node provides new placement capacity
but does not move or replicate an occupied member’s state.
Cron trigger state is also native SQLite state on the Worker’s assigned durable
member. Workerd alarms wake one organization coordinator, which leases due
deliveries before calling the trusted runtime dispatcher. Restarting the member
preserves schedules and their last outcomes; destroying its data node removes
them with the rest of the disposable local state. There is no separate polling
scheduler service on control-01.
Security boundaries
Management APIs and private services use the fleet WireGuard network and are
forwarded only to host loopback. HAProxy is the public Worker frontend; it
selects a route only from the exact tenant Worker hostname and replaces trusted
routing headers before a request reaches workerd.
Workers share long-lived workerd pool processes: four on the compact profile
or eight across the two fleet compute nodes. Isolation comes from workerd/V8
isolates, restricted bindings, hostname routing, and process hardening. It is
deliberately not VM-grade isolation between Workers sharing a process. Read the
network topology before changing
service exposure or resource limits.
Local profiles use checked-in development credentials. The public Latitude profile instead generates random bootstrap and service credentials locally and passes them as an untracked Ansible variable file. The Worker-secret master key is generated on the selected control-plane node and stored root-only. The repository does not yet provide production identity federation or automatic secret rotation.
Public origins and HTTPS groundwork
The ordinary local ingress remains HTTP before its optional private-CA profile.
Worker URLs and Access callbacks use the
operator-configured platform_external_scheme, control_api_external_port,
and worker_base_domain. Default ports are omitted from generated URLs. These
settings describe the public side of a future TLS-terminating edge, not the
WireGuard transport between services.
For normal browser development against native HAProxy TLS, use make https-up.
The persistent profile uses the private ACME server, trusted
*.ermahgerd.localhost certificates, and host port 8443. Keep it active until
you need to return to HTTP, destroy the fleet, or run the isolated
renewal-under-traffic rehearsal with make https-check; in those cases use
make https-down first.
See HTTPS and domains for preparation, recovery,
certificate settings, and what this does not prove about public hosting.
HAProxy discards untrusted Forwarded and X-Forwarded-Host headers and
overwrites X-Forwarded-Proto with the configured public scheme. The private
workerd listener uses that trusted value for request.url; protected requests
retain it through Access. Do not expose that listener directly or trust a
browser-supplied forwarding header.
Changing platform_external_scheme to https does not by itself install a
certificate or enable a TLS listener. Enable and configure the certificate
manager and HAProxy TLS inventory together. The public Latitude profile supplies
real HTTP-01 validation, portal and Access origins, secure cookies, and a public
TLS listener. It deliberately keeps the administrator portal private.
Custom-domain routing is not implemented. A wildcard such as
*.workers.example.com also does not cover
blog.tenant.workers.example.com.
Observability retention
Prometheus retains up to seven days or 8 GB; Loki and Tempo retain seven days. Journald is bounded to 512 MiB persistent and 128 MiB runtime storage. Those settings are retention targets, not disk quotas, so inspect free disk space and collector/backend health before increasing load. See Observability.
Verification
make checks # local lint, types, tests, architecture, Worker checks
make observability-check # live metrics, logs, traces, and dashboard verification
make telemetry-check # tenant-scoped telemetry and authorization checks
make isolation-check # live process, network, filesystem, and resource checks
make storage-check # live Worker binding + SigV4 object round trip
make queue-check # live retry + restart + deploy cutover + R2 + DLQ proof
make load # 60-second 10 RPS k6 profile
make load-update # compute and R2/queue traffic during Wrangler deploymentsmake checks does not require a running fleet. The guest-dependent commands
refuse to proceed if Lima is not running or if their required Worker is absent.
For source changes, make lint checks Python formatting, repository-wide
JavaScript/TypeScript/CSS formatting, console lint rules (including React hook
dependencies), and JavaScript syntax. Use npm --prefix console run format to
apply the JavaScript formatting rules. Generated bindings and upstream vendor
code retain their original format. make javascript-check checks tracked and
new, non-ignored JavaScript sources without a hand-maintained file list.