Skip to content

feat: install script and nodkey update #88

Closed

Description

@groupsky

Today an install takes many steps by hand (docs/setup.md section 2). You download the files, check the checksum and check the attestation with gh. Then you take nodkey out of the tarball and install it. An update repeats all of this (section 13). From a phone over SSH, this is slow and easy to get wrong.

Plan

  • install.sh in the root of the repository installs the newest release, or --version X.Y.Z. You run it as root on the host itself: the broker host or an agent machine. It needs only sh, curl, tar, gzip, sha256sum, install, mktemp and uname.
  • nodkey update [--version X.Y.Z] runs the same script, built into the binary with go:embed. So the code that checks a new release comes from the build that is already installed and checked, not fresh from the network. go:embed cannot reach a parent directory, so the embed lives in a small package at the root of the module (or the implementer picks a better place).
  • Owner decision, 2026-10-17: run the install as root on each host, never as the agent's user. The agent cannot change what root downloads. The old way, where you download on a trusted machine and copy, stays in the guide as the second way.

Steps of the script

  1. Linux only, on amd64 or arm64.
  2. The version: --version, or the newest release from https://api.github.com/repos/code6x/nodkey/releases/latest, with no login. Only vX.Y.Z is accepted.
  3. If that version is installed already, say so and stop. Install an older version than the installed one only with --version. A build from the source (dev, or not X.Y.Z) counts as "unknown version".
  4. Download the tarball for this machine, SHA256SUMS and the bundle from ci: publish the attestation bundle with each release #87. Use HTTPS only and a new private temp directory.
  5. Check the SHA-256 of the tarball against its own line in SHA256SUMS.
  6. Get gh: download a pinned version, and check it against a SHA-256 written in the script, one per arch. With --gh PATH, use a gh that is installed already instead. It must be 2.93.0 or newer: older versions have known bugs in attestation verify (GHSA-fgw4-v983-mgp8, GHSA-8xvp-7hj6-mcj9).
  7. Run gh attestation verify --bundle ... with the identity of docs/releasing.md and --deny-self-hosted-runners. Use no login and an empty config: unset GH_TOKEN, GITHUB_TOKEN, GH_HOST, GH_ENTERPRISE_TOKEN and GITHUB_ENTERPRISE_TOKEN. Put HOME, GH_CONFIG_DIR and the XDG_* folders in the temp directory, and turn off gh's update notice and prompts.
  8. Take only nodkey out of the tarball. Check that nodkey version of the new file prints the expected version.
  9. Install it as /usr/local/bin/nodkey (or --bin-dir DIR), mode 0755, owned by root when the script runs as root. Replace the old file in one step (a rename in the same directory), never half.
  10. Say what to restart: the broker (systemctl restart nodkey, which ends all grants) and each nodkey ssh-agent. Restart nothing yourself.
  11. Remove the temp directory, also after a failure.

Any failed step stops the script with a clear message and installs nothing. Write the script so that a half download through curl ... | sh runs nothing: all work in functions, and one call at the end.

nodkey update

  • It runs only as root. If not, it says to run sudo nodkey update.
  • It updates the file it runs from (its resolved path).
  • It passes --version on, and adds nothing else from the environment.

Done when

  • install.sh does steps 1 to 11. Every failure installs nothing.
  • The pinned gh is 2.93.0 or newer. Its SHA-256s match the official checksums file of that gh release, and that file passes gh attestation verify --owner cli. The pull request says how this was checked. (The checksums file itself has no attestation. So both gh tarballs were checked with gh attestation verify instead, and their SHA-256s match the checksums file and GitHub's asset digests. See feat: install script and nodkey update #95.)
  • nodkey update runs the embedded script. A test checks that it is the same as install.sh.
  • Tests cover the happy path and each deny path, with no real network: a bad checksum, a missing line in SHA256SUMS, a failed verify, a bad version from the API, the wrong version in the new binary, an older version without --version, a wrong OS and a wrong arch. For example, use a fake curl on PATH and --gh with a fake gh.
  • shellcheck checks install.sh in the lint job of CI, with the tool pinned like the others.
  • docs/setup.md: section 2 offers the installer first, as root on each host, and the old way second. The rule in section 1 matches. Section 13 uses nodkey update. The README quick start matches.
  • DESIGN.md (the command table in section 15, and section 18) and THREAT-MODEL.md are updated. The threat model says what the first install trusts (the script from main), what an update trusts (the script in the installed binary, the attestation, the pinned gh), and what a stolen release App key can and cannot do now (T20).
  • After the next release, install.sh installs it on a clean Debian host with no GitHub login. Tick this after that release.

Activity

  1. groupsky commented on Oct 17, 2026

    @groupsky
    ContributorAuthor

    Done in #95 (cee02b8), except the last box. I reopen the issue until then: after the next release, install.sh must install it on a clean Debian host with no GitHub login. The low points of the last review are in #94.

  2. groupsky commented on Oct 17, 2026

    @groupsky
    ContributorAuthor

    Checked on 2026-10-17 with the 0.2.0 release, in a clean debian:trixie container, as root, with no GitHub login. install.sh from main installed 0.2.0: it found the newest release, downloaded the files and the pinned gh 2.102.0, found the attestation valid, and installed /usr/local/bin/nodkey as root:root, mode 0755. nodkey update then said "Nothing to do". nodkey update --reinstall ran the full check again through the script inside the binary, and passed. nodkey version on a terminal printed nodkey: this is the newest release. Nothing was left in /tmp.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions