Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ jobs:
run: scripts/test-mobile-worktree-overrides.sh
- name: File size ratchet unit tests
run: node --test scripts/check-file-sizes-core.test.mjs
- name: Disk space preflight unit tests
run: scripts/test-check-disk-space.sh

rust-lint:
name: Rust Lint
Expand Down
20 changes: 20 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,26 @@ Adminer on `:8082`, Keycloak on `:8180` for local OAuth/OIDC testing, MinIO on
`:9000` for media storage, and Prometheus on `:9090` for metrics) and runs all
pending database migrations.

#### Disk space for local checks

Rust test, clippy, and Tauri builds can add roughly 15 GiB to a cold Cargo
target. Before build-heavy pre-push jobs start, Buzz reserves that headroom and
requires at least 10 GiB to remain afterward. With the defaults, the guard
therefore blocks below 25 GiB free. Documentation-only pushes skip the check.
If the preflight blocks, free space or run `just clean` before retrying.

Developers with a warm shared target can tune the estimate for one push without
skipping the remaining hooks:

```bash
BUZZ_DISK_BUILD_RESERVE_GIB=8 git push # default: 15
BUZZ_DISK_MIN_FREE_GIB=5 git push # default: 10
BUZZ_SKIP_DISK_PREFLIGHT=1 git push # bypass this guard only
```

Use the escape hatch only after checking available disk space yourself. CI is
unaffected by this local pre-push guard.

### Running the Relay and Desktop App

