Skip to content

macOS

The same static binary as everywhere else, built for darwin on both Apple silicon and Intel. macOS is not a build target that is merely compiled and hoped for: every change runs the whole unit suite and the end-to-end suite on a macOS runner, beside Linux and Windows.

Terminal window
curl -fsSL https://raw.githubusercontent.com/jmrplens/ghchronicle/main/install.sh | bash

It works out the architecture, takes the newest release, checks the archive against the checksum that release published, and puts the binary in /usr/local/bin when that is writable. When it is not, it takes the first of ~/.local/bin and ~/bin that exists and is already on your PATH, and falls back to ~/.local/bin when neither is. A checksum that does not match stops it without installing anything, and there is no flag to skip that step. When cosign 2.4.2 or newer is already on the machine it also verifies that the checksum file itself came from the release workflow, and a signature cosign does not confirm stops it, with what cosign said. An older cosign cannot read the signature bundle, so then, as with no cosign at all, it says that only the checksum was verified. It ends by running what it installed, so the version you see is the file it just wrote, and it says so when another ghchronicle earlier in your PATH still wins the name.

Pin a version, or choose where it goes:

Terminal window
curl -fsSL https://raw.githubusercontent.com/jmrplens/ghchronicle/main/install.sh | VERSION=2.6.5 BIN_DIR=~/bin bash

The rest of this page is that same install done by hand, which is what to follow when you want to know exactly what landed where, or when you would rather not run a script you did not write.

Release archives say darwin, which is the name of the system the Go toolchain uses; macOS is the name Apple uses for the same thing. uname -m answers which architecture.

uname -m reportsThe machineThe archive to take
arm64Apple silicondarwin_arm64
x86_64Inteldarwin_amd64
Terminal window
VERSION=2.6.5
arch=$(uname -m); case "$arch" in x86_64) arch=amd64 ;; esac
base=https://github.com/jmrplens/ghchronicle/releases/download/v$VERSION
curl -fsSLO "$base/ghchronicle_${VERSION}_darwin_${arch}.tar.gz"

Two files are published beside the archives: checksums.txt, which holds the SHA-256 of every archive, and checksums.txt.sigstore.json, which is a signature over that file.

  1. Take the checksum file and its signature.

    Terminal window
    curl -fsSLO "$base/checksums.txt"
    curl -fsSLO "$base/checksums.txt.sigstore.json"
  2. Check the archive against it. macOS ships shasum rather than the sha256sum of a Linux box, so the line for your archive is selected and piped in, which works whatever version of shasum the system has.

    Terminal window
    grep "darwin_${arch}.tar.gz$" checksums.txt | shasum -a 256 -c -
    ghchronicle_2.6.5_darwin_arm64.tar.gz: OK
  3. Check the checksum file itself, if you have cosign 2.4.2 or newer. An older one cannot read the bundle and fails whatever the file says.

    Terminal window
    cosign verify-blob \
    --certificate-identity-regexp 'https://github.com/jmrplens/ghchronicle/.github/workflows/release.yml@refs/tags/.*' \
    --certificate-oidc-issuer https://token.actions.githubusercontent.com \
    --bundle checksums.txt.sigstore.json \
    checksums.txt
    Verified OK

The signing is keyless: the identity being verified is the workflow that ran, recorded in a public transparency log, which is why the two --certificate flags are not optional. Without them cosign would confirm that somebody signed the file, which is not the question.

Then let it write the configuration:

Terminal window
ghchronicle -setup

It asks for a token and where the numbers go, checks each answer against the thing it names, and writes a config.yaml and, if you want one, a launchd agent in ~/Library/LaunchAgents. The installer above offers to run it as its last step.

The archive holds three files and no directory.

  • Directoryghchronicle_VERSION_darwin_ARCH.tar.gz
    • ghchronicle the binary
    • LICENSE
    • README.md
Terminal window
tar -xzf "ghchronicle_${VERSION}_darwin_${arch}.tar.gz" ghchronicle
sudo install -m 755 ghchronicle /usr/local/bin/
ghchronicle -version

