name: CI on: push: branches: [main] pull_request: branches: [main] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: https://git.johnogle.info/johno/gitea-actions/nix-setup@v1 - name: Check flake run: nix flake check env: NIX_CONFIG: "access-tokens = git.johnogle.info=${{ secrets.GITEA_ACCESS_TOKEN }}" build-and-cache: runs-on: ubuntu-latest needs: check if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v6 - uses: https://git.johnogle.info/johno/gitea-actions/nix-setup@v1 - name: Setup SSH for cache run: | 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-opencode custom-qmd openclaw-runtime-closure custom-nextcloud-talk-desktop qt-pinned-jellyfin-media-player qt-pinned-stremio nix-deck-kernel ) FAILED=() SKIPPED=() for pkg in "${PACKAGES[@]}"; do echo "::group::Building $pkg" # Check if package is already cached by evaluating its store path and checking the remote OUT_PATH=$(nix eval ".#$pkg.outPath" --raw 2>/dev/null) if [ -n "$OUT_PATH" ] && ssh -i ~/.ssh/cache_key ${{ secrets.CACHE_USER }}@${{ secrets.CACHE_HOST }} \ "nix path-info '$OUT_PATH' >/dev/null 2>&1"; then echo "⏭ $pkg already cached ($OUT_PATH), skipping" SKIPPED+=("$pkg") echo "::endgroup::" continue fi # --cores 2 limits parallel jobs to reduce RAM pressure on john-endesktop if BUILD_OUTPUT=$(nix build ".#$pkg" --no-link --print-out-paths --cores 2 2>&1); then OUT_PATH=$(echo "$BUILD_OUTPUT" | grep '^/nix/store/' | tail -1) echo "$BUILD_OUTPUT" echo "Store path: $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" "$OUT_PATH" # Create GC root to prevent garbage collection 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 $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 [ ${#SKIPPED[@]} -gt 0 ]; then echo "Skipped (already cached): ${SKIPPED[*]}" fi 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 }}" build-and-push-openclaw: name: Build & Push OpenClaw Image runs-on: ubuntu-latest needs: check if: github.event_name == 'push' && github.ref == 'refs/heads/main' outputs: image_tag: ${{ steps.meta.outputs.tag }} steps: - uses: actions/checkout@v6 - uses: https://git.johnogle.info/johno/gitea-actions/nix-setup@v1 - name: Setup SSH for cache run: | 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: Generate image tag id: meta run: | # Read the image tag from the nix definition's tag attribute # buildLayeredImage sets tag from openclawImageTag in default.nix IMAGE_TAG=$(nix eval .#packages.x86_64-linux.openclaw-image.imageTag --raw 2>/dev/null || \ nix eval .#openclaw-image.imageTag --raw 2>/dev/null || \ nix eval .#openclaw-image.outPath --raw 2>/dev/null | xargs basename | sed 's/.*-//') # Fallback to short SHA if tag extraction fails if [ -z "$IMAGE_TAG" ] || [ "$IMAGE_TAG" = "tar.gz" ]; then IMAGE_TAG=$(echo "${{ github.sha }}" | cut -c1-7) fi echo "tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT echo "Image will be tagged: ${IMAGE_TAG}" - name: Build Docker image with Nix run: nix build .#openclaw-image --cores 2 env: NIX_CONFIG: "access-tokens = git.johnogle.info=${{ secrets.GITEA_ACCESS_TOKEN }}" - name: Load and tag image run: | docker load < result docker tag openclaw:${{ steps.meta.outputs.tag }} registry.johnogle.info/openclaw:${{ steps.meta.outputs.tag }} docker tag openclaw:${{ steps.meta.outputs.tag }} registry.johnogle.info/openclaw:latest - name: Login to registry run: | echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.johnogle.info -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin - name: Push image run: | # Push versioned tag with retry (large images can timeout on slow connections) for i in 1 2 3; do if docker push registry.johnogle.info/openclaw:${{ steps.meta.outputs.tag }}; then break fi echo "Push attempt $i failed, retrying in 10s..." sleep 10 done # Push latest tag — mostly a manifest push since layers already exist docker push registry.johnogle.info/openclaw:latest || \ echo "::warning::Failed to push :latest tag (versioned tag already pushed)"