Repository navigation
feat(install): Homebrew formula + Nix flake (runner validation) #285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b6666d5
9291601
585a984
7acb0f9
e5eb586
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| name: Brew/Nix smoke | ||
|
|
||
| # Validate the Homebrew formula and the Nix flake on real runners (the dev host has | ||
| # neither). Each installs the published 0.10.0 standalone binary and asserts | ||
| # `clauster --version`. Manual + on changes to the packaging files. Non-required. | ||
| on: | ||
| workflow_dispatch: | ||
| pull_request: | ||
| paths: | ||
| - Formula/clauster.rb | ||
| - flake.nix | ||
| - flake.lock | ||
| - .github/workflows/brew-nix-smoke.yml | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: brew-nix-smoke-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| brew: | ||
| name: Homebrew (macos-arm64) | ||
| runs-on: macos-latest | ||
| timeout-minutes: 20 | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| persist-credentials: false | ||
| - name: brew install via a local tap + verify | ||
| run: | | ||
| set -euo pipefail | ||
| # Modern Homebrew refuses a bare local formula path — it must live in a tap. | ||
| # Stage our formula into an ephemeral local tap (mirrors real consumption via | ||
| # a homebrew-<x> repo) and install from there. | ||
| brew tap-new schubydoo/local --no-git | ||
| tap_dir="$(brew --repository schubydoo/local)" | ||
| mkdir -p "$tap_dir/Formula" | ||
| cp ./Formula/clauster.rb "$tap_dir/Formula/clauster.rb" | ||
| brew install --formula schubydoo/local/clauster | ||
| clauster --version | ||
| clauster --version | grep -q '^clauster' | ||
|
|
||
|
Comment on lines
+42
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strengthen version assertions to catch wrong-binary regressions. Line 43 and Line 64 only assert the output starts with Suggested change- clauster --version | grep -q '^clauster'
+ clauster --version | grep -q '^clauster 0\.10\.0$'
...
- ./result/bin/clauster --version | grep -q '^clauster'
+ ./result/bin/clauster --version | grep -q '^clauster 0\.10\.0$'Also applies to: 63-65 |
||
| nix: | ||
| name: Nix flake (x86_64-linux) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 25 | ||
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Install Nix, build + run the flake | ||
| run: | | ||
| set -euo pipefail | ||
| # Single-user install (no daemon/systemd needed in CI). | ||
| sh <(curl -L https://nixos.org/nix/install) --no-daemon | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's look at the full context of the workflow file
cat -n .github/workflows/brew-nix-smoke.ymlRepository: schubydoo/clauster Length of output: 3015 🏁 Script executed: # Search for Nix-related GitHub Actions or official installers
rg -i "nix.*action|nixos.*action" --type yamlRepository: schubydoo/clauster Length of output: 44 🌐 Web query:
💡 Result: There is no single "official" installer for Nix on GitHub Actions, as multiple community-maintained actions exist [1][2][3][4]. Consequently, there is no single "pinned commit" that covers all users [5][3]. Instead, you should choose a specific action and pin it to a commit hash or release tag for security and reproducibility [3][6]. Commonly used Nix installers for GitHub Actions include: 1. cachix/install-nix-action: This is a widely used community action [4][7]. While it uses semantic versioning (e.g., v31, v31.9.1) for releases, you can pin it to a specific git commit hash in your workflow file to ensure absolute reproducibility [5][6]. Example usage: - uses: cachix/install-nix-action@2126ae7 2. DeterminateSystems/determinate-nix-action: This action is maintained by Determinate Systems and supports explicit version pinning for their installer [3][8]. It is automatically tagged for every release [3]. Example usage: - uses: DeterminateSystems/determinate-nix-action@v3.17.2 3. NixOS/nix-installer-action: An experimental action associated with the official NixOS organization [1]. It is currently in a testing phase and primarily tracks the main branch [1]. To pin any of these actions securely: - Find the desired tag or commit on the action's GitHub repository "Releases" or "Commits" page [5][9]. - Replace the version tag (e.g., Citations:
🌐 Web query:
💡 Result: The curl | sh (or similar pipe-to-shell) pattern is considered a high-risk practice in CI/CD environments, including GitHub Actions, due to its susceptibility to supply chain attacks and lack of integrity verification [1][2][3]. Primary Risks: 1. Arbitrary Code Execution: The pattern downloads and executes remote code in a single, unverified step. If the host, CDN, DNS, or network path is compromised, an attacker can serve malicious code that executes with the full privileges of the CI runner [1][2][3]. 2. Secret Exfiltration: Because the script runs in the CI environment, it has access to workflow secrets, environment variables, and tokens. Compromised scripts are frequently used to exfiltrate these credentials [3]. 3. Man-in-the-Middle (MitM) Attacks: Piping directly to a shell bypasses the ability to inspect the script's contents or verify cryptographic signatures before execution [3]. 4. Unintended Content: Remote servers may intermittently fail or serve unexpected content (e.g., HTML error pages) that can cause unpredictable or dangerous behavior when piped into a shell [4]. Recommended Secure Practices: 1. Use Vetted Actions: Replace ad-hoc shell scripts with official, well-maintained GitHub Actions. When using third-party actions, pin them to a specific commit SHA rather than a tag or branch [1][2]. 2. Download and Verify: If a script must be fetched, split the process into distinct steps: - Download the file to a local path [1][2]. - Verify its integrity using cryptographic hashes (e.g., SHA-256) or GPG signatures provided by the distributor [1][5][3]. - Execute the verified file only after the integrity check passes [1][2][3]. 3. Version Control: Whenever possible, vendor the script into your repository under version control to ensure the code remains immutable and audit-trailed [5]. 4. Use Security Tools: Employ automated security scanners (e.g., Semgrep, Poutine, or Pipefort) to detect and block unsafe pipe-to-shell patterns in your pipeline definitions [6][2][4]. Runtime security tools (e.g., kntrl-action) can further monitor and block suspicious process chains or unauthorized network access during workflow execution [7]. The critical takeaway is to treat all remote, unverified content as untrusted code [3][4]. Always verify the provenance and integrity of external scripts before execution [1][3]. Citations:
Replace inline shell installer with pinned GitHub Action for Nix. Line 57 executes
This eliminates supply-chain attack surface, prevents silent failures from transient fetch issues, and aligns with how this workflow already pins |
||
| . "$HOME/.nix-profile/etc/profile.d/nix.sh" | ||
| export NIX_CONFIG="experimental-features = nix-command flakes" | ||
| # --no-update-lock-file uses the committed flake.lock and errors if it's | ||
| # stale (rather than silently regenerating it), asserting lock/flake match. | ||
| nix build .#clauster -L --no-update-lock-file | ||
| ./result/bin/clauster --version | ||
| ./result/bin/clauster --version | grep -q '^clauster' | ||
| nix run .#clauster --no-update-lock-file -- --version | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # Homebrew formula for the signed standalone clauster binary. | ||
| # | ||
| # brew install --formula ./Formula/clauster.rb | ||
| # | ||
| # Covers the published 0.10.0 targets: macOS arm64 (Apple Silicon) and Linux | ||
| # x86_64 (Linuxbrew). macOS Intel and Linux arm64 binaries are not built yet | ||
| # (those install via pip/uv/uvx for now). | ||
| class Clauster < Formula | ||
| desc "Self-hosted web UI for spawning and managing Claude Code remote-control bridges" | ||
| homepage "https://github.com/schubydoo/clauster" | ||
| version "0.10.0" | ||
| license "Apache-2.0" | ||
|
|
||
| on_macos do | ||
| on_arm do | ||
| url "https://github.com/schubydoo/clauster/releases/download/v0.10.0/clauster-0.10.0-macos-arm64" | ||
| sha256 "be06f18dae69680bcffbf7b167faed6fec6cc81bf50329fbb39a20d9d8b3efa2" | ||
| end | ||
| end | ||
|
|
||
| on_linux do | ||
| on_intel do | ||
| url "https://github.com/schubydoo/clauster/releases/download/v0.10.0/clauster-0.10.0-linux-x86_64" | ||
| sha256 "c1052df6ee5bf0519e33c2981c522b59143281bea2f7cb9075c62f07aa4c8c87" | ||
| end | ||
| end | ||
|
|
||
| def install | ||
| # The release asset downloads under its versioned name; install it as `clauster`. | ||
| bin.install Dir["clauster-*"].first => "clauster" | ||
| end | ||
|
|
||
| test do | ||
| assert_match "clauster #{version}", shell_output("#{bin}/clauster --version") | ||
| end | ||
| end |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| { | ||
| description = "clauster — self-hosted web UI for Claude Code remote-control bridges"; | ||
|
|
||
| inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; | ||
|
|
||
| outputs = | ||
| { self, nixpkgs }: | ||
| let | ||
| version = "0.10.0"; | ||
|
|
||
| # The published 0.10.0 standalone binaries, keyed by Nix system. macOS Intel | ||
| # and Linux arm64 are not built yet (use pip/uv there for now). | ||
| assets = { | ||
| "x86_64-linux" = { | ||
| file = "clauster-${version}-linux-x86_64"; | ||
| sha256 = "c1052df6ee5bf0519e33c2981c522b59143281bea2f7cb9075c62f07aa4c8c87"; | ||
| }; | ||
| "aarch64-darwin" = { | ||
| file = "clauster-${version}-macos-arm64"; | ||
| sha256 = "be06f18dae69680bcffbf7b167faed6fec6cc81bf50329fbb39a20d9d8b3efa2"; | ||
| }; | ||
| }; | ||
|
|
||
| systems = builtins.attrNames assets; | ||
| forAllSystems = nixpkgs.lib.genAttrs systems; | ||
|
|
||
| packageFor = | ||
| system: | ||
| let | ||
| pkgs = nixpkgs.legacyPackages.${system}; | ||
| asset = assets.${system}; | ||
| in | ||
| pkgs.stdenvNoCC.mkDerivation { | ||
| pname = "clauster"; | ||
| inherit version; | ||
|
|
||
| src = pkgs.fetchurl { | ||
| url = "https://github.com/schubydoo/clauster/releases/download/v${version}/${asset.file}"; | ||
| inherit (asset) sha256; | ||
| }; | ||
|
|
||
| dontUnpack = true; | ||
|
|
||
| # The Linux binary is a PyInstaller one-file ELF; patch its loader/RPATH to | ||
| # the Nix store. Darwin needs no patching. | ||
| nativeBuildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.autoPatchelfHook ]; | ||
| buildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [ | ||
| (pkgs.lib.getLib pkgs.stdenv.cc.cc) | ||
| pkgs.zlib | ||
| ]; | ||
|
|
||
| installPhase = '' | ||
| runHook preInstall | ||
| install -Dm755 "$src" "$out/bin/clauster" | ||
| runHook postInstall | ||
| ''; | ||
|
|
||
| meta = with pkgs.lib; { | ||
| description = "Self-hosted web UI for spawning and managing Claude Code remote-control bridges"; | ||
| homepage = "https://github.com/schubydoo/clauster"; | ||
| license = licenses.asl20; | ||
| platforms = systems; | ||
| mainProgram = "clauster"; | ||
| sourceProvenance = [ sourceTypes.binaryNativeCode ]; | ||
| }; | ||
| }; | ||
| in | ||
| { | ||
| packages = forAllSystems (system: rec { | ||
| clauster = packageFor system; | ||
| default = clauster; | ||
| }); | ||
|
|
||
| apps = forAllSystems (system: rec { | ||
| clauster = { | ||
| type = "app"; | ||
| program = "${self.packages.${system}.clauster}/bin/clauster"; | ||
| }; | ||
| default = clauster; | ||
| }); | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pin an explicit ARM macOS runner to keep the brew job valid.
Line 25 uses
macos-latest, but the formula under test is arm-only on macOS (on_arminFormula/clauster.rb). Ifmacos-latestever resolves to Intel, this smoke test fails for infra reasons rather than formula correctness. Pin a known ARM label (for example,macos-14) for deterministic architecture.Suggested change
📝 Committable suggestion