/usr/local/bin is on the default PATH of every macOS install, on both architectures, because /etc/paths lists it first.

The binary needs a configuration file and a token, and the quickstart writes both in six steps. With those in place:

Terminal window
ghchronicle -config config.yaml -list # what would be collected
ghchronicle -config config.yaml -once # one sweep, then exit

launchd is what macOS has instead of systemd, and the choice it asks you to make first is agent or daemon.

A LaunchAgentA LaunchDaemon
Lives in~/Library/LaunchAgents//Library/LaunchDaemons/
Runs asyouroot, or the UserName you give it
Runs whenyou are logged inthe machine is up, from boot
Good fora laptop you usea Mac that stays on

The agent is the one to start with. It needs no sudo, and a collector that stops while the laptop’s owner is logged out loses nothing that the next sweep does not pick up.

~/Library/LaunchAgents/io.jmrp.ghchronicle.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>io.jmrp.ghchronicle</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/ghchronicle</string>
<string>-config</string>
<string>/Users/you/Library/Application Support/ghchronicle/config.yaml</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>GITHUB_TOKEN</key>
<string>github_pat_...</string>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/you/Library/Logs/ghchronicle.log</string>
<key>StandardErrorPath</key>
<string>/Users/you/Library/Logs/ghchronicle.log</string>
</dict>
</plist>
  1. Protect the file before it holds a token, then load it.

    Terminal window
    chmod 600 ~/Library/LaunchAgents/io.jmrp.ghchronicle.plist
    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/io.jmrp.ghchronicle.plist
  2. See that it is up, and read what it says.

    Terminal window
    launchctl print gui/$(id -u)/io.jmrp.ghchronicle
    tail -f ~/Library/Logs/ghchronicle.log
  3. After editing the file, unload it and load it again. launchd reads the property list once, when it is bootstrapped.

    Terminal window
    launchctl bootout gui/$(id -u)/io.jmrp.ghchronicle
    launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/io.jmrp.ghchronicle.plist

StartInterval is launchd’s cron, in seconds, and -once is the mode that suits it. Replace KeepAlive with it and add -once to ProgramArguments:

<key>StartInterval</key>
<integer>3600</integer>

Keep state_file on a path that survives, in this mode above all. Without it every run collects every family, whatever its cadence, and walks the stargazer list, the whole star history and the co-authored pull requests again; the cache file beside it is what lets a run ask GitHub only for what changed.

Go 1.27.1 or newer is what the module declares. The build sets CGO_ENABLED=0, so the Xcode command line tools are not needed for it.

Terminal window
go install github.com/jmrplens/ghchronicle/v2/cmd/ghchronicle@latest

Lands in $(go env GOPATH)/bin, which is ~/go/bin unless you moved it, and that directory has to be on your PATH. A binary built this way has no commit or build date to report: those two are stamped by the release build, and a module downloaded through the proxy carries no checkout to read them from. -version names what the build does record instead, the module version the go command fetched and the Go release that compiled it:

ghchronicle 2.6.5 (module v2.6.5, built with <go version>)

The collector looks for a configuration file in no particular place: -config defaults to config.yaml relative to the working directory, and there is no search path behind it. macOS has no /etc/ghchronicle habit, so these are the conventional places rather than ones the tool knows:

FileFor an agentFor a daemon
Configuration~/Library/Application Support/ghchronicle//usr/local/etc/ghchronicle/
State, ledger, cache~/Library/Application Support/ghchronicle//usr/local/var/ghchronicle/
Log~/Library/Logs/ghchronicle.log/usr/local/var/log/

state_file has a default of its own, ghchronicle-state.json in the working directory, with the write ledger beside it as ghchronicle-state-written.bin and the cache as ghchronicle-state-cache.bin. A launchd job’s working directory is not something to rely on. Set it. From 2.6.1 on a path setting takes a leading ~ as the home directory, so an agent’s configuration can say state_file: ~/Library/Application Support/ghchronicle/state.json; an older release reads the ~ as a directory name, and wants /Users/you/... written out.

Written and maintained by
MIT licenceRelease history