murk-cli 0.7.0

Encrypted secrets manager for developers — one file, age encryption, git-friendly
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
      - run: cargo fmt --check
      - run: cargo clippy --all-features --all-targets -- -D warnings
      - uses: EmbarkStudios/cargo-deny-action@bb137d7af7e4fb67e5f82a49c4fce4fad40782fe # v2
      - uses: taiki-e/install-action@f092c064826410a38929a5791d2c0225b94432fe # cargo-audit
        with:
          tool: cargo-audit
      - run: cargo audit --ignore RUSTSEC-2023-0071
      - name: Check version parity (Cargo.toml vs node/package.json)
        run: |
          cargo_ver=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)
          node_ver=$(sed -n 's/.*"version": "\(.*\)".*/\1/p' node/package.json | head -1)
          if [ "$cargo_ver" != "$node_ver" ]; then
            echo "::error::Version mismatch: Cargo.toml=$cargo_ver node/package.json=$node_ver"
            exit 1
          fi

  msrv:
    name: MSRV
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
        with:
          toolchain: "1.89"
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
      - run: cargo check --all-features --all-targets --locked

  test:
    name: Test (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
      - uses: taiki-e/install-action@f092c064826410a38929a5791d2c0225b94432fe # nextest
      - run: cargo nextest run --all-features --profile ci
      - uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1
        if: always()
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: target/nextest/ci/junit.xml

  test-gate:
    name: Test
    runs-on: ubuntu-latest
    needs: test
    if: always()
    permissions: {}
    steps:
      - run: |
          if [[ "${{ needs.test.result }}" != "success" ]]; then
            echo "Test matrix failed"
            exit 1
          fi

  coverage:
    name: Coverage
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
      - uses: taiki-e/install-action@f092c064826410a38929a5791d2c0225b94432fe # cargo-llvm-cov
        with:
          tool: cargo-llvm-cov
      - run: cargo llvm-cov --codecov --output-path codecov.json -- --test-threads=1
      - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v5
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          files: codecov.json
          fail_ci_if_error: false

  demo-test:
    name: VHS Dress Rehearsal
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
      - run: sudo apt-get install -y direnv
      - run: make test-demos

  # Exercises the end-user install path: install.sh against a locally-built
  # release fixture, then a real encrypt/decrypt roundtrip with the installed
  # binary. The other channels are intentionally not installed here — they are
  # built and tested elsewhere: crates.io (cargo build/test in this file),
  # PyPI bindings (python.yaml), npm bindings (node.yaml), and Homebrew (the
  # tap formula is bumped by release.yaml). This job guards install.sh, the
  # path with no other coverage.
  install-smoke:
    name: Install smoke (${{ matrix.os }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
      - run: cargo build --release
      - name: Smoke-test install.sh against a local fixture
        shell: bash
        run: |
          set -euo pipefail
          target=$(rustc -vV | sed -n 's/^host: //p')
          tag=v0.0.0-smoke
          archive="murk-${tag}-${target}.tar.gz"

          # Build a fixture shaped like a real GitHub release: a tarball holding
          # the murk binary, plus a matching SHA256SUMS.
          rel="$RUNNER_TEMP/release"
          mkdir -p "$rel" "$RUNNER_TEMP/stage"
          cp target/release/murk "$RUNNER_TEMP/stage/murk"
          tar czf "$rel/$archive" -C "$RUNNER_TEMP/stage" murk
          if command -v sha256sum >/dev/null; then
            ( cd "$rel" && sha256sum "$archive" > SHA256SUMS )
          else
            ( cd "$rel" && shasum -a 256 "$archive" > SHA256SUMS )
          fi

          # Serve the fixture and install through install.sh as a fresh user
          # would, pointed at the fixture instead of github.com.
          python3 -m http.server 8099 --directory "$rel" >/dev/null 2>&1 &
          server=$!
          trap 'kill "$server" 2>/dev/null || true' EXIT
          for _ in $(seq 1 40); do
            curl -fsS http://localhost:8099/SHA256SUMS -o /dev/null 2>/dev/null && break
            sleep 0.25
          done

          bindir="$RUNNER_TEMP/bin"
          mkdir -p "$bindir"
          MURK_TAG="$tag" \
          MURK_BASE_URL=http://localhost:8099 \
          MURK_SKIP_ATTESTATION=1 \
          MURK_INSTALL_DIR="$bindir" \
            sh install.sh

          # The installed binary must start and complete an encrypt/decrypt
          # roundtrip in a clean temp directory with an isolated HOME.
          export PATH="$bindir:$PATH"
          murk --version
          export HOME="$RUNNER_TEMP/home"
          vault="$RUNNER_TEMP/vault"
          mkdir -p "$HOME" "$vault"
          cd "$vault"
          printf 'smoke-user\n' | murk init
          . ./.env
          printf 'hunter2\n' | murk add SMOKE_KEY
          got=$(murk get SMOKE_KEY)
          test "$got" = "hunter2"
          echo "install smoke passed: install.sh + init/add/get roundtrip"

  vhs-build:
    name: VHS Build Binary
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
      - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2
      - run: rustup target add x86_64-unknown-linux-musl
      - run: sudo apt-get install -y musl-tools
      - run: cargo build --release --target x86_64-unknown-linux-musl
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: murk-musl
          path: target/x86_64-unknown-linux-musl/release/murk
          retention-days: 1

  vhs-record:
    name: VHS (${{ matrix.tape }})
    needs: vhs-build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    strategy:
      fail-fast: false
      matrix:
        tape: [hero, team, offboard, eve, recovery, github, direnv, mallory, ssh]
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: murk-musl
          path: target/x86_64-unknown-linux-musl/release/
      - run: chmod +x target/x86_64-unknown-linux-musl/release/murk
      - name: Build VHS image with git and direnv
        run: |
          echo 'FROM ghcr.io/charmbracelet/vhs
          RUN apt-get update --allow-releaseinfo-change && apt-get install -y --no-install-recommends git direnv && rm -rf /var/lib/apt/lists/*' | docker build -t vhs-git -
      - name: Record tape
        run: docker run --rm -v $PWD:/vhs -e PATH="/vhs/target/x86_64-unknown-linux-musl/release:$PATH" vhs-git demo/${{ matrix.tape }}.tape
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: gif-${{ matrix.tape }}
          path: demo/${{ matrix.tape }}.gif
          retention-days: 1

  vhs:
    name: VHS
    needs: vhs-record
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          pattern: gif-*
          merge-multiple: true
          path: demo/
      - uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./demo
          publish_branch: demo
          keep_files: true