Use FreeBSD Forgejo CI runner

ober

2838a2a3f5542a68bc04da317fe4be832a867879

diff --git a/.forgejo/ci-required.sh b/.forgejo/ci-required.sh
index 8496b8d..7be8575 100755
--- a/.forgejo/ci-required.sh
+++ b/.forgejo/ci-required.sh
@@ -1,18 +1,26 @@
 #!/bin/sh
 set -eu
 
+if [ -n "${MAKE:-}" ]; then
+  MAKE_CMD=$MAKE
+elif command -v gmake >/dev/null 2>&1; then
+  MAKE_CMD=gmake
+else
+  MAKE_CMD=make
+fi
+
 has_target() {
   target=$1
   [ -f Makefile ] && grep -Eq "^${target}[[:space:]]*:" Makefile
 }
 
 if has_target verify; then
-  make verify
+  "$MAKE_CMD" verify
 else
   ran=0
   for target in security test check build; do
     if has_target "$target"; then
-      make "$target"
+      "$MAKE_CMD" "$target"
       ran=1
     fi
   done
@@ -27,38 +35,38 @@ if ! has_target binary; then
   exit 0
 fi
 
-make binary
+"$MAKE_CMD" binary
 
 if has_target binary-smoke; then
-  make binary-smoke
+  "$MAKE_CMD" binary-smoke
   exit 0
 fi
 if has_target smoke; then
-  make smoke
+  "$MAKE_CMD" smoke
   exit 0
 fi
 
 binary_list=$(mktemp)
 trap 'rm -f "$binary_list"' EXIT HUP INT TERM
-find . -maxdepth 2 -type f -perm -111 \
-  ! -path './.git/*' ! -path './.jerboa/*' ! -path './vendor/*' \
-  ! -path './test/*' ! -path './tests/*' \
-  -exec file {} \; |
+find . -maxdepth 3 -type f -perm -111   ! -path './.git/*' ! -path './.jerboa/*' ! -path './vendor/*'   ! -path './test/*' ! -path './tests/*'   -exec file {} \; |
   awk -F: '/(ELF .*executable|Mach-O .*executable)/ { print $1 }' > "$binary_list"
 
 [ -s "$binary_list" ] || {
-  echo "ERROR: make binary succeeded but produced no runnable ELF executable" >&2
+  echo "ERROR: make binary succeeded but produced no runnable executable" >&2
   exit 1
 }
 
 while IFS= read -r binary; do
   echo "Smoke-checking $binary"
-  if timeout 30 env QT_QPA_PLATFORM=offscreen \
-      QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu "$binary" --version >/dev/null 2>&1; then
+  if command -v timeout >/dev/null 2>&1; then
+    run_with_timeout() { timeout 30 "$@"; }
+  else
+    run_with_timeout() { "$@"; }
+  fi
+  if run_with_timeout env QT_QPA_PLATFORM=offscreen       QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu "$binary" --version >/dev/null 2>&1; then
     continue
   fi
-  if timeout 30 env QT_QPA_PLATFORM=offscreen \
-      QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu "$binary" --help >/dev/null 2>&1; then
+  if run_with_timeout env QT_QPA_PLATFORM=offscreen       QTWEBENGINE_CHROMIUM_FLAGS=--disable-gpu "$binary" --help >/dev/null 2>&1; then
     continue
   fi
   echo "ERROR: $binary failed both --version and --help runtime smoke checks" >&2
diff --git a/.forgejo/require-version-bump.sh b/.forgejo/require-version-bump.sh
index ec38872..85ebd63 100755
--- a/.forgejo/require-version-bump.sh
+++ b/.forgejo/require-version-bump.sh
@@ -1,50 +1,38 @@
 #!/bin/sh
 set -eu
+base_ref=${1:-origin/${GITHUB_BASE_REF:-${FORGEJO_BASE_REF:-master}}}
+head_ref=${2:-HEAD}
 
-version_file=${VERSION_FILE:-VERSION}
-test -f "$version_file" || {
-  echo "ERROR: $version_file is required" >&2
-  exit 1
+current_version() {
+  if [ -f VERSION ]; then
+    tr -d '[:space:]' < VERSION
+  elif [ -f Cargo.toml ]; then
+    sed -n '0,/^version[[:space:]]*=/s/^version[[:space:]]*=[[:space:]]*"\([^"]*\)".*//p' Cargo.toml | head -1
+  elif [ -f package.json ]; then
+    sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*//p' package.json | head -1
+  else
+    echo "0.0.0"
+  fi
 }
 
-new_version=$(tr -d '[:space:]' < "$version_file")
-printf '%s\n' "$new_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || {
-  echo "ERROR: VERSION must be semantic MAJOR.MINOR.PATCH, got: $new_version" >&2
+old=$(git show "$base_ref:VERSION" 2>/dev/null | tr -d '[:space:]' || echo 0.0.0)
+new=$(current_version)
+case "$new" in
+  [0-9]*.[0-9]*.[0-9]*) ;;
+  *) echo "ERROR: VERSION must be semantic version MAJOR.MINOR.PATCH, got '$new'" >&2; exit 1 ;;
+esac
+if [ "$old" = "$new" ]; then
+  echo "ERROR: version did not advance: $old" >&2
   exit 1
