# Linux

The whole path on Linux: the right archive, the signature, the PATH, a service, and building it yourself.

Source: https://jmrplens.github.io/ghchronicle/install/linux/

One static binary, `CGO_ENABLED=0`, so there is no C library to install and no
distribution to match. A release archive and a `PATH` entry is the whole
install; everything below that is about doing it deliberately.

## Pick the archive

Release archives are named
`ghchronicle_<version>_linux_<arch>.tar.gz`, with `<arch>` either `amd64` or
`arm64`. `uname -m` answers which.

| `uname -m` reports | The archive to take |
| ------------------ | ------------------- |
| `x86_64`           | `linux_amd64`       |
| `aarch64`          | `linux_arm64`       |

```sh
VERSION=1.0.0
arch=$(uname -m); case "$arch" in x86_64) arch=amd64 ;; aarch64) arch=arm64 ;; esac
base=https://github.com/jmrplens/ghchronicle/releases/download/v$VERSION
curl -fsSLO "$base/ghchronicle_${VERSION}_linux_${arch}.tar.gz"
```

> **Name the version rather than asking for the newest**
>
> The repository publishes a moving `v1` tag beside the numbered releases,
> because the Action is listed on the Marketplace and that listing needs one.
> The `v1` release carries **no files at all**, so never build a download URL
> from the major tag: there is nothing behind it to download. Take the version
> from the
> [releases page](https://github.com/jmrplens/ghchronicle/releases) and write
> it down, as above.

## 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. So the chain is one signature and a digest: verify
the file, then verify the archive against the file.

1. Take the checksum file and its signature.

    ```sh
    curl -fsSLO "$base/checksums.txt"
    curl -fsSLO "$base/checksums.txt.sigstore.json"
    ```

2. Check the archive against it. `--ignore-missing` is what lets one line of a
   twelve-line file be checked without the other eleven archives being present.

    ```sh
    sha256sum --ignore-missing -c checksums.txt
    ```

    ```text
    ghchronicle_1.0.0_linux_amd64.tar.gz: OK
    ```

3. Check the checksum file itself, if you have
   [cosign](https://docs.sigstore.dev/cosign/system_config/installation/).

    ```sh
    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
    ```

    ```text
    Verified OK
    ```

The signing is keyless: there is no public key to fetch and no private key for
anyone to lose, because the identity being verified is the workflow that ran,
recorded in a public transparency log. That is what the two `--certificate`
flags say, and why they are not optional: without them cosign would confirm
that somebody signed the file, which is not the question.

## Put it on the PATH

The archive holds three files and no directory, so extract it somewhere you
meant to.

- ghchronicle_1.0.0_linux_amd64.tar.gz
  - ghchronicle the binary
  - LICENSE
  - README.md

- **For everyone**

  ```sh
  tar -xzf ghchronicle_1.0.0_linux_amd64.tar.gz ghchronicle
  sudo install -m 755 ghchronicle /usr/local/bin/
  ghchronicle -version
  ```

- **For one account**

  ```sh
  mkdir -p ~/.local/bin
  tar -xzf ghchronicle_1.0.0_linux_amd64.tar.gz -C ~/.local/bin ghchronicle
  chmod 755 ~/.local/bin/ghchronicle
  ghchronicle -version
  ```

  `~/.local/bin` is on the `PATH` of most distributions already. If
  `ghchronicle -version` answers "command not found", it is not on yours.

```text
ghchronicle 1.0.0 (commit 4e5dfc2, built 2026-09-14T23:04:02Z)
```

## Run it once

The binary needs a configuration file and a token, and
[the quickstart](/ghchronicle/start/quickstart/) writes both in six steps. With
those in place:

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

## Keep it running

[systemd](/ghchronicle/install/systemd/) is the arrangement this documentation
treats as the default on Linux, and the unit there is hardened rather than
minimal, because this is very likely the only process on the host holding a
GitHub token with read access to every repository of an account.

A scheduler works too:
[cron with `-once`](/ghchronicle/install/systemd/#cron-instead-of-a-service) is
a single line, at the cost of giving every family the same cadence.

## Build it from source

Go 1.27.1 or newer is what the module declares. Nothing else is needed: the
build sets `CGO_ENABLED=0`, so there is no compiler and no header package to
find.

- **go install**

  ```sh
  go install github.com/jmrplens/ghchronicle/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 reports its version but not its commit or its
  build date:

  ```text
  ghchronicle 1.0.0 (commit unknown, built unknown)
  ```

  The version comes from the `VERSION` file the module embeds; the other two
  are stamped by the release build and by nothing else, and a module
  downloaded through the proxy carries no checkout to read them from.

- **make**

  ```sh
  git clone https://github.com/jmrplens/ghchronicle
  cd ghchronicle
  make build      # into bin/ghchronicle
  make install    # into GOBIN, stamped like a release build
  ```

  `make build` stamps the version, the short commit and the commit date, so
  `-version` says exactly which tree it came from. `make` with no target
  lists every target there is.

## 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. So the path is a decision you make once and then pass
on every invocation. What the rest of this documentation assumes:

| File               | Path                               | Mode                        |
| ------------------ | ---------------------------------- | --------------------------- |
| Configuration      | `/etc/ghchronicle/config.yaml`     | world readable, no secrets  |
| Tokens             | `/etc/ghchronicle/ghchronicle.env` | `600`                       |
| State and ledger   | `/var/lib/ghchronicle/`            | written by the service user |

`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`. That default is fine for a first run in a
directory you made, and wrong for a service, whose working directory is not
something to rely on. Set it.
