Add local act CI runner and document @latest-v2

Add an .actrc, runner Dockerfile, push event payload, and
scripts/dev/act_ci.sh to run ci.yml jobs locally with nektos/act.
The script refuses --bind/--reuse in .actrc, since a bound checkout
runs as root and rewrites ownership and modes in the working tree.

Document the @latest-v2 version label in the README.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
awalsh128 2026-10-23 09:16:44 -07:00
parent fd8f3d3fd1
commit 0bada9deed
5 changed files with 167 additions and 1 deletions

5
.actrc Normal file
View file

@ -0,0 +1,5 @@
--workflows .github/workflows/ci.yml
--eventpath .github/act/events/push.json
--container-architecture linux/amd64
--pull=false
-P ubuntu-latest=cache-apt-pkgs-action-ci:local

18
.github/act/Dockerfile vendored Normal file
View file

@ -0,0 +1,18 @@
FROM ghcr.io/catthehacker/ubuntu:act-latest
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Keep local runner dependencies explicit so act behavior stays close to ubuntu-latest.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
gnupg \
jq \
lsb-release \
sudo \
unzip \
wget \
xz-utils \
&& rm -rf /var/lib/apt/lists/*

14
.github/act/events/push.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
"ref": "refs/heads/main-2",
"repository": {
"name": "cache-apt-pkgs-action",
"full_name": "awalsh128/cache-apt-pkgs-action",
"owner": {
"name": "awalsh128",
"login": "awalsh128"
}
},
"head_commit": {
"id": "0000000000000000000000000000000000000000"
}
}

View file

@ -29,7 +29,8 @@ Create a workflow `.yml` file in your repositories `.github/workflows` directory
There are three kinds of version labels you can use.
- `@latest` - This will give you the latest release.
- `@latest` - This will give you the latest release of **v1, not v2**.
- `@latest-v2` - This will give you the latest release of **v2**.
- `@v#` - Major only will give you the latest release for that major version only (e.g. `v1`).
- Branch
- version 1.x

128
scripts/dev/act_ci.sh Executable file
View file

@ -0,0 +1,128 @@
#!/usr/bin/env bash
set -euo pipefail
# Must match the -P runner image in .actrc.
IMAGE_TAG="cache-apt-pkgs-action-ci:local"
usage() {
cat <<'EOF'
Usage:
scripts/dev/act_ci.sh build-image
scripts/dev/act_ci.sh quick
scripts/dev/act_ci.sh integration
scripts/dev/act_ci.sh job <job-name>
scripts/dev/act_ci.sh push
Commands:
build-image Build the local act runner image.
quick Run core CI quality jobs.
integration Run action integration/regression jobs.
job Run a specific ci.yml job by name.
push Run the full push workflow simulation.
EOF
}
require_tools() {
command -v docker >/dev/null 2>&1 || {
echo "docker is required" >&2
exit 1
}
command -v act >/dev/null 2>&1 || {
echo "nektos/act is required: https://github.com/nektos/act" >&2
exit 1
}
}
build_image() {
docker build -f .github/act/Dockerfile -t "${IMAGE_TAG}" .
}
# Workflow, event, platform, and runner image flags come from the repo-root .actrc.
act_run() {
act "$@"
}
run_quick() {
local jobs=(
setup-and-cache
linters
action-deps-pinned
testing
)
for job in "${jobs[@]}"; do
echo "Running job: ${job}"
act_run push --job "${job}"
done
}
run_integration() {
local jobs=(
list_versions
standard_workflow_install
standard_workflow_restore
standard_workflow_add_package
standard_workflow_restore_add_package
regression_84_literal_block_install
regression_84_literal_block_restore
regression_84_folded_block_install
regression_84_folded_block_restore
)
for job in "${jobs[@]}"; do
echo "Running job: ${job}"
act_run push --job "${job}"
done
}
main() {
require_tools
cd "$(git rev-parse --show-toplevel)"
# ci.yml checks out explicit refs; with --bind (or a --reuse'd bound container) that checkout
# runs as root against this working tree and cleans away all uncommitted and ignored files.
if grep -qE '^(--bind|--reuse)\b' .actrc; then
echo ".actrc must not set --bind or --reuse; checkout would wipe the working tree" >&2
exit 1
fi
if [[ $# -lt 1 ]]; then
usage
exit 1
fi
case "$1" in
build-image)
build_image
;;
quick)
build_image
run_quick
;;
integration)
build_image
run_integration
;;
job)
if [[ $# -ne 2 ]]; then
echo "job requires a job name" >&2
usage
exit 1
fi
build_image
act_run push --job "$2"
;;
push)
build_image
act_run push
;;
*)
echo "Unknown command: $1" >&2
usage
exit 1
;;
esac
}
main "$@"