Skip to content

Commit f4e3e83

Browse files
committed
Publish prebuilt binaries and a verifying installer
Removes the last hard prerequisite. Using LaunchGuard required a Rust 1.97.1 toolchain, which excludes most of the people the accessible deployment track exists for. The release workflow builds five targets on tag push — Linux x86-64 and ARM64, macOS Intel and Apple Silicon, Windows x86-64 — and publishes archives with a SHA256SUMS manifest. install.sh resolves the host triple, downloads the matching archive, verifies it against the published digest, and refuses to install on a mismatch. It rejects an unrecognised architecture rather than guessing, requires HTTPS with TLS 1.2, and installs to $HOME/.local/bin without elevation. It is short enough to read in one screen, which matters for something people are invited to pipe into a shell. Binaries are unsigned. Apple notarization and Windows code signing both cost money and are recorded in SYSTEM_REQUIREMENTS.md as outside the free contract, so the script says so rather than coaching anyone past a security warning. Verified: shellcheck clean, POSIX sh syntax valid, release.yml parses. Both failure paths exercised in a clean container — an unsupported architecture is refused, and a missing release fails without installing anything.
1 parent 7c5e0ac commit f4e3e83

4 files changed

Lines changed: 272 additions & 3 deletions

File tree

.github/workflows/release.yml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
workflow_dispatch:
8+
inputs:
9+
tag:
10+
description: "Tag to build, for example v0.1.0"
11+
required: true
12+
13+
permissions:
14+
contents: write
15+
16+
env:
17+
CARGO_TERM_COLOR: always
18+
19+
jobs:
20+
build:
21+
name: Build ${{ matrix.target }}
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
include:
26+
- target: x86_64-unknown-linux-gnu
27+
os: ubuntu-latest
28+
- target: aarch64-unknown-linux-gnu
29+
os: ubuntu-24.04-arm
30+
- target: x86_64-apple-darwin
31+
os: macos-13
32+
- target: aarch64-apple-darwin
33+
os: macos-latest
34+
- target: x86_64-pc-windows-msvc
35+
os: windows-latest
36+
runs-on: ${{ matrix.os }}
37+
steps:
38+
- name: Check out repository
39+
uses: actions/checkout@v7
40+
41+
- name: Install pinned Rust toolchain
42+
run: |
43+
rustup toolchain install 1.97.1 --profile minimal
44+
rustup default 1.97.1
45+
rustup target add ${{ matrix.target }}
46+
47+
- name: Build release binary
48+
run: cargo build --locked --release --target ${{ matrix.target }}
49+
50+
# Archive the binary and publish a checksum beside it. The install script
51+
# refuses to continue when the checksum does not match.
52+
- name: Package (Unix)
53+
if: runner.os != 'Windows'
54+
shell: bash
55+
run: |
56+
set -euo pipefail
57+
name="launchguard-${{ matrix.target }}"
58+
mkdir -p "dist/$name"
59+
cp "target/${{ matrix.target }}/release/launchguard" "dist/$name/"
60+
cp README.md LICENSE "dist/$name/"
61+
tar -czf "dist/$name.tar.gz" -C dist "$name"
62+
shasum -a 256 "dist/$name.tar.gz" | awk '{print $1" "'"$name"'".tar.gz"}' > "dist/$name.tar.gz.sha256"
63+
64+
- name: Package (Windows)
65+
if: runner.os == 'Windows'
66+
shell: pwsh
67+
run: |
68+
$name = "launchguard-${{ matrix.target }}"
69+
New-Item -ItemType Directory -Force -Path "dist/$name" | Out-Null
70+
Copy-Item "target/${{ matrix.target }}/release/launchguard.exe" "dist/$name/"
71+
Copy-Item README.md, LICENSE "dist/$name/"
72+
Compress-Archive -Path "dist/$name" -DestinationPath "dist/$name.zip"
73+
$hash = (Get-FileHash "dist/$name.zip" -Algorithm SHA256).Hash.ToLower()
74+
"$hash $name.zip" | Out-File -Encoding ascii "dist/$name.zip.sha256"
75+
76+
- name: Upload artifacts
77+
uses: actions/upload-artifact@v4
78+
with:
79+
name: launchguard-${{ matrix.target }}
80+
path: |
81+
dist/*.tar.gz
82+
dist/*.zip
83+
dist/*.sha256
84+
if-no-files-found: error
85+
86+
publish:
87+
name: Publish release
88+
needs: build
89+
runs-on: ubuntu-latest
90+
steps:
91+
- name: Check out repository
92+
uses: actions/checkout@v7
93+
94+
- name: Download artifacts
95+
uses: actions/download-artifact@v4
96+
with:
97+
path: dist
98+
merge-multiple: true
99+
100+
- name: Collect checksums
101+
shell: bash
102+
run: |
103+
set -euo pipefail
104+
cat dist/*.sha256 > dist/SHA256SUMS
105+
echo "Published artifacts:"
106+
cat dist/SHA256SUMS
107+
108+
# Binaries are unsigned. Signing needs a paid Apple Developer Program
109+
# membership and a Windows code-signing certificate, both recorded in
110+
# docs/SYSTEM_REQUIREMENTS.md as costs outside the free contract.
111+
- name: Create release
112+
env:
113+
GH_TOKEN: ${{ github.token }}
114+
TAG: ${{ github.event.inputs.tag || github.ref_name }}
115+
shell: bash
116+
run: |
117+
set -euo pipefail
118+
gh release create "$TAG" \
119+
--repo "${{ github.repository }}" \
120+
--title "LaunchGuard $TAG" \
121+
--notes "Unsigned prebuilt binaries. Verify against \`SHA256SUMS\` before use.
122+
123+
Install:
124+
125+
\`\`\`bash
126+
curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/main/install.sh | sh
127+
\`\`\`
128+
129+
Or download the archive for your platform, check its digest, and place \`launchguard\` on your PATH." \
130+
dist/*.tar.gz dist/*.zip dist/SHA256SUMS

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,24 @@ typed events and operations rather than implementing separate workflows.
9696

9797
## Quick start
9898

99-
LaunchGuard requires Rust 1.97.1. The repository pins the toolchain and commits
100-
its dependency lockfile. Trivy and OSV-Scanner are optional; a missing scanner
99+
Once a release is published, no toolchain is needed:
100+
101+
```bash
102+
curl -fsSL https://raw.githubusercontent.com/bananatruck/launchguard/main/install.sh | sh
103+
launchguard doctor
104+
launchguard setup
105+
launchguard audit ./path/to/project
106+
```
107+
108+
The installer verifies the published SHA-256 before installing and refuses to
109+
continue on a mismatch. Binaries are unsigned, so macOS and Windows may warn on
110+
first run. Read [`install.sh`](install.sh) before piping it to a shell — it is
111+
deliberately short enough to audit.
112+
113+
### From source
114+
115+
Building requires Rust 1.97.1. The repository pins the toolchain and commits its
116+
dependency lockfile. Trivy and OSV-Scanner are optional; a missing scanner
101117
degrades coverage instead of failing the run.
102118

103119
```bash

docs/PHASE_3.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,15 @@ them completed with 26 findings, 23 confirmed by both scanners, and no
160160
degradation. A second `setup` reported both already present without
161161
re-downloading.
162162

163+
Distribution is implemented. `.github/workflows/release.yml` builds all five
164+
targets on tag push and publishes archives with a `SHA256SUMS` manifest.
165+
`install.sh` resolves the host triple, downloads the matching archive, verifies
166+
it against the published digest, and refuses to install on a mismatch. It is
167+
short enough to read before piping to a shell, which is the point.
168+
163169
Remaining in this phase:
164170

165-
- Release binaries, checksums, and the install script.
171+
- Cutting the first tagged release, so the install script has something to fetch.
166172
- Disk and memory probing for preview admission control.
167173
- Threading the capability report through `RunRecord` so a stored run states
168174
which capabilities were present when it was produced.

install.sh

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/bin/sh
2+
# LaunchGuard installer.
3+
#
4+
# Downloads a prebuilt binary, verifies its published SHA-256, and installs it.
5+
# Read this script before piping it to a shell; it is deliberately short enough
6+
# to audit in one screen.
7+
#
8+
# curl -fsSL https://raw.githubusercontent.com/bananatruck/launchguard/main/install.sh | sh
9+
#
10+
# Environment:
11+
# LAUNCHGUARD_VERSION tag to install, default: latest release
12+
# LAUNCHGUARD_BIN_DIR install directory, default: $HOME/.local/bin
13+
#
14+
# Binaries are unsigned. macOS and Windows may warn on first run.
15+
16+
set -eu
17+
18+
REPO="bananatruck/launchguard"
19+
BIN_DIR="${LAUNCHGUARD_BIN_DIR:-$HOME/.local/bin}"
20+
21+
fail() {
22+
printf 'launchguard: %s\n' "$1" >&2
23+
exit 1
24+
}
25+
26+
need() {
27+
command -v "$1" >/dev/null 2>&1 || fail "$1 is required but not installed"
28+
}
29+
30+
need uname
31+
need mktemp
32+
command -v curl >/dev/null 2>&1 || command -v wget >/dev/null 2>&1 ||
33+
fail "curl or wget is required"
34+
35+
fetch() {
36+
if command -v curl >/dev/null 2>&1; then
37+
curl -fsSL --proto '=https' --tlsv1.2 -o "$2" "$1"
38+
else
39+
wget -qO "$2" "$1"
40+
fi
41+
}
42+
43+
# Resolve the target triple from the running host.
44+
os="$(uname -s)"
45+
arch="$(uname -m)"
46+
case "$os" in
47+
Linux) os_part="unknown-linux-gnu" ;;
48+
Darwin) os_part="apple-darwin" ;;
49+
*) fail "unsupported operating system: $os (Windows users: download the zip from the releases page)" ;;
50+
esac
51+
case "$arch" in
52+
x86_64 | amd64) arch_part="x86_64" ;;
53+
aarch64 | arm64) arch_part="aarch64" ;;
54+
*) fail "unsupported architecture: $arch" ;;
55+
esac
56+
target="${arch_part}-${os_part}"
57+
58+
version="${LAUNCHGUARD_VERSION:-}"
59+
if [ -z "$version" ]; then
60+
tmp_tag="$(mktemp)"
61+
fetch "https://api.github.com/repos/${REPO}/releases/latest" "$tmp_tag" ||
62+
fail "could not reach the GitHub releases API"
63+
version="$(sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' "$tmp_tag" | head -n 1)"
64+
rm -f "$tmp_tag"
65+
[ -n "$version" ] || fail "no published release found"
66+
fi
67+
68+
archive="launchguard-${target}.tar.gz"
69+
base="https://github.com/${REPO}/releases/download/${version}"
70+
work="$(mktemp -d)"
71+
trap 'rm -rf "$work"' EXIT
72+
73+
printf 'launchguard: installing %s for %s\n' "$version" "$target"
74+
75+
fetch "${base}/${archive}" "${work}/${archive}" ||
76+
fail "could not download ${archive} for ${version}"
77+
fetch "${base}/SHA256SUMS" "${work}/SHA256SUMS" ||
78+
fail "could not download SHA256SUMS; refusing to install an unverified binary"
79+
80+
# Verify before anything is made executable. A mismatch is fatal.
81+
expected="$(grep " ${archive}\$" "${work}/SHA256SUMS" | awk '{print $1}' | head -n 1)"
82+
[ -n "$expected" ] || fail "no published checksum for ${archive}"
83+
84+
if command -v sha256sum >/dev/null 2>&1; then
85+
actual="$(sha256sum "${work}/${archive}" | awk '{print $1}')"
86+
elif command -v shasum >/dev/null 2>&1; then
87+
actual="$(shasum -a 256 "${work}/${archive}" | awk '{print $1}')"
88+
else
89+
fail "sha256sum or shasum is required to verify the download"
90+
fi
91+
92+
if [ "$expected" != "$actual" ]; then
93+
fail "checksum mismatch for ${archive}
94+
expected ${expected}
95+
actual ${actual}
96+
Refusing to install."
97+
fi
98+
printf 'launchguard: checksum verified\n'
99+
100+
tar -xzf "${work}/${archive}" -C "$work" ||
101+
fail "could not extract ${archive}"
102+
103+
mkdir -p "$BIN_DIR"
104+
install -m 0755 "${work}/launchguard-${target}/launchguard" "${BIN_DIR}/launchguard" 2>/dev/null ||
105+
{
106+
cp "${work}/launchguard-${target}/launchguard" "${BIN_DIR}/launchguard"
107+
chmod 0755 "${BIN_DIR}/launchguard"
108+
}
109+
110+
printf 'launchguard: installed to %s/launchguard\n' "$BIN_DIR"
111+
112+
case ":${PATH}:" in
113+
*":${BIN_DIR}:"*) ;;
114+
*) printf 'launchguard: add %s to your PATH to run it by name\n' "$BIN_DIR" ;;
115+
esac
116+
117+
printf '\nNext: run "launchguard doctor" to see what this host can do.\n'

0 commit comments

Comments
 (0)