Skip to content

feat: liveness probes, recovery strategies, and skip_if rename #492

feat: liveness probes, recovery strategies, and skip_if rename

feat: liveness probes, recovery strategies, and skip_if rename #492

Workflow file for this run

name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
commits:
name: Conventional Commits
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate PR commits follow conventional format
run: |
set -e
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
PATTERN='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+'
FAILED=0
while IFS= read -r sha; do
MSG=$(git log --format='%s' -1 "$sha")
if ! echo "$MSG" | grep -qE "$PATTERN"; then
echo "❌ $sha: $MSG"
FAILED=1
else
echo "✅ $sha: $MSG"
fi
done < <(git rev-list "$BASE".."$HEAD")
if [ "$FAILED" -eq 1 ]; then
echo ""
echo "Some commits do not follow conventional commit format."
echo "Expected: type(scope)?: description"
echo "Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
exit 1
fi
website:
name: Website Docker Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build website container
run: docker build -t veld-website website/
schema:
name: Validate JSON Schema
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Validate schema and configs
run: |
chmod +x tests/validate-schema.sh
./tests/validate-schema.sh --install
go-test:
name: Go Module Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Test caddy/inject module
working-directory: caddy/inject
run: go test -v ./...
check:
name: Check & Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: crates/veld-daemon/frontend/package-lock.json
- name: Install frontend dependencies
run: cd crates/veld-daemon/frontend && npm ci
- name: cargo fmt --check
run: cargo fmt --all -- --check
- name: cargo clippy
run: cargo clippy --workspace -- -D warnings
- name: cargo test
run: cargo test --workspace
frontend:
name: Frontend (TypeScript)
runs-on: ubuntu-latest
defaults:
run:
working-directory: crates/veld-daemon/frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: crates/veld-daemon/frontend/package-lock.json
- run: npm ci
- name: Type-check
run: npm run typecheck
- name: Test
run: npm test
integration:
name: Integration Tests (macOS)
runs-on: macos-latest
needs: check
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: crates/veld-daemon/frontend/package-lock.json
- name: Install frontend dependencies
run: cd crates/veld-daemon/frontend && npm ci
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build all binaries
run: cargo build
# Build Caddy with the veld_inject plugin from this branch.
# install_caddy() just checks the binary exists — no markers needed.
- name: Build Caddy with local veld_inject
run: |
export PATH="$(go env GOPATH)/bin:$PATH"
XCADDY_VERSION=$(cat .xcaddy-version)
go install "github.com/caddyserver/xcaddy/cmd/xcaddy@v${XCADDY_VERSION}"
mkdir -p "$HOME/.local/lib/veld"
xcaddy build \
--with github.com/prosperity-solutions/veld/caddy/inject=./caddy/inject \
--output "$HOME/.local/lib/veld/caddy"
chmod +x "$HOME/.local/lib/veld/caddy"
# Stash a copy so we can restore after cleanup steps.
cp "$HOME/.local/lib/veld/caddy" /tmp/caddy-preserve
- name: Verify CLI basics
run: |
./target/debug/veld --version
./target/debug/veld --help
- name: Test config parsing (testproject)
working-directory: testproject
run: |
../target/debug/veld nodes
../target/debug/veld nodes --json
../target/debug/veld graph frontend:local
- name: Verify setup subcommands exist
run: |
set -e
./target/debug/veld setup --help | grep -q "unprivileged"
./target/debug/veld setup --help | grep -q "privileged"
./target/debug/veld setup --help | grep -q "hammerspoon"
./target/debug/veld doctor --help | grep -q "json"
echo "Setup subcommands and doctor verified."
- name: Test Python servers directly
run: |
set -e
python3 testproject/backend/server.py 19555 &
PID_B=$!
python3 testproject/frontend/server.py 19556 &
PID_F=$!
sleep 2
curl -sf http://localhost:19555/health
curl -sf http://localhost:19555/
curl -sf http://localhost:19556/
kill $PID_B $PID_F
wait $PID_B $PID_F 2>/dev/null || true
- name: Run full integration tests
run: |
chmod +x tests/integration.sh
sudo ./tests/integration.sh \
--veld-bin "$(pwd)/target/debug/veld" \
--project-dir "$(pwd)/testproject"
- name: "Cleanup after integration tests"
run: |
sudo "$(pwd)/target/debug/veld" uninstall 2>/dev/null || true
curl -s -X POST http://localhost:2019/stop 2>/dev/null || true
sudo rm -rf "$HOME/.veld" "$HOME/.local/lib/veld" /usr/local/lib/veld 2>/dev/null || true
sudo chown -R "$(whoami)" "$HOME/.local" 2>/dev/null || true
sudo launchctl bootout system/dev.veld.helper 2>/dev/null || true
launchctl bootout "gui/$(id -u)/dev.veld.helper" 2>/dev/null || true
launchctl bootout "gui/$(id -u)/dev.veld.daemon" 2>/dev/null || true
sleep 2
# Restore the Caddy binary for subsequent tests.
mkdir -p "$HOME/.local/lib/veld"
cp /tmp/caddy-preserve "$HOME/.local/lib/veld/caddy"
chmod +x "$HOME/.local/lib/veld/caddy"
- name: "Test: unprivileged setup lifecycle"
run: |
set -e
VELD="$(pwd)/target/debug/veld"
echo "=== Unprivileged setup ==="
# Setup may fail on CA trust in CI (no keychain / Caddy timing) — allow partial success
$VELD setup unprivileged || echo "WARNING: setup returned non-zero (CA trust may have failed in CI)"
echo ""
echo "=== Run doctor (diagnostics) ==="
$VELD doctor || echo "WARNING: doctor reported issues (expected in CI — Caddy may not start)"
$VELD doctor --json > /tmp/doctor-unpriv.json 2>/dev/null || true
cat /tmp/doctor-unpriv.json
echo ""
echo "=== Verify setup.json ==="
cat ~/.veld/setup.json
grep -q '"unprivileged"' ~/.veld/setup.json
echo ""
echo "=== Verify helper is running on user socket ==="
test -S ~/.veld/helper.sock
echo "Helper socket OK"
echo ""
echo "=== Check if Caddy is running ==="
CADDY_OK=0
for i in 1 2 3 4 5; do
if curl -sf http://localhost:2019/id/veld-sentinel 2>/dev/null | grep -q "veld"; then
CADDY_OK=1
break
fi
sleep 2
done
if [ "$CADDY_OK" = "1" ]; then
echo "Caddy is running (sentinel verified)"
curl -sf http://localhost:2019/config/apps/http/servers/veld/listen | grep -q "18443"
echo "Caddy listening on port 18443"
echo ""
echo "=== Start a service in unprivileged mode ==="
cd testproject
$VELD start frontend:local --name unpriv-test
sleep 2
echo ""
echo "=== Verify URLs have :18443 ==="
$VELD urls --name unpriv-test --json | grep -q "18443"
echo ""
echo "=== Stop ==="
$VELD stop --name unpriv-test
cd ..
else
echo "WARNING: Caddy did not start in CI (timing issue), skipping HTTPS tests"
fi
echo ""
echo "=== Uninstall (unprivileged) ==="
$VELD uninstall || echo "WARNING: uninstall returned non-zero"
echo ""
echo "=== Verify cleanup ==="
sleep 2
if ! curl -sf http://localhost:2019/config/ >/dev/null 2>&1; then
echo "Caddy stopped OK"
else
echo "WARNING: Caddy still running (may need manual cleanup)"
fi
if [ ! -f ~/.veld/setup.json ]; then
echo "setup.json removed OK"
else
echo "WARNING: setup.json still exists"
fi
echo ""
echo "=== Unprivileged lifecycle PASSED ==="
- name: "Cleanup between lifecycle tests (unpriv → priv)"
if: always()
run: |
curl -s -X POST http://localhost:2019/stop 2>/dev/null || true
launchctl bootout "gui/$(id -u)/dev.veld.helper" 2>/dev/null || true
launchctl bootout "gui/$(id -u)/dev.veld.daemon" 2>/dev/null || true
rm -rf "$HOME/.veld" 2>/dev/null || true
rm -f "$HOME/.local/lib/veld/caddy-data" 2>/dev/null || true
# Privileged setup runs as root. sudo on macOS may resolve
# lib_dir() to /usr/local/lib/veld. Ensure Caddy is there too.
sudo mkdir -p /usr/local/lib/veld
sudo cp /tmp/caddy-preserve /usr/local/lib/veld/caddy
sudo chmod +x /usr/local/lib/veld/caddy
sleep 1
- name: "Test: privileged setup lifecycle"
run: |
set -e
VELD="$(pwd)/target/debug/veld"
echo "=== Privileged setup ==="
sudo $VELD setup privileged
echo ""
echo "=== Run doctor (diagnostics) ==="
$VELD doctor || echo "WARNING: doctor reported issues (expected in CI)"
$VELD doctor --json > /tmp/doctor-priv.json 2>/dev/null || true
cat /tmp/doctor-priv.json
echo ""
echo "=== Verify setup.json ==="
cat ~/.veld/setup.json
grep -q '"privileged"' ~/.veld/setup.json
echo ""
echo "=== Verify helper is running on system socket ==="
test -S /var/run/veld-helper.sock
echo "System socket OK"
echo ""
echo "=== Check Caddy ==="
CADDY_OK=0
for i in 1 2 3 4 5; do
if curl -sf http://localhost:2019/id/veld-sentinel 2>/dev/null | grep -q "veld"; then
CADDY_OK=1
break
fi
sleep 2
done
if [ "$CADDY_OK" = "1" ]; then
echo "Caddy running (sentinel verified)"
curl -sf http://localhost:2019/config/apps/http/servers/veld/listen | grep -q '":443"'
echo "Caddy on port 443"
echo ""
echo "=== Start a service in privileged mode ==="
cd testproject
if $VELD start frontend:local --name priv-test 2>&1; then
sleep 2
URLS=$($VELD urls --name priv-test --json 2>/dev/null || true)
if echo "$URLS" | grep -q "18443"; then
echo "FAIL: URLs contain :18443 in privileged mode"
exit 1
fi
echo "Clean URLs OK"
$VELD stop --name priv-test 2>/dev/null || true
else
echo "WARNING: veld start failed in CI (non-fatal)"
fi
cd ..
else
echo "WARNING: Caddy did not start, skipping service tests"
fi
echo ""
echo "=== Uninstall (privileged) ==="
sudo $VELD uninstall || echo "WARNING: uninstall returned non-zero"
echo ""
echo "=== Verify cleanup ==="
sleep 2
if ! curl -sf http://localhost:2019/config/ >/dev/null 2>&1; then
echo "Caddy stopped OK"
else
echo "WARNING: Caddy still running"
fi
echo "=== Privileged lifecycle PASSED ==="
- name: "Cleanup between lifecycle tests (priv → auto)"
if: always()
run: |
sudo curl -s -X POST http://localhost:2019/stop 2>/dev/null || true
sudo launchctl bootout system/dev.veld.helper 2>/dev/null || true
sudo rm -rf "$HOME/.veld" "$HOME/.local/lib/veld" /usr/local/lib/veld 2>/dev/null || true
sudo chown -R "$(whoami)" "$HOME/.local" 2>/dev/null || true
sleep 1
# Restore the Caddy binary for subsequent tests.
mkdir -p "$HOME/.local/lib/veld"
cp /tmp/caddy-preserve "$HOME/.local/lib/veld/caddy"
chmod +x "$HOME/.local/lib/veld/caddy"
- name: "Test: auto-bootstrap (no setup)"
run: |
set -e
VELD="$(pwd)/target/debug/veld"
echo "=== Auto-bootstrap: start without any setup ==="
cd testproject
# Auto-bootstrap may fail in CI due to Caddy startup issues — allow soft failure
if $VELD start frontend:local --name auto-test 2>&1; then
sleep 2
echo ""
echo "=== Verify it auto-bootstrapped ==="
cat ~/.veld/setup.json
grep -q '"auto"' ~/.veld/setup.json
echo ""
echo "=== Verify URLs have :18443 ==="
$VELD urls --name auto-test --json | grep -q "18443"
echo ""
echo "=== Stop ==="
$VELD stop --name auto-test
else
echo "WARNING: auto-bootstrap veld start failed in CI (Caddy may not start in this environment)"
# Still verify the setup.json was written
if [ -f ~/.veld/setup.json ]; then
grep -q '"auto"' ~/.veld/setup.json && echo "setup.json mode=auto OK"
fi
fi
cd ..
echo ""
echo "=== Cleanup auto-bootstrap state ==="
curl -s http://localhost:2019/stop >/dev/null 2>&1 || true
rm -rf "$HOME/.veld"
sleep 1
echo "=== Auto-bootstrap PASSED ==="
# ── Release build matrix — catch build failures before merge ──────
release-build:
name: Release Build (${{ matrix.suffix }})
runs-on: ${{ matrix.os }}
needs: check
strategy:
fail-fast: false
matrix:
include:
- target: aarch64-apple-darwin
os: macos-latest
suffix: macos-arm64
goos: darwin
goarch: arm64
- target: x86_64-apple-darwin
os: macos-latest
suffix: macos-amd64
goos: darwin
goarch: amd64
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
suffix: linux-amd64
goos: linux
goarch: amd64
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
suffix: linux-arm64
cross: true
goos: linux
goarch: arm64
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: crates/veld-daemon/frontend/package-lock.json
- name: Install frontend dependencies
run: cd crates/veld-daemon/frontend && npm ci
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Install cross-compilation tools
if: matrix.cross
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
echo '[target.aarch64-unknown-linux-gnu]' >> ~/.cargo/config.toml
echo 'linker = "aarch64-linux-gnu-gcc"' >> ~/.cargo/config.toml
- name: Build release binaries
run: cargo build --release --target ${{ matrix.target }}
- name: Build Caddy with veld_inject
run: |
export PATH="$(go env GOPATH)/bin:$PATH"
XCADDY_VERSION=$(cat .xcaddy-version)
go install "github.com/caddyserver/xcaddy/cmd/xcaddy@v${XCADDY_VERSION}"
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} xcaddy build \
--with github.com/prosperity-solutions/veld/caddy/inject=./caddy/inject \
--output ./caddy-build-test
rm -f ./caddy-build-test
injection-test:
name: Injection E2E (macOS)
runs-on: macos-latest
needs: check
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: crates/veld-daemon/frontend/package-lock.json
- name: Install frontend dependencies
run: cd crates/veld-daemon/frontend && npm ci
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build veld
run: cargo build
- name: Install Next.js deps
working-directory: testproject-nextjs
run: npm install
- name: Build Caddy with local veld_inject
run: |
export PATH="$(go env GOPATH)/bin:$PATH"
XCADDY_VERSION=$(cat .xcaddy-version)
go install "github.com/caddyserver/xcaddy/cmd/xcaddy@v${XCADDY_VERSION}"
mkdir -p "$HOME/.local/lib/veld"
xcaddy build \
--with github.com/prosperity-solutions/veld/caddy/inject=./caddy/inject \
--output "$HOME/.local/lib/veld/caddy"
chmod +x "$HOME/.local/lib/veld/caddy"
- name: Setup veld (unprivileged)
run: |
./target/debug/veld setup unprivileged || echo "WARNING: setup returned non-zero (CA trust may have failed)"
- name: Wait for Caddy
run: |
for i in $(seq 1 15); do
if curl -sf http://localhost:2019/id/veld-sentinel 2>/dev/null | grep -q "veld"; then
echo "Caddy is running"
break
fi
sleep 2
done
- name: Run injection E2E tests
run: |
chmod +x tests/test-injection.sh
./tests/test-injection.sh \
--veld-bin "$(pwd)/target/debug/veld" \
--project-dir "$(pwd)/testproject-nextjs"
- name: Cleanup
if: always()
run: |
./target/debug/veld uninstall 2>/dev/null || true
curl -s -X POST http://localhost:2019/stop 2>/dev/null || true
rm -rf "$HOME/.veld" "$HOME/.local/lib/veld" 2>/dev/null || true
launchctl bootout "gui/$(id -u)/dev.veld.helper" 2>/dev/null || true
launchctl bootout "gui/$(id -u)/dev.veld.daemon" 2>/dev/null || true