Compare commits

...

20 Commits

Author SHA1 Message Date
Pablo Carranza
6a9a447f86 Report errors by setting raw_output when it's error level (#645)
This solves #643 by setting the "raw_output" entry attribute when the log level is error.  This results in the log line being shipped to the Gitea UI.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/645
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Co-authored-by: Pablo Carranza <pcarranza@gmail.com>
Co-committed-by: Pablo Carranza <pcarranza@gmail.com>
2025-06-05 17:53:13 +00:00
Jack Jackson
5302c25feb Add environment variables for OIDC token service (#674)
Resurrecting [this PR](https://gitea.com/gitea/act_runner/pulls/272) (a dependency of [this one](https://github.com/go-gitea/gitea/pull/33945)) after the original author [lost motivation](https://github.com/go-gitea/gitea/pull/25664#issuecomment-2737099259) - though, to be clear, all credit belongs to them, and all blame for mistakes or misunderstandings to me.

Co-authored-by: Søren L. Hansen <sorenisanerd@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/674
Reviewed-by: ChristopherHX <christopherhx@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jack Jackson <scubbojj@gmail.com>
Co-committed-by: Jack Jackson <scubbojj@gmail.com>
2025-05-08 01:58:31 +00:00
Christopher Homberger
a616ed1a10 feat: register interactive with values from cli (#682)
I used to be able to do something like `./act_runner register --instance https://gitea.com --token testdcff --name test` on GitHub Actions Runners, but act_runner always asked me to enter instance, token etc. again and requiring me to use `--no-interactive` including passing everything per cli.

My idea was to extract the preset input of some stages to skip the prompt for this value if it is a non empty string. Labels is the only question that has been asked more than once if validation failed, in this case the error path have to unset the values of the input structure to not end in a non-interactive loop.

_I have written this initially for my own gitea runner, might be useful to everyone using the official runner as well_

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/682
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
Co-committed-by: Christopher Homberger <christopher.homberger@web.de>
2025-05-08 01:57:53 +00:00
Christopher Homberger
f0b5aff3bb fix: invalid label NoInteractive exit code (#683)
* add test
* return validation error not nil from function

Closes #665

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/683
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
Co-committed-by: Christopher Homberger <christopher.homberger@web.de>
2025-05-07 17:17:26 +00:00
Christopher Homberger
44b4736703 feat: docker env vars for ephemeral and once (#685)
* GITEA_RUNNER_EPHEMERAL=1
* GITEA_RUNNER_ONCE=1

Related https://gitea.com/gitea/act_runner/issues/684

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/685
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
Co-committed-by: Christopher Homberger <christopher.homberger@web.de>
2025-05-07 15:43:05 +00:00
Christopher Homberger
b1ae30dda8 ephemeral act runner (#649)
Works for both interactive and non-interactive registration mode.

A further enhancement would be jitconfig support of the daemon command, because after some changes in Gitea Actions the registration token became reusable.

removing runner and fail seems not possible at the current api level

Part of https://github.com/go-gitea/gitea/pull/33570

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/649
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Christopher Homberger <christopher.homberger@web.de>
Co-committed-by: Christopher Homberger <christopher.homberger@web.de>
2025-03-13 21:57:44 +00:00
techknowlogick
0d687268c7 act_runner requires go 1.24 now 2025-03-02 05:36:24 +00:00
techknowlogick
425a570261 use new docker image URLs (#661)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/661
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Co-committed-by: techknowlogick <techknowlogick@gitea.com>
2025-03-01 20:21:52 +00:00
Lunny Xiao
4c8179ee12 upgrade to go1.24, act to 0.261.4 and actions-proto-go to 0.4.1 (#662)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/662
Reviewed-by: ChristopherHX <christopherhx@noreply.gitea.com>
2025-03-01 20:18:36 +00:00
Pablo Carranza
5ae13f0bd7 Update xgo version to 1.24 (#651)
Co-authored-by: techknowlogick <techknowlogick@gitea.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/651
Co-authored-by: Pablo Carranza <pcarranza@gmail.com>
Co-committed-by: Pablo Carranza <pcarranza@gmail.com>
2025-02-15 16:07:18 +00:00
Lunny Xiao
3510152e36 Fix Makefile make docker (#641)
Fix #640

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/641
2025-01-29 03:27:47 +00:00
armistace
8dfb805c62 Update examples/kubernetes/dind-docker.yaml to reflect recent changes to Dockerfile (#633)
With the changes made two months ago for the Dockerfile /opt/act/run.sh no longer exists in the docker container, this caused this example to fail, updating the example so that it correctly references run.sh now located in /usr/local/bin

I have used this to deploy on my own cluster and it is now working swimmingly

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/633
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: armistace <armistace@noreply.gitea.com>
Co-committed-by: armistace <armistace@noreply.gitea.com>
2025-01-26 02:10:17 +00:00
Zettat123
a7080f5457 Update examples for GITEA_RUNNER_REGISTRATION_TOKEN env (#630)
For https://github.com/go-gitea/gitea/pull/32946

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/630
Reviewed-by: Lunny Xiao <lunny@noreply.gitea.com>
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2025-01-05 22:25:32 +00:00
techknowlogick
8b72d1c7ae add s390x and riscv64 as an arch for binaries 2024-12-09 18:49:38 +00:00
garet90
8bc0275e74 feat: add once flag to daemon command (#19) (#598)
Once flag polls and completes one job then exits.

I use this with Windows Sandbox (and creating users with local brew install on Mac) to create a fresh environment every time.

Co-authored-by: Garet Halliday <garet@pit.dev>
Co-authored-by: Jason Song <wolfogre@noreply.gitea.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/598
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-by: Jason Song <wolfogre@noreply.gitea.com>
Co-authored-by: garet90 <garet90@noreply.gitea.com>
Co-committed-by: garet90 <garet90@noreply.gitea.com>
2024-11-06 17:16:08 +00:00
Jason Song
0348aaac59 Wait for the Docker daemon to be ready before starting act runner (#620)
Follow #619.

Wait for the Docker daemon to be ready before starting act runner.

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/620
Co-authored-by: Jason Song <i@wolfogre.com>
Co-committed-by: Jason Song <i@wolfogre.com>
2024-11-06 07:38:31 +00:00
Jason Song
9712481bed Support basic, dind and dind-rootless as multiple kinds of images (#619)
- `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>
2024-11-06 03:15:51 +00:00
Lunny Xiao
b5f901b2d9 Upgrade act from v0.261.2 -> v0.261.3 (#607)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/607
Reviewed-by: Jason Song <wolfogre@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2024-10-18 04:33:57 +00:00
frank-dspeed
0e2a3e00f5 examples/vm/rootless-docker.md aktualisiert (#487)
Depending on the VM's existing users the id can vary

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/487
Reviewed-by: techknowlogick <techknowlogick@noreply.gitea.com>
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: frank-dspeed <frank-dspeed@noreply.gitea.com>
Co-committed-by: frank-dspeed <frank-dspeed@noreply.gitea.com>
2024-09-30 01:55:04 +00:00
Schallbert
b282356e9e update example for docker-compose to allow fix 502 errors in case Gitea not yet ready on runner startup (#605)
Minimalistic approach: Only adds what is needed to fix #600
Context: https://blog.schallbert.de/en/fix-gitea-runner/

Reviewed-on: https://gitea.com/gitea/act_runner/pulls/605
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Zettat123 <zettat123@noreply.gitea.com>
Co-authored-by: Schallbert <schallbert@mailbox.org>
Co-committed-by: Schallbert <schallbert@mailbox.org>
2024-09-26 05:54:54 +00:00
34 changed files with 353 additions and 157 deletions

View File

@ -66,6 +66,7 @@ jobs:
with:
context: .
file: ./Dockerfile
target: basic
platforms: |
linux/amd64
linux/arm64
@ -73,13 +74,25 @@ jobs:
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
- name: Build and push dind-rootless
- name: Build and push dind
uses: docker/build-push-action@v5
env:
ACTIONS_RUNTIME_TOKEN: "" # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile.rootless
file: ./Dockerfile
target: dind
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind
- name: Build and push dind-rootless
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
target: dind-rootless
platforms: |
linux/amd64
linux/arm64

View File

@ -73,6 +73,7 @@ jobs:
with:
context: .
file: ./Dockerfile
target: basic
platforms: |
linux/amd64
linux/arm64
@ -81,13 +82,26 @@ jobs:
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}
- name: Build and push dind-rootless
- name: Build and push dind
uses: docker/build-push-action@v5
env:
ACTIONS_RUNTIME_TOKEN: "" # See https://gitea.com/gitea/act_runner/issues/119
with:
context: .
file: ./Dockerfile.rootless
file: ./Dockerfile
target: dind
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-dind
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind
- name: Build and push dind-rootless
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
target: dind-rootless
platforms: |
linux/amd64
linux/arm64

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
act_runner
/act_runner
.env
.runner
coverage.txt

View File

@ -16,6 +16,8 @@ builds:
- amd64
- arm
- arm64
- s390x
- riscv64
goarm:
- "5"
- "6"

View File

@ -1,16 +1,54 @@
FROM golang:1.23-alpine AS builder
FROM golang:1.24-alpine AS builder
# Do not remove `git` here, it is required for getting runner version when executing `make build`
RUN apk add --no-cache make git
ARG GOPROXY
ENV GOPROXY=${GOPROXY:-}
COPY . /opt/src/act_runner
WORKDIR /opt/src/act_runner
RUN make clean && make build
FROM alpine
RUN apk add --no-cache git bash tini
FROM docker:dind AS dind
RUN apk add --no-cache s6 bash git
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
COPY scripts/run.sh /opt/act/run.sh
COPY scripts/run.sh /usr/local/bin/run.sh
COPY scripts/s6 /etc/s6
ENTRYPOINT ["/sbin/tini","--","/opt/act/run.sh"]
VOLUME /data
ENTRYPOINT ["s6-svscan","/etc/s6"]
FROM docker:dind-rootless AS dind-rootless
USER root
RUN apk add --no-cache s6 bash git
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
COPY scripts/run.sh /usr/local/bin/run.sh
COPY scripts/s6 /etc/s6
VOLUME /data
RUN mkdir -p /data && chown -R rootless:rootless /etc/s6 /data
ENV DOCKER_HOST=unix:///run/user/1000/docker.sock
USER rootless
ENTRYPOINT ["s6-svscan","/etc/s6"]
FROM alpine AS basic
RUN apk add --no-cache tini bash git
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
COPY scripts/run.sh /usr/local/bin/run.sh
VOLUME /var/run/docker.sock
VOLUME /data
ENTRYPOINT ["/sbin/tini","--","run.sh"]

View File

@ -1,24 +0,0 @@
FROM golang:1.23-alpine AS builder
# Do not remove `git` here, it is required for getting runner version when executing `make build`
RUN apk add --no-cache make git
COPY . /opt/src/act_runner
WORKDIR /opt/src/act_runner
RUN make clean && make build
FROM docker:dind-rootless
USER root
RUN apk add --no-cache \
git bash supervisor
COPY --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
COPY /scripts/supervisord.conf /etc/supervisord.conf
COPY /scripts/run.sh /opt/act/run.sh
COPY /scripts/rootless.sh /opt/act/rootless.sh
RUN mkdir /data \
&& chown rootless:rootless /data
USER rootless
ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

View File

@ -7,7 +7,7 @@ GO ?= go
SHASUM ?= shasum -a 256
HAS_GO = $(shell hash $(GO) > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
XGO_VERSION := go-1.18.x
XGO_VERSION := go-1.24.x
GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10
LINUX_ARCHS ?= linux/amd64,linux/arm64
@ -170,7 +170,6 @@ docker:
ARG_DISABLE_CONTENT_TRUST=--disable-content-trust=false; \
fi; \
docker build $${ARG_DISABLE_CONTENT_TRUST} -t $(DOCKER_REF) .
docker build $${ARG_DISABLE_CONTENT_TRUST} -t $(DOCKER_ROOTLESS_REF) -f Dockerfile.rootless .
clean:
$(GO) clean -x -i ./...

View File

@ -58,9 +58,9 @@ INFO Enter the runner token:
fe884e8027dc292970d4e0303fe82b14xxxxxxxx
INFO Enter the runner name (if set empty, use hostname: Test.local):
INFO Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-latest:docker://gitea/runner-images:ubuntu-latest):
INFO Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest):
INFO Registering runner, name=Test.local, instance=http://192.168.8.8:3000/, labels=[ubuntu-latest:docker://gitea/runner-images:ubuntu-latest ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04 ubuntu-20.04:docker://gitea/runner-images:ubuntu-20.04].
INFO Registering runner, name=Test.local, instance=http://192.168.8.8:3000/, labels=[ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04 ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04].
DEBU Successfully pinged the Gitea instance server
INFO Runner registered successfully.
```

View File

@ -5,12 +5,27 @@
gitea:
image: gitea/gitea
...
healthcheck:
# checks availability of Gitea's front-end with curl
test: ["CMD", "curl", "-f", "<instance_url>"]
interval: 10s
retries: 3
start_period: 30s
timeout: 10s
environment:
# GITEA_RUNNER_REGISTRATION_TOKEN can be used to set a global runner registration token.
# The Gitea version must be v1.23 or higher.
# It's also possible to use GITEA_RUNNER_REGISTRATION_TOKEN_FILE to pass the location.
# - GITEA_RUNNER_REGISTRATION_TOKEN=<user-defined registration token>
runner:
image: gitea/act_runner
restart: always
depends_on:
- gitea
gitea:
# required so runner can attach to gitea, see "healthcheck"
condition: service_healthy
restart: true
volumes:
- ./data/act_runner:/data
- /var/run/docker.sock:/var/run/docker.sock

View File

@ -12,6 +12,9 @@ spec:
---
apiVersion: v1
data:
# The registration token can be obtained from the web UI, API or command-line.
# You can also set a pre-defined global runner registration token for the Gitea instance via
# `GITEA_RUNNER_REGISTRATION_TOKEN`/`GITEA_RUNNER_REGISTRATION_TOKEN_FILE` environment variable.
token: << base64 encoded registration token >>
kind: Secret
metadata:
@ -46,7 +49,7 @@ spec:
containers:
- name: runner
image: gitea/act_runner:nightly
command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...'; sleep 5; done; /sbin/tini -- /opt/act/run.sh"]
command: ["sh", "-c", "while ! nc -z localhost 2376 </dev/null; do echo 'waiting for docker daemon...'; sleep 5; done; /sbin/tini -- run.sh"]
env:
- name: DOCKER_HOST
value: tcp://localhost:2376

View File

@ -12,7 +12,10 @@ spec:
---
apiVersion: v1
data:
token: << runner registration token goes here >>
# The registration token can be obtained from the web UI, API or command-line.
# You can also set a pre-defined global runner registration token for the Gitea instance via
# `GITEA_RUNNER_REGISTRATION_TOKEN`/`GITEA_RUNNER_REGISTRATION_TOKEN_FILE` environment variable.
token: << base64 encoded registration token >>
kind: Secret
metadata:
name: runner-secret

View File

@ -11,7 +11,8 @@ As `root`:
```bash
useradd -m rootless
passwd rootless
```
apt-get install -y uidmap # Not mentioned but needed for docker rootless.
```
- Install [`docker-ce`](https://docs.docker.com/engine/install/)
- (Recommended) Disable the system-wide Docker daemon
@ -21,12 +22,19 @@ As `root`:
As the `rootless` user:
- Follow the instructions for [enabling rootless mode](https://docs.docker.com/engine/security/rootless/)
- Add the following lines to the `/home/rootless/.bashrc`:
- Add the following line to the `/home/rootless/.bashrc`:
```bash
export XDG_RUNTIME_DIR=/home/rootless/.docker/run
export PATH=/home/rootless/bin:$PATH
export DOCKER_HOST=unix:///run/user/1001/docker.sock
for f in ./.bashrc.d/*.bash; do echo "Processing $f file..."; . "$f"; done
```
- Create the .bashrc.d directory `mkdir ~/.bashrc.d`
- Add the following lines to the `/home/rootless/.bashrc.d/rootless-docker.bash`:
```bash
export XDG_RUNTIME_DIR=/home/rootless/.docker/run
export PATH=/home/rootless/bin:$PATH
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
```
- Reboot. Ensure that the Docker process is working.

6
go.mod
View File

@ -1,9 +1,9 @@
module gitea.com/gitea/act_runner
go 1.23
go 1.24
require (
code.gitea.io/actions-proto-go v0.4.0
code.gitea.io/actions-proto-go v0.4.1
code.gitea.io/gitea-vet v0.2.3
connectrpc.com/connect v1.16.2
github.com/avast/retry-go/v4 v4.6.0
@ -98,4 +98,4 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
)
replace github.com/nektos/act => gitea.com/gitea/act v0.261.2
replace github.com/nektos/act => gitea.com/gitea/act v0.261.4

8
go.sum
View File

@ -1,13 +1,13 @@
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
code.gitea.io/actions-proto-go v0.4.1 h1:l0EYhjsgpUe/1VABo2eK7zcoNX2W44WOnb0MSLrKfls=
code.gitea.io/actions-proto-go v0.4.1/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
gitea.com/gitea/act v0.261.2 h1:yAhxlt11gpRmF7CeVsVjgLg1Ph0xxroJ/l2fWaYyl84=
gitea.com/gitea/act v0.261.2/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8=
gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=

View File

@ -39,15 +39,18 @@ func Execute(ctx context.Context) {
registerCmd.Flags().StringVar(&regArgs.Token, "token", "", "Runner token")
registerCmd.Flags().StringVar(&regArgs.RunnerName, "name", "", "Runner name")
registerCmd.Flags().StringVar(&regArgs.Labels, "labels", "", "Runner tags, comma separated")
registerCmd.Flags().BoolVar(&regArgs.Ephemeral, "ephemeral", false, "Configure the runner to be ephemeral and only ever be able to pick a single job (stricter than --once)")
rootCmd.AddCommand(registerCmd)
// ./act_runner daemon
var daemArgs daemonArgs
daemonCmd := &cobra.Command{
Use: "daemon",
Short: "Run as a runner daemon",
Args: cobra.MaximumNArgs(1),
RunE: runDaemon(ctx, &configFile),
Args: cobra.MaximumNArgs(0),
RunE: runDaemon(ctx, &daemArgs, &configFile),
}
daemonCmd.Flags().BoolVar(&daemArgs.Once, "once", false, "Run one job then exit")
rootCmd.AddCommand(daemonCmd)
// ./act_runner exec

View File

@ -28,7 +28,7 @@ import (
"gitea.com/gitea/act_runner/internal/pkg/ver"
)
func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command, args []string) error {
func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
cfg, err := config.LoadDefault(*configFile)
if err != nil {
@ -122,9 +122,24 @@ func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command,
poller := poll.New(cfg, cli, runner)
go poller.Poll()
if daemArgs.Once || reg.Ephemeral {
done := make(chan struct{})
go func() {
defer close(done)
poller.PollOnce()
}()
// shutdown when we complete a job or cancel is requested
select {
case <-ctx.Done():
case <-done:
}
} else {
go poller.Poll()
<-ctx.Done()
}
<-ctx.Done()
log.Infof("runner: %s shutdown initiated, waiting %s for running jobs to complete before shutting down", resp.Msg.Runner.Name, cfg.Runner.ShutdownTimeout)
ctx, cancel := context.WithTimeout(context.Background(), cfg.Runner.ShutdownTimeout)
@ -134,45 +149,57 @@ func runDaemon(ctx context.Context, configFile *string) func(cmd *cobra.Command,
if err != nil {
log.Warnf("runner: %s cancelled in progress jobs during shutdown", resp.Msg.Runner.Name)
}
return nil
}
}
type daemonArgs struct {
Once bool
}
// initLogging setup the global logrus logger.
func initLogging(cfg *config.Config) {
callPrettyfier := func(f *runtime.Frame) (string, string) {
// get function name
s := strings.Split(f.Function, ".")
funcname := "[" + s[len(s)-1] + "]"
// get file name and line number
_, filename := path.Split(f.File)
filename = "[" + filename + ":" + strconv.Itoa(f.Line) + "]"
return funcname, filename
}
isTerm := isatty.IsTerminal(os.Stdout.Fd())
format := &log.TextFormatter{
DisableColors: !isTerm,
FullTimestamp: true,
DisableColors: !isTerm,
FullTimestamp: true,
CallerPrettyfier: callPrettyfier,
}
log.SetFormatter(format)
if l := cfg.Log.Level; l != "" {
level, err := log.ParseLevel(l)
if err != nil {
log.WithError(err).
Errorf("invalid log level: %q", l)
}
l := cfg.Log.Level
if l == "" {
log.Infof("Log level not set, sticking to info")
return
}
// debug level
if level == log.DebugLevel {
log.SetReportCaller(true)
format.CallerPrettyfier = func(f *runtime.Frame) (string, string) {
// get function name
s := strings.Split(f.Function, ".")
funcname := "[" + s[len(s)-1] + "]"
// get file name and line number
_, filename := path.Split(f.File)
filename = "[" + filename + ":" + strconv.Itoa(f.Line) + "]"
return funcname, filename
}
log.SetFormatter(format)
}
level, err := log.ParseLevel(l)
if err != nil {
log.WithError(err).
Errorf("invalid log level: %q", l)
}
if log.GetLevel() != level {
log.Infof("log level changed to %v", level)
log.SetLevel(level)
}
// debug level
switch level {
case log.DebugLevel, log.TraceLevel:
log.SetReportCaller(true) // Only in debug or trace because it takes a performance toll
log.Infof("Log level %s requested, setting up report caller for further debugging", level)
}
if log.GetLevel() != level {
log.Infof("log level set to %v", level)
log.SetLevel(level)
}
}

View File

@ -484,7 +484,7 @@ func loadExecCmd(ctx context.Context) *cobra.Command {
execCmd.PersistentFlags().BoolVarP(&execArg.noSkipCheckout, "no-skip-checkout", "", false, "Do not skip actions/checkout")
execCmd.PersistentFlags().BoolVarP(&execArg.debug, "debug", "d", false, "enable debug log")
execCmd.PersistentFlags().BoolVarP(&execArg.dryrun, "dryrun", "n", false, "dryrun mode")
execCmd.PersistentFlags().StringVarP(&execArg.image, "image", "i", "gitea/runner-images:ubuntu-latest", "Docker image to use. Use \"-self-hosted\" to run directly on the host.")
execCmd.PersistentFlags().StringVarP(&execArg.image, "image", "i", "docker.gitea.com/runner-images:ubuntu-latest", "Docker image to use. Use \"-self-hosted\" to run directly on the host.")
execCmd.PersistentFlags().StringVarP(&execArg.network, "network", "", "", "Specify the network to which the container will connect")
execCmd.PersistentFlags().StringVarP(&execArg.githubInstance, "gitea-instance", "", "", "Gitea instance to use.")

View File

@ -52,7 +52,7 @@ func runRegister(ctx context.Context, regArgs *registerArgs, configFile *string)
}
} else {
go func() {
if err := registerInteractive(ctx, *configFile); err != nil {
if err := registerInteractive(ctx, *configFile, regArgs); err != nil {
log.Fatal(err)
return
}
@ -75,6 +75,7 @@ type registerArgs struct {
Token string
RunnerName string
Labels string
Ephemeral bool
}
type registerStage int8
@ -91,9 +92,9 @@ const (
)
var defaultLabels = []string{
"ubuntu-latest:docker://gitea/runner-images:ubuntu-latest",
"ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04",
"ubuntu-20.04:docker://gitea/runner-images:ubuntu-20.04",
"ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest",
"ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04",
"ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04",
}
type registerInputs struct {
@ -101,6 +102,7 @@ type registerInputs struct {
Token string
RunnerName string
Labels []string
Ephemeral bool
}
func (r *registerInputs) validate() error {
@ -125,6 +127,22 @@ func validateLabels(ls []string) error {
return nil
}
func (r *registerInputs) stageValue(stage registerStage) string {
switch stage {
case StageInputInstance:
return r.InstanceAddr
case StageInputToken:
return r.Token
case StageInputRunnerName:
return r.RunnerName
case StageInputLabels:
if len(r.Labels) > 0 {
return strings.Join(r.Labels, ",")
}
}
return ""
}
func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *config.Config) registerStage {
// must set instance address and token.
// if empty, keep current stage.
@ -178,7 +196,8 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co
}
if validateLabels(r.Labels) != nil {
log.Infoln("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-latest:docker://gitea/runner-images:ubuntu-latest)")
log.Infoln("Invalid labels, please input again, leave blank to use the default labels (for example, ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest)")
r.Labels = nil
return StageInputLabels
}
return StageWaitingForRegistration
@ -186,11 +205,25 @@ func (r *registerInputs) assignToNext(stage registerStage, value string, cfg *co
return StageUnknown
}
func registerInteractive(ctx context.Context, configFile string) error {
func initInputs(regArgs *registerArgs) *registerInputs {
inputs := &registerInputs{
InstanceAddr: regArgs.InstanceAddr,
Token: regArgs.Token,
RunnerName: regArgs.RunnerName,
Ephemeral: regArgs.Ephemeral,
}
regArgs.Labels = strings.TrimSpace(regArgs.Labels)
// command line flag.
if regArgs.Labels != "" {
inputs.Labels = strings.Split(regArgs.Labels, ",")
}
return inputs
}
func registerInteractive(ctx context.Context, configFile string, regArgs *registerArgs) error {
var (
reader = bufio.NewReader(os.Stdin)
stage = StageInputInstance
inputs = new(registerInputs)
)
cfg, err := config.LoadDefault(configFile)
@ -200,13 +233,17 @@ func registerInteractive(ctx context.Context, configFile string) error {
if f, err := os.Stat(cfg.Runner.File); err == nil && !f.IsDir() {
stage = StageOverwriteLocalConfig
}
inputs := initInputs(regArgs)
for {
printStageHelp(stage)
cmdString, err := reader.ReadString('\n')
if err != nil {
return err
cmdString := inputs.stageValue(stage)
if cmdString == "" {
printStageHelp(stage)
var err error
cmdString, err = reader.ReadString('\n')
if err != nil {
return err
}
}
stage = inputs.assignToNext(stage, strings.TrimSpace(cmdString), cfg)
@ -242,7 +279,7 @@ func printStageHelp(stage registerStage) {
hostname, _ := os.Hostname()
log.Infof("Enter the runner name (if set empty, use hostname: %s):\n", hostname)
case StageInputLabels:
log.Infoln("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-latest:docker://gitea/runner-images:ubuntu-latest):")
log.Infoln("Enter the runner labels, leave blank to use the default labels (comma-separated, for example, ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest):")
case StageWaitingForRegistration:
log.Infoln("Waiting for registration...")
}
@ -253,17 +290,7 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
if err != nil {
return err
}
inputs := &registerInputs{
InstanceAddr: regArgs.InstanceAddr,
Token: regArgs.Token,
RunnerName: regArgs.RunnerName,
Labels: defaultLabels,
}
regArgs.Labels = strings.TrimSpace(regArgs.Labels)
// command line flag.
if regArgs.Labels != "" {
inputs.Labels = strings.Split(regArgs.Labels, ",")
}
inputs := initInputs(regArgs)
// specify labels in config file.
if len(cfg.Runner.Labels) > 0 {
if regArgs.Labels != "" {
@ -271,6 +298,9 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
}
inputs.Labels = cfg.Runner.Labels
}
if len(inputs.Labels) == 0 {
inputs.Labels = defaultLabels
}
if inputs.RunnerName == "" {
inputs.RunnerName, _ = os.Hostname()
@ -278,7 +308,7 @@ func registerNoInteractive(ctx context.Context, configFile string, regArgs *regi
}
if err := inputs.validate(); err != nil {
log.WithError(err).Errorf("Invalid input, please re-run act command.")
return nil
return err
}
if err := doRegister(ctx, cfg, inputs); err != nil {
return fmt.Errorf("Failed to register runner: %w", err)
@ -321,10 +351,11 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs)
}
reg := &config.Registration{
Name: inputs.RunnerName,
Token: inputs.Token,
Address: inputs.InstanceAddr,
Labels: inputs.Labels,
Name: inputs.RunnerName,
Token: inputs.Token,
Address: inputs.InstanceAddr,
Labels: inputs.Labels,
Ephemeral: inputs.Ephemeral,
}
ls := make([]string, len(reg.Labels))
@ -339,6 +370,7 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs)
Version: ver.Version(),
AgentLabels: ls, // Could be removed after Gitea 1.20
Labels: ls,
Ephemeral: reg.Ephemeral,
}))
if err != nil {
log.WithError(err).Error("poller: cannot register new runner")
@ -350,6 +382,11 @@ func doRegister(ctx context.Context, cfg *config.Config, inputs *registerInputs)
reg.Name = resp.Msg.Runner.Name
reg.Token = resp.Msg.Runner.Token
if inputs.Ephemeral != resp.Msg.Runner.Ephemeral {
// TODO we cannot remove the configuration via runner api, if we return an error here we just fill the database
log.Error("poller: cannot register new runner as ephemeral upgrade Gitea to gain security, run-once will be used automatically")
}
if err := config.SaveRegistration(cfg.Runner.File, reg); err != nil {
return fmt.Errorf("failed to save runner config: %w", err)
}

View File

@ -0,0 +1,19 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cmd
import (
"testing"
"gotest.tools/v3/assert"
)
func TestRegisterNonInteractiveReturnsLabelValidationError(t *testing.T) {
err := registerNoInteractive(t.Context(), "", &registerArgs{
Labels: "label:invalid",
Token: "token",
InstanceAddr: "http://localhost:3000",
})
assert.Error(t, err, "unsupported schema: invalid")
}

View File

@ -70,6 +70,15 @@ func (p *Poller) Poll() {
close(p.done)
}
func (p *Poller) PollOnce() {
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
p.pollOnce(limiter)
// signal that we're done
close(p.done)
}
func (p *Poller) Shutdown(ctx context.Context) error {
p.shutdownPolling()
@ -101,6 +110,19 @@ func (p *Poller) Shutdown(ctx context.Context) error {
func (p *Poller) poll(wg *sync.WaitGroup, limiter *rate.Limiter) {
defer wg.Done()
for {
p.pollOnce(limiter)
select {
case <-p.pollingCtx.Done():
return
default:
continue
}
}
}
func (p *Poller) pollOnce(limiter *rate.Limiter) {
for {
if err := limiter.Wait(p.pollingCtx); err != nil {
if p.pollingCtx.Err() != nil {
@ -114,6 +136,7 @@ func (p *Poller) poll(wg *sync.WaitGroup, limiter *rate.Limiter) {
}
p.runTaskWithRecover(p.jobsCtx, task)
return
}
}

View File

@ -163,6 +163,12 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
preset.Token = t
}
if actionsIdTokenRequestUrl := taskContext["actions_id_token_request_url"].GetStringValue(); actionsIdTokenRequestUrl != "" {
r.envs["ACTIONS_ID_TOKEN_REQUEST_URL"] = actionsIdTokenRequestUrl
r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = taskContext["actions_id_token_request_token"].GetStringValue()
task.Secrets["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = r.envs["ACTIONS_ID_TOKEN_REQUEST_TOKEN"]
}
giteaRuntimeToken := taskContext["gitea_runtime_token"].GetStringValue()
if giteaRuntimeToken == "" {
// use task token to action api token for previous Gitea Server Versions

View File

@ -33,14 +33,14 @@ runner:
# The interval for fetching the job from the Gitea instance.
fetch_interval: 2s
# The labels of a runner are used to determine which jobs the runner can run, and how to run them.
# Like: "macos-arm64:host" or "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest"
# Find more images provided by Gitea at https://gitea.com/gitea/runner-images .
# Like: "macos-arm64:host" or "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
# Find more images provided by Gitea at https://gitea.com/docker.gitea.com/runner-images .
# If it's empty when registering, it will ask for inputting labels.
# If it's empty when execute `daemon`, will use labels in `.runner` file.
labels:
- "ubuntu-latest:docker://gitea/runner-images:ubuntu-latest"
- "ubuntu-22.04:docker://gitea/runner-images:ubuntu-22.04"
- "ubuntu-20.04:docker://gitea/runner-images:ubuntu-20.04"
- "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
- "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
- "ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04"
cache:
# Enable cache server to use actions/cache.

View File

@ -14,12 +14,13 @@ const registrationWarning = "This file is automatically generated by act-runner.
type Registration struct {
Warning string `json:"WARNING"` // Warning message to display, it's always the registrationWarning constant
ID int64 `json:"id"`
UUID string `json:"uuid"`
Name string `json:"name"`
Token string `json:"token"`
Address string `json:"address"`
Labels []string `json:"labels"`
ID int64 `json:"id"`
UUID string `json:"uuid"`
Name string `json:"name"`
Token string `json:"token"`
Address string `json:"address"`
Labels []string `json:"labels"`
Ephemeral bool `json:"ephemeral"`
}
func LoadRegistration(file string) (*Registration, error) {

View File

@ -79,7 +79,7 @@ func (l Labels) PickPlatform(runsOn []string) string {
// So the runner receives a task with a label that the runner doesn't have,
// it happens when the user have edited the label of the runner in the web UI.
// TODO: it may be not correct, what if the runner is used as host mode only?
return "gitea/runner-images:ubuntu-latest"
return "docker.gitea.com/runner-images:ubuntu-latest"
}
func (l Labels) Names() []string {

View File

@ -143,6 +143,12 @@ func (r *Reporter) Fire(entry *log.Entry) error {
if step.StartedAt == nil {
step.StartedAt = timestamppb.New(timestamp)
}
// Force reporting log errors as raw output to prevent silent failures
if entry.Level == log.ErrorLevel {
entry.Data["raw_output"] = true
}
if v, ok := entry.Data["raw_output"]; ok {
if rawOutput, ok := v.(bool); ok && rawOutput {
if row := r.parseLogRow(entry); row != nil {

View File

@ -1,9 +0,0 @@
#!/usr/bin/env bash
# wait for docker daemon
while ! nc -z localhost 2376 </dev/null; do
echo 'waiting for docker daemon...'
sleep 5
done
. /opt/act/run.sh

View File

@ -16,6 +16,13 @@ EXTRA_ARGS=""
if [[ ! -z "${GITEA_RUNNER_LABELS}" ]]; then
EXTRA_ARGS="${EXTRA_ARGS} --labels ${GITEA_RUNNER_LABELS}"
fi
if [[ ! -z "${GITEA_RUNNER_EPHEMERAL}" ]]; then
EXTRA_ARGS="${EXTRA_ARGS} --ephemeral"
fi
RUN_ARGS=""
if [[ ! -z "${GITEA_RUNNER_ONCE}" ]]; then
RUN_ARGS="${RUN_ARGS} --once"
fi
# In case no token is set, it's possible to read the token from a file, i.e. a Docker Secret
if [[ -z "${GITEA_RUNNER_REGISTRATION_TOKEN}" ]] && [[ -f "${GITEA_RUNNER_REGISTRATION_TOKEN_FILE}" ]]; then
@ -54,4 +61,4 @@ fi
unset GITEA_RUNNER_REGISTRATION_TOKEN
unset GITEA_RUNNER_REGISTRATION_TOKEN_FILE
exec act_runner daemon ${CONFIG_ARG}
exec act_runner daemon ${CONFIG_ARG} ${RUN_ARGS}

3
scripts/s6/act_runner/finish Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
exec s6-svscanctl -t /etc/s6

5
scripts/s6/act_runner/run Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
s6-svwait -U /etc/s6/docker
exec run.sh

6
scripts/s6/docker/data/check Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
if ! docker info &> /dev/null; then
echo "Waiting for Docker daemon to start..."
exit 1
fi

4
scripts/s6/docker/finish Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
exec s6-svscanctl -t /etc/s6

View File

@ -0,0 +1 @@
3

3
scripts/s6/docker/run Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
exec s6-notifyoncheck dockerd-entrypoint.sh

View File

@ -1,17 +0,0 @@
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
[program:dockerd]
command=/usr/local/bin/dockerd-entrypoint.sh
[program:act_runner]
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
redirect_stderr=true
command=/opt/act/rootless.sh
[eventlistener:processes]
command=bash -c "echo READY && read line && kill -SIGQUIT $PPID"
events=PROCESS_STATE_STOPPED,PROCESS_STATE_EXITED,PROCESS_STATE_FATAL