diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddd8042..8a26f8a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,15 +64,20 @@ jobs: target-branch: main build: - needs: release - if: ${{ needs.release.outputs.release_created }} + # Builds on every run, so a broken binary shows up before a release; uploads on releases. + needs: [test, release] + if: ${{ !cancelled() && needs.test.result == 'success' && needs.release.result != 'failure' }} strategy: matrix: include: - os: ubuntu-latest - nix_system: x86_64-linux + target: x86_64-unknown-linux-musl + - os: ubuntu-24.04-arm + target: aarch64-unknown-linux-musl + - os: macos-15 + target: aarch64-apple-darwin - os: macos-15 - nix_system: aarch64-darwin + target: x86_64-apple-darwin runs-on: ${{ matrix.os }} permissions: contents: write @@ -81,21 +86,45 @@ jobs: - name: Checkout uses: actions/checkout@v7 - - name: Install Nix - uses: DeterminateSystems/nix-installer-action@v23 + # Plain cargo, not Nix: the binaries must not depend on /nix/store + - name: Install Rust + run: | + rustup show active-toolchain + rustup target add ${{ matrix.target }} + + - name: Install musl + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y musl-tools - - name: Build with Nix - run: nix build . --system ${{ matrix.nix_system }} + - name: Build + run: cargo build --release --locked --target ${{ matrix.target }} + + - name: Check the binary runs on its own + run: | + bin=target/${{ matrix.target }}/release/keysafe + file "$bin" + if [ "$RUNNER_OS" = macOS ]; then + otool -L "$bin" + ! otool -L "$bin" | grep -q /nix/store + else + file "$bin" | grep -Eq 'static(ally|-pie)? linked' + fi + case "${{ matrix.target }}" in + x86_64-apple-darwin) ;; # cross-compiled; the arm64 runner can't always run it + *) "$bin" --version ;; + esac - name: Prepare Release Assets + if: ${{ needs.release.outputs.release_created }} run: | - mkdir -p release && install -m 0755 result/bin/keysafe release/keysafe-${{ matrix.nix_system }} + mkdir -p release && install -m 0755 target/${{ matrix.target }}/release/keysafe release/keysafe-${{ matrix.target }} - name: Upload Release Assets + if: ${{ needs.release.outputs.release_created }} uses: softprops/action-gh-release@v3 with: tag_name: ${{ needs.release.outputs.tag_name }} - files: release/keysafe-${{ matrix.nix_system }} + files: release/keysafe-${{ matrix.target }} publish: needs: [release, build] diff --git a/Cargo.toml b/Cargo.toml index fff18f9..07d9a80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,12 @@ documentation = "https://github.com/keysafe-dev/keysafe" homepage = "https://github.com/keysafe-dev/keysafe" repository = "https://github.com/keysafe-dev/keysafe" + +# `cargo binstall keysafe` downloads the binary attached to the GitHub release +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }" +pkg-fmt = "bin" + [dependencies] anyhow = "1.0.104" clap = { version = "4.6.7", features = ["derive", "env", "string"] } diff --git a/README.md b/README.md index 6120a92..13e78d8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > Your 1Password secrets in every shell: cached in the system keychain, exported as environment variables or files, and added to ssh-agent. -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![CI](https://github.com/keysafe-dev/keysafe/actions/workflows/ci.yml/badge.svg)](https://github.com/keysafe-dev/keysafe/actions/workflows/ci.yml) +[![crates.io](https://img.shields.io/crates/v/keysafe.svg)](https://crates.io/crates/keysafe) [![Downloads](https://img.shields.io/crates/d/keysafe.svg)](https://crates.io/crates/keysafe) [![CI](https://github.com/keysafe-dev/keysafe/actions/workflows/ci.yml/badge.svg)](https://github.com/keysafe-dev/keysafe/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) `keysafe` is not another password manager: 1Password stays the source of truth. It reads a YAML config of profiles, fetches each secret from 1Password on first use, and caches it in the macOS Keychain or the Linux Secret Service. After that, secrets come from the keychain: your shell starts without waiting for 1Password or asking for Touch ID. @@ -21,19 +21,32 @@ Add one line to your shell's startup file and `keysafe` sets up your secrets in ## Installation +**Cargo** (builds from [crates.io](https://crates.io/crates/keysafe)): + +```bash +cargo install keysafe +``` + +**Prebuilt binary** with [cargo-binstall](https://github.com/cargo-bins/cargo-binstall), for macOS (Apple Silicon, Intel) and Linux (x86-64, arm64): + +```bash +cargo binstall keysafe +``` + **Nix:** ```bash nix profile install github:keysafe-dev/keysafe ``` -**Cargo:** +**Download:** each [release](https://github.com/keysafe-dev/keysafe/releases/latest) has a binary per platform: `keysafe-aarch64-apple-darwin`, `keysafe-x86_64-apple-darwin`, `keysafe-x86_64-unknown-linux-musl` and `keysafe-aarch64-unknown-linux-musl`. The Linux binaries are static and run on any distribution. ```bash -cargo install --git https://github.com/keysafe-dev/keysafe +curl -fsSL --create-dirs -o ~/.local/bin/keysafe https://github.com/keysafe-dev/keysafe/releases/latest/download/keysafe-aarch64-apple-darwin +chmod +x ~/.local/bin/keysafe ``` -**Prebuilt:** download `keysafe-` from the [latest release](https://github.com/keysafe-dev/keysafe/releases/latest). +Then set up your shell (see [Shell integration](#shell-integration)) and run `keysafe doctor` to check everything. ## Configuration