Mirrors every release artifact into Cloudflare R2 alongside the existing AWS S3
upload, so both buckets carry the same objects during a parallel period. S3 is
untouched and stays authoritative for now; once R2 is confirmed complete it can
be dropped by deleting the `blobs:` block and pointing this at R2 alone.
### Why `publishers:` and not a second `blobs:` entry
goreleaser's blob pipe authenticates from the global `AWS_*` environment and has
no per-entry credential fields, so two different credential sets (AWS S3 and
Cloudflare R2) cannot coexist in `blobs:`. Custom publishers do support
per-entry `env:`, which is the supported way to isolate the two.
### Why `curl --aws-sigv4` and not `aws`/`rclone`
The CI image `docker.gitea.com/runner-images:ubuntu-latest` ships none of `aws`,
`rclone`, `s3cmd` or `mc`, but does ship curl 8.5 with `--aws-sigv4`. This keeps
the change zero-install. Credentials are fed to curl through a config file on
stdin rather than argv, so they never appear in the process list.
### Behaviour
- Object layout is identical to S3 (`gitea-runner/<version>/<artifact>`), so
migrating consumers later means changing only the host, not the path.
- Nightly gets R2 too, matching the existing nightly-to-S3 behaviour.
- Missing R2 configuration fails the build. Because custom publishers run as the
very last step of the publish pipeline, a preflight `--check-config` step runs
right after checkout so the failure happens before anything is built or
published rather than after the release already exists.
- Verified against a local MinIO instance (site region `auto`, matching R2):
successful upload byte-compared after download, plus the missing-variable,
wrong-credentials, missing-file and bad-argument paths.
### Required repository secrets
These must be configured before the next release run, otherwise the new
preflight step will fail the workflow:
- `R2_ENDPOINT` (full base URL, e.g. `https://<account>.r2.cloudflarestorage.com`)
- `R2_BUCKET`
- `R2_ACCESS_KEY_ID`
- `R2_SECRET_ACCESS_KEY`
### Known, deliberate wart
The publisher runs 109 times for 73 distinct object keys: goreleaser's release
pipe already registers `release.extra_files` as `UploadableFile` artifacts, and
`internal/exec` appends the publisher's own `extra_files` on top with no
de-duplication. It cannot be globbed away because `gobwas/glob` has no
substring-exclusion matcher, so `./**.sha256` cannot be narrowed to exclude
`*.xz.sha256`. It is harmless since PUT is idempotent, and the redundant
`./**.xz` glob is kept on purpose so the publisher declares its own complete
file set instead of implicitly depending on the `release:` block's globs. This
is documented in a comment above the block.
Reviewed-on: https://gitea.com/gitea/runner/pulls/1114
Reviewed-by: Zettat123 <39446+zettat123@noreply.gitea.com>
Running the full suite under `-race` (dropping `-short`) exposed pre-existing data races in parallel matrix-job execution, fixed by not sharing mutable state across combinations:
- `containerDaemonSocket()`/`validVolumes()` derive per-job values instead of mutating shared `Config`
- `getWorkflowSecrets` builds a fresh map, `rc.steps()` clones each step, and go-git workdir access is serialized
- every write to a shared `Job`'s result/outputs runs under a per-`Job` lock, each combo interpolating outputs from a pristine snapshot (last wins, as on GitHub)
### Test suite
- capability gates (docker / network / host-tools / Linux) replace the `-short` skips, and the suite runs offline via local fixtures (the artifact flow uses an in-process loopback server, only the docker-action force-pull needs the network)
- drops redundant tests, adds a regression test for https://gitea.com/gitea/runner/issues/981 and a docker-in-docker harness (`make test-dind`)
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/994
Reviewed-by: Nicolas <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
`s6-svscan` starts services in parallel, so `act_runner/run` could invoke `s6-svwait` before s6 had created the docker service's `supervise/` directory, failing with `s6-svwait: fatal: unable to s6_svstatus_read: No such file or directory`. Poll for the directory before waiting.
Fixes#760
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/851
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
- `basic`: Only the runner process in the container; users need to mount the Docker socket to it.
- `dind`: A Docker daemon will be started in the container with the root user.
- `dind-rootless`: A Docker daemon will be started in the container with a rootless user.
Use s6 instead of supervisord to start processes.
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/619
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
## Description
Issue described in #460
## Changes
- Edited `supervisord.conf` to exit if it detects any of the supervisored processes exiting.
- minor text fix
## Notes
Without this change (or something similar), if act_runner fails, then the container will stay up as a zombie container - it does nothing and does not restart. After this change, if act_runner fails (e.g. due to Gitea instance being down), then supervisord will exit and the container will be restarted.
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/462
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: davidfrickert <david.frickert@protonmail.com>
Co-committed-by: davidfrickert <david.frickert@protonmail.com>
Added environment variable `RUNNER_STATE_FILE` to let users specify
where `run.sh` looks for the runner JSON file. Defaults to ``.runner``
to preserve the original behavior.
Addresses issue #368. It's not my preferred solution but it's the least invasive one I can think of.
I'm happy to make any changes you want.
I didn't see an appropriate place to reference the change in the documentation. I will add documentation wherever you think is appropriate.
To test this I did the following:
* Built an image with the `make docker` command and pushed it to my private registry.
* Added that private image as the image in my existing docker stack that was exhibiting the behavior described in #368.
* Added the RUNNER_STATE_FILE environment variable pointing to the runner JSON file set in my `config.yml` (``/data/runner.json`` in this case).
* Configured a new runner in gitea and added the token as an environment variable in the stack config.
* Deployed the stack and verified the new runner was recognized (in the idle state).
* Force updated the runner service to restart the container.
* Verified the same runner was still recognized in gitea (in the idle state) once it was back up and running.
Here is the relevant config. It's stored as a template so I've left the things that would normally be redacted as they are in the template.
```
runner:
image: hub.hax.in.net/haxwithaxe/act_runner:dev1
networks:
- swarm-net
environment:
RUNNER_STATE_FILE: /data/runner.json
CONFIG_FILE: /data/config.yml
GITEA_INSTANCE_URL: "https://git_gitea"
GITEA_RUNNER_REGISTRATION_TOKEN: "{{ git_runner_reg_token }}"
GITEA_RUNNER_NAME: "git_runner"
volumes:
- runner_data:/data
- /var/run/docker.sock:/var/run/docker.sock
```
`runner_data` is a glusterfs subvolume
Thanks for creating this gitea specific fork. Apart from the issue this pull request addresses it works so well I don't have to think about it once it's set up.
Co-authored-by: haxwithaxe <spam@haxwithaxe.net>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/377
Co-authored-by: haxwithaxe <haxwithaxe@noreply.gitea.com>
Co-committed-by: haxwithaxe <haxwithaxe@noreply.gitea.com>