Set up Forgejo CI/CD policy
ober
7d5ee40dc091408cc01c89a5347cf343a29489be
deleted file mode 100644 --- a/.builds/ci.yml +++ /dev/null @@ -1,48 +0,0 @@ -image: debian/stable -arch: amd64 -packages: -- bash -- build-essential -- ca-certificates -- curl -- file -- git -- libncurses-dev -- make -- pkg-config -- tar -- libfuse-dev -- libssl-dev -- zlib1g-dev -- liblz4-dev -sources: -- https://git.sr.ht/~lisp/jerboa -- https://git.sr.ht/~lisp/jerboa-drive -artifacts: -- jerboa-drive/jerboa-drive-0.1.0.jpkg -tasks: -- build-jerboa: | - umask 022 - cd jerboa - sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile - export CARGO_BUILD_JOBS=1 - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --profile minimal --default-toolchain 1.94.1 - source "$HOME/.cargo/env" - make jerboa CHEZ_CONFIGURE_EXTRA=--disable-x11 JERBOA_NATIVE_FEATURES=tls JERBOA_CC_OPT=-O0 - echo 'export PATH="$HOME/jerboa/dist:$PATH"' >> ~/.buildenv -- package: | - umask 022 - cd jerboa-drive - J="$HOME/jerboa/dist/jerboa" - mkdir -p "$HOME/.cache" && chmod -R go-w "$HOME/.cache" - "$J" pkg verify - "$J" pkg policy - "$J" pkg build - "$J" pkg pack --output "$PWD/jerboa-drive-0.1.0.jpkg" - "$J" pkg verify "$PWD/jerboa-drive-0.1.0.jpkg" - "$J" pkg verify --reproduce -- verify: | - umask 022 - cd jerboa-drive - PATH="$HOME/jerboa/dist:$PATH" JERBUILD="$HOME/jerboa/dist/jerbuild" make verify new file mode 100755 --- /dev/null +++ b/.forgejo/ci-required.sh @@ -0,0 +1,68 @@ +#!/bin/sh +set -eu + +has_target() { + target=$1 + [ -f Makefile ] && grep -Eq "^${target}[[:space:]]*:" Makefile +} + +if has_target verify; then + make verify +else + ran=0 + for target in security test check build; do + if has_target "$target"; then + make "$target" + ran=1 + fi + done + [ "$ran" = 1 ] || { + echo "ERROR: no verify, test, check, or build target is available" >&2 + exit 1 + } +fi + +if ! has_target binary; then + echo "No standalone binary target; full repository verification passed." + exit 0 +fi + +make binary + +if has_target binary-smoke; then + make binary-smoke + exit 0 +fi +if has_target smoke; then + make 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 {} \; | + 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 + 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 + continue + fi + if timeout 30 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 + exit 1 +done < "$binary_list" + +echo "Full verification, binary build, and runtime smoke checks passed." new file mode 100755 --- /dev/null +++ b/.forgejo/require-version-bump.sh @@ -0,0 +1,50 @@ +#!/bin/sh +set -eu + +version_file=${VERSION_FILE:-VERSION} +test -f "$version_file" || { + echo "ERROR: $version_file is required" >&2 + exit 1 +} + +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 + 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" new file mode 100644 --- /dev/null +++ b/.forgejo/workflows/ci.yaml @@ -0,0 +1,68 @@ +name: required-ci + +on: + pull_request: + branches: [main] + push: + branches: [main] + tags: ['v*'] + workflow_dispatch: + +jobs: + required: + runs-on: docker + container: + image: debian:stable + steps: + - name: Install system dependencies + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + bash build-essential ca-certificates curl file git libfuse-dev \ + libgl1-mesa-dev liblz4-dev libncurses-dev libqt5gui5 libssl-dev \ + libx11-dev make pkg-config tar zlib1g-dev + - name: Check out jerboa-drive + uses: https://code.forgejo.org/actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + persist-credentials: false + - name: Install Rust + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | + sh -s -- -y --profile minimal --default-toolchain 1.94.1 + echo "$HOME/.cargo/bin" >> "$FORGEJO_PATH" + - name: Fetch and build Jerboa + run: | + mkdir ../jerboa + git -C ../jerboa init + git -C ../jerboa remote add origin https://git.jerboa.sh/ober/jerboa.git + git -C ../jerboa fetch --depth 1 origin 6a5230800b3599147f04bd5b1493bc996047506a + git -C ../jerboa checkout --detach FETCH_HEAD + test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 6eb4710558222c4e2ef9b076491f7db5af5e3748 + . "$HOME/.cargo/env" + CARGO_BUILD_JOBS=1 make -C ../jerboa jerboa \ + CHEZ_CONFIGURE_EXTRA=--disable-x11 JERBOA_NATIVE_FEATURES=tls \ + JERBOA_CC_OPT=-O0 + - name: Build, test, and smoke-check + run: | + . "$HOME/.cargo/env" + PATH="$PWD/../jerboa/dist:$PATH" \ + JERBUILD="$PWD/../jerboa/dist/jerbuild" \ + sh .forgejo/ci-required.sh + - name: Verify and pack package + run: | + version=$(cat VERSION) + J="$PWD/../jerboa/dist/jerboa" + mkdir -p "$HOME/.cache" + chmod -R go-w "$HOME/.cache" + "$J" pkg verify + "$J" pkg policy + "$J" pkg build + "$J" pkg pack --output "$PWD/jerboa-drive-$version.jpkg" + "$J" pkg verify "$PWD/jerboa-drive-$version.jpkg" + "$J" pkg verify --reproduce + - name: Upload package + uses: https://code.forgejo.org/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3 + with: + name: jerboa-drive-package + path: jerboa-drive-*.jpkg + if-no-files-found: error new file mode 100644 --- /dev/null +++ b/.forgejo/workflows/version-policy.yaml @@ -0,0 +1,23 @@ +name: version-policy + +on: + pull_request: + branches: [main] + +jobs: + required: + runs-on: docker + container: + image: debian:stable + steps: + - name: Install Git + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends git ca-certificates + - 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 new file mode 100644 --- /dev/null +++ b/.gitsafeignore @@ -0,0 +1,5 @@ +.forgejo/workflows/ci.yaml:high-entropy-hex:25 +.forgejo/workflows/ci.yaml:high-entropy-hex:38 +.forgejo/workflows/ci.yaml:high-entropy-hex:40 +.forgejo/workflows/ci.yaml:high-entropy-hex:64 +.forgejo/workflows/version-policy.yaml:high-entropy-hex:18 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,21 @@ +## STOP: Forgejo Pull Requests Are Mandatory + +Every change to this repository must use the Forgejo pull-request workflow. + +1. Start from the current remote default branch and create a dedicated feature, fix, or chore branch **before editing**. +2. Make only the scoped changes on that branch. +3. Run every repository-required test and build. If the repository produces binaries, build them and run a meaningful smoke check (such as the documented startup, `--help`, or `--version`). Do not commit while any required check fails. +4. Commit the verified changes on the feature branch and push that branch to `origin`. +5. Open a pull request on `git.jerboa.sh` targeting the default branch. A human must review, approve, and merge it. + +Absolute bans: never commit or push directly to `main` or `master`; never self-approve or self-merge; never bypass branch protection; and never leave completed changes only in a local branch. Release work and urgent fixes follow the same branch-and-PR process. + +### Every PR Must Advance the Version + +`VERSION` is the authoritative repository version. Every pull request must change it to a strictly greater semantic version (`MAJOR.MINOR.PATCH`). Use a patch increment for fixes and maintenance, a minor increment for backward-compatible features, and a major increment for breaking changes. Keep package manifests, generated version constants, release artifact names, and user-visible version output synchronized with `VERSION`. + +Forgejo CI compares the proposed `VERSION` with the target branch and rejects an unchanged, malformed, or lower version. + ## STOP: Editing `.ss`/`.sls` Files — Mandatory Rules These rules exist because local-model sessions have lost **hours** fighting @@ -371,18 +389,17 @@ improved versions of the above. When working in a Jerboa project, **ONLY modify files in the current repo** unless the user explicitly names another path. Common sibling repos that exist but must NOT be touched without explicit instruction: -- `~/mine/jerboa-mcp` — Legacy node MCP, superseded. The active MCP server now lives in THIS repo at `mcp/` + `data/`. Don't modify the legacy repo unless told. -- `~/mine/jerboa-shell` — Only modify when user explicitly says to work there. -- `~/mine/gerbil-mcp` — **NEVER touch**. Deprecated. -- `~/mine/gerbil-orig` — Read-only reference for upstream Gerbil. Never modify. +- the legacy Jerboa MCP sibling checkout — Legacy node MCP, superseded. The active MCP server now lives in THIS repo at `mcp/` + `data/`. Don't modify the legacy repo unless told. +- the Jerboa shell sibling checkout — Only modify when user explicitly says to work there. +- the deprecated Gerbil MCP sibling checkout — **NEVER touch**. Deprecated. +- the upstream Gerbil reference checkout — Read-only reference for upstream Gerbil. Never modify. If a user instruction mentions a file path, use EXACTLY that path. Do not substitute a similar-looking path from another repo. ### Never Reference Sibling Checkouts in Build Files Build files (Makefile, shell scripts, CI config) must **never** resolve a -dependency via a relative sibling path (`../jerboa-foo`) or an absolute -`~/mine/jerboa-foo` path. That layout is specific to this one machine — +dependency via a relative sibling path (`..`-relative) or an absolute machine-specific sibling path. That layout is specific to this one machine — other users and CI do not have it. Always vendor instead: fetch/clone the dependency into `vendor/` (or this repo's equivalent) at build time, or use a pinned-release fetch script, so the build is reproducible without @@ -392,7 +409,7 @@ A sibling-path fallback is not just a portability bug: it can silently substitute a full alternate source tree (build config, embedded data, secrets) for the vendored one, with no equivalent safety default, changing what actually gets built without any indication. If you find one -(`grep -rn '\.\./jerboa\|~/mine/jerboa'` over Makefiles/scripts), remove it +(for example, any sibling-checkout reference in Makefiles or scripts), remove it and vendor properly instead. --- --- a/Makefile +++ b/Makefile @@ -238,7 +238,7 @@ security: @bash scripts/package-security-test.sh @test -f dependency-lock.tsv || { echo "missing dependency-lock.tsv" >&2; exit 1; } @test -f support/fetch-locked-deps.sh || { echo "missing locked dependency fetcher" >&2; exit 1; } - @! grep -R -E 'uses:[[:space:]]*[^[:space:]]+@(v[0-9]+|main|master|stable)([[:space:]]|$$)' .github/workflows + @! grep -R -E 'uses:[[:space:]]*[^[:space:]]+@(v[0-9]+|main|master|stable)([[:space:]]|$$)' .forgejo/workflows @! grep -R -F 'git clone --depth 1' .github support scripts @awk -F '\t' 'NF != 4 || $$1 !~ /^[a-z0-9-]+$$/ || $$3 !~ /^[0-9a-f]{40}$$/ || $$4 !~ /^[0-9a-f]{40}$$/ { exit 1 } END { if (NR != 9) exit 1 }' dependency-lock.tsv @grep -q 'sanitize-evidence' docs/release-evidence.md || { echo "missing release evidence sanitizer docs" >&2; exit 1; } new file mode 100644 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.1.1 --- a/dependency-lock.tsv +++ b/dependency-lock.tsv @@ -1,9 +1,9 @@ -jerboa-aws https://git.sr.ht/~lisp/jerboa-aws c1e6296c2ca17ec1e3f100876d433b12f613e22a 5c17cc5fc069c06b7a3f40510c3ae05e2b9911f7 -jerboa-proton-bridge https://git.sr.ht/~lisp/jerboa-proton-bridge 43035ebf6b7e679d160699469f7e9c66f922478c dc1b1d495eaae53475376004862dd28fd38dc2b3 -jerboa-yubikey https://git.sr.ht/~lisp/jerboa-yubikey 0091e16bfe2f5d96e7fb18ce016e5b1ce010edae a1fb427573692992cf68fea8eb0aea984cce3bce -jerboa-pgp https://git.sr.ht/~lisp/jerboa-pgp 38c681f41abde4d8ae05fa922c4383035c5e90e6 60300c5757b4bdfe15fb85e24baa9b2053b01a1b -jerboa-crypto https://git.sr.ht/~lisp/jerboa-crypto 74675676105775de23c55df65da27d8d2ef4a1d7 a49d8a0ce63731081f7fb140e13eff8d3efb850e -jerboa-fuse https://git.sr.ht/~lisp/jerboa-fuse 020ab5f8872261142b4f4c8d9379945db42f59fc 7184ad9a92719af4a08850a5f7a9ce4263ababdb -jerboa-mail https://git.sr.ht/~lisp/jerboa-mail 5d6382371decb5ce93016b46a1710d65255401b0 48b15dbdb9e18749b6195f1cbbd7d2804c54b6a6 -jerboa-https https://git.sr.ht/~lisp/jerboa-https c3a4be506edcdddeed37c4b7723bdd226a8511c1 f979837172aff40da3108c365afe45616579176d -jerboa-ssl https://git.sr.ht/~lisp/jerboa-ssl 270b858534941baad6420e4906a26c279f4a8678 4d629174bbff9b1cd2ff1aa696be0cd33bf462e4 +jerboa-aws https://git.jerboa.sh/ober/jerboa-aws c1e6296c2ca17ec1e3f100876d433b12f613e22a 5c17cc5fc069c06b7a3f40510c3ae05e2b9911f7 +jerboa-proton-bridge https://git.jerboa.sh/ober/jerboa-proton-bridge 43035ebf6b7e679d160699469f7e9c66f922478c dc1b1d495eaae53475376004862dd28fd38dc2b3 +jerboa-yubikey https://git.jerboa.sh/ober/jerboa-yubikey 0091e16bfe2f5d96e7fb18ce016e5b1ce010edae a1fb427573692992cf68fea8eb0aea984cce3bce +jerboa-pgp https://git.jerboa.sh/ober/jerboa-pgp 38c681f41abde4d8ae05fa922c4383035c5e90e6 60300c5757b4bdfe15fb85e24baa9b2053b01a1b +jerboa-crypto https://git.jerboa.sh/ober/jerboa-crypto 74675676105775de23c55df65da27d8d2ef4a1d7 a49d8a0ce63731081f7fb140e13eff8d3efb850e +jerboa-fuse https://git.jerboa.sh/ober/jerboa-fuse 020ab5f8872261142b4f4c8d9379945db42f59fc 7184ad9a92719af4a08850a5f7a9ce4263ababdb +jerboa-mail https://git.jerboa.sh/ober/jerboa-mail 5d6382371decb5ce93016b46a1710d65255401b0 48b15dbdb9e18749b6195f1cbbd7d2804c54b6a6 +jerboa-https https://git.jerboa.sh/ober/jerboa-https c3a4be506edcdddeed37c4b7723bdd226a8511c1 f979837172aff40da3108c365afe45616579176d +jerboa-ssl https://git.jerboa.sh/ober/jerboa-ssl 270b858534941baad6420e4906a26c279f4a8678 4d629174bbff9b1cd2ff1aa696be0cd33bf462e4 --- a/docs/jerboa-drive-plan.md +++ b/docs/jerboa-drive-plan.md @@ -7,8 +7,8 @@ reviewing results. ## Current Starting State - Current local repo path: `/Users/user/mine/jerboa-drive`. -- Current sourcehut remote: `git@git.sr.ht:~lisp/jerboa-drive`. -- Sourcehut repo: `~lisp/jerboa-drive`, unlisted. +- Current Forgejo remote: `ssh://git@git.jerboa.sh:2222/ober/jerboa-drive`. +- Forgejo repo: `ober/jerboa-drive`. - Installed binary name remains `jdrive`. - Current repo has dirty work from Proton Drive work. Do not discard it. - `~/mine/jerboa-aws` already has basic S3 bucket/object APIs: @@ -30,13 +30,12 @@ Status: complete as of the repo rename. Keep this section as audit/history. - inspect `git status --short` - commit only if verification requirements can be met; otherwise leave dirty work intact and document it in the handoff notes. -3. Rename the sourcehut repo with `hut`: - - `hut git update jerboa-protonstorage --name jerboa-drive --visibility unlisted` - - verify with `hut git show jerboa-drive` +3. Rename the repository in Forgejo and verify the canonical + `ober/jerboa-drive` project is reachable. 4. Rename the local directory: - from parent dir: `mv ~/mine/jerboa-protonstorage ~/mine/jerboa-drive` 5. Fix the git remote: - - `git remote set-url origin git@git.sr.ht:~lisp/jerboa-drive` + - `git remote set-url origin ssh://git@git.jerboa.sh:2222/ober/jerboa-drive` - verify with `git remote -v` 6. Rename project metadata and docs: - `jerboa-protonstorage` -> `jerboa-drive` @@ -46,7 +45,7 @@ Status: complete as of the repo rename. Keep this section as audit/history. Acceptance gate: - `pwd` is `/Users/user/mine/jerboa-drive`. - `git remote -v` points at `~lisp/jerboa-drive`. -- `hut git show jerboa-drive` succeeds and shows unlisted visibility. +- `git ls-remote https://git.jerboa.sh/ober/jerboa-drive.git` succeeds. - `jdrive --help` still works after reinstall. ## Phase 1: Define Product Boundary --- a/jpkg.sexp +++ b/jpkg.sexp @@ -1,6 +1,6 @@ (package (name "@ober/jerboa-drive") - (version "0.1.0") + (version "0.1.1") (description "Cloud drive abstraction for Jerboa") (license "MIT") (source "https://git.jerboa.sh/ober/jerboa-drive") --- a/support/ensure-jerboa.sh +++ b/support/ensure-jerboa.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Bootstrap a project-local Jerboa toolchain from SourceHut release artifacts. +# Bootstrap a project-local Jerboa toolchain from Forgejo release artifacts. # # Usage: # support/ensure-jerboa.sh v0.2.3 .jerboa/bin @@ -14,8 +14,8 @@ usage() { version=$1 bindir=${2:-.jerboa/bin} -repo=${JERBOA_RELEASE_REPO:-~lisp/jerboa} -origin=${JERBOA_RELEASE_ORIGIN:-https://git.sr.ht} +repo=${JERBOA_RELEASE_REPO:-ober/jerboa} +origin=${JERBOA_RELEASE_ORIGIN:-https://git.jerboa.sh} if [ "${JERBOA_RELEASE_TARGET:-}" ]; then target=$JERBOA_RELEASE_TARGET @@ -41,7 +41,7 @@ case "$target" in esac file="jerboa-${version}-${target}.tar.gz" -base=${JERBOA_RELEASE_BASE:-${origin}/${repo}/refs/download/${version}} +base=${JERBOA_RELEASE_BASE:-${origin}/${repo}/releases/download/${version}} url="${base%/}/${file}" sum_url="${url}.sha256" --- a/support/fetch-locked-deps.sh +++ b/support/fetch-locked-deps.sh @@ -28,7 +28,7 @@ while IFS="$tab" read -r name repository commit tree extra; do *[!a-z0-9-]*|'') echo "ERROR: unsafe dependency name: $name" >&2; exit 1 ;; esac case "$repository" in - https://git.sr.ht/~lisp/jerboa-*) ;; + https://git.jerboa.sh/ober/jerboa-*) ;; *) echo "ERROR: unapproved dependency origin for $name" >&2; exit 1 ;; esac printf '%s\n' "$commit" | grep -Eq '^[0-9a-f]{40}$' || {