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.
The one-line install
Section titled “The one-line install”curl -fsSL https://raw.githubusercontent.com/jmrplens/ghchronicle/main/install.sh | bashIt 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:
curl -fsSL https://raw.githubusercontent.com/jmrplens/ghchronicle/main/install.sh | VERSION=2.6.5 BIN_DIR=~/bin bashThe 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.
Pick the archive
Section titled “Pick the archive”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 reports | The machine | The archive to take |
|---|---|---|
arm64 | Apple silicon | darwin_arm64 |
x86_ | Intel | darwin_amd64 |
VERSION=2.6.5arch=$(uname -m); case "$arch" in x86_64) arch=amd64 ;; esacbase=https://github.com/jmrplens/ghchronicle/releases/download/v$VERSIONcurl -fsSLO "$base/ghchronicle_${VERSION}_darwin_${arch}.tar.gz"Check what you downloaded
Section titled “Check what you downloaded”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.
-
Take the checksum file and its signature.
Terminal window curl -fsSLO "$base/checksums.txt"curl -fsSLO "$base/checksums.txt.sigstore.json" -
Check the archive against it. macOS ships
shasumrather than thesha256sumof a Linux box, so the line for your archive is selected and piped in, which works whatever version ofshasumthe system has.Terminal window grep "darwin_${arch}.tar.gz$" checksums.txt | shasum -a 256 -c -ghchronicle_2.6.5_darwin_arm64.tar.gz: OK -
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.txtVerified 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:
ghchronicle -setupIt 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.
Put it on the PATH
Section titled “Put it on the PATH”The archive holds three files and no directory.
Directoryghchronicle_VERSION_darwin_ARCH.tar.gz
- ghchronicle the binary
- LICENSE
- README.md
tar -xzf "ghchronicle_${VERSION}_darwin_${arch}.tar.gz" ghchroniclesudo 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.
mkdir -p ~/bintar -xzf "ghchronicle_${VERSION}_darwin_${arch}.tar.gz" -C ~/bin ghchroniclechmod 755 ~/bin/ghchronicleecho 'export PATH="$HOME/bin:$PATH"' >> ~/.zprofile~/bin is not on the PATH by default, hence the last line. zsh is the
login shell on every supported macOS.
Run it once
Section titled “Run it once”The binary needs a configuration file and a token, and the quickstart writes both in six steps. With those in place:
ghchronicle -config config.yaml -list # what would be collectedghchronicle -config config.yaml -once # one sweep, then exitKeep it running with launchd
Section titled “Keep it running with launchd”launchd is what macOS has instead of systemd, and the choice it asks you to make first is agent or daemon.
| A LaunchAgent | A LaunchDaemon | |
|---|---|---|
| Lives in | ~/Library/LaunchAgents/ | /Library/LaunchDaemons/ |
| Runs as | you | root, or the UserName you give it |
| Runs when | you are logged in | the machine is up, from boot |
| Good for | a laptop you use | a 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.
<?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>-
Protect the file before it holds a token, then load it.
Terminal window chmod 600 ~/Library/LaunchAgents/io.jmrp.ghchronicle.plistlaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/io.jmrp.ghchronicle.plist -
See that it is up, and read what it says.
Terminal window launchctl print gui/$(id -u)/io.jmrp.ghchronicletail -f ~/Library/Logs/ghchronicle.log -
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.ghchroniclelaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/io.jmrp.ghchronicle.plist
Or one sweep on a timer
Section titled “Or one sweep on a timer”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.
Build it from source
Section titled “Build it from source”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.
go install github.com/jmrplens/ghchronicle/v2/cmd/ghchronicle@latestLands 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>)git clone https://github.com/jmrplens/ghchroniclecd ghchroniclemake build # into bin/ghchroniclemake install # into GOBIN, stamped like a release buildThe Makefile is written for GNU Make 3.81, which is the version macOS
ships, so the stock make runs it.
Where the files go
Section titled “Where the files go”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:
| File | For an agent | For 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.