Add beads and gastown to CI cache, consolidate CI workflow
- Add packages/beads and packages/gastown with shared definitions - Expose custom-beads and custom-gastown in flake packages output - Consolidate CI from matrix (8 parallel jobs) to single job with loop - Saves ~12 minutes of redundant nix-setup time per run - Uses ::group:: for collapsible log sections per package Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,50 +23,64 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: check
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- custom-claude-code
|
||||
- custom-app-launcher-server
|
||||
- custom-mcrcon-rbw
|
||||
- custom-tea-rbw
|
||||
- custom-rclone-torbox-setup
|
||||
- qt-pinned-jellyfin-media-player
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: https://git.johnogle.info/johno/gitea-actions/nix-setup@v1
|
||||
|
||||
- name: Build ${{ matrix.package }}
|
||||
id: build
|
||||
- name: Setup SSH for cache
|
||||
run: |
|
||||
OUT_PATH=$(nix build .#${{ matrix.package }} --no-link --print-out-paths)
|
||||
echo "out_path=$OUT_PATH" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
NIX_CONFIG: "access-tokens = git.johnogle.info=${{ secrets.GITEA_ACCESS_TOKEN }}"
|
||||
|
||||
- name: Sign and push to cache
|
||||
run: |
|
||||
# Write signing key
|
||||
echo "${{ secrets.NIX_SIGNING_KEY }}" > /tmp/signing-key
|
||||
chmod 600 /tmp/signing-key
|
||||
|
||||
# Sign the closure
|
||||
nix store sign --key-file /tmp/signing-key -r "${{ steps.build.outputs.out_path }}"
|
||||
|
||||
# Setup SSH key for cache push
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.CACHE_SSH_KEY }}" > ~/.ssh/cache_key
|
||||
chmod 600 ~/.ssh/cache_key
|
||||
ssh-keyscan -H ${{ secrets.CACHE_HOST }} >> ~/.ssh/known_hosts 2>/dev/null || true
|
||||
|
||||
- name: Setup signing key
|
||||
run: |
|
||||
echo "${{ secrets.NIX_SIGNING_KEY }}" > /tmp/signing-key
|
||||
chmod 600 /tmp/signing-key
|
||||
|
||||
- name: Build, sign, and cache all packages
|
||||
run: |
|
||||
PACKAGES=(
|
||||
custom-claude-code
|
||||
custom-app-launcher-server
|
||||
custom-mcrcon-rbw
|
||||
custom-tea-rbw
|
||||
custom-rclone-torbox-setup
|
||||
custom-beads
|
||||
custom-gastown
|
||||
qt-pinned-jellyfin-media-player
|
||||
)
|
||||
|
||||
FAILED=()
|
||||
for pkg in "${PACKAGES[@]}"; do
|
||||
echo "::group::Building $pkg"
|
||||
if OUT_PATH=$(nix build ".#$pkg" --no-link --print-out-paths 2>&1); then
|
||||
echo "Built: $OUT_PATH"
|
||||
|
||||
# Sign the closure
|
||||
nix store sign --key-file /tmp/signing-key -r "$OUT_PATH"
|
||||
|
||||
# Push to cache
|
||||
nix copy --to "ssh-ng://${{ secrets.CACHE_USER }}@${{ secrets.CACHE_HOST }}?ssh-key=$HOME/.ssh/cache_key" "${{ steps.build.outputs.out_path }}"
|
||||
nix copy --to "ssh-ng://${{ secrets.CACHE_USER }}@${{ secrets.CACHE_HOST }}?ssh-key=$HOME/.ssh/cache_key" "$OUT_PATH"
|
||||
|
||||
# Create GC root to prevent garbage collection
|
||||
OUT_HASH=$(basename "${{ steps.build.outputs.out_path }}" | cut -d'-' -f1)
|
||||
OUT_HASH=$(basename "$OUT_PATH" | cut -d'-' -f1)
|
||||
ssh -i ~/.ssh/cache_key ${{ secrets.CACHE_USER }}@${{ secrets.CACHE_HOST }} \
|
||||
"mkdir -p /nix/var/nix/gcroots/ci-cache && ln -sfn ${{ steps.build.outputs.out_path }} /nix/var/nix/gcroots/ci-cache/${OUT_HASH}"
|
||||
"mkdir -p /nix/var/nix/gcroots/ci-cache && ln -sfn $OUT_PATH /nix/var/nix/gcroots/ci-cache/${OUT_HASH}"
|
||||
|
||||
echo "✓ $pkg cached successfully"
|
||||
else
|
||||
echo "✗ $pkg failed to build"
|
||||
FAILED+=("$pkg")
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
done
|
||||
|
||||
if [ ${#FAILED[@]} -gt 0 ]; then
|
||||
echo "::error::Failed packages: ${FAILED[*]}"
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
NIX_CONFIG: "access-tokens = git.johnogle.info=${{ secrets.GITEA_ACCESS_TOKEN }}"
|
||||
|
||||
14
flake.nix
14
flake.nix
@@ -234,7 +234,7 @@
|
||||
];
|
||||
};
|
||||
|
||||
# Packages for CI caching (custom packages and qt-pinned)
|
||||
# Packages for CI caching (custom packages, flake inputs, and qt-pinned)
|
||||
packages = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
@@ -246,6 +246,9 @@
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
# Version strings for flake input packages
|
||||
beadsRev = builtins.substring 0 8 (inputs.beads.rev or "unknown");
|
||||
gastownRev = builtins.substring 0 8 (inputs.gastown.rev or "unknown");
|
||||
in {
|
||||
"custom-claude-code" = pkgs.custom.claude-code;
|
||||
"custom-app-launcher-server" = pkgs.custom.app-launcher-server;
|
||||
@@ -253,6 +256,15 @@
|
||||
"custom-tea-rbw" = pkgs.custom.tea-rbw;
|
||||
"custom-rclone-torbox-setup" = pkgs.custom.rclone-torbox-setup;
|
||||
"qt-pinned-jellyfin-media-player" = pkgsQt.jellyfin-media-player;
|
||||
# Flake input packages (beads, gastown) - these get version from input rev
|
||||
"custom-beads" = pkgs.callPackage ./packages/beads {
|
||||
src = inputs.beads;
|
||||
version = "0.49.6-${beadsRev}";
|
||||
};
|
||||
"custom-gastown" = pkgs.callPackage ./packages/gastown {
|
||||
src = inputs.gastown;
|
||||
version = "unstable-${gastownRev}";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
41
packages/beads/default.nix
Normal file
41
packages/beads/default.nix
Normal file
@@ -0,0 +1,41 @@
|
||||
# Beads package - issue tracker for AI-supervised coding workflows
|
||||
# Takes src as argument so it can be called from both overlay and flake packages
|
||||
{ lib
|
||||
, stdenv
|
||||
, buildGoModule
|
||||
, fetchurl
|
||||
, go_1_25
|
||||
, git
|
||||
, pkg-config
|
||||
, icu
|
||||
, src
|
||||
, version ? "unknown"
|
||||
}:
|
||||
|
||||
let
|
||||
# nixpkgs ships Go 1.25.5, but beads' dolt deps require Go >= 1.25.6
|
||||
go_1_25_6 = go_1_25.overrideAttrs (old: rec {
|
||||
version = "1.25.6";
|
||||
src = fetchurl {
|
||||
url = "https://go.dev/dl/go${version}.src.tar.gz";
|
||||
hash = "sha256-WMv3ceRNdt5vVtGeM7d9dFoeSJNAkih15GWFuXXCsFk=";
|
||||
};
|
||||
});
|
||||
buildGoModule_1_25_6 = buildGoModule.override { go = go_1_25_6; };
|
||||
in
|
||||
buildGoModule_1_25_6 {
|
||||
pname = "beads";
|
||||
inherit version src;
|
||||
subPackages = [ "cmd/bd" ];
|
||||
doCheck = false;
|
||||
# Regenerated vendorHash for commit 6a51223b (dolt server mode, Go 1.25.6)
|
||||
vendorHash = "sha256-9RMy0+ZBFg1BAl8Z0EuZK4XVm9QYVekS9i/1ErOIB/c=";
|
||||
nativeBuildInputs = [ git pkg-config ];
|
||||
buildInputs = [ icu ];
|
||||
meta = with lib; {
|
||||
description = "beads (bd) - An issue tracker designed for AI-supervised coding workflows";
|
||||
homepage = "https://github.com/steveyegge/beads";
|
||||
license = licenses.mit;
|
||||
mainProgram = "bd";
|
||||
};
|
||||
}
|
||||
38
packages/gastown/default.nix
Normal file
38
packages/gastown/default.nix
Normal file
@@ -0,0 +1,38 @@
|
||||
# Gastown package - multi-agent workspace manager
|
||||
# Takes src as argument so it can be called from both overlay and flake packages
|
||||
{ lib
|
||||
, buildGoModule
|
||||
, src
|
||||
, version ? "unknown"
|
||||
}:
|
||||
|
||||
buildGoModule {
|
||||
pname = "gastown";
|
||||
inherit version src;
|
||||
vendorHash = "sha256-ripY9vrYgVW8bngAyMLh0LkU/Xx1UUaLgmAA7/EmWQU=";
|
||||
subPackages = [ "cmd/gt" ];
|
||||
doCheck = false;
|
||||
|
||||
# Must match ldflags from gastown Makefile - BuiltProperly=1 is required
|
||||
# or gt will error with "This binary was built with 'go build' directly"
|
||||
ldflags = [
|
||||
"-X github.com/steveyegge/gastown/internal/cmd.Version=${version}"
|
||||
"-X github.com/steveyegge/gastown/internal/cmd.Commit=${version}"
|
||||
"-X github.com/steveyegge/gastown/internal/cmd.BuildTime=nix-build"
|
||||
"-X github.com/steveyegge/gastown/internal/cmd.BuiltProperly=1"
|
||||
];
|
||||
|
||||
# Bug fixes not yet merged upstream
|
||||
patches = [
|
||||
./gastown-fix-validate-recipient.patch
|
||||
./gastown-fix-agent-bead-address-title.patch
|
||||
./gastown-statusline-optimization.patch
|
||||
];
|
||||
|
||||
meta = with lib; {
|
||||
description = "Gas Town - multi-agent workspace manager by Steve Yegge";
|
||||
homepage = "https://github.com/steveyegge/gastown";
|
||||
license = licenses.mit;
|
||||
mainProgram = "gt";
|
||||
};
|
||||
}
|
||||
15
packages/gastown/gastown-fix-agent-bead-address-title.patch
Normal file
15
packages/gastown/gastown-fix-agent-bead-address-title.patch
Normal file
@@ -0,0 +1,15 @@
|
||||
diff --git a/internal/mail/router.go b/internal/mail/router.go
|
||||
--- a/internal/mail/router.go
|
||||
+++ b/internal/mail/router.go
|
||||
@@ -315,7 +315,10 @@ func agentBeadToAddress(bead *agentBead) string {
|
||||
}
|
||||
|
||||
// For other hq- agents, fall back to description parsing
|
||||
- return parseAgentAddressFromDescription(bead.Description)
|
||||
+ if bead.Title != "" && strings.Contains(bead.Title, "/") {
|
||||
+ return bead.Title
|
||||
+ }
|
||||
+ return parseAgentAddressFromDescription(bead.Description)
|
||||
}
|
||||
|
||||
// Handle gt- prefixed IDs (legacy format)
|
||||
13
packages/gastown/gastown-fix-validate-recipient.patch
Normal file
13
packages/gastown/gastown-fix-validate-recipient.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git a/internal/mail/router.go b/internal/mail/router.go
|
||||
index b864c069..4b6a045b 100644
|
||||
--- a/internal/mail/router.go
|
||||
+++ b/internal/mail/router.go
|
||||
@@ -646,7 +646,7 @@ func (r *Router) validateRecipient(identity string) error {
|
||||
}
|
||||
|
||||
for _, agent := range agents {
|
||||
- if agentBeadToAddress(agent) == identity {
|
||||
+ if AddressToIdentity(agentBeadToAddress(agent)) == AddressToIdentity(identity) {
|
||||
return nil // Found matching agent
|
||||
}
|
||||
}
|
||||
135
packages/gastown/gastown-statusline-optimization.patch
Normal file
135
packages/gastown/gastown-statusline-optimization.patch
Normal file
@@ -0,0 +1,135 @@
|
||||
diff --git a/internal/cmd/statusline.go b/internal/cmd/statusline.go
|
||||
index 2edf1be8..00253eea 100644
|
||||
--- a/internal/cmd/statusline.go
|
||||
+++ b/internal/cmd/statusline.go
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
+ "time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
@@ -14,6 +15,37 @@ import (
|
||||
"github.com/steveyegge/gastown/internal/tmux"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
+// statusLineCacheTTL is how long cached status output remains valid.
|
||||
+const statusLineCacheTTL = 10 * time.Second
|
||||
+
|
||||
+// statusLineCachePath returns the cache file path for a session.
|
||||
+func statusLineCachePath(session string) string {
|
||||
+ return filepath.Join(os.TempDir(), fmt.Sprintf("gt-status-%s", session))
|
||||
+}
|
||||
+
|
||||
+// getStatusLineCache returns cached status if fresh, empty string otherwise.
|
||||
+func getStatusLineCache(session string) string {
|
||||
+ path := statusLineCachePath(session)
|
||||
+ info, err := os.Stat(path)
|
||||
+ if err != nil {
|
||||
+ return ""
|
||||
+ }
|
||||
+ if time.Since(info.ModTime()) > statusLineCacheTTL {
|
||||
+ return ""
|
||||
+ }
|
||||
+ data, err := os.ReadFile(path)
|
||||
+ if err != nil {
|
||||
+ return ""
|
||||
+ }
|
||||
+ return string(data)
|
||||
+}
|
||||
+
|
||||
+// setStatusLineCache writes status to cache file.
|
||||
+func setStatusLineCache(session, status string) {
|
||||
+ path := statusLineCachePath(session)
|
||||
+ _ = os.WriteFile(path, []byte(status), 0644)
|
||||
+}
|
||||
+
|
||||
|
||||
var (
|
||||
statusLineSession string
|
||||
@@ -34,6 +66,19 @@ func init() {
|
||||
func runStatusLine(cmd *cobra.Command, args []string) error {
|
||||
t := tmux.NewTmux()
|
||||
|
||||
+ // Optimization: skip expensive beads queries for detached sessions
|
||||
+ if statusLineSession != "" {
|
||||
+ if !t.IsSessionAttached(statusLineSession) {
|
||||
+ fmt.Print("○ |")
|
||||
+ return nil
|
||||
+ }
|
||||
+ // Check cache for attached sessions too
|
||||
+ if cached := getStatusLineCache(statusLineSession); cached != "" {
|
||||
+ fmt.Print(cached)
|
||||
+ return nil
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
// Get session environment
|
||||
var rigName, polecat, crew, issue, role string
|
||||
|
||||
@@ -150,7 +195,11 @@ func runWorkerStatusLine(t *tmux.Tmux, session, rigName, polecat, crew, issue st
|
||||
|
||||
// Output
|
||||
if len(parts) > 0 {
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -389,7 +438,11 @@ func runMayorStatusLine(t *tmux.Tmux) error {
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -458,7 +511,11 @@ func runDeaconStatusLine(t *tmux.Tmux) error {
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -526,7 +583,11 @@ func runWitnessStatusLine(t *tmux.Tmux, rigName string) error {
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -617,7 +678,11 @@ func runRefineryStatusLine(t *tmux.Tmux, rigName string) error {
|
||||
}
|
||||
}
|
||||
|
||||
- fmt.Print(strings.Join(parts, " | ") + " |")
|
||||
+ output := strings.Join(parts, " | ") + " |"
|
||||
+ if statusLineSession != "" {
|
||||
+ setStatusLineCache(statusLineSession, output)
|
||||
+ }
|
||||
+ fmt.Print(output)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user