Files
vantage-agent/CLAUDE.md
T
mrhid6 6b2c5f6f62
Agent Release / build (push) Successful in 2m23s
Agent Release / msi (push) Successful in 3m10s
refactor: stand up vantage-agent as its own repository
agent/ becomes the repository root, so the module is
gitea.hostxtra.co.uk/vantage/vantage-agent, and installer/ comes with it.

Releases move to this repository, and internal/sync's self-update URLs
move with them. Agents built before this point have the old
mrhid6/vantage path compiled in and will fail the push-button update;
re-running the server-generated /update one-liner on each host moves
them onto a build that knows the new address.
2026-09-08 08:58:30 +00:00

7.3 KiB

Vantage agent (vantage-agent)

The lightweight Go agent installed on every managed server, and the Windows installer that packages it. Extracted from the vantage monorepo with its history; the agent is the repository root, so the module is gitea.hostxtra.co.uk/vantage/vantage-agent.

vantage-agent/
├── cmd/main.go                  # flags: -generate-key
├── internal/
│   ├── checker/                 # monitor check execution
│   ├── config/                  # config.yaml load/save
│   ├── exec/                    # workflow step execution
│   ├── grpc/                    # client + the codec registration
│   ├── inventory/               # CPU/mem/disk collection (linux/other/windows)
│   ├── keys/                    # authorized_keys read/diff/write
│   ├── monitors/                # agent-run monitor loop
│   ├── proxy/                   # console relay, always from 127.0.0.1
│   ├── sync/                    # poll loop + command stream + self-update
│   ├── updates/                 # OS package update check/apply
│   ├── winexec/                 # PowerShell invocation on Windows
│   └── workloads/               # containers and units/services
├── installer/                   # Windows: setup.ps1, nssm.exe, WiX .wxs
└── .gitea/workflows/agent-release.yml

Relationship to the other repositories

Repository Relationship
vantage-shared a private Go module. grpc/pb and grpc/codec are the wire contract; proto/ there documents them
vantage the control plane. No import in either direction — the coupling is the gRPC wire, and it is entirely mediated by vantage-shared

vantage-shared is private, so every Go build needs GOPRIVATE=gitea.hostxtra.co.uk/* plus a credential. CI writes a netrc from REGISTRY_USER + RELEASE_TOKEN (that token needs read access to the vantage org) — twice, because the msi job is Windows and Go looks for _netrc in the profile directory there, not .netrc. Locally, either a netrc or git config --global url."git@gitea.hostxtra.co.uk:".insteadOf https://gitea.hostxtra.co.uk/.

A wire change is three steps, in order

shared/grpc/pb is hand-written and shared by both sides, so a new message is a compile error rather than a silent disagreement — but only once each side moves:

  1. release vantage-shared (and change proto/vantage/v1/vantage.proto in the same commit as the Go types)
  2. bump the pin in vantage's server/go.mod — live at the next push to main
  3. bump the pin here — live only at the next agent/v* tag

The control plane runs ahead of the fleet in between. That was true before the split too; it is now explicit in two go.mod files rather than implicit in a shared directory.

Releases

Tags are agent/v*, not bare v*, even though this repository is only the agent. The tag is not a private detail: it is what the fleet downloads by, what UpdateAgentCmd carries, and what the control plane greps release tag names for when answering GET /api/agent/latest-version. Renaming it would be a second breaking change stacked on the repository move.

git tag agent/v1.2.0 && git push origin agent/v1.2.0

Builds linux/amd64, linux/arm64 and windows/amd64, writes checksums.txt, creates the Gitea release. A second msi job on windows-2022 builds the exe again, packages it with WiX and appends the MSI to the same release through the APIgitea-release-action cannot find a tag with a slash in it.

The self-update path, and what the move broke

internal/sync downloads its own replacement from <gitea>/vantage/vantage-agent/releases/download/<tag>/…, verifying the SHA-256 from checksums.txt before swapping itself. That path is compiled into the binary.

Every agent built before this move has the old path — mrhid6/vantage — baked in, and releases are no longer published there. For those agents the push-button update in the UI will fail: the download 404s. They are not stranded, because /update and /update.ps1 are generated by the control plane at request time and point wherever the current server says, so re-running the update one-liner on a host moves it onto a build that knows the new address. After that, self-update works again permanently.

This was a deliberate choice — the alternative was publishing releases to a repository that no longer holds the source — but it means the fleet needs one pass of the update one-liner, and the control plane must be redeployed with the new release paths first, or the one-liner points at the old repository too.

What the agent will not do

  • It never reboots a host. ApplyUpdatesCmd installs and stops there; inventory.reboot_required reports that one is owed.
  • It decides what it will not touch. The protected workload set is computed and enforced agent-side — vantage-agent.service, VantageAgent on Windows, and its own container ID from /proc/self/cgroup. The control plane may name a target; the agent decides what it will do to itself. A server-side denylist alone would be bypassed by the next dispatch path someone adds, and the failure is unrecoverable from the UI.
  • The console relay dials 127.0.0.1 only. The host is hardcoded here, so the control plane can name a port and nothing else.
  • No authorized_keys management on Windows, and no package inventory: a Windows agent never calls ReportPackages, so no server_packages document exists for it at all — a different, earlier state than the unsupported a Linux distribution reaches when its family has no security feed.

Platform split

Windows support is build tags, not runtime branches — systemd_linux.go / services_windows.go and the matching control_ and logs_ pairs. Windows collection runs PowerShell through internal/winexec, and every script that reports data emits JSON that a build-tag-free parser reads, so those parsers are tested on Linux. This module has no Windows CI: the control verbs and serviceDisplayName emit no JSON, have no parser, and are exercised only by running the agent on Windows.

Windows updates go through the Windows Update COM API (Microsoft.Update.Session) rather than the PSWindowsUpdate module, which would need a PowerShell Gallery install on every host and fails on an air-gapped fleet. CurrentVersion is empty on Windows and NewVersion carries the KB article ID: a Windows update is not a version bump of a named package.

Two constants that mirror the control plane

Neither can be shared — this is a separate module and the control plane's are under internal/ — so both must change in step, by hand:

  • the workload log cap, 500 lines and 256KB whichever binds first, mirrored in the control plane's services.MaxWorkloadLogLines
  • streamHealthyAfter and the 70s command-stream watchdog, which pair with the control plane's 20s PingCmd. The watchdog arms only after a first ping has been seen, so an older server that sends none is treated as working rather than put into a reconnect loop.