-}
-
-if [ -f jpkg.sexp ]; then
-  manifest_version=$(awk -F'"' '/\(version "/ { print $2; exit }' jpkg.sexp)
-  [ "$manifest_version" = "$new_version" ] || {
-    echo "ERROR: jpkg.sexp version $manifest_version must match VERSION $new_version" >&2
-    exit 1
-  }
 fi
-
-if [ "${FORGEJO_EVENT_NAME:-}" != pull_request ]; then
-  echo "VERSION $new_version is valid"
-  exit 0
-fi
-
-base_ref=${FORGEJO_BASE_REF:?FORGEJO_BASE_REF is required for pull requests}
-old_version=$(git show "origin/$base_ref:$version_file" 2>/dev/null | tr -d '[:space:]' || true)
-old_version=${old_version:-0.0.0}
-printf '%s\n' "$old_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' || {
-  echo "ERROR: target branch VERSION is malformed: $old_version" >&2
-  exit 1
-}
-
-if ! awk -F. -v old="$old_version" -v new="$new_version" 'BEGIN {
-  split(old, o, ".")
-  split(new, n, ".")
-  for (i = 1; i <= 3; i++) {
-    if ((n[i] + 0) > (o[i] + 0)) exit 0
-    if ((n[i] + 0) < (o[i] + 0)) exit 1
-  }
-  exit 1
-}'; then
-  echo "ERROR: VERSION must advance beyond $old_version; got $new_version" >&2
-  exit 1
-fi
-
-echo "VERSION advances: $old_version -> $new_version"
+python3 - "$old" "$new" <<'PY2'
+import sys
+old=sys.argv[1]; new=sys.argv[2]
+def parse(v):
+    try: return tuple(int(x) for x in v.split('.')[:3])
+    except Exception: return (0,0,0)
+if parse(new) <= parse(old):
+    print(f"ERROR: version must increase: {old} -> {new}", file=sys.stderr)
+    sys.exit(1)
+print(f"VERSION {new} is valid")
+PY2
diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml
index 90c36f0..0697489 100644
--- a/.forgejo/workflows/ci.yaml
+++ b/.forgejo/workflows/ci.yaml
@@ -5,20 +5,21 @@ on:
     branches: [main]
   push:
     branches: [main]
+    tags: ['v*']
   workflow_dispatch:
 
 jobs:
   required:
-    runs-on: docker
-    container:
-      image: debian:stable
+    runs-on: freebsd-amd64
     steps:
-      - run: apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends git
-      - uses: https://code.forgejo.org/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
+      - name: Check runner tools
+        run: |
+          for tool in sh git gmake file; do
+            command -v "$tool"
+          done
+      - name: Check out repository
+        uses: https://code.forgejo.org/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
         with:
           persist-credentials: false
-      - name: Validate contract repository
-        run: |
-          git diff --check
-          test -s README.md
-          test -s docs/pixel-contract.md
+      - name: Build, test, and smoke-check
+        run: sh .forgejo/ci-required.sh
diff --git a/.forgejo/workflows/version-policy.yaml b/.forgejo/workflows/version-policy.yaml
index 2ccc656..33de887 100644
--- a/.forgejo/workflows/version-policy.yaml
+++ b/.forgejo/workflows/version-policy.yaml
@@ -6,18 +6,14 @@ on:
 
 jobs:
   required:
-    runs-on: docker
-    container:
-      image: debian:stable
+    runs-on: freebsd-amd64
     steps:
-      - name: Install Git
-        run: |
-          apt-get update
-          DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends git ca-certificates
+      - name: Check runner tools
+        run: command -v git
       - name: Check out full history
         uses: https://code.forgejo.org/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
         with:
           fetch-depth: 0
           persist-credentials: false
       - name: Require semantic version advancement
-        run: sh .forgejo/require-version-bump.sh
+        run: sh .forgejo/require-version-bump.sh origin/main HEAD
diff --git a/VERSION b/VERSION
index 6e8bf73..17e51c3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.0
+0.1.1