From b6666d5ca7c56aad3d1a84f7b960e49cbe4c0bcf Mon Sep 17 00:00:00 2001 From: Schuby <12485317+schubydoo@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:29:14 -0700 Subject: [PATCH 1/5] feat(install): Homebrew formula + Nix flake (runner-validated) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the two remaining prong-1 package paths, written to be validated on GitHub runners (the dev host has neither brew nor nix): - Formula/clauster.rb — installs the signed standalone binary on macOS arm64 + Linux x86_64. - flake.nix — `nix run github:schubydoo/clauster`; wraps the binary with autoPatchelfHook on Linux. Covers x86_64-linux + aarch64-darwin. - .github/workflows/brew-nix-smoke.yml — brew install on macos-latest + nix build/run on ubuntu-latest, asserting `clauster --version`. Experimental: open as a draft so the pull_request trigger runs the smoke jobs; promote once green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/brew-nix-smoke.yml | 56 +++++++++++++++++++ Formula/clauster.rb | 36 ++++++++++++ flake.nix | 82 ++++++++++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 .github/workflows/brew-nix-smoke.yml create mode 100644 Formula/clauster.rb create mode 100644 flake.nix diff --git a/.github/workflows/brew-nix-smoke.yml b/.github/workflows/brew-nix-smoke.yml new file mode 100644 index 000000000..5ab76189b --- /dev/null +++ b/.github/workflows/brew-nix-smoke.yml @@ -0,0 +1,56 @@ +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 + verify + run: | + set -euo pipefail + brew install --formula ./Formula/clauster.rb + clauster --version + clauster --version | grep -q '^clauster' + + 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 + . "$HOME/.nix-profile/etc/profile.d/nix.sh" + export NIX_CONFIG="experimental-features = nix-command flakes" + nix build .#clauster -L + ./result/bin/clauster --version + ./result/bin/clauster --version | grep -q '^clauster' + nix run .#clauster -- --version diff --git a/Formula/clauster.rb b/Formula/clauster.rb new file mode 100644 index 000000000..2bae7ace2 --- /dev/null +++ b/Formula/clauster.rb @@ -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 diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..d57b0b5ef --- /dev/null +++ b/flake.nix @@ -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; + }); + }; +} From 92916010d10a3c91b2968fceab5c2a8674459db0 Mon Sep 17 00:00:00 2001 From: Schuby <12485317+schubydoo@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:31:33 -0700 Subject: [PATCH 2/5] ci(install): install the brew formula via an ephemeral local tap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modern Homebrew rejects a bare local formula path (must be in a tap), so stage the formula into a local tap and install from there — also a more realistic test of how the formula is consumed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/brew-nix-smoke.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/brew-nix-smoke.yml b/.github/workflows/brew-nix-smoke.yml index 5ab76189b..706554471 100644 --- a/.github/workflows/brew-nix-smoke.yml +++ b/.github/workflows/brew-nix-smoke.yml @@ -28,10 +28,17 @@ jobs: - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - - name: brew install + verify + - name: brew install via a local tap + verify run: | set -euo pipefail - brew install --formula ./Formula/clauster.rb + # 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- 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' From 585a9845106dd644c1d6a4b1cfde549c5180b37d Mon Sep 17 00:00:00 2001 From: Schuby <12485317+schubydoo@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:40:55 -0700 Subject: [PATCH 3/5] ci(install): emit flake.lock artifact from the nix smoke job Temporary helper to capture a reproducible lockfile (no nix on the dev host); the lock gets committed and this step removed. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/brew-nix-smoke.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/brew-nix-smoke.yml b/.github/workflows/brew-nix-smoke.yml index 706554471..fe588843d 100644 --- a/.github/workflows/brew-nix-smoke.yml +++ b/.github/workflows/brew-nix-smoke.yml @@ -61,3 +61,11 @@ jobs: ./result/bin/clauster --version ./result/bin/clauster --version | grep -q '^clauster' nix run .#clauster -- --version + # Emit the generated lockfile so we can commit a reproducible flake.lock + # (the dev host has no nix to `nix flake lock` locally). + - name: Upload flake.lock + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: flake-lock + path: flake.lock + if-no-files-found: error From 7acb0f9e89f2bffa1b69721e6a7e0706787f6e3e Mon Sep 17 00:00:00 2001 From: Schuby <12485317+schubydoo@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:43:21 -0700 Subject: [PATCH 4/5] feat(install): pin flake.lock; assert it with nix --locked in CI Commit the runner-generated flake.lock (nixpkgs pinned) so `nix run github:schubydoo/clauster` is reproducible, and switch the nix smoke job to --locked so a stale lock fails CI. Drops the temporary upload step. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/brew-nix-smoke.yml | 14 ++++---------- flake.lock | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 flake.lock diff --git a/.github/workflows/brew-nix-smoke.yml b/.github/workflows/brew-nix-smoke.yml index fe588843d..23ea98153 100644 --- a/.github/workflows/brew-nix-smoke.yml +++ b/.github/workflows/brew-nix-smoke.yml @@ -57,15 +57,9 @@ jobs: sh <(curl -L https://nixos.org/nix/install) --no-daemon . "$HOME/.nix-profile/etc/profile.d/nix.sh" export NIX_CONFIG="experimental-features = nix-command flakes" - nix build .#clauster -L + # --locked asserts the committed flake.lock is consistent with flake.nix + # (fails instead of silently regenerating it). + nix build .#clauster -L --locked ./result/bin/clauster --version ./result/bin/clauster --version | grep -q '^clauster' - nix run .#clauster -- --version - # Emit the generated lockfile so we can commit a reproducible flake.lock - # (the dev host has no nix to `nix flake lock` locally). - - name: Upload flake.lock - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: flake-lock - path: flake.lock - if-no-files-found: error + nix run .#clauster --locked -- --version diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..4c01a0e37 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1781454065, + "narHash": "sha256-d2xfDjnfRuf/xYGdu9VVRHiav/2w5hDL/5cw2TuVAXw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9eac87a12312b8f60dd52e1c6e1a265f6fc7f5fc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} From e5eb5869b956bf6b26608235cf1fa99d13b8f3f1 Mon Sep 17 00:00:00 2001 From: Schuby <12485317+schubydoo@users.noreply.github.com> Date: Mon, 15 Jun 2026 10:45:19 -0700 Subject: [PATCH 5/5] ci(install): use --no-update-lock-file (--locked is unrecognised) Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/brew-nix-smoke.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/brew-nix-smoke.yml b/.github/workflows/brew-nix-smoke.yml index 23ea98153..e928e6783 100644 --- a/.github/workflows/brew-nix-smoke.yml +++ b/.github/workflows/brew-nix-smoke.yml @@ -57,9 +57,9 @@ jobs: sh <(curl -L https://nixos.org/nix/install) --no-daemon . "$HOME/.nix-profile/etc/profile.d/nix.sh" export NIX_CONFIG="experimental-features = nix-command flakes" - # --locked asserts the committed flake.lock is consistent with flake.nix - # (fails instead of silently regenerating it). - nix build .#clauster -L --locked + # --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 --locked -- --version + nix run .#clauster --no-update-lock-file -- --version