Client reference¶
The detail behind the winner client: what each part does, and the rules it
follows. Installing it, signing in and choosing repos is
Laptop client guide; what a laptop sends for the web page, and how long a choice made there can take, is
Choices made on the web page.
Folder access on macOS¶
macOS guards Desktop and Documents: a background program that opens either
makes the system ask you first. The hourly reporter never opens one on its
own. Instead, when some of your sessions from the last seven days ran in
Desktop or Documents, winner login asks once, while you are at the
terminal:
- It says which of the two folders it is about and what will be read: the git history file (the reflog) of the repositories there, and nothing else.
- macOS shows its own dialog asking whether "winner" may access the folder, one dialog per folder. Login waits up to two minutes for your answer.
- Allow lets the hourly reporter record start commits for sessions in that folder. Don't Allow leaves those sessions recorded without a start commit. Nothing else changes either way.
If no recent session ran in either folder, nothing is asked. If you do not
answer within two minutes, nothing is recorded for that folder, it stays
unread, and the next winner login asks again. A Desktop or Documents that
is a link to somewhere else, or is missing, is not asked about; login says
so in one line. Inside an allowed folder, a
session directory that is a link leading out of the folder is not read. Your answer is kept in ~/.agentsview/fleetview-folder-access.json,
and the hourly reporter reads only that file to decide; it never tries a
folder to find out.
Before it reads a reflog, the reporter works out where the session's
repository really is, and then reads one file of it: the HEAD reflog in the
repository's .git directory. To find it, it checks that the repository's
HEAD and logs are there, that the session folder is not itself a git
directory, and in a linked worktree it also
reads the .git file and the gitdir and commondir files it leads to.
Nothing else in the repository is opened. It runs no git, so it needs none
installed (winner login and winner repos do: git 2.36 or newer, see
Requirements). This is so on every system the reporter
runs on. A session is recorded without a start commit when its
folder is reached through a link into a folder you did not allow, when its
.git is a link or a file that is not a linked worktree's own (one made by
git worktree add is read as before), when it is inside a git submodule or
inside a .git directory, or when its repository keeps no reflog file: one
with the reflog switched off, or one in git's reftable format.
To change the answer, open System Settings > Privacy & Security > Files and
Folders > winner, switch the folder on or off, then run winner login again
so the new answer is recorded. For a folder you have already answered,
winner login reads the system's current answer again only when a session
from the last seven days ran there; it does so without asking again. It
tells you when the answer changed, and a Don't Allow that still holds is
printed again at every login, with how to change it. If macOS has lost your answer it asks
again, and login says so while it waits.
Between two logins the hourly reporter trusts the recorded answer. If macOS loses an Allow you gave in that time, the reporter's next read of that folder makes macOS ask again with nobody at the terminal: the one case in which the background job asks. A folder you switched off in that time is refused by macOS, and its sessions are recorded without a start commit.
The answer belongs to the winner program macOS showed in the dialog, at the
place it is installed. After winner is replaced by a build macOS treats as
a different program, or moved, the folders are left unread until the next
winner login. A winner with no code signature macOS checks is never
asked. The macOS binary of the current release is one: macOS never asks
about Desktop or Documents, winner login says so when your sessions ran
there, and sessions in those folders are recorded without a start commit.
A signed build changes this.
Downloads, iCloud and other cloud folders, and external volumes are never opened and never asked about; sessions there are recorded without a start commit.
The collector version ceiling¶
Before the device flow, winner login checks the version of this laptop's
session collector (agentsview) against the control plane's, which is the
ceiling: a laptop ahead of
the control plane would fail to push. Newer is refused - on a Mac or Linux,
enrollment stops and prints, with the versions filled in:
This laptop's agentsview (0.45.0) is newer than the control plane's (0.44.0).
Sessions would fail to push: the control plane migrates its own schema,
and laptops must not run ahead of it.
Bring this laptop down to agentsview 0.44.0, then run `winner login` again:
winner update --control-url 'https://<control-plane-host>'
An older laptop only warns and enrolls anyway:
WARNING: this laptop's agentsview (0.42.0) is older than the control plane's (0.44.0).
Enrollment can continue; bring this laptop to that version when convenient:
winner update
On a Mac or Linux both messages point at winner update, which converges
the collector to the control plane's version in either direction. The refusal
spells out the control plane it checked against, because it exits before that
login records one: a bare winner update there would resolve whatever the
laptop had recorded before - another control plane, or nothing at all. The
warning needs no flag, since that login goes on to enroll and record the URL.
winner update installs macOS and Linux builds only, so on any other
platform both messages link the collector's own release page for the control
plane's version instead. That link is that version's release page, not the
latest release, which can be past the ceiling: a refused laptop would reinstall and
be refused again.
On a Mac or Linux, against a control plane that names no winner version, the advice
is the same: winner update converges the collector against any control plane
that names a collector version, which is the only kind this check compares
against. Only the winner half
is left out, because there is no version to converge it to (an untagged
build names none), so both messages add:
This control plane names no winner version (every deployment before 0.5.0,
and any untagged build), so that run converges agentsview only and
leaves the winner binary alone.
A control plane that names an older winner gets no such line: the command
converges that binary too, downwards.
A control plane that reports no collector version skips the check, with a
warning. An already-enrolled
laptop keeps pushing with the collector (and winner) it has until
winner update (below) runs on a Mac or Linux, or someone installs
agentsview by hand elsewhere. On a Mac or Linux, winner login without
--no-service installs the daily check that runs it unattended (below), so
nobody has to run it there.
winner update¶
winner update converges this laptop's winner and its session collector to the
versions its control plane runs, in either direction: a laptop ahead is
brought back down, since a newer collector is denied on every push. Each
binary is independent: one the control plane names no version for is left
alone, so a control plane that publishes no winner version converges
the collector only. It finds the control plane the way winner login does, so
an enrolled laptop needs no flags.
winner update # converge each binary the control plane names a version for
winner update --check # report what would change; install nothing
winner update --force # also replace a `winner` the installer did not place, or an `agentsview` whose version cannot be read
winner comes from the control plane's own download route and
the collector from its own project's GitHub releases, unmodified; both are checked
against the SHA256SUMS published beside them before anything installed
is touched. For winner that manifest counts only once SHA256SUMS.sig
beside it verifies against the Winner AI release key compiled into the
running binary, so an update whose manifest is unsigned, or signed by
another key, is refused and the installed binary stays. The collector is
checked against its project's own SHA256SUMS alone: we cannot sign another
project's manifest. A winner the installer did not place is left alone
without --force, since that binary was chosen on purpose; --force also
reinstalls a collector whose version cannot be read. A binary in a
directory this user cannot write is skipped: the other still updates, and
the run exits non-zero naming the skipped binary and why. A failed
collector download or install likewise leaves winner to update, and the
run exits non-zero with the failure. Run winner update --check first: it
reports the same skips without installing anything, though it cannot foresee
a download that fails.
Running on its own¶
On macOS, winner login installs the launch agent com.getwinner.update,
which runs winner update with no flags when winner login installs it and
at every macOS sign-in after that (a reboot included, since the person signs
in again), then
every day at noon; a noon the laptop slept through becomes one run when it
wakes. It follows whichever control plane the last winner login recorded;
a login that could not record its control plane removes the job rather than
leave it following an older one. The rules above hold unchanged: a winner
the installer did not place is left alone, while the collector still
converges. A run that cannot reach the control plane fails before it touches
anything; the push keeps running on the binaries it has, and the next day's
run tries again.
Every run appends to ~/Library/Logs/winner-update.log.
launchctl print gui/$(id -u)/com.getwinner.update # loaded, schedule, last exit
tail ~/Library/Logs/winner-update.log # what the last runs did
# stop it, across reboots too
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.getwinner.update.plist
launchctl disable gui/$(id -u)/com.getwinner.update
# arm it again
launchctl enable gui/$(id -u)/com.getwinner.update
winner login
Re-running winner login replaces the job rather than adding a second one.
A job you disabled needs the enable first, or login warns that automatic
updates were not installed.
On Linux, winner login installs two systemd user timers instead.
winner-update.timer runs winner update when login installs it and every
day at noon; it is persistent, so a noon the laptop was off or asleep for
becomes one run at its next start. It follows the recorded control plane
exactly as the launch agent does. winner-reporter.timer runs winner
report when login installs it and hourly after that; once the machine
restarts, its first run comes an hour after your session starts the timer.
Their runs append to ~/.agentsview/winner-update.log and
~/.agentsview/winner-reporter.log.
systemctl --user list-timers 'winner-*' # both timers and their next runs
systemctl --user status winner-update.timer # enabled, active, last trigger
tail ~/.agentsview/winner-update.log # what the last runs did
# stop one, across reboots too
systemctl --user disable --now winner-update.timer
As with the push, the next winner login enables both timers again. They
belong to your login session like the push unit, so a machine with no login
session needs the loginctl enable-linger step in
Machines with no login session. A Linux machine that does not
run systemd (WSL without systemd=true, most containers) gets neither:
login says so and prints the crontab lines that would do the same, with the
absolute path to winner and your PATH baked in, for you to add with
crontab -e.
When winner fails¶
winner login prints this notice once the laptop is enrolled:
If
winnerfails after this laptop is enrolled, it sends a short report to your organisation's Winner AI server: the command, the step, the HTTP status, the kind of error, the versions, and your operating system and processor type. The report never contains prompts, code, repository names, file paths, emails or the text of an error. If your organisation's operator has switched telemetry on, the server passes those fields on to Winner AI's error reports and, under a scrambled laptop id, its usage counts.winnersends the report nowhere else.
Only winner login and winner update report. The step is one of
enroll, write_config and install_service for a login, and fetch,
verify, swap and converge for an update. The report also names the
laptop's handle, its machine_name, which the control plane uses only to
find your own enrolled laptop and never passes on. What the server does with
it is in Model and telemetry.
A failure stays on screen only, and nothing is sent:
- while a login reaches the control plane and signs in, at every login;
- when a login's enrolment fails and the laptop holds no earlier enrolment with that control plane to report as, as at a first login;
- when the control plane cannot be reached directly, as from a laptop that
reaches it only through a proxy:
winnernever sends the report through a proxy or to any other host; - on a laptop that last signed in with Auth0 rather than the control plane's
own login, for
winner update, because getting a token would contact Auth0; - on any system other than macOS or Linux, on any processor other than
amd64 or arm64, and from a build of
winnerthat carries no release version.
The report never changes how the command ends: the error is printed first, and the command exits 1 whether or not the report arrived. A failed install of the background push is reported too, and that login still succeeds. The report follows no redirect and waits at most ten seconds.
Collecting diagnostics¶
winner diagnostics gathers what support needs about a laptop's push into
one file, which you read and then attach to an e-mail yourself:
winner diagnostics # ~/winner-diagnostics-<UTC time>.txt
winner diagnostics -o ~/diag.txt # a file you name; an existing one is never overwritten
The file holds the winner and collector (agentsview) versions, the operating system
and architecture, the push service's state and whether its service file
exists, the address of the control plane the laptop enrolled against
(scheme and host only), the time and first line of the newest push
failure, and the last 50 lines of the push log. It is written even when
the push service is not installed, and says so.
It is meant to hold no password, token, e-mail address, repository name or
path: every log line passes Winner AI's redaction rule, which removes
passwords, tokens and e-mail addresses, and then a stricter pass that
replaces paths outside Winner AI's and the collector's own folders and a few
binary folders with [path], the machine and database role names with
[machine] and [role], the organisation's schema with [org], and the
laptop's address in a refused connection with [host]. A web address keeps
only its scheme and host. Your home folder shows as ~. The command never opens the reporter's token file or the collector's
config, which hold the sign-in token and the database password. The push
log is written by the collector, though, and a pattern cannot prove every line
of it clean, so read the file before you send it.
Without -o the file goes in your home folder rather than the current
one, which may be a checkout it could be committed from; a second run in
the same second adds a number to the name.
The command contacts nothing: it reads local files, asks launchctl or
systemctl about the push service, and runs agentsview version (the collector's own version command).
Signing and the background item's name¶
macOS names a background item, in the "Background Items Added" notification
and in the Login Items pane, after the Developer ID that signed the program
the launch agent runs. That is why the launch agent runs winner: the
name shown is whoever signed winner. Until the
binary is signed, macOS shows the binary's own name instead of Winner AI.
Signing is not what lets the binary run. macOS applies the
com.apple.quarantine attribute from applications that opt in (browsers,
Mail, AirDrop), not from curl in a terminal, so a binary fetched by the
installer carries no quarantine flag and Gatekeeper never challenges it.
Getting Winner AI's name onto the item needs the binary signed with Winner AI's Developer ID and notarized, which comes in a later release.
Requirements¶
The installer, the install.sh your server serves, needs what follows;
the list says where it refuses and where it only warns.
- macOS 13 (Ventura) or later, Apple Silicon or Intel. The floor is the
toolchain's: the client is built with Go 1.27, which links macOS binaries
for 13.0 and up. The installer checks the operating system, never the macOS
version. It checks that
ssh-keygenis there on both systems, and on Linux also the C library and thessh-keygenversion, below. The universal binary lands in~/.local/bin(FLEETVIEW_BIN_DIRoverrides it) with nosudo; the installer says so if that directory is missing fromPATH. Nothing is installed unless the download matches a checksum: theSHA256SUMSentry served with it, orFLEETVIEW_AGENT_SHA256when a mirror supplies the digest instead. A servedSHA256SUMSis trusted only onceSHA256SUMS.sigbeside it verifies against the Winner AI release key embedded in the script, with thessh-keygenevery supported macOS ships (-Y verifyneeds OpenSSH 8.1; macOS 13 carries 9.0). A missing or bad signature, or nossh-keygen, installs nothing. WithFLEETVIEW_AGENT_SHA256set no signature is fetched or checked, and the script says so: the digest is yours, not the mirror's. The installer then runs the binary and fails if it will not start, but it has already been moved into place by then, so a failure there leaves it on disk to remove by hand. - The collector,
agentsview, when it is not onPATHor already in that directory, is installed at the version the control plane reports, from its project's GitHub release (github.comand thegithubusercontent.comhost it redirects release downloads to must be reachable), into the same directory aswinner, with nosudo, and only if it matches that release'sSHA256SUMSand reports that version when run. A control plane that reports no version, one reached over plainhttpto a host that is not loopback, or a run with noFLEETVIEW_CONTROL_URLto ask, fails and names the release page instead of falling back to the latest release. The control plane is recorded forwinner loginbefore this step, so a failure here still leaveswinnerready. A collector already installed is left alone; the version check atwinner loginstill catches one at the wrong version. - Linux: Ubuntu 20.04 or later, or Debian 11 or later, on amd64 (
uname -mprintsx86_64) or arm64 (aarch64). The same command installs the staticwinner-linux-<arch>binary into~/.local/binwith nosudo, under the same signature and checksum rules as on macOS, and the collector from its project'slinux_<arch>tarball at the control plane's version. Two floors come from what the install needs, and each is refused, with the reason, before anything is downloaded. The collector's Linux builds need glibc 2.28 or newer and its project publishes no musl or static build, so Ubuntu 18.04 and Alpine are refused. The signature check needsssh-keygenfrom OpenSSH 8.1 or later (ssh -V; theopenssh-clientpackage), so a machine without it, or with an older one such as Debian 10's 7.9, is refused too, unlessFLEETVIEW_AGENT_URLandFLEETVIEW_AGENT_SHA256supply the digest and no signature is checked. The checksum usessha256sum, orshasumwhere that is missing. Everything after enrollment is the same, the background push andwinner updateand its daily timer included. - git 2.36 or newer, for
winner loginandwinner repos, which list the laptop's repos with it. The listing needsworktree list -z(2.36), and a git older than 2.31 answers its questions wrongly without failing, sowinnerchecks the version and stops instead: the first login on a newly enrolled laptop chooses no repos and sends nothing, andwinner reposfails naming the git it found. The background push and the hourly reporter run no git. Stock git is new enough on Ubuntu 24.04, Debian 12 and macOS with Command Line Tools 14 or later, and too old on Ubuntu 20.04 and 22.04 and Debian 11, which stay supported, so the installer warns, never refuses, when git is missing, older or unreadable.git --versionshows yours. To get a newer one: - macOS:
xcode-select --install, orbrew install git. -
Ubuntu 20.04 or 22.04, from the git-core PPA (its 20.04 builds are no longer updated):
sudo add-apt-repository ppa:git-core/ppa sudo apt update && sudo apt install git -
Debian 11:
bullseye-backportscarries git 2.39, now only from Debian's archive and no longer updated, so upgrading to Debian 12 is the lasting fix:echo 'deb http://archive.debian.org/debian bullseye-backports main' | sudo tee /etc/apt/sources.list.d/bullseye-backports.list sudo apt update sudo apt install -t bullseye-backports git
Then choose the repos with winner repos pick, or, on a laptop enrolled
before repo selection that kept sending every repo, run winner login.
- FLEETVIEW_CONTROL_URL names the control plane when the script is run
from a checkout instead of fetched from one; the served copy has it filled
in.
- FLEETVIEW_AGENT_URL fetches the binary from somewhere other than the
control plane, a mirror say. It needs no token; set FLEETVIEW_GH_TOKEN
only for an https URL that wants a GitHub token, which is sent as an
Authorization: token header and never over plain http. The SHA256SUMS
manifest is looked for beside that URL, so the URL has to be a plain
scheme://host/path naming the file. A presigned URL (anything with a
query string), a URL with a # fragment, a URL with no path and a
trailing-slash path have nothing beside them: set FLEETVIEW_AGENT_SHA256
to the expected digest, 64 hex characters, and the installer skips the
manifest fetch and the signature check. Without one of the two it
refuses before downloading anything. A mirror that serves a manifest must
also serve the release's SHA256SUMS.sig beside it, unchanged, or the
installer refuses. A mirror that is not https needs FLEETVIEW_AGENT_SHA256 even
when a manifest does sit beside it: both files would travel in the clear,
so whoever can swap the binary can swap the manifest too. That refusal
also comes before any download.
- curl 7.58 or newer, if you set FLEETVIEW_GH_TOKEN. From 7.58 curl keeps
a custom Authorization header on the original host instead of forwarding
it across a redirect elsewhere. Every macOS and Linux release this installer
supports ships a newer curl (Ubuntu 20.04 has 7.68, Debian 11 7.74), so
nothing checks the version at runtime.
Where the control plane runs¶
Your team's stack runs at https://<control-plane-host>. Enrolled laptops push to its Postgres on port 5432 over
TLS, verifying the server against the system trust store
(sslmode=verify-full): Postgres presents the same publicly trusted
certificate as the https edge, so a renewal on the host changes nothing
here. A laptop whose collector config (~/.agentsview/config.toml) carries sslrootcert= pins one
certificate instead of trusting the system store, and fails once the server
presents another; one winner login against a control plane that hands out
no certificate to pin rewrites the config without it.