142 lines
7.5 KiB
Markdown
142 lines
7.5 KiB
Markdown
# 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.
|
|
|
|
```bash
|
|
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 API** - `gitea-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.
|
|
|
|
## Writing style
|
|
|
|
Never use em dashes (the long dash character) anywhere: code, comments, UI copy, docs, commit messages. Use a plain hyphen ` - `, a comma, a colon, or split the sentence instead.
|