Skip to content

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:

  1. 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.
  2. 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.
  3. 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 winner fails 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. winner sends 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: winner never 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 winner that 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-keygen is there on both systems, and on Linux also the C library and the ssh-keygen version, below. The universal binary lands in ~/.local/bin (FLEETVIEW_BIN_DIR overrides it) with no sudo; the installer says so if that directory is missing from PATH. Nothing is installed unless the download matches a checksum: the SHA256SUMS entry served with it, or FLEETVIEW_AGENT_SHA256 when a mirror supplies the digest instead. A served SHA256SUMS is trusted only once SHA256SUMS.sig beside it verifies against the Winner AI release key embedded in the script, with the ssh-keygen every supported macOS ships (-Y verify needs OpenSSH 8.1; macOS 13 carries 9.0). A missing or bad signature, or no ssh-keygen, installs nothing. With FLEETVIEW_AGENT_SHA256 set 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 on PATH or already in that directory, is installed at the version the control plane reports, from its project's GitHub release (github.com and the githubusercontent.com host it redirects release downloads to must be reachable), into the same directory as winner, with no sudo, and only if it matches that release's SHA256SUMS and reports that version when run. A control plane that reports no version, one reached over plain http to a host that is not loopback, or a run with no FLEETVIEW_CONTROL_URL to ask, fails and names the release page instead of falling back to the latest release. The control plane is recorded for winner login before this step, so a failure here still leaves winner ready. A collector already installed is left alone; the version check at winner login still catches one at the wrong version.
  • Linux: Ubuntu 20.04 or later, or Debian 11 or later, on amd64 (uname -m prints x86_64) or arm64 (aarch64). The same command installs the static winner-linux-<arch> binary into ~/.local/bin with no sudo, under the same signature and checksum rules as on macOS, and the collector from its project's linux_<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 needs ssh-keygen from OpenSSH 8.1 or later (ssh -V; the openssh-client package), so a machine without it, or with an older one such as Debian 10's 7.9, is refused too, unless FLEETVIEW_AGENT_URL and FLEETVIEW_AGENT_SHA256 supply the digest and no signature is checked. The checksum uses sha256sum, or shasum where that is missing. Everything after enrollment is the same, the background push and winner update and its daily timer included.
  • git 2.36 or newer, for winner login and winner repos, which list the laptop's repos with it. The listing needs worktree list -z (2.36), and a git older than 2.31 answers its questions wrongly without failing, so winner checks the version and stops instead: the first login on a newly enrolled laptop chooses no repos and sends nothing, and winner repos fails 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 --version shows yours. To get a newer one:
  • macOS: xcode-select --install, or brew 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-backports carries 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.