Repository navigation
Conversation
Replace the frozen v1 agent (spectated) on main with the v2 agent that was developed in spectate/platform (agent/). - Module path github.com/spectate/agent/v2 (v1 was released as v1.x.y). - Tags v2.x.y; releases via goreleaser on tag push, never marked latest so v1's install-latest.sh keeps resolving releases/latest to v1.5.0. - install.sh downloads from this repository's releases and verifies checksums.txt; each release ships install.sh pinned to its version. - CI: gofmt, go mod verify/tidy, vet (linux + darwin), test -race, build, shellcheck and goreleaser check; actions pinned by commit SHA. - v1 source stays available at the v1.x.y tags; install-latest.sh is kept unchanged for existing v1 installs.
…elf-update - 410 HOST_DELETED is its own disposition: the agent clears the spool, logs one error, persists host_deleted in state.json and exits 78, which the unit does not restart on (RestartPreventExitStatus=78). Later starts exit the same way without calling the API; register --force clears it. - 401 keeps spooling but retries hourly (honouring a longer Retry-After) and logs at ERROR at most once an hour. - Every request carries X-Agent-Time (unix ms at send time). A clock_offset_ms beyond 30s is warned about hourly; timestamps are not shifted (the server corrects them). - Collectors run concurrently under the round deadline; a stuck collector is reported once and not restarted until it returns. Each mountpoint's statfs has its own deadline and a hanging mountpoint is skipped until its call returns. Network/automount filesystems are excluded by default (needed on macOS; gopsutil already drops them on Linux). - Bundled CA roots (x509roots/fallback) for hosts without a CA bundle. - spectate-agent update [--version] [--check] [--if-newer]: newest stable v2 release via the GitHub API, ed25519-signed checksums.txt verified with the key embedded at build time, sha256 check, atomic replace with a self-test, systemctl restart. Downloads only from GitHub hosts. - agent_update from ingest is logged daily and shown in status; optional daily auto-update timer (install --auto-update, install.sh --auto-update). - cmd/release-sign (keygen/sign/verify); goreleaser signs checksums.txt and uploads checksums.txt.sig; the release fails without the key. install.sh verifies the signature with OpenSSL 3 when available.
v1 (spectated) is no longer supported, so releases are no longer kept off "latest": install.sh downloads from releases/latest instead of looking up the newest v2 tag through the GitHub API, and releases/latest/download/install.sh is a stable installer URL. install-latest.sh (v1) stays until the dashboard's add-host command uses the v2 installer; it stops working once v2.0.0 is released.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the v2 host agent (
spectate-agent), which was developed in the platform monorepo underagent/, into this repository so it can be worked on here. It replaces the v1 agent (spectated), which is no longer supported.v1
v1.0.0tov1.5.0and their releases stay on GitHub, and the v1 source stays available at those tags. Stable v2 releases are marked latest, soreleases/latestmoves to v2 with the first v2 release.install-latest.sh(the v1 installer) stops working at that moment, because it downloadsreleases/latest/download/spectated_<os>_<arch>.tar.gz. It stays onmainfor now: the platform dashboard's Add host dialog still serves it fromraw.githubusercontent.com/spectate/agent/main/install-latest.sh. Delete it once the dashboard uses the v2 installer.build.yml) are removed frommain.install.sh --import-legacymoves a v1 host to v2 and keeps its token, identity and history.Why replace the code instead of adding a
v2/directory: keeping v1 onmaingains nothing that the tags don't already provide. A root-level module with the/v2suffix is the standard Go "major version" layout, and the repo, CI and releases stay single-purpose.Changes
Module path is
github.com/spectate/agent/v2. This repo already hasv1.x.ytags forgithub.com/spectate/agent, and Go's import compatibility rule requires the/v2suffix forv2.x.ytags. All imports and ldflags are updated.go install github.com/spectate/agent/v2/cmd/spectate-agent@v2.x.ywill work.Tag scheme is plain
v2.x.y. The monorepo usedagent/v*in the Makefile andv2.x.yin the installer. Now both usev2.x.y: Makefilegit describe --match 'v2.*',release.ymlruns onv2.*.*, and suffixed tags such asv2.1.0-rc.1become pre-releases.install.shdownloads from this public repo's releases. Before, it pointed at the privatespectate/platform.checksums.txtand the hardened systemd unit (spectate-agent install) are unchanged.--version 2.0.1flag.install.shasset withDEFAULT_VERSIONset to that release, so a release's installer installs the matching binary. The asset is generated by a goreleaser hook and listed inchecksums.txt.main(DEFAULT_VERSION="") downloads fromreleases/latest.Protocol schema: there is now a copy at
proto/spectate/agent/v2/ingest.proto, byte-identical to the canonical one in platformlaravel/proto.make protomaps it to this module with--go_opt=M....ingest.pb.gowas regenerated, and only its embeddedgo_packagechanged.proto-phpis removed, since PHP bindings are generated in the platform repo..goreleaser.yaml: stable releases are marked latest (pre-releases never are); release targetspectate/agent, linux/darwin × amd64/arm64 static binaries, tarballs and checksums.go mod tidy -diffandgo testrun in the before hooks; the oldgo mod tidycould leave the tree dirty..github/workflows/ci.ymlruns on PRs and pushes tomain:go mod verifyandgo mod tidy -diffgo vet(linux and darwin),go test -race, linux and darwin buildsshellcheck install.shandgoreleaser checkThe main job is named
vet-lintbecause branch protection requires that check..github/workflows/release.yml: on av2.*.*tag it runs vet and test, then goreleaser. The workflow default iscontents: read; only the release job getscontents: write, and it usesGITHUB_TOKENrather than the oldGH_TOKENsecret. Every action is pinned by commit SHA. Dependabot now also updates github-actions.Removed v1 files:
cmd/,internal/,main/,pkg/, the three goreleaser configs,.releaserc.json,package*.json,CHANGELOG.mdand.env.example. All of them remain in git history and at the v1 tags.README: covers install, release, the module path and a v1 section. LICENSE, CODE_OF_CONDUCT and CONTRIBUTING are unchanged.
Installer URL:
get.spectate.net/agent.shMake
get.spectate.net/agent.sha redirect tohttps://github.com/spectate/agent/releases/latest/download/install.sh. That asset is the newest stable release's installer: it is covered bychecksums.txt, carries the signing key and installs the binary of its own release. Since v2 is "latest", the redirect never needs updating. The documented command already follows redirects:get.spectate.netdoesn't resolve yet.Protocol
The agent registers with
POST /agent-api/v2/register(install token → host token) and ships toPOST /agent-api/v2/ingest(Authorization: Bearer <host token>, gzip + protobufspectate.agent.v2.IngestRequest). Every ingest response carries the server-side config. Protocol docs, v1 vs v2: https://github.com/spectate/platform/blob/main/docs/host-agent.md (in the private platform repo).Deleted hosts, clock offset, hung mounts, signed self-update
The second commit implements the agent side of the API contract, which platform
mainalready serves.410+HOST_DELETED) is its own disposition (transport.HostDeleted). The agent clears the spool and the current window, logs one ERROR (This host was removed from Spectate. Remove the agent: sudo spectate-agent uninstall), writeshost_deleted: truetostate.jsonand exits with status 78. The unit hasRestartPreventExitStatus=78, so systemd does not restart it. If the agent is started again with the marker set, it logs the same message and exits 78 before calling the API.register --forcewrites a fresh state, which clears the marker. A410without that code is still retried.401): the agent keeps spooling, but retries every hour (jittered down by up to 15 min) instead of capping at 5 min. A longerRetry-Afteris honoured. The ERROR is logged at most once an hour.X-Agent-Timewith the agent's unix time in ms, set inClient.doright before the request goes out. Ifclock_offset_msis beyond ±30 s, the agent logs a WARN at most once an hour:host clock is off by Ns; samples are corrected by the server, but fix NTP. The agent never shifts its own timestamps.registerprints the same warning.statfsgets its own 2 s deadline. A mountpoint whose call is still hanging is skipped until that call returns, so one dead NFS mount doesn't hide the other disks.exclude_filesystemsgains network and automount filesystems, listed below. On Linux, gopsutil's non-alllisting already drops all of them, because it keeps only non-nodeventries of/proc/filesystems. macOS lists every mount, includingnfs/smbfs/afpfs/webdav/autofs, so the list is what keeps them out there.golang.org/x/crypto/x509roots/fallbackis blank-imported, so hosts without a CA bundle work. It adds about 260 KB to the binary.spectate-agent update [--version X] [--check] [--if-newer] [--no-restart](root):v2.x.ythroughGET api.github.com/repos/spectate/agent/releases?per_page=30, so a pre-release or an old tag is never installed.spectate-agent_<os>_<arch>,checksums.txtandchecksums.txt.sig. It verifies the ed25519 signature with the key embedded at build time (version.UpdatePublicKey), then the sha256.versionself-test, rename, then a directory fsync. After that it runssystemctl restart. The timer's--if-newerusestry-restart, so a stopped agent stays stopped. On macOS it prints thelaunchctlcommands instead.api.github.com,github.com,objects.githubusercontent.comandrelease-assets.githubusercontent.com, over https, including redirects. GitHub now redirects asset downloads torelease-assets.*, so that host had to be added.SPECTATE_UPDATE_API_URL, used for tests, is honoured only by-devbuilds.--insecure-skip-signatureis passed.agent_updatein ingest responses: when a version newer than the running one is announced, the agent logs INFO once a day (spectate-agent X is available: sudo spectate-agent update). It also stores the version instate.json, andspectate-agent statusshows it.install.sh --auto-updateorspectate-agent install --auto-updatewritesspectate-agent-update.service(oneshot, root,update --if-newer) andspectate-agent-update.timer(daily,RandomizedDelaySec=6h,Persistent=true).uninstallremoves both. Both units passsystemd-analyze verify.cmd/release-signhas three commands:keygen,sign <file>(key fromSPECTATE_RELEASE_SIGNING_KEY) andverify <file>.signrefuses to run without the key. IfSPECTATE_RELEASE_PUBLIC_KEYis set, it must belong to that private key, which catches a secret/variable mismatch.checksums.txtwith it (signs:), uploadschecksums.txt.sig, and puts the public key into the ldflags and the release'sinstall.sh.release.ymlfails before goreleaser if either the secret or the variable is missing, so nothing unsigned is published.make snapshotuses--skip=sign.install.shsignature check (my call): it still verifies the sha256. The release-asset copy now carries the public key and also verifieschecksums.txt.sigwithopenssl pkeyutl -verify -rawinwhen OpenSSL 3 is present. With LibreSSL or OpenSSL 1.1 it prints a warning and relies on the checksum. The repo copy has no key, so it checks checksums only.spectate-agent updatealways verifies the signature.Default config change for the server (
laravel/config/host_agent.php, already on platformmain)defaults.disk.exclude_filesystemschanged from['squashfs', 'vfat']to:No other defaults changed.
Verification (local)
The self-update end-to-end test (
cmd/spectate-agent/update_e2e_test.go, skipped with-short) does the following:2.0.0-devbinary and a2.1.0release with a throwaway key, and serves fake release JSON and assets, with redirects, fromhttptest--checkdoesn't install anythingchecksums.txtare rejected and leave the binary untouched--if-neweris silent when the agent is current, release builds ignore the API override, and keyless builds refuse to updateThe
install.shOpenSSL path was tested under sh and dash with good, foreign and tampered signatures, and with LibreSSL (warning fallback).After merging
go run ./cmd/release-sign keygenon a trusted machine.gh secret set SPECTATE_RELEASE_SIGNING_KEY -R spectate/agent(paste the private key) andgh variable set SPECTATE_RELEASE_PUBLIC_KEY -R spectate/agent --body <public key>.UPDATE_PUBLIC_KEY=""ininstall.shonmain, so the repo copy verifies signatures too. The goreleaser hook overwrites that line in the release asset either way.get.spectate.net/agent.shathttps://github.com/spectate/agent/releases/latest/download/install.sh(DNS first: the name doesn't resolve yet).git tag -s v2.0.0 -m v2.0.0 && git push origin v2.0.0. It becomes "latest", which ends v1 installs.install-latest.shhere.../agent/internal/encode/testdata/*. Move those fixtures intolaravel/testsbeforeagent/is removed from the monorepo. Keeplaravel/protoandproto/here in sync.