Pre-release documentation.
Commands may change. The binaries are named gridweaved, gridweave, gridweave-admin and gridweave-agent. GridWeave is under construction and not released: there is no download, no installer package and nothing is code-signed.
Choose a shape
GridWeave is one server binary (gridweaved) with one role per service, plus the operator tool gridweave-admin. State lives only in PostgreSQL, the message bus (NATS with JetStream) and object storage, so roles can be split and copied. There is no released version: you build the binaries or the container images yourself.
| Shape | What it is | Status | Verified on |
|---|---|---|---|
| All-in-one trial | make dev-docker or make dev: every role in one process, development credentials | Run Trial only | An Apple-silicon Mac |
| Single host | gridweaved all-in-one under systemd, local PostgreSQL 16, TLS, one host | Partial Not production ready | Ubuntu 24.04.3 ARM64 virtual machine |
| Containers and Compose | Server and agent images; a stack with the roles in separate containers, a TLS proxy, PostgreSQL and NATS | Partial Evaluation | A Mac with a Docker-compatible runtime |
| Helm chart | The roles as Deployments on Kubernetes 1.30 or later, with a migration hook | Partial Evaluation and pre-production | One single-node k3s 1.31 cluster |
Not production ready.
The default key store keeps private keys, the certificate authority's included, as files on the server host and the server warns about it. There is no package, no high availability, no automated backup, no signed release and no published image. The limits are listed below.
The all-in-one trial
The quickest path is the dockerised development environment in the Quickstart. It needs only Docker with Compose and GNU Make. For fast iteration on the code there is a host-process alternative, make dev, which also needs Go, Rust, Node with pnpm and starts PostgreSQL, Keycloak and Mailpit in Docker. Both use ports 8080, 8443, 5173 and 8081, so run one.
A single host with systemd
This is the procedure the product's deployment page ran on an Ubuntu 24.04.3 ARM64 virtual machine on 2026-11-13. One gridweaved all-in-one process serves the console (the web application is embedded in the binary), the API and the agent gateway on port 8443, and runs the scheduled jobs. PostgreSQL is the only other service. The reference files (gridweaved.service, gridweaved.yaml.example, gridweaved.env.example) are in deploy/single-host/ of the repository. Note on the rename: the procedure was run before the binaries were renamed. The commands and file names here use the new names, the rename was checked by building, testing and by installing the agent on the same virtual machine, but the server procedure itself was not run again under the new names.
1. Build
You need Go 1.27.4 or newer (the go command fetches the pinned toolchain itself), Node and pnpm. On the build machine:
make build-release
This builds the web application, embeds it in the server and writes bin/gridweaved, bin/gridweave-admin and bin/gridweave for the build machine's own architecture. To run on another operating system or architecture, cross-compile with matching GOOS and GOARCH; the product's deployment page lists the exact commands it used for Linux ARM64.
2. Prepare the host
sudo apt-get install -y postgresql
sudo useradd --system --home-dir /var/lib/gridweave --shell /usr/sbin/nologin --user-group gridweave
sudo install -d -m 0755 -o root -g root /opt/gridweave
sudo install -d -m 0750 -o root -g gridweave /etc/gridweave /etc/gridweave/tls
sudo install -d -m 0700 -o gridweave -g gridweave /var/lib/gridweave
sudo install -m 0755 -o root -g root gridweaved gridweave-admin /opt/gridweave/
Create the database login and put its URL, the only secret, in a root-only file. The password is generated on the host and never shown:
DBPW=$(openssl rand -hex 24)
printf "CREATE ROLE gridweave LOGIN PASSWORD '%s';\nCREATE DATABASE gridweave OWNER gridweave;\n" "$DBPW" | sudo -u postgres psql -v ON_ERROR_STOP=1
sudo install -m 0600 -o root -g root /dev/null /etc/gridweave/gridweaved.env
printf 'NS_DATABASE_URL=postgres://gridweave:%s@127.0.0.1:5432/gridweave?sslmode=disable\n' "$DBPW" | sudo tee /etc/gridweave/gridweaved.env >/dev/null
unset DBPW
sslmode=disable is for PostgreSQL on the same host over the loopback. Use require or verify-full for a database on another machine.
3. TLS certificate and configuration
Put your certificate chain (leaf first) in /etc/gridweave/tls/web.crt (mode 0644) and the private key in web.key (mode 0640, owner root, group gridweave). For a test, a self-signed certificate that covers the address works:
sudo openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes -days 365 -subj "/CN=gridweave.test" \
-addext "subjectAltName=IP:HOSTADDR,DNS:localhost" -keyout /etc/gridweave/tls/web.key -out /etc/gridweave/tls/web.crt
The server re-reads both files when they change (checked every 10 seconds) and keeps the old certificate, with a warning, if the new pair cannot be loaded. Automatic issuance (ACME) is not built. Agents do not use this certificate: the agent listener's certificate comes from the server's own authority, and agents trust it through a pinned root.
Copy gridweaved.yaml.example to /etc/gridweave/gridweaved.yaml (mode 0640, owner root, group gridweave). The file has no secrets; replace the two placeholders with the name browsers use and the names and addresses agents use:
node_id: gridweave-1
data_dir: /var/lib/gridweave # key store, embedded bus, object store
log:
level: info
http:
listen: ":443" # console, API, health and metrics
public_url: https://gridweave.example.com
tls:
cert_file: /etc/gridweave/tls/web.crt
key_file: /etc/gridweave/tls/web.key
agent:
listen: ":8443" # agent connections (mutual TLS)
hostnames: # the listener certificate is issued for these
- gridweave.example.com
- 203.0.113.10
Every key can be overridden by an NS_* environment variable (http.listen is NS_HTTP_LISTEN) and unknown keys are refused. An agent that connects to a name missing from agent.hostnames fails the TLS check.
4. Create the database roles once, then migrate
The schema depends on three group roles: ns_app, ns_platform (the only one with BYPASSRLS, which only a superuser may create) and ns_audit. The database login is an ordinary one: not a superuser, owner of the database, a member of the three roles. A superuser creates the roles once. If you migrate before that, gridweave-admin migrate changes nothing and prints the SQL to run, with the login's own name:
CREATE ROLE ns_app NOLOGIN;
CREATE ROLE ns_platform NOLOGIN BYPASSRLS;
CREATE ROLE ns_audit NOLOGIN;
GRANT ns_app, ns_platform, ns_audit TO gridweave;
Run it as the PostgreSQL superuser, for example sudo -u postgres psql -v ON_ERROR_STOP=1 -d gridweave, then migrate. The three roles are cluster-wide: if another GridWeave database in the same cluster already created them, only the GRANT is missing. gridweave-admin reads the same configuration as the server. Run it as the gridweave user from a directory that user can read (/var/lib/gridweave), with the environment file loaded. The product's page defines a small shell helper for this; the commands are:
gridweave-admin migrate --config /etc/gridweave/gridweaved.yaml # applied N migration(s): [...]
gridweave-admin migrate --config /etc/gridweave/gridweaved.yaml # schema is up to date
gridweave-admin schema-version --config /etc/gridweave/gridweaved.yaml
5. Certificate authority and first administrator
gridweave-admin pki init --config /etc/gridweave/gridweaved.yaml
gridweave-admin pki status --config /etc/gridweave/gridweaved.yaml
umask 077; openssl rand -base64 18 > ~/.gw-admin-pw
gridweave-admin bootstrap --username admin --password-stdin --config /etc/gridweave/gridweaved.yaml < ~/.gw-admin-pw
pki init logs development key storage in use and prints root fingerprint (agent ca_pin): ..., the 64-digit pin agents use. bootstrap creates the tenant default and a platform administrator; run twice it does nothing. Without --password-stdin it generates a password and prints it once. Sign in, change the password, then delete the file. Run pki init again after every upgrade: it creates only what is missing.
6. Service, firewall, verify
sudo install -m 0644 -o root -g root deploy/single-host/gridweaved.service /etc/systemd/system/gridweaved.service
sudo systemctl daemon-reload
sudo systemctl enable --now gridweaved.service
sudo ufw allow 22/tcp && sudo ufw allow 443/tcp && sudo ufw allow 8443/tcp && sudo ufw --force enable
The unit runs /opt/gridweave/gridweaved all-in-one --config /etc/gridweave/gridweaved.yaml as the gridweave user with Restart=always, starts after PostgreSQL and binds port 443 through CAP_NET_BIND_SERVICE. From another machine, curl --cacert web.crt https://HOSTADDR/healthz answers {"status":"ok"}, /readyz answers {"failed":[],"status":"ready"} and /api/v1/system/info names the version. /metrics (Prometheus text) needs no sign-in, so restrict who can reach port 443. After kill -9 the service was back in about 2.3 seconds, and after a host reboot it started by itself.
Signed in as an administrator, GET /api/v1/system/warnings shows kms.development_keys on this setup. That is true and is the reason it is not production ready.
Containers and Compose
One image holds the server (/app/server is gridweaved, and /app/admin is gridweave-admin): static, non-root and without a shell, with the role as its argument. A second image holds the Linux agent for demos, fleet simulation and CI. Build them:
make image # gridweave:<version>, for this machine's architecture
make image-agent # gridweave-agent:<version>
The split-role Compose stack runs api, agent-gateway, worker, relay and web as separate containers behind a Caddy TLS proxy, with PostgreSQL and NATS:
deploy/container/gen-secrets.sh
docker compose -f deploy/container/compose.yaml up -d
docker compose -f deploy/container/compose.yaml run --rm -T admin bootstrap --username admin --password-stdin < admin-password.txt
Create admin-password.txt yourself with umask 077. The console and API are on https://localhost:18800, with Caddy's own certificate authority, and agents connect to https://localhost:18801. A one-time script creates the three database group roles as the PostgreSQL superuser, then gridweave-admin migrate runs as a plain login.
The product's run on 2026-11-13 covered: all jobs exiting 0, sign-in through the proxy, a Linux agent container enrolling and running a script, a Mac agent enrolling over the published port, and the API and gateway recovering after being killed. An S3 overlay (compose.s3.yaml) was rendered with docker compose config but a full run against a real S3 service was not done. See the limits: one shared key volume, a one-node bus without credentials, a self-issued proxy certificate and no backups.
Kubernetes with the Helm chart
The chart is in deploy/helm/gridweave/ (chart 0.1.0, application 0.1.0-dev). It needs Kubernetes 1.30 or later, an external PostgreSQL 16, an external NATS with JetStream, the three database group roles created once by a superuser, and the server image in a registry the cluster can pull from. No image is published: build it with make image and push it yourself.
kubectl create namespace gridweave
kubectl -n gridweave create secret generic gridweave-db --from-file=url=./db-url.txt
helm install gw deploy/helm/gridweave -n gridweave -f my-values.yaml
The secret holds the database URL of the application login. The least a release needs:
publicUrl: https://gridweave.example.com
agentHostnames: [agents.example.com]
image: {repository: registry.example.com/gridweave, tag: "0.1.0-dev"}
database: {existingSecret: {name: gridweave-db, key: url}}
bus: {url: nats://nats.messaging.svc:4222}
relay: {region: eu-west}
ingress: {enabled: true, className: nginx, certManager: {issuerName: letsencrypt}}
A hook runs gridweave-admin migrate and then pki init before the role Deployments start, and again on every helm upgrade. Create the first administrator once with the password on standard input:
kubectl -n gridweave exec -i deploy/gw-gridweave-api -- /app/admin bootstrap --username admin --password-stdin
The agent Service is a LoadBalancer by default because the agent listener is mutual TLS over TCP and must not be terminated by an L7 proxy.
| Check | Status |
|---|---|
helm lint, golden renders, kubeconform on 78 resources, rendered security invariants | Run |
| Install on one single-node k3s 1.31 cluster; all roles ready; first administrator; a Mac agent enrolled and ran a script | Run |
| Rolling restart of the gateway with six agents connected | Partial Each agent was offline about a second per replacement; the API's view dipped further and the cause is unknown |
Ingress and HTTPRoute, cert-manager, autoscalers, network policies, more than one node, ReadWriteMany storage, a real schema-changing upgrade | Not run |
| OpenBao key store in the chart, pulling from a registry, any managed Kubernetes | Not run |
First-install notes
Key store
Every private key (the root, the Device, Service and Command authorities, and the command-signing key) and a wrapping key for stored secrets go through one key-management interface. Choose a driver with kms.driver:
| Driver | Where keys live | Use | Status |
|---|---|---|---|
file (default) | Plain key files in kms.file.path, default <data_dir>/keys | Development and CI only; raises the warning kms.development_keys | Default, development |
file-encrypted | The same files, encrypted under a master key you keep elsewhere (kms.file.master_key_file, kms.file.master_key or a systemd credential) | One host without OpenBao; no warning | Built Not the default |
openbao | The Transit engine of OpenBao (or Vault); keys never leave it; kms.openbao.address is required | Production; fails closed when unreachable, with a token and policy per role | Built Tested against a real OpenBao; the chart's OpenBao values were rendered, not run |
gridweave-admin kms generate-master-key --out FILE # a master key for file-encrypted, written mode 0600
gridweave-admin kms status
gridweave-admin kms migrate --to openbao|file-encrypted [--dry-run]
gridweave-admin kms rotate | kms rewrap [--dry-run]
With the file drivers, api and agent-gateway share one key directory, and whoever can read it holds every key. Back up the key directory together with the database, or the database cannot be read after a loss of the host. An offline-root ceremony, key recovery with dual control and PKCS#11 or cloud drivers are not built.
Object storage
objstore.driver is local (files under objstore.local.path, for all-in-one installs) or s3 (an S3-compatible service, required when roles run in separate processes). The S3 settings are objstore.s3.endpoint, region, bucket, access_key_id and secret_access_key (given as env:NAME or file:/path, never in the file as plain text). TLS to the service is always verified. Test it:
gridweave-admin objstore check
It writes, reads back, lists and deletes a probe object and reports each step. Recordings and the agent release store use object storage. It was verified only against SeaweedFS and local TLS test servers, not AWS S3, Ceph, MinIO or Garage. Presigned URLs, object lock and lifecycle rules are not built.
Mail settings
mail.driver is none (the default), smtp, log or file. With none nothing is sent and password reset by e-mail is unavailable. For a relay set mail.from_address (required for every driver but none), mail.from_name and mail.smtp.host, port, security (starttls by default, tls or none), username, password (as env:NAME or file:/path), ehlo_name and ca_file. The relay's certificate is always verified, and with starttls the server refuses to send if the relay does not offer it. A tenant can override the sender and the templates. The worker role sends the mail. The SMTP driver was run against an in-process server and Mailpit only: a production relay, a real mailbox and real mail clients were not tried.
Public URL and proxies
Set http.public_url: links in mail and identity-provider redirects use it, and operations that send a link answer 503 without a trusted origin. Sign-in cookies are __Host- cookies, so the console needs HTTPS (or localhost). Behind a TLS-terminating proxy leave http.tls unset and list the proxy in http.trusted_proxies. Address allow and deny lists for browsers and for agents exist at installation and tenant level.
Enrol an agent on a machine
An agent needs four things: the agent address (https://HOST:8443), the tenant slug (default), the root certificate with its fingerprint (the pin), and a single-use enrolment token.
- On the server host, write the public root certificate and read the pin:
gridweave-admin pki export-root FILEprintsroot fingerprint (agent ca_pin): .... - In the console, open Device groups, create a group, open it and choose Create enrolment token (validity in hours, number of uses). The token is shown once.
- On the machine, put the token in a file only root can read, then install. The token is read from
NSAGENT_ENROLLMENT_TOKENor from--token-file, never from the command line.
Run a dry run first. It writes everything below a scratch directory, registers nothing and needs no root:
./gridweave-agent install --root /tmp/gw-dry --server-url https://HOST:8443 --tenant default --ca-file root.pem --ca-pin PIN --skip-enroll
Then, as root:
sudo ./gridweave-agent install --server-url https://HOST:8443 --tenant default --ca-file root.pem --ca-pin PIN --token-file ~/enroll.token
sudo /usr/local/lib/gridweave-agent/gridweave-agent status
sudo /usr/local/lib/gridweave-agent/gridweave-agent uninstall
On Linux the agent installs to /usr/local/lib/gridweave-agent, its configuration to /etc/gridweave-agent/agent.toml and its state and device key to /var/lib/gridweave-agent, and runs as the systemd unit gridweave-agent.service (Restart=always). It runs as root because scripts run as the agent and as other users. Run status with sudo: without root it reports installed: no while the service runs. A used, expired or unknown token is refused with the same message, TOKEN_INVALID. The service, unit, directories and socket are all named gridweave-agent; if install finds a leftover installation under the earlier name (nsagent), it stops with exit status 2 before writing anything and tells you to run the old binary’s uninstall first. The installed agent also updates itself when a rollout offers it a release.
| Platform | Status | Notes |
|---|---|---|
| Linux with systemd | Run | Ubuntu 24.04.3 ARM64 virtual machine: install with enrolment, status, restart by systemd after a hard kill in about six seconds, uninstall, and install, run and uninstall again under the new names. Not tested: x86-64, other distributions, non-systemd init, proxies (the agent has no proxy support) |
| macOS | Dry run only | The LaunchDaemon path wrote its files in a dry run (the renamed property list was checked with plutil only); launchctl registration, a restart under launchd and uninstall never ran. A foreground agent (gridweave-agent enroll then gridweave-agent run) did run and enrol |
| Windows | Not verified | Built and tested on one Windows 11 ARM64 virtual machine that has become unreachable; the installer script was never run, and the renamed service names were type-checked only. Nothing is code-signed |
For a foreground agent, write an agent.toml with server_url, tenant, ca_file, ca_pin and data_dir, then run gridweave-agent enroll --config agent.toml --token-file FILE and gridweave-agent run --config agent.toml. The agent refuses a configuration file that others can write (mode 0664, for example): use chmod 600. Agent packages (.tar.gz with an install script, .deb, a macOS .pkg, a Windows .zip) can be built with the product's make release-* targets, and every one is unsigned.
Operating it
| Task | How | Status |
|---|---|---|
| Status and logs | systemctl status gridweaved, journalctl -u gridweaved -f (JSON lines; secrets are not logged) | Run |
| Liveness, readiness, metrics | /healthz, /readyz (503 while PostgreSQL is down, recovers without restart), /metrics | Run |
| Back up | pg_dump --format=custom of the database and a tarball of /var/lib/gridweave/keys, taken together. Nothing automates it | Run Restore tested into the same cluster only |
| Upgrade | Stop, replace the binaries, gridweave-admin migrate, gridweave-admin pki init, start. Migrations are forward-only: to go back, restore a backup | Partial Tested as a no-op with binaries from the same commit |
| Update the agents | Import a signed release with gridweave-admin releases import --dir DIR, then create a rollout in the console (rings, window, concurrency, health gate). Installed agents fetch, verify and apply it themselves and roll back if the new build does not reconnect. Details: staged rollouts | Partial Run on Linux under systemd; macOS launchd and Windows not verified; development signing keys |
| Check the audit chain | gridweave-admin audit verify prints ok and the entry count per tenant, and one line for the platform | Run |
| Reset the administrator's password | gridweave-admin account reset --tenant default --user admin --password-stdin. It also removes second factors and ends sessions | Run |
Honest limits
- Development key store by default. The warning
kms.development_keysappears on every server using thefiledriver. - No high availability. One host runs one PostgreSQL and one
gridweavedwith an embedded bus and a local object store. The Compose bus is one node without credentials. - No automated backups, no backup-failure warning and an untested restore onto a new host.
- One login for every database role in the reference deployment. The design asks for separate logins for the platform and audit roles (
database.platform_url,database.audit_url) in production. - Nothing is signed and nothing is published. No installer, image or update key is code-signed, the update-manifest keys in the repository are development keys, and there is no install page or enrolment link.
- CI is disabled. The local check set is the gate.
- Narrow testing. The product has run on one macOS machine and one Ubuntu 24.04 ARM64 virtual machine. No x86-64, no other distribution, no Active Directory, no real mail relay, no hardware Intel AMT, and no 10,000-agent run on a reference Linux host.
- Not built: the
bridgerole (it exits with code 3), remote desktop, notification delivery, passkeys, reports. - Container image: the image scan of the container feature reported one HIGH-severity finding in a dependency (go-jose 4.1.4). The server now builds with 4.1.6, the fixed version, but the image scan was not re-run on that build.
The roadmap page has the product-wide status.