```bash
Expand Down
13 changes: 8 additions & 5 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,30 @@ commit-msg:
run: 'git interpret-trailers --if-exists doNothing --trailer "Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed ''s/ [0-9]* [+-][0-9]*$//'')" --in-place {1}'

pre-push:
# Guard each build-heavy job before it reaches Cargo, Node, or Flutter. Keep
# branch-skew unguarded and let the existing globs skip documentation-only
# pushes. A cold local gate can otherwise consume the disk safety margin.
parallel: true
commands:
branch-skew:
run: ./scripts/check-branch-skew.sh
rust-tests:
glob: ["crates/**", "migrations/**", "schema/**", "Cargo.toml", "Cargo.lock", "rust-toolchain.toml", "deny.toml", "scripts/run-tests.sh", "justfile"]
run: just test-unit
run: ./scripts/check-disk-space.sh && just test-unit
desktop-check:
glob: ["desktop/**", "pnpm-lock.yaml"]
exclude: ["desktop/src-tauri/**"]
run: just desktop-check
run: ./scripts/check-disk-space.sh && just desktop-check
desktop-test:
glob: ["desktop/**", "pnpm-lock.yaml"]
exclude: ["desktop/src-tauri/**"]
run: just desktop-test
run: ./scripts/check-disk-space.sh && just desktop-test
desktop-tauri-checks:
# Keep local lint parity with Desktop Core CI for every path that can
# affect the Tauri crate or its path dependencies. Run clippy and tests
# serially so parallel pre-push hooks do not contend for Cargo's lock.
glob: ["desktop/src-tauri/**", "crates/**", "migrations/**", "schema/**", "Cargo.toml", "Cargo.lock", "rust-toolchain.toml", "deny.toml", "scripts/run-tests.sh", "justfile"]
run: just desktop-tauri-clippy && just desktop-tauri-test
run: ./scripts/check-disk-space.sh && just desktop-tauri-clippy && just desktop-tauri-test
mobile-test:
glob: ["mobile/**"]
run: just mobile-test
run: ./scripts/check-disk-space.sh && just mobile-test
50 changes: 50 additions & 0 deletions scripts/check-disk-space.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
set -euo pipefail

repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)

case "${BUZZ_SKIP_DISK_PREFLIGHT:-}" in
1|true|TRUE|yes|YES)
echo "Disk preflight skipped (BUZZ_SKIP_DISK_PREFLIGHT=1)."
exit 0
;;
esac

reserve_gib=${BUZZ_DISK_BUILD_RESERVE_GIB:-15}
min_free_gib=${BUZZ_DISK_MIN_FREE_GIB:-10}

if [[ ! "$reserve_gib" =~ ^[0-9]+$ ]]; then
echo "BUZZ_DISK_BUILD_RESERVE_GIB must be a non-negative integer." >&2
exit 2
fi
if [[ ! "$min_free_gib" =~ ^[0-9]+$ ]]; then
echo "BUZZ_DISK_MIN_FREE_GIB must be a non-negative integer." >&2
exit 2
fi

if ! available_kib=$(df -Pk "$repo_root" 2>/dev/null | awk 'NR == 2 { print $4 }'); then
echo "Disk preflight warning: could not determine free space; continuing." >&2
exit 0
fi
if [[ ! "${available_kib:-}" =~ ^[0-9]+$ ]]; then
echo "Disk preflight warning: could not determine free space; continuing." >&2
exit 0
fi

reserve_kib=$((reserve_gib * 1024 * 1024))
min_free_kib=$((min_free_gib * 1024 * 1024))
post_build_kib=$((available_kib - reserve_kib))

available_gib=$(awk -v kib="$available_kib" 'BEGIN { printf "%.1f", kib / 1048576 }')
post_build_gib=$(awk -v kib="$post_build_kib" 'BEGIN { printf "%.1f", kib / 1048576 }')

if ((post_build_kib < min_free_kib)); then
cat >&2 <<EOF
Disk preflight blocked: ${available_gib} GiB is free now, but reserving ${reserve_gib} GiB for Buzz's local checks would leave ${post_build_gib} GiB.
The configured minimum is ${min_free_gib} GiB. Free disk space before pushing, or adjust BUZZ_DISK_BUILD_RESERVE_GIB / BUZZ_DISK_MIN_FREE_GIB if this checkout is already warm.
To bypass only this guard while keeping the other hooks, set BUZZ_SKIP_DISK_PREFLIGHT=1.
EOF
exit 1
fi

echo "Disk preflight passed: ${available_gib} GiB free; estimated post-check free space ${post_build_gib} GiB (minimum ${min_free_gib} GiB)."
121 changes: 121 additions & 0 deletions scripts/test-check-disk-space.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env bash
set -euo pipefail

repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
check_disk="${repo_root}/scripts/check-disk-space.sh"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

mkdir -p "$tmp/bin"
cat >"$tmp/bin/df" <<'MOCK'
#!/usr/bin/env bash
printf '%s\n' \
'Filesystem 1024-blocks Used Available Capacity Mounted on' \
"/dev/mock ${MOCK_DISK_TOTAL_KIB} 1 ${MOCK_DISK_AVAILABLE_KIB} 1% /"
MOCK
chmod +x "$tmp/bin/df"

run_check() {
PATH="$tmp/bin:$PATH" \
BUZZ_DISK_BUILD_RESERVE_GIB=15 \
BUZZ_DISK_MIN_FREE_GIB=10 \
"$check_disk" "$@"
}

# A 15 GiB build reserve must still leave the configured 10 GiB minimum.
MOCK_DISK_TOTAL_KIB=$((100 * 1024 * 1024)) \
MOCK_DISK_AVAILABLE_KIB=$((25 * 1024 * 1024)) \
run_check >/dev/null

if MOCK_DISK_TOTAL_KIB=$((100 * 1024 * 1024)) \
MOCK_DISK_AVAILABLE_KIB=$((24 * 1024 * 1024)) \
run_check >"$tmp/low.out" 2>&1; then
echo "disk preflight accepted insufficient post-build free space" >&2
exit 1
fi
grep -Fq 'Disk preflight blocked' "$tmp/low.out"
grep -Fq 'minimum is 10 GiB' "$tmp/low.out"
grep -Fq 'BUZZ_SKIP_DISK_PREFLIGHT=1' "$tmp/low.out"

if PATH="$tmp/bin:$PATH" \
MOCK_DISK_TOTAL_KIB=$((100 * 1024 * 1024)) \
MOCK_DISK_AVAILABLE_KIB=$((40 * 1024 * 1024)) \
BUZZ_DISK_BUILD_RESERVE_GIB=15 \
BUZZ_DISK_MIN_FREE_GIB=invalid \
"$check_disk" >"$tmp/invalid.out" 2>&1; then
echo "disk preflight accepted an invalid absolute minimum" >&2
exit 1
fi
grep -Fq 'BUZZ_DISK_MIN_FREE_GIB must be a non-negative integer' \
"$tmp/invalid.out"

# The escape hatch bypasses only this guard, leaving the other hooks intact.
MOCK_DISK_TOTAL_KIB=$((100 * 1024 * 1024)) \
MOCK_DISK_AVAILABLE_KIB=1 \
BUZZ_SKIP_DISK_PREFLIGHT=1 \
run_check >/dev/null

# Verify the behavior through a real Git pre-push, not only config inspection.
fixture="$tmp/hook-repo"
remote="$tmp/remote.git"
git init --bare -q "$remote"
git init -q -b main "$fixture"
git -C "$fixture" config user.name test
git -C "$fixture" config user.email test@example.com
git -C "$fixture" remote add origin "$remote"
mkdir -p "$fixture/scripts"
cp "$check_disk" "$fixture/scripts/check-disk-space.sh"
cp "$repo_root/scripts/check-branch-skew.sh" "$fixture/scripts/check-branch-skew.sh"
cp "$repo_root/lefthook.yml" "$fixture/lefthook.yml"
git -C "$fixture" add .
git -C "$fixture" -c core.hooksPath=/dev/null commit -qm baseline
git -C "$fixture" -c core.hooksPath=/dev/null push -q -u origin main
(
cd "$fixture"
lefthook install --force >/dev/null
)

# Documentation-only pushes skip all guarded jobs.
printf '%s\n' '# docs' >"$fixture/README.md"
git -C "$fixture" add .
git -C "$fixture" -c core.hooksPath=/dev/null commit -qm docs-change
PATH="$tmp/bin:$PATH" \
MOCK_DISK_TOTAL_KIB=$((100 * 1024 * 1024)) \
MOCK_DISK_AVAILABLE_KIB=1 \
git -C "$fixture" push -q

mkdir -p "$fixture/crates/example/src"
printf '%s\n' 'pub fn example() {}' >"$fixture/crates/example/src/lib.rs"
git -C "$fixture" add .
git -C "$fixture" -c core.hooksPath=/dev/null commit -qm heavy-change
if PATH="$tmp/bin:$PATH" \
MOCK_DISK_TOTAL_KIB=$((100 * 1024 * 1024)) \
MOCK_DISK_AVAILABLE_KIB=$((24 * 1024 * 1024)) \
git -C "$fixture" push >"$tmp/hook.out" 2>&1; then
echo "real pre-push hook bypassed the disk preflight" >&2
exit 1
fi
if ! grep -Fq 'Disk preflight blocked' "$tmp/hook.out"; then
echo "real pre-push failed for an unexpected reason:" >&2
cat "$tmp/hook.out" >&2
exit 1
fi

# Unsupported df output fails open so the hook remains portable.
cat >"$tmp/bin/df" <<'MOCK'
#!/usr/bin/env bash
echo unsupported
MOCK
chmod +x "$tmp/bin/df"
run_check >"$tmp/unsupported.out" 2>&1
grep -Fq 'could not determine free space' "$tmp/unsupported.out"

[[ "$(grep -Fc 'run: ./scripts/check-disk-space.sh &&' "$repo_root/lefthook.yml")" -eq 5 ]]
if grep -Fq 'setup:' "$repo_root/lefthook.yml"; then
echo "disk preflight must not use Lefthook setup on pinned version 2.1.3" >&2
exit 1
fi
grep -Fq 'scripts/test-check-disk-space.sh' \
"$repo_root/.github/workflows/ci.yml"

echo "disk-space preflight tests passed"