Skip to content

remove cache step from coverage in hopes that might fix the failure #455

remove cache step from coverage in hopes that might fix the failure

remove cache step from coverage in hopes that might fix the failure #455

Workflow file for this run

name: Build, Test and Release
permissions:
contents: read
on:
push:
branches:
- '**'
tags: ['*']
jobs:
build-host_agents:
name: Build host_agent for ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
runner: macos-latest
- target: x86_64-apple-darwin
runner: macos-latest
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
- target: x86_64-unknown-linux-musl
runner: ubuntu-latest
- target: aarch64-unknown-linux-musl
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- uses: ./.github/actions/rust-build
with:
target: ${{ matrix.target }}
bin-name: shuthost_host_agent
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: host_agent-${{ matrix.target }}
path: target/${{ matrix.target }}/release/shuthost_host_agent
build-coordinators:
name: Build Controller for ${{ matrix.target }}
needs: build-host_agents
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
runner: macos-latest
- target: x86_64-apple-darwin
runner: macos-latest
- target: x86_64-unknown-linux-gnu
runner: ubuntu-latest
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
- target: x86_64-unknown-linux-musl
runner: ubuntu-latest
- target: aarch64-unknown-linux-musl
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Download all host_agent binaries
uses: actions/download-artifact@v4
with:
path: ./rehydrate
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Rehydrate target directory structure
run: |
mkdir -p target
for dir in ./rehydrate/host_agent-*; do
target=$(basename "$dir" | sed 's/^host_agent-//')
mkdir -p "target/$target/release"
cp "$dir/shuthost_host_agent" "target/$target/release/"
chmod +x "target/$target/release/shuthost_host_agent"
done
- uses: ./.github/actions/rust-build
with:
target: ${{ matrix.target }}
bin-name: shuthost_coordinator
additional-flags: --features include_linux_agents,include_macos_agents
- name: Upload coordinator binary
uses: actions/upload-artifact@v4
with:
name: coordinator-${{ matrix.target }}
path: target/${{ matrix.target }}/release/shuthost_coordinator
test:
name: Run Tests
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run tests
run: cargo test --workspace
test-service-installation:
name: Test Service Installation & Startup
runs-on: ${{ matrix.runner }}
needs: [build-host_agents, build-coordinators]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
runner: ubuntu-latest
init: systemd
docker_image: docker.io/heywoodlh/systemd:latest
binary_target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
runner: ubuntu-latest
init: openrc
docker_image: docker.io/heywoodlh/openrc:latest
binary_target: x86_64-unknown-linux-musl
- os: macos-latest
runner: macos-latest
init: launchd
binary_target: aarch64-apple-darwin
- os: ubuntu-latest
runner: ubuntu-latest
init: serviceless
binary_target: x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
- name: Download coordinator binary
uses: actions/download-artifact@v4
with:
name: coordinator-${{ matrix.binary_target }}
- run: chmod +x shuthost_coordinator
- name: Test on Linux (systemd/openrc)
if: ${{ matrix.init == 'systemd' || matrix.init == 'openrc' }}
run: |
set -x
if [ "${{ matrix.init }}" = "systemd" ]; then
docker run --privileged --rm -itd --name testinit ${{ matrix.docker_image }} journalctl -f
elif [ "${{ matrix.init }}" = "openrc" ]; then
docker run --privileged --memory=2g --rm -itd --name testinit ${{ matrix.docker_image }} sh -c "
set -x
rc-update add syslog default;
rc-service syslog start;
touch /var/log/messages;
tail -f /var/log/messages;
"
fi
# Install dependencies
if [ "${{ matrix.init }}" = "systemd" ]; then
docker exec testinit bash -c "apt-get update; apt-get install -y curl"
elif [ "${{ matrix.init }}" = "openrc" ]; then
docker exec testinit sh -c "apk update"
docker exec testinit sh -c "apk --no-cache add curl"
fi
# Copy controller binary
docker cp ./shuthost_coordinator testinit:/
# Install the coordinator as a service (it gets started automatically)
docker exec testinit sh -c "RUST_BACKTRACE=1 ./shuthost_coordinator install root --port 8080 --bind 127.0.0.1"
# Wait for coordinator to be ready (timeout 30s)
for i in $(seq 1 30); do
docker exec testinit curl -fsSL http://localhost:8080/login && break || sleep 1
done
docker exec testinit curl -fsSL http://localhost:8080/login || { echo "Coordinator service is not running"; exit 1; }
# Run agent installer (downloads from running coordinator)
docker exec testinit sh -c "
export RUST_BACKTRACE=1;
curl -fsSL http://localhost:8080/download/host_agent_installer.sh | sh -s http://localhost:8080
"
# Wait for agent to be ready (timeout 30s) - wait for process to appear
for i in $(seq 1 30); do
if docker exec testinit pgrep -af shuthost_host_agent >/dev/null 2>&1; then
break
fi
sleep 1
done
# Check on agent service according to init system
if [ "${{ matrix.init }}" = "systemd" ]; then
docker exec testinit systemctl status shuthost_host_agent
elif [ "${{ matrix.init }}" = "openrc" ]; then
docker exec testinit rc-service shuthost_host_agent status
fi
# Check logs or process running
docker exec testinit ps aux | grep shuthost_host_agent
# Clean up
docker stop testinit
- name: Test on macOS (launchd)
if: ${{ matrix.os == 'macos-latest' }}
run: |
set -x
sudo ./shuthost_coordinator install $(whoami) --port 8080 --bind 127.0.0.1
# Wait for coordinator to be ready (timeout 30s)
for i in $(seq 1 30); do
curl -fsSL http://localhost:8080/login && break || sleep 1
done
curl -fsSL http://localhost:8080/download/host_agent_installer.sh | sh -s http://localhost:8080
# Wait for agent to be installed/started (timeout 30s)
for i in $(seq 1 30); do
launchctl print system/com.github_9smtm6.shuthost_host_agent >/dev/null 2>&1 && break || sleep 1
done
launchctl print system/com.github_9smtm6.shuthost_host_agent
- name: Test on Linux (serviceless)
if: ${{ matrix.init == 'serviceless' }}
run: |
set -x
# Start the coordinator in the background
sudo ./shuthost_coordinator install $(whoami) --port 8080 --bind 127.0.0.1
# Wait for coordinator to be ready (timeout 30s)
for i in $(seq 1 30); do
curl -fsSL http://localhost:8080/login && break || sleep 1
done
curl -fsSL http://localhost:8080/login || { echo "Coordinator service is not running"; exit 1; }
# Download and install host_agent with serviceless init system
curl -fsSL http://localhost:8080/download/host_agent_installer.sh | sh -s http://localhost:8080 --init-system serviceless
# Wait for the agent to start (timeout 30s) - check for listening port or process
for i in $(seq 1 30); do
sudo ss -ltnp | grep ':5757' >/dev/null 2>&1 || sudo lsof -i :5757 >/dev/null 2>&1 || sudo netstat -tlnp | grep ':5757' >/dev/null 2>&1 || sudo pgrep -af '/tmp/selfbin' >/dev/null 2>&1 && break || true
sleep 1
done
# Check if the port is in use (default 5757)
sudo ss -ltnp | grep ':5757' || sudo lsof -i :5757 || sudo netstat -tlnp | grep ':5757'
# Check for a fitting process in the process list
sudo pgrep -af '/tmp/selfbin' || sudo ps aux | grep '/tmp/selfbin'
test-client-scripts:
name: Test Client Scripts (install and client) on Multiple Platforms
runs-on: ${{ matrix.runner }}
needs: [build-coordinators]
strategy:
fail-fast: false
matrix:
include:
- runner: macos-latest
platform: macos
shell: zsh -c
coordinator_bin: coordinator-aarch64-apple-darwin
- runner: ubuntu-latest
platform: ubuntu-docker
docker_image: ubuntu:latest
shell: docker exec run_container bash -c
coordinator_bin: coordinator-x86_64-unknown-linux-gnu
- runner: ubuntu-latest
platform: alpine-docker
docker_image: alpine:latest
shell: docker exec run_container sh -c
coordinator_bin: coordinator-x86_64-unknown-linux-gnu
steps:
- name: Download coordinator binary
uses: actions/download-artifact@v4
with:
name: ${{ matrix.coordinator_bin }}
- name: Start controller service (background)
run: |
chmod +x shuthost_coordinator
sudo ./shuthost_coordinator install $(whoami) --port 8080 --bind 127.0.0.1
# Wait for coordinator to be ready (timeout 30s)
for i in $(seq 1 30); do
curl -fsSL http://localhost:8080/login && break || sleep 1
done
curl -fsSL http://localhost:8080/login || { echo "Controller service is not running"; exit 1; }
- name: Prepare macOS
if: ${{ matrix.platform == 'macos' }}
run: |
brew install curl openssl || true
- name: Prepare ubuntu-docker
if: ${{ matrix.platform == 'ubuntu-docker' }}
run: |
docker run -itd --network=host --name=run_container ${{ matrix.docker_image }}
${{ matrix.shell }} "apt-get update; apt-get install -y curl openssl bsdmainutils"
- name: Prepare alpine-docker
if: ${{ matrix.platform == 'alpine-docker' }}
run: |
docker run -itd --network=host --name=run_container ${{ matrix.docker_image }}
${{ matrix.shell }} "apk update"
${{ matrix.shell }} "apk add --no-cache curl openssl busybox-extras"
- name: Install and run client
run: |
# Run installer and capture output
CLIENT_INSTALL_OUT=$(${{ matrix.shell }} "curl -fsSL http://localhost:8080/download/client_installer.sh | sh -s http://localhost:8080" 2>&1 || true)
echo "--- installer output start ---"
echo "$CLIENT_INSTALL_OUT"
CLIENT_PATH=$(echo "$CLIENT_INSTALL_OUT" | grep -Eo '/[^ ]*shuthost_client_[^ ]*' | head -n1)
echo "Detected client path: $CLIENT_PATH"
if [ -z "$CLIENT_PATH" ]; then
echo "Client path not found in installer output"; exit 1
fi
# Wait for the client binary to appear/be executable (timeout 20s)
for i in $(seq 1 20); do
# execute the test inside the same environment/shell used to run the installer
${{ matrix.shell }} "test -x $CLIENT_PATH" && break || sleep 1
done
# Run the client and capture output.
# We expect this to fail, since the client never was registered.
# We only want to test for the request sent off to the coordinator in this test
OUTPUT=$(${{ matrix.shell }} "$CLIENT_PATH take testhost http://localhost:8080 2>&1 || true")
echo "--- client output start ---"
echo "$OUTPUT"
echo "--- client output end ---"
# Check for the curl request: look for 'curl' plus both header names anywhere in the output
echo "$OUTPUT" | grep -E "curl" | grep -E "X-Client-ID" | grep -E "X-Request" || (echo 'Client request format not found!' && exit 1)
container-build:
name: Build & Push Alpine Containers (SHA only)
needs:
- build-coordinators
runs-on: ubuntu-latest
permissions:
packages: write
strategy:
fail-fast: false
matrix:
include:
- platform: amd64
rustc_target: x86_64-unknown-linux-musl
- platform: arm64
rustc_target: aarch64-unknown-linux-musl
steps:
- uses: actions/checkout@v4
- name: Download MUSL coordinator binary
uses: actions/download-artifact@v4
with:
name: coordinator-${{ matrix.rustc_target }}
path: ./rehydrate
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare coordinator binary
run: |
mkdir -p target/${{ matrix.rustc_target }}/release
cp ./rehydrate/shuthost_coordinator target/${{ matrix.rustc_target }}/release/shuthost_coordinator
chmod +x target/${{ matrix.rustc_target }}/release/shuthost_coordinator
- name: Get release texts
id: release_texts
uses: ./.github/actions/get-release-texts
- name: Build & Push Multi-Arch Alpine Image (SHA only)
uses: docker/build-push-action@v6
with:
context: .
file: ./Containerfile
platforms: linux/${{ matrix.platform }}
push: true
tags: |
ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:${{matrix.platform }}-${{ github.sha }}
build-args: |
RUSTC_TARGET=${{ matrix.rustc_target }}
container-merge:
name: Merge Multi-Arch Manifest
needs: container-build
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- uses: actions/checkout@v4
- name: Get release texts
id: release_texts
uses: ./.github/actions/get-release-texts
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create and Push Multi-Arch Manifest
run: |
docker buildx imagetools create \
--tag ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:${{ github.sha }} \
ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:amd64-${{ github.sha }} \
ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:arm64-${{ github.sha }}
container-test:
name: Test Container Image
needs: container-build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get release texts
id: release_texts
uses: ./.github/actions/get-release-texts
- name: Pull image
run: |
docker pull ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:amd64-${{ github.sha }}
- name: Test image runs
run: |
docker run --rm ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:amd64-${{ github.sha }} shuthost_coordinator --help
test-compatibility:
name: Test Compatibility on Older OS Releases
needs: build-coordinators
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- rustc_target: x86_64-apple-darwin
runner: macos-13
- rustc_target: x86_64-apple-darwin
runner: macos-14
- rustc_target: aarch64-apple-darwin
runner: macos-14
- rustc_target: x86_64-apple-darwin
runner: macos-15
- rustc_target: aarch64-apple-darwin
runner: macos-15
# this OS should get musl recommended instead
# - rustc_target: x86_64-unknown-linux-gnu
# runner: ubuntu-22.04
# - rustc_target: aarch64-unknown-linux-gnu
# runner: ubuntu-22.04-arm
- rustc_target: x86_64-unknown-linux-musl
runner: ubuntu-22.04
- rustc_target: aarch64-unknown-linux-musl
runner: ubuntu-22.04-arm
- rustc_target: x86_64-unknown-linux-gnu
runner: ubuntu-24.04
- rustc_target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
- rustc_target: x86_64-unknown-linux-musl
runner: ubuntu-24.04
- rustc_target: aarch64-unknown-linux-musl
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- name: Download coordinator binary
uses: actions/download-artifact@v4
with:
name: coordinator-${{ matrix.rustc_target }}
path: ./bin
- name: Download host_agent binary
uses: actions/download-artifact@v4
with:
name: host_agent-${{ matrix.rustc_target }}
path: ./bin
- name: Test Controller Binary
run: |
chmod +x ./bin/shuthost_coordinator
./bin/shuthost_coordinator --help
- name: Test Host Agent Binary
run: |
chmod +x ./bin/shuthost_host_agent
./bin/shuthost_host_agent --help
playwright-tests:
name: Run Playwright (Web GUI) tests
runs-on: macos-latest
steps:
- name: Checkout code (with LFS because pngs for visual regression are saved there)
uses: actions/checkout@v4
with:
lfs: true
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 'latest'
- name: Install test fonts (Inter & Cascadia Code)
run: |
set -eux
# Homebrew cask fonts; fail if they can't be installed.
brew install --cask font-inter font-cascadia-code
# Verify via fc-list; if fc-list isn't present this will fail which
# is desired so the CI run fails when fonts are not available.
fc-list | grep -i inter >/dev/null 2>&1 || { echo "Inter font not found on macOS after install" >&2; exit 1; }
fc-list | grep -i cascadia >/dev/null 2>&1 || { echo "Cascadia Code not found on macOS after install" >&2; exit 1; }
sudo fc-cache -f -v
- name: Install node deps, Playwright Chromium and system deps
run: |
cd frontend
npm ci
npm run install-chromium-ci
- uses: Swatinem/rust-cache@v2
- uses: ./.github/actions/rust-build
with:
bin-name: shuthost_coordinator
- name: Run Playwright tests
env:
CI: true
run: |
cd frontend
npx playwright test
- name: Upload Playwright report + test-results (zip via Actions)
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts-${{ github.run_id }}
path: |
frontend/playwright-report
frontend/test-results
compression-level: 9
container-release:
name: Tag & Push Container (branch/tag/latest)
needs:
- container-merge
- container-test
- test-service-installation
- test-client-scripts
- test
- test-compatibility
- playwright-tests
runs-on: ubuntu-latest
permissions:
packages: write
if: ${{ github.event_name == 'push' }}
steps:
- uses: actions/checkout@v4
- name: Get release texts
id: release_texts
uses: ./.github/actions/get-release-texts
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Retag multi-arch image with branch/tag name
run: |
docker buildx imagetools create \
--tag ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:${{ steps.release_texts.outputs.tag }} \
ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:${{ github.sha }}
- name: Retag multi-arch image as latest (if tag)
if: startsWith(github.ref, 'refs/tags/')
run: |
docker buildx imagetools create \
--tag ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:latest \
ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:${{ github.sha }}
release:
name: Upload Release Binaries
needs:
- test-service-installation
- test-client-scripts
- test
- test-compatibility
- playwright-tests
if: ${{ github.event_name == 'push' }}
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download all coordinator binaries
uses: actions/download-artifact@v4
with:
pattern: coordinator-*
path: ./release-assets
- name: Prepare release assets directory
run: |
# Move coordinator binaries to a common directory with target-based binary names
# this is required, as otherwise there'd be naming conflicts
mkdir -p release-assets/flat
for dir in release-assets/coordinator-*; do
target=$(basename "$dir" | sed 's/^coordinator-//')
if [ -f "$dir/shuthost_coordinator" ]; then
cp "$dir/shuthost_coordinator" "release-assets/flat/shuthost_coordinator-$target"
fi
done
- name: Get release texts
id: release_texts
uses: ./.github/actions/get-release-texts
- name: Upload binaries to release (create one if not existing)
uses: softprops/action-gh-release@v2
if: ${{ github.event_name == 'push' }}
with:
fail_on_unmatched_files: true
files: ./release-assets/flat/*
tag_name: ${{ steps.release_texts.outputs.tag }}
prerelease: ${{ !startsWith(github.ref, 'refs/tags/') }}
generate_release_notes: true
body: |
📦 **Container Image** available at:
```
ghcr.io/${{ steps.release_texts.outputs.repo_lowercase }}/shuthost-coordinator:${{ steps.release_texts.outputs.tag }}
```
Also available as `:latest` if this is a tagged release.