#!/bin/sh # sigil installer — https://sigil-cli.daystra.com/install.sh # # curl -fsSL https://sigil-cli.daystra.com/install.sh | sh # # Shipped targets (read from latest.txt, the same manifest `sigil update` uses): # macOS arm64 — Developer ID signed; identity verified against the release pin # Linux x86_64 — glibc >= 2.28; SHA-256 + an Ed25519 release signature checked with # openssl against the key pinned below (once a key is pinned) # Anything else exits with a clear message — build from source instead. # # Env: SIGIL_INSTALL_DIR (default ~/.local/bin), SIGIL_CHANNEL (default below), # SIGIL_INSTALL_SKIP_SIGNATURE=1 (Linux: skip the signature check, loudly; at your own risk). set -eu CHANNEL="${SIGIL_CHANNEL:-https://sigil-cli.daystra.com}" DIR="${SIGIL_INSTALL_DIR:-$HOME/.local/bin}" DEST="$DIR/sigil" # Same pin as src/update.rs PINNED_REQUIREMENT: Apple-issued chain AND Mark's team. PIN='anchor apple generic and certificate leaf[subject.OU] = RY99V66U84' # Linux release-signing public key (Ed25519). This installer is served from the same origin as # the binary and its signature, so a pin HERE only defends against a tampered artifact or # manifest. To verify independently, compare it with the copies published elsewhere: the # sigil-cli README ("Release signing key") and the fleet MANIFEST. While this function is # empty no key is pinned, and Linux installs fall back to SHA-256 only (and say so). pinned_pubkey() { cat <<'PEM' -----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAy7f/9iJ7NtWX0FMWRXwEWEXpFky2CE2sCWo6sBNN4kU= -----END PUBLIC KEY----- PEM } die() { printf 'sigil-install: %s\n' "$*" >&2; exit 1; } say() { printf 'sigil-install: %s\n' "$*" >&2; } os="$(uname -s)"; arch="$(uname -m)" case "$os/$arch" in Darwin/arm64) key=arm64 ;; Linux/x86_64|Linux/amd64) key=linux_x86_64 ;; Darwin/x86_64) die "Intel macOS has no published build yet — build from source (cargo build --release)." ;; Linux/aarch64|Linux/arm64) die "Linux arm64 has no published build yet — build from source (cargo build --release)." ;; *) die "$os/$arch is not a shipped target (macOS arm64, Linux x86_64) — build from source." ;; esac command -v curl >/dev/null 2>&1 || die "curl is required" # Never clobber a symlinked install: on a dev machine ~/.local/bin/sigil points # into a build tree whose path the Keychain ACLs trust. Replacing the link with # a file would silently break that trust. if [ -L "$DEST" ]; then die "$DEST is a symlink (a source/dev install) — refusing to replace it. Use SIGIL_INSTALL_DIR=... to install elsewhere." fi # On macOS an existing install updates itself with the stronger check built in. if [ "$os" = Darwin ] && [ -x "$DEST" ]; then die "sigil is already installed at $DEST — run \`sigil update\` instead." fi tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' EXIT INT TERM curl -fsSL "$CHANNEL/latest.txt" -o "$tmp/latest.txt" || die "could not fetch $CHANNEL/latest.txt" field() { sed -n "s/^$1=//p" "$tmp/latest.txt" | head -n 1 | tr -d '\r'; } ver="$(field version)"; url="$(field "url_$key")"; sum="$(field "sha256_$key")" [ -n "$ver" ] || die "manifest has no version" [ -n "$url" ] && [ -n "$sum" ] || die "sigil $ver has no $os/$arch build on the channel — build from source." case "$url" in https://*) ;; *) die "refusing non-https artifact URL" ;; esac say "downloading sigil $ver ($os/$arch)…" curl -fsSL "$url" -o "$tmp/sigil" || die "download failed: $url" if command -v sha256sum >/dev/null 2>&1; then got="$(sha256sum "$tmp/sigil" | cut -d' ' -f1)" else got="$(shasum -a 256 "$tmp/sigil" | cut -d' ' -f1)"; fi [ "$got" = "$sum" ] || die "SHA-256 mismatch (expected $sum, got $got) — not installing." if [ "$os" = Linux ]; then pinned_pubkey > "$tmp/pin.pem" if [ ! -s "$tmp/pin.pem" ]; then say "note: no release signing key is pinned in this installer yet; integrity is SHA-256 only." elif [ "${SIGIL_INSTALL_SKIP_SIGNATURE:-}" = 1 ]; then say "WARNING: SIGIL_INSTALL_SKIP_SIGNATURE=1, so the release signature was NOT checked." say "WARNING: integrity rests on a SHA-256 served from the same origin as the binary." else sig="$(field "sig_$key")" # A pinned key plus an unsigned build is a downgrade, not a legacy case. [ -n "$sig" ] || die "the manifest carries no signature for this build; refusing (a pinned key never accepts an unsigned build)." command -v openssl >/dev/null 2>&1 \ || die "openssl is required to verify the release signature (Debian/Ubuntu: apt-get install openssl; Fedora/RHEL: dnf install openssl; Arch: pacman -S openssl). To skip at your own risk: SIGIL_INSTALL_SKIP_SIGNATURE=1." printf '%s' "$sig" | openssl base64 -d -A > "$tmp/sig.bin" 2>/dev/null \ || die "could not decode the manifest signature; not installing." if ! openssl pkeyutl -verify -pubin -inkey "$tmp/pin.pem" -rawin -in "$tmp/sigil" \ -sigfile "$tmp/sig.bin" >"$tmp/verify.out" 2>&1; then if grep -qi 'signature verification failure' "$tmp/verify.out"; then die "release signature check FAILED: this binary was not signed by the pinned key. Not installing." fi die "openssl could not run the Ed25519 check ($(openssl version 2>/dev/null)); OpenSSL 1.1.1 or newer is required. Not installing." fi say "release signature verified (Ed25519, pinned key)" fi fi if [ "$os" = Darwin ]; then codesign --verify --strict -R="$PIN" "$tmp/sigil" 2>/dev/null \ || die "code-signature check against the release pin FAILED — not installing." xattr -d com.apple.quarantine "$tmp/sigil" 2>/dev/null || true fi chmod 0755 "$tmp/sigil" mkdir -p "$DIR" cp "$tmp/sigil" "$DEST.new" && mv -f "$DEST.new" "$DEST" say "installed $("$DEST" --version 2>/dev/null || echo "sigil $ver") -> $DEST" case ":$PATH:" in *":$DIR:"*) ;; *) say "add $DIR to your PATH: export PATH=\"$DIR:\$PATH\"" ;; esac if [ "$os" = Linux ]; then command -v secret-tool >/dev/null 2>&1 \ || say "note: the default Linux backend needs secret-tool (Debian/Ubuntu: libsecret-tools; Arch: libsecret) and a running Secret Service (gnome-keyring/kwallet)." say "headless/CI with no Secret Service: use --backend env (SIGIL_ASSUME_YES=1 is honored only there)." say "updates on Linux: re-run this installer (\`sigil update\` is macOS-only)." fi