Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48a589eb79 | ||
|
|
fef300dd5b | ||
|
|
49a0b6f167 | ||
|
|
e5fdaadbd2 | ||
|
|
ab382dc256 | ||
|
|
2c66de4df2 | ||
|
|
9de33d4252 | ||
|
|
ff8375c6e1 | ||
| 3e22214bbd | |||
| b4e90db372 | |||
| c6b9e0c2d1 | |||
| aefab79307 | |||
| a6c08576a9 | |||
|
|
90d11b8692 | ||
|
|
c4b57fbcb2 | ||
|
|
e6dbe2a1ca | ||
|
|
774f316c8b | ||
|
|
823e9489d6 | ||
|
|
dc38bf1895 | ||
|
|
96b866a3a8 | ||
|
|
3b11bac2ad | ||
|
|
47caafd037 | ||
|
|
50e0509007 |
@@ -1,85 +0,0 @@
|
||||
---
|
||||
name: release-nightly
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
DOCKER_ORG: gitea
|
||||
DOCKER_LATEST: nightly
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
args: release --nightly
|
||||
env:
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
S3_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.AWS_BUCKET }}
|
||||
GORELEASER_FORCE_TOKEN: "gitea"
|
||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-image:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
variant:
|
||||
- target: basic
|
||||
tag_suffix: ""
|
||||
- target: dind
|
||||
tag_suffix: "-dind"
|
||||
- target: dind-rootless
|
||||
tag_suffix: "-dind-rootless"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0 # all history for all branches and tags
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker BuildX
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Echo the tag
|
||||
run: echo "${{ env.DOCKER_ORG }}/act_runner:nightly${{ matrix.variant.tag_suffix }}"
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
target: ${{ matrix.variant.target }}
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.DOCKER_ORG }}/act_runner:nightly${{ matrix.variant.tag_suffix }}
|
||||
@@ -1,111 +1,76 @@
|
||||
name: release-tag
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # all history for all branches and tags
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: "go.mod"
|
||||
- name: Import GPG key
|
||||
id: import_gpg
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.PASSPHRASE }}
|
||||
fingerprint: CC64B1DB67ABBEECAB24B6455FC346329753F4B0
|
||||
- name: goreleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
args: release
|
||||
env:
|
||||
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
|
||||
AWS_REGION: ${{ secrets.AWS_REGION }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
S3_REGION: ${{ secrets.AWS_REGION }}
|
||||
S3_BUCKET: ${{ secrets.AWS_BUCKET }}
|
||||
GORELEASER_FORCE_TOKEN: "gitea"
|
||||
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
|
||||
release-image:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
env:
|
||||
DOCKER_ORG: gitea
|
||||
DOCKER_LATEST: latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # all history for all branches and tags
|
||||
go-version-file: 'go.mod'
|
||||
cache: false
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker BuildX
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Get Meta
|
||||
id: meta
|
||||
- name: Build
|
||||
run: |
|
||||
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
||||
echo REPO_VERSION=${GITHUB_REF_NAME#v} >> $GITHUB_OUTPUT
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
EXT=""
|
||||
if [ "${{ matrix.goos }}" = "windows" ]; then
|
||||
EXT=".exe"
|
||||
fi
|
||||
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \
|
||||
go build -ldflags "-X gitea.com/gitea/act_runner/internal/pkg/ver.version=${VERSION}" \
|
||||
-o act_runner-${{ matrix.goos }}-${{ matrix.goarch }}${EXT}
|
||||
env:
|
||||
GOPRIVATE: git.marketally.com
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
target: basic
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ 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: act_runner-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: act_runner-*
|
||||
|
||||
- name: Build and push dind
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
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
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build and push dind-rootless
|
||||
uses: docker/build-push-action@v5
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
target: dind-rootless
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-dind-rootless
|
||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind-rootless
|
||||
path: artifacts
|
||||
|
||||
- name: Prepare release files
|
||||
run: |
|
||||
mkdir -p release
|
||||
find artifacts -type f -name 'act_runner-*' -exec mv {} release/ \;
|
||||
cd release && sha256sum * > checksums.txt
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: release/*
|
||||
generate_release_notes: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
name: checks
|
||||
name: CI
|
||||
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: check and test
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- name: vet checks
|
||||
cache: false
|
||||
|
||||
- name: Vet
|
||||
run: make vet
|
||||
- name: build
|
||||
env:
|
||||
GOPRIVATE: git.marketally.com
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
- name: test
|
||||
env:
|
||||
GOPRIVATE: git.marketally.com
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
env:
|
||||
GOPRIVATE: git.marketally.com
|
||||
|
||||
@@ -16,6 +16,7 @@ builds:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- loong64
|
||||
- s390x
|
||||
- riscv64
|
||||
goarm:
|
||||
|
||||
@@ -17,7 +17,7 @@ RUN make clean && make build
|
||||
### DIND VARIANT
|
||||
#
|
||||
#
|
||||
FROM docker:dind AS dind
|
||||
FROM docker:28-dind AS dind
|
||||
|
||||
RUN apk add --no-cache s6 bash git tzdata
|
||||
|
||||
@@ -32,7 +32,7 @@ ENTRYPOINT ["s6-svscan","/etc/s6"]
|
||||
### DIND-ROOTLESS VARIANT
|
||||
#
|
||||
#
|
||||
FROM docker:dind-rootless AS dind-rootless
|
||||
FROM docker:28-dind-rootless AS dind-rootless
|
||||
|
||||
USER root
|
||||
RUN apk add --no-cache s6 bash git tzdata
|
||||
@@ -59,8 +59,6 @@ RUN apk add --no-cache tini bash git tzdata
|
||||
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"]
|
||||
|
||||
121
HOWTOSTART.md
Normal file
121
HOWTOSTART.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# How to Start a GitCaddy Runner
|
||||
|
||||
This guide explains how to set up and start a GitCaddy Actions runner (act_runner) to execute your CI/CD workflows.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Linux, macOS, or Windows machine
|
||||
- Network access to your GitCaddy/Gitea instance
|
||||
- (Optional) Docker installed for container-based workflows
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Download the Runner
|
||||
|
||||
Download the latest release from the [releases page](https://git.marketally.com/gitcaddy/act_runner/releases) or build from source:
|
||||
|
||||
```bash
|
||||
git clone https://git.marketally.com/gitcaddy/act_runner.git
|
||||
cd act_runner
|
||||
make build
|
||||
```
|
||||
|
||||
### 2. Register the Runner
|
||||
|
||||
Get a registration token from your GitCaddy instance:
|
||||
- **Global runners**: Admin Area → Actions → Runners → Create Runner
|
||||
- **Organization runners**: Organization Settings → Actions → Runners
|
||||
- **Repository runners**: Repository Settings → Actions → Runners
|
||||
|
||||
Then register:
|
||||
|
||||
```bash
|
||||
./act_runner register --no-interactive \
|
||||
--instance https://your-gitea-instance.com \
|
||||
--token YOUR_REGISTRATION_TOKEN \
|
||||
--name my-runner \
|
||||
--labels linux,ubuntu-latest
|
||||
```
|
||||
|
||||
### 3. Start the Runner
|
||||
|
||||
```bash
|
||||
./act_runner daemon
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Runner Labels
|
||||
|
||||
Labels determine which jobs the runner can execute. Configure labels during registration or edit them in the admin UI.
|
||||
|
||||
Common labels:
|
||||
- `linux`, `linux-latest` - Linux runners
|
||||
- `windows`, `windows-latest` - Windows runners
|
||||
- `macos`, `macos-latest` - macOS runners
|
||||
- `ubuntu`, `ubuntu-latest` - Ubuntu-specific
|
||||
- `self-hosted` - Self-hosted runners
|
||||
|
||||
### Running as a Service
|
||||
|
||||
#### Linux (systemd)
|
||||
|
||||
```bash
|
||||
sudo cat > /etc/systemd/system/act_runner.service << 'SERVICE'
|
||||
[Unit]
|
||||
Description=GitCaddy Actions Runner
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=runner
|
||||
WorkingDirectory=/opt/act_runner
|
||||
ExecStart=/opt/act_runner/act_runner daemon
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
SERVICE
|
||||
|
||||
sudo systemctl enable act_runner
|
||||
sudo systemctl start act_runner
|
||||
```
|
||||
|
||||
### Docker Support
|
||||
|
||||
For workflows that use container actions, ensure Docker is installed and the runner user has access:
|
||||
|
||||
```bash
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
## Capabilities Detection
|
||||
|
||||
The runner automatically detects and reports:
|
||||
- Operating system and architecture
|
||||
- Available shells (bash, sh, powershell)
|
||||
- Installed tools (node, python, go, etc.)
|
||||
- Docker availability
|
||||
- Disk space and network bandwidth
|
||||
|
||||
These capabilities help admins understand what each runner can handle.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Runner not connecting
|
||||
|
||||
1. Check network connectivity to your GitCaddy instance
|
||||
2. Verify the registration token is valid
|
||||
3. Check firewall rules allow outbound HTTPS
|
||||
|
||||
### Jobs not running
|
||||
|
||||
1. Verify runner labels match the job's `runs-on` requirement
|
||||
2. Check runner is online in the admin panel
|
||||
3. Review runner logs: `journalctl -u act_runner -f`
|
||||
|
||||
## More Information
|
||||
|
||||
- [act_runner Repository](https://git.marketally.com/gitcaddy/act_runner)
|
||||
- [GitCaddy Documentation](https://git.marketally.com/gitcaddy/gitea)
|
||||
13
Makefile
13
Makefile
@@ -1,7 +1,6 @@
|
||||
DIST := dist
|
||||
EXECUTABLE := act_runner
|
||||
GOFMT ?= gofumpt -l
|
||||
DIST := dist
|
||||
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
||||
GO ?= go
|
||||
SHASUM ?= shasum -a 256
|
||||
@@ -21,6 +20,8 @@ DOCKER_TAG ?= nightly
|
||||
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
||||
DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless
|
||||
|
||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||
|
||||
ifneq ($(shell uname), Darwin)
|
||||
EXTLDFLAGS = -extldflags "-static" $(null)
|
||||
else
|
||||
@@ -102,7 +103,15 @@ fmt-check:
|
||||
exit 1; \
|
||||
fi;
|
||||
|
||||
test: fmt-check
|
||||
.PHONY: deps-tools
|
||||
deps-tools: ## install tool dependencies
|
||||
$(GO) install $(GOVULNCHECK_PACKAGE)
|
||||
|
||||
.PHONY: security-check
|
||||
security-check: deps-tools
|
||||
GOEXPERIMENT= $(GO) run $(GOVULNCHECK_PACKAGE) -show color ./...
|
||||
|
||||
test: fmt-check security-check
|
||||
@$(GO) test -v -cover -coverprofile coverage.txt ./... && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||
|
||||
.PHONY: vet
|
||||
|
||||
221
README.md
221
README.md
@@ -1,108 +1,195 @@
|
||||
# act runner
|
||||
# GitCaddy Act Runner
|
||||
|
||||
Act runner is a runner for Gitea based on [Gitea fork](https://gitea.com/gitea/act) of [act](https://github.com/nektos/act).
|
||||
A Gitea Actions runner with enhanced capability detection and reporting for AI-friendly workflow generation.
|
||||
|
||||
> **This is a GitCaddy fork** of [gitea.com/gitea/act_runner](https://gitea.com/gitea/act_runner) with runner capability discovery features.
|
||||
|
||||
## Overview
|
||||
|
||||
Act Runner executes Gitea Actions workflows using [act](https://github.com/nektos/act). This fork adds automatic capability detection, enabling Gitea to expose runner capabilities via API for AI tools to query before generating workflows.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Capability Detection**: Automatically detects OS, architecture, Docker support, available shells, and installed tools
|
||||
- **Capability Reporting**: Reports capabilities to Gitea server during runner declaration
|
||||
- **Full Compatibility**: Drop-in replacement for standard act_runner
|
||||
- **Multi-Platform**: Supports Linux, macOS, and Windows
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
### Download Pre-built Binary
|
||||
|
||||
Docker Engine Community version is required for docker mode. To install Docker CE, follow the official [install instructions](https://docs.docker.com/engine/install/).
|
||||
|
||||
### Download pre-built binary
|
||||
|
||||
Visit [here](https://dl.gitea.com/act_runner/) and download the right version for your platform.
|
||||
|
||||
### Build from source
|
||||
Download from [Releases](https://git.marketally.com/gitcaddy/act_runner/releases):
|
||||
|
||||
```bash
|
||||
# Linux (amd64)
|
||||
curl -L -o act_runner https://git.marketally.com/gitcaddy/act_runner/releases/download/v0.3.1-gitcaddy/act_runner-linux-amd64
|
||||
chmod +x act_runner
|
||||
|
||||
# macOS (Apple Silicon)
|
||||
curl -L -o act_runner https://git.marketally.com/gitcaddy/act_runner/releases/download/v0.3.1-gitcaddy/act_runner-darwin-arm64
|
||||
chmod +x act_runner
|
||||
```
|
||||
|
||||
### Build from Source
|
||||
|
||||
```bash
|
||||
git clone https://git.marketally.com/gitcaddy/act_runner.git
|
||||
cd act_runner
|
||||
make build
|
||||
```
|
||||
|
||||
### Build a docker image
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
make docker
|
||||
```
|
||||
### 1. Enable Actions in Gitea
|
||||
|
||||
## Quickstart
|
||||
Add to your Gitea `app.ini`:
|
||||
|
||||
Actions are disabled by default, so you need to add the following to the configuration file of your Gitea instance to enable it:
|
||||
|
||||
```ini
|
||||
[actions]
|
||||
ENABLED=true
|
||||
ENABLED = true
|
||||
```
|
||||
|
||||
### Register
|
||||
### 2. Register the Runner
|
||||
|
||||
```bash
|
||||
./act_runner register
|
||||
./act_runner register \
|
||||
--instance https://your-gitea-instance.com \
|
||||
--token YOUR_RUNNER_TOKEN \
|
||||
--name my-runner \
|
||||
--labels ubuntu-latest,docker
|
||||
```
|
||||
|
||||
And you will be asked to input:
|
||||
|
||||
1. Gitea instance URL, like `http://192.168.8.8:3000/`. You should use your gitea instance ROOT_URL as the instance argument
|
||||
and you should not use `localhost` or `127.0.0.1` as instance IP;
|
||||
2. Runner token, you can get it from `http://192.168.8.8:3000/admin/actions/runners`;
|
||||
3. Runner name, you can just leave it blank;
|
||||
4. Runner labels, you can just leave it blank.
|
||||
|
||||
The process looks like:
|
||||
|
||||
```text
|
||||
INFO Registering runner, arch=amd64, os=darwin, version=0.1.5.
|
||||
WARN Runner in user-mode.
|
||||
INFO Enter the Gitea instance URL (for example, https://gitea.com/):
|
||||
http://192.168.8.8:3000/
|
||||
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://docker.gitea.com/runner-images:ubuntu-latest):
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
You can also register with command line arguments.
|
||||
|
||||
```bash
|
||||
./act_runner register --instance http://192.168.8.8:3000 --token <my_runner_token> --no-interactive
|
||||
```
|
||||
|
||||
If the registry succeed, it will run immediately. Next time, you could run the runner directly.
|
||||
|
||||
### Run
|
||||
### 3. Start the Runner
|
||||
|
||||
```bash
|
||||
./act_runner daemon
|
||||
```
|
||||
|
||||
### Run with docker
|
||||
On startup, the runner will:
|
||||
1. Detect system capabilities (OS, arch, Docker, shells, tools)
|
||||
2. Report capabilities to Gitea via the Declare API
|
||||
3. Begin polling for jobs
|
||||
|
||||
```bash
|
||||
docker run -e GITEA_INSTANCE_URL=https://your_gitea.com -e GITEA_RUNNER_REGISTRATION_TOKEN=<your_token> -v /var/run/docker.sock:/var/run/docker.sock --name my_runner gitea/act_runner:nightly
|
||||
## Capability Detection
|
||||
|
||||
The runner automatically detects:
|
||||
|
||||
| Category | Examples |
|
||||
|----------|----------|
|
||||
| **OS/Arch** | linux/amd64, darwin/arm64, windows/amd64 |
|
||||
| **Container Runtime** | Docker, Podman |
|
||||
| **Shells** | bash, sh, zsh, powershell, cmd |
|
||||
| **Tools** | Node.js, Go, Python, Java, .NET, Rust |
|
||||
| **Features** | Cache support, Docker Compose |
|
||||
|
||||
### Example Capabilities JSON
|
||||
|
||||
```json
|
||||
{
|
||||
"os": "linux",
|
||||
"arch": "amd64",
|
||||
"docker": true,
|
||||
"docker_compose": true,
|
||||
"container_runtime": "docker",
|
||||
"shell": ["bash", "sh"],
|
||||
"tools": {
|
||||
"node": ["18.19.0"],
|
||||
"go": ["1.21.5"],
|
||||
"python": ["3.11.6"]
|
||||
},
|
||||
"features": {
|
||||
"cache": true,
|
||||
"docker_services": true
|
||||
},
|
||||
"limitations": []
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration
|
||||
## Configuration
|
||||
|
||||
You can also configure the runner with a configuration file.
|
||||
The configuration file is a YAML file, you can generate a sample configuration file with `./act_runner generate-config`.
|
||||
Create a config file or use command-line flags:
|
||||
|
||||
```bash
|
||||
./act_runner generate-config > config.yaml
|
||||
./act_runner -c config.yaml daemon
|
||||
```
|
||||
|
||||
You can specify the configuration file path with `-c`/`--config` argument.
|
||||
Example configuration:
|
||||
|
||||
```yaml
|
||||
log:
|
||||
level: info
|
||||
|
||||
runner:
|
||||
file: .runner
|
||||
capacity: 1
|
||||
timeout: 3h
|
||||
labels:
|
||||
- ubuntu-latest:docker://node:18-bullseye
|
||||
- ubuntu-22.04:docker://ubuntu:22.04
|
||||
|
||||
container:
|
||||
docker_host: ""
|
||||
force_pull: false
|
||||
privileged: false
|
||||
|
||||
cache:
|
||||
enabled: true
|
||||
dir: ~/.cache/actcache
|
||||
```
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
```bash
|
||||
./act_runner -c config.yaml register # register with config file
|
||||
./act_runner -c config.yaml daemon # run with config file
|
||||
docker run -d \
|
||||
--name act_runner \
|
||||
-e GITEA_INSTANCE_URL=https://your-gitea.com \
|
||||
-e GITEA_RUNNER_REGISTRATION_TOKEN=<token> \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ./data:/data \
|
||||
gitcaddy/act_runner:latest
|
||||
```
|
||||
|
||||
You can read the latest version of the configuration file online at [config.example.yaml](internal/pkg/config/config.example.yaml).
|
||||
## GitCaddy Integration
|
||||
|
||||
### Example Deployments
|
||||
This runner is designed to work with the [GitCaddy Gitea fork](https://git.marketally.com/gitcaddy/gitea), which provides:
|
||||
|
||||
Check out the [examples](examples) directory for sample deployment types.
|
||||
- **Runner Capabilities API** (`/api/v2/repos/{owner}/{repo}/actions/runners/capabilities`)
|
||||
- **Workflow Validation API** for pre-flight checks
|
||||
- **Action Compatibility Database** for GitHub Actions mapping
|
||||
|
||||
### How It Works
|
||||
|
||||
```
|
||||
|
||||
act_runner Gitea AI Tool
|
||||
| | |
|
||||
| Declare + Capabilities | |
|
||||
|---------------------------->| |
|
||||
| | |
|
||||
| | GET /api/v2/.../caps |
|
||||
| |<------------------------|
|
||||
| | |
|
||||
| | Runner capabilities |
|
||||
| |------------------------>|
|
||||
| | |
|
||||
| | Generates workflow |
|
||||
| | with correct config |
|
||||
|
||||
```
|
||||
|
||||
## Related Projects
|
||||
|
||||
| Project | Description |
|
||||
|---------|-------------|
|
||||
| [gitcaddy/gitea](https://git.marketally.com/gitcaddy/gitea) | Gitea with AI-friendly enhancements |
|
||||
| [gitcaddy/actions-proto-go](https://git.marketally.com/gitcaddy/actions-proto-go) | Protocol definitions with capability support |
|
||||
|
||||
## Upstream
|
||||
|
||||
This project is a fork of [gitea.com/gitea/act_runner](https://gitea.com/gitea/act_runner). We contribute enhancements back to upstream where appropriate.
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
||||
47
go.mod
47
go.mod
@@ -1,39 +1,44 @@
|
||||
module gitea.com/gitea/act_runner
|
||||
|
||||
go 1.24
|
||||
go 1.24.0
|
||||
|
||||
toolchain go1.24.11
|
||||
|
||||
require (
|
||||
code.gitea.io/actions-proto-go v0.4.1
|
||||
code.gitea.io/actions-proto-go v0.5.2
|
||||
code.gitea.io/gitea-vet v0.2.3
|
||||
connectrpc.com/connect v1.16.2
|
||||
github.com/avast/retry-go/v4 v4.6.0
|
||||
github.com/docker/docker v25.0.5+incompatible
|
||||
github.com/docker/docker v25.0.13+incompatible
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/nektos/act v0.0.0 // will be replaced
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/term v0.31.0
|
||||
golang.org/x/time v0.5.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
github.com/stretchr/testify v1.11.1
|
||||
golang.org/x/term v0.36.0
|
||||
golang.org/x/time v0.12.0
|
||||
google.golang.org/protobuf v1.35.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.1
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.37.0
|
||||
|
||||
require (
|
||||
cyphar.com/go-pathrs v0.2.1 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/containerd/containerd v1.7.13 // indirect
|
||||
github.com/containerd/containerd v1.7.29 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/creack/pty v1.1.21 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/cli v25.0.3+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.2 // indirect
|
||||
@@ -65,10 +70,11 @@ require (
|
||||
github.com/moby/buildkit v0.12.5 // indirect
|
||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
||||
github.com/moby/sys/user v0.1.0 // indirect
|
||||
github.com/moby/sys/user v0.3.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/opencontainers/selinux v1.13.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -89,15 +95,20 @@ require (
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/crypto v0.43.0 // indirect
|
||||
golang.org/x/net v0.45.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/tools v0.23.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/nektos/act => gitea.com/gitea/act v0.261.7
|
||||
replace github.com/nektos/act => gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742
|
||||
|
||||
replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.16.2
|
||||
|
||||
// Remove after
|
||||
replace github.com/distribution/reference v0.6.0 => github.com/distribution/reference v0.5.0
|
||||
|
||||
// Use GitCaddy fork with capability support
|
||||
replace code.gitea.io/actions-proto-go => git.marketally.com/gitcaddy/actions-proto-go v0.5.7
|
||||
|
||||
98
go.sum
98
go.sum
@@ -1,13 +1,15 @@
|
||||
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=
|
||||
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
|
||||
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
|
||||
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.7 h1:0tX0EdWo5uZUgsN/iJGyEAtagqYURrbOuWxyx+LV1Wk=
|
||||
gitea.com/gitea/act v0.261.7/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||
git.marketally.com/gitcaddy/actions-proto-go v0.5.7 h1:RUbafr3Vkw2l4WfSwa+oF+Ihakbm05W0FlAmXuQrDJc=
|
||||
git.marketally.com/gitcaddy/actions-proto-go v0.5.7/go.mod h1:RPu21UoRD3zSAujoZR6LJwuVNa2uFRBveadslczCRfQ=
|
||||
gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742 h1:ulcquQluJbmNASkh6ina70LvcHEa9eWYfQ+DeAZ0VEE=
|
||||
gitea.com/gitea/act v0.261.7-0.20251202193638-5417d3ac6742/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=
|
||||
@@ -17,8 +19,8 @@ github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
||||
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
||||
github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
|
||||
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
|
||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
@@ -31,15 +33,15 @@ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqy
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is=
|
||||
github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4=
|
||||
github.com/containerd/containerd v1.7.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE=
|
||||
github.com/containerd/containerd v1.7.29/go.mod h1:azUkWcOvHrWvaiUjSQH0fjzuHIwSPg1WL5PshGP4Szs=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||
github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -49,8 +51,8 @@ github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6
|
||||
github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
|
||||
github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v25.0.13+incompatible h1:YeBrkUd3q0ZoRDNoEzuopwCLU+uD8GZahDHwBdsTnkU=
|
||||
github.com/docker/docker v25.0.13+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
@@ -134,8 +136,10 @@ github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkV
|
||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
||||
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
|
||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
@@ -144,10 +148,10 @@ github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84=
|
||||
github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s=
|
||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -187,8 +191,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928 h1:zjNCuOOhh1TKRU0Ru3PPPJt80z7eReswCao91gBLk00=
|
||||
github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928/go.mod h1:PCFYfAEfKT+Nd6zWvUpsXduMR1bXFLf0uGSlEF05MCI=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
@@ -217,8 +221,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMey
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
@@ -227,26 +231,26 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -261,18 +265,18 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
@@ -284,15 +288,15 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
||||
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
||||
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
@@ -30,6 +30,20 @@ import (
|
||||
"gitea.com/gitea/act_runner/internal/pkg/ver"
|
||||
)
|
||||
|
||||
const (
|
||||
// DiskSpaceWarningThreshold is the percentage at which to warn about low disk space
|
||||
DiskSpaceWarningThreshold = 85.0
|
||||
// DiskSpaceCriticalThreshold is the percentage at which to log critical warnings
|
||||
DiskSpaceCriticalThreshold = 95.0
|
||||
// CapabilitiesUpdateInterval is how often to update capabilities (including disk space)
|
||||
CapabilitiesUpdateInterval = 5 * time.Minute
|
||||
// BandwidthTestInterval is how often to run bandwidth tests (hourly)
|
||||
BandwidthTestInterval = 1 * time.Hour
|
||||
)
|
||||
|
||||
// Global bandwidth manager - accessible for triggering manual tests
|
||||
var bandwidthManager *envcheck.BandwidthManager
|
||||
|
||||
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)
|
||||
@@ -136,8 +150,30 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
|
||||
|
||||
runner := run.NewRunner(cfg, reg, cli)
|
||||
|
||||
// Detect runner capabilities for AI-friendly workflow generation
|
||||
dockerHost := cfg.Container.DockerHost
|
||||
if dockerHost == "" {
|
||||
if dh, err := getDockerSocketPath(""); err == nil {
|
||||
dockerHost = dh
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize bandwidth manager with the Gitea server address
|
||||
bandwidthManager = envcheck.NewBandwidthManager(reg.Address, BandwidthTestInterval)
|
||||
bandwidthManager.Start(ctx)
|
||||
log.Infof("bandwidth manager started, testing against: %s", reg.Address)
|
||||
|
||||
capabilities := envcheck.DetectCapabilities(ctx, dockerHost)
|
||||
// Include initial bandwidth result if available
|
||||
capabilities.Bandwidth = bandwidthManager.GetLastResult()
|
||||
capabilitiesJson := capabilities.ToJSON()
|
||||
log.Infof("detected capabilities: %s", capabilitiesJson)
|
||||
|
||||
// Check disk space and warn if low
|
||||
checkDiskSpaceWarnings(capabilities)
|
||||
|
||||
// declare the labels of the runner before fetching tasks
|
||||
resp, err := runner.Declare(ctx, ls.Names())
|
||||
resp, err := runner.Declare(ctx, ls.Names(), capabilitiesJson)
|
||||
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
|
||||
log.Errorf("Your Gitea version is too old to support runner declare, please upgrade to v1.21 or later")
|
||||
return err
|
||||
@@ -149,7 +185,11 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
|
||||
resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels)
|
||||
}
|
||||
|
||||
// Start periodic capabilities update goroutine
|
||||
go periodicCapabilitiesUpdate(ctx, runner, ls.Names(), dockerHost)
|
||||
|
||||
poller := poll.New(cfg, cli, runner)
|
||||
poller.SetBandwidthManager(bandwidthManager)
|
||||
|
||||
if daemArgs.Once || reg.Ephemeral {
|
||||
done := make(chan struct{})
|
||||
@@ -183,6 +223,67 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
|
||||
}
|
||||
}
|
||||
|
||||
// checkDiskSpaceWarnings logs warnings if disk space is low
|
||||
func checkDiskSpaceWarnings(capabilities *envcheck.RunnerCapabilities) {
|
||||
if capabilities.Disk == nil {
|
||||
return
|
||||
}
|
||||
|
||||
usedPercent := capabilities.Disk.UsedPercent
|
||||
freeGB := float64(capabilities.Disk.Free) / (1024 * 1024 * 1024)
|
||||
|
||||
if usedPercent >= DiskSpaceCriticalThreshold {
|
||||
log.Errorf("CRITICAL: Disk space critically low! %.1f%% used, only %.2f GB free. Runner may fail to execute jobs!", usedPercent, freeGB)
|
||||
} else if usedPercent >= DiskSpaceWarningThreshold {
|
||||
log.Warnf("WARNING: Disk space running low. %.1f%% used, %.2f GB free. Consider cleaning up disk space.", usedPercent, freeGB)
|
||||
}
|
||||
}
|
||||
|
||||
// periodicCapabilitiesUpdate periodically updates capabilities including disk space and bandwidth
|
||||
func periodicCapabilitiesUpdate(ctx context.Context, runner *run.Runner, labelNames []string, dockerHost string) {
|
||||
ticker := time.NewTicker(CapabilitiesUpdateInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Debug("stopping periodic capabilities update")
|
||||
if bandwidthManager != nil {
|
||||
bandwidthManager.Stop()
|
||||
}
|
||||
return
|
||||
case <-ticker.C:
|
||||
// Detect updated capabilities (disk space changes over time)
|
||||
capabilities := envcheck.DetectCapabilities(ctx, dockerHost)
|
||||
|
||||
// Include latest bandwidth result
|
||||
if bandwidthManager != nil {
|
||||
capabilities.Bandwidth = bandwidthManager.GetLastResult()
|
||||
}
|
||||
|
||||
capabilitiesJson := capabilities.ToJSON()
|
||||
|
||||
// Check for disk space warnings
|
||||
checkDiskSpaceWarnings(capabilities)
|
||||
|
||||
// Send updated capabilities to server
|
||||
_, err := runner.Declare(ctx, labelNames, capabilitiesJson)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug("failed to update capabilities")
|
||||
} else {
|
||||
bandwidthInfo := ""
|
||||
if capabilities.Bandwidth != nil {
|
||||
bandwidthInfo = fmt.Sprintf(", bandwidth: %.1f Mbps", capabilities.Bandwidth.DownloadMbps)
|
||||
}
|
||||
log.Debugf("capabilities updated: disk %.1f%% used, %.2f GB free%s",
|
||||
capabilities.Disk.UsedPercent,
|
||||
float64(capabilities.Disk.Free)/(1024*1024*1024),
|
||||
bandwidthInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type daemonArgs struct {
|
||||
Once bool
|
||||
}
|
||||
|
||||
@@ -18,13 +18,15 @@ import (
|
||||
"gitea.com/gitea/act_runner/internal/app/run"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||
"gitea.com/gitea/act_runner/internal/pkg/envcheck"
|
||||
)
|
||||
|
||||
type Poller struct {
|
||||
client client.Client
|
||||
runner *run.Runner
|
||||
cfg *config.Config
|
||||
tasksVersion atomic.Int64 // tasksVersion used to store the version of the last task fetched from the Gitea.
|
||||
client client.Client
|
||||
runner *run.Runner
|
||||
cfg *config.Config
|
||||
tasksVersion atomic.Int64 // tasksVersion used to store the version of the last task fetched from the Gitea.
|
||||
bandwidthManager *envcheck.BandwidthManager
|
||||
|
||||
pollingCtx context.Context
|
||||
shutdownPolling context.CancelFunc
|
||||
@@ -57,6 +59,11 @@ func New(cfg *config.Config, client client.Client, runner *run.Runner) *Poller {
|
||||
}
|
||||
}
|
||||
|
||||
// SetBandwidthManager sets the bandwidth manager for on-demand testing
|
||||
func (p *Poller) SetBandwidthManager(bm *envcheck.BandwidthManager) {
|
||||
p.bandwidthManager = bm
|
||||
}
|
||||
|
||||
func (p *Poller) Poll() {
|
||||
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
|
||||
wg := &sync.WaitGroup{}
|
||||
@@ -157,11 +164,23 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
|
||||
reqCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.FetchTimeout)
|
||||
defer cancel()
|
||||
|
||||
// Detect capabilities including current disk space
|
||||
caps := envcheck.DetectCapabilities(ctx, p.cfg.Container.DockerHost)
|
||||
|
||||
// Include latest bandwidth result if available
|
||||
if p.bandwidthManager != nil {
|
||||
caps.Bandwidth = p.bandwidthManager.GetLastResult()
|
||||
}
|
||||
|
||||
capsJson := caps.ToJSON()
|
||||
|
||||
// Load the version value that was in the cache when the request was sent.
|
||||
v := p.tasksVersion.Load()
|
||||
resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(&runnerv1.FetchTaskRequest{
|
||||
TasksVersion: v,
|
||||
}))
|
||||
fetchReq := &runnerv1.FetchTaskRequest{
|
||||
TasksVersion: v,
|
||||
CapabilitiesJson: capsJson,
|
||||
}
|
||||
resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(fetchReq))
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
err = nil
|
||||
}
|
||||
@@ -174,6 +193,18 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Check if server requested a bandwidth test
|
||||
if resp.Msg.RequestBandwidthTest && p.bandwidthManager != nil {
|
||||
log.Info("Server requested bandwidth test, running now...")
|
||||
go func() {
|
||||
result := p.bandwidthManager.RunTest(ctx)
|
||||
if result != nil {
|
||||
log.Infof("Bandwidth test completed: %.1f Mbps download, %.0f ms latency",
|
||||
result.DownloadMbps, result.Latency)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if resp.Msg.TasksVersion > v {
|
||||
p.tasksVersion.CompareAndSwap(v, resp.Msg.TasksVersion)
|
||||
}
|
||||
@@ -182,7 +213,7 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// got a task, set `tasksVersion` to zero to focre query db in next request.
|
||||
// got a task, set tasksVersion to zero to force query db in next request.
|
||||
p.tasksVersion.CompareAndSwap(resp.Msg.TasksVersion, 0)
|
||||
|
||||
return resp.Msg.Task, true
|
||||
|
||||
@@ -249,9 +249,10 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
||||
return execErr
|
||||
}
|
||||
|
||||
func (r *Runner) Declare(ctx context.Context, labels []string) (*connect.Response[runnerv1.DeclareResponse], error) {
|
||||
func (r *Runner) Declare(ctx context.Context, labels []string, capabilitiesJson string) (*connect.Response[runnerv1.DeclareResponse], error) {
|
||||
return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{
|
||||
Version: ver.Version(),
|
||||
Labels: labels,
|
||||
Version: ver.Version(),
|
||||
Labels: labels,
|
||||
CapabilitiesJson: capabilitiesJson,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ runner:
|
||||
env_file: .env
|
||||
# The timeout for a job to be finished.
|
||||
# Please note that the Gitea instance also has a timeout (3h by default) for the job.
|
||||
# So the job could be stopped by the Gitea instance if it's timeout is shorter than this.
|
||||
# So the job could be stopped by the Gitea instance if its timeout is shorter than this.
|
||||
timeout: 3h
|
||||
# The timeout for the runner to wait for running jobs to finish when shutting down.
|
||||
# Any running jobs that haven't finished after this timeout will be cancelled.
|
||||
@@ -39,13 +39,13 @@ runner:
|
||||
github_mirror: ''
|
||||
# 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://docker.gitea.com/runner-images:ubuntu-latest"
|
||||
# Find more images provided by Gitea at https://gitea.com/docker.gitea.com/runner-images .
|
||||
# Find more images provided by Gitea at https://gitea.com/gitea/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://docker.gitea.com/runner-images:ubuntu-latest"
|
||||
- "ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04"
|
||||
- "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.
|
||||
@@ -72,7 +72,7 @@ container:
|
||||
network: ""
|
||||
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
|
||||
privileged: false
|
||||
# And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway).
|
||||
# Any other options to be used when the container is started (e.g., --add-host=my.gitea.url:host-gateway).
|
||||
options:
|
||||
# The parent directory of a job's working directory.
|
||||
# NOTE: There is no need to add the first '/' of the path as act_runner will add it automatically.
|
||||
@@ -90,7 +90,7 @@ container:
|
||||
# valid_volumes:
|
||||
# - '**'
|
||||
valid_volumes: []
|
||||
# overrides the docker client host with the specified one.
|
||||
# Overrides the docker client host with the specified one.
|
||||
# If it's empty, act_runner will find an available docker host automatically.
|
||||
# If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers.
|
||||
# If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work.
|
||||
|
||||
209
internal/pkg/envcheck/bandwidth.go
Normal file
209
internal/pkg/envcheck/bandwidth.go
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package envcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BandwidthInfo holds network bandwidth test results
|
||||
type BandwidthInfo struct {
|
||||
DownloadMbps float64 `json:"download_mbps"`
|
||||
UploadMbps float64 `json:"upload_mbps,omitempty"`
|
||||
Latency float64 `json:"latency_ms,omitempty"`
|
||||
TestedAt time.Time `json:"tested_at"`
|
||||
}
|
||||
|
||||
// BandwidthManager handles periodic bandwidth testing
|
||||
type BandwidthManager struct {
|
||||
serverURL string
|
||||
lastResult *BandwidthInfo
|
||||
mu sync.RWMutex
|
||||
testInterval time.Duration
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
// NewBandwidthManager creates a new bandwidth manager
|
||||
func NewBandwidthManager(serverURL string, testInterval time.Duration) *BandwidthManager {
|
||||
return &BandwidthManager{
|
||||
serverURL: serverURL,
|
||||
testInterval: testInterval,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start begins periodic bandwidth testing
|
||||
func (bm *BandwidthManager) Start(ctx context.Context) {
|
||||
// Run initial test
|
||||
bm.RunTest(ctx)
|
||||
|
||||
// Start periodic testing
|
||||
go func() {
|
||||
ticker := time.NewTicker(bm.testInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
bm.RunTest(ctx)
|
||||
case <-bm.stopChan:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop stops the periodic testing
|
||||
func (bm *BandwidthManager) Stop() {
|
||||
close(bm.stopChan)
|
||||
}
|
||||
|
||||
// RunTest runs a bandwidth test and stores the result
|
||||
func (bm *BandwidthManager) RunTest(ctx context.Context) *BandwidthInfo {
|
||||
result := TestBandwidth(ctx, bm.serverURL)
|
||||
|
||||
bm.mu.Lock()
|
||||
bm.lastResult = result
|
||||
bm.mu.Unlock()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetLastResult returns the most recent bandwidth test result
|
||||
func (bm *BandwidthManager) GetLastResult() *BandwidthInfo {
|
||||
bm.mu.RLock()
|
||||
defer bm.mu.RUnlock()
|
||||
return bm.lastResult
|
||||
}
|
||||
|
||||
// TestBandwidth tests network bandwidth to the Gitea server
|
||||
func TestBandwidth(ctx context.Context, serverURL string) *BandwidthInfo {
|
||||
if serverURL == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
info := &BandwidthInfo{
|
||||
TestedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Test latency first
|
||||
info.Latency = testLatency(ctx, serverURL)
|
||||
|
||||
// Test download speed
|
||||
info.DownloadMbps = testDownloadSpeed(ctx, serverURL)
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func testLatency(ctx context.Context, serverURL string) float64 {
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(reqCtx, "HEAD", serverURL, nil)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
latency := time.Since(start).Seconds() * 1000 // Convert to ms
|
||||
return float64(int(latency*100)) / 100 // Round to 2 decimals
|
||||
}
|
||||
|
||||
func testDownloadSpeed(ctx context.Context, serverURL string) float64 {
|
||||
// Try multiple endpoints to accumulate ~1MB of data
|
||||
endpoints := []string{
|
||||
"/assets/css/index.css",
|
||||
"/assets/js/index.js",
|
||||
"/assets/img/logo.svg",
|
||||
"/assets/img/logo.png",
|
||||
"/",
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
|
||||
var totalBytes int64
|
||||
var totalDuration time.Duration
|
||||
targetBytes := int64(1024 * 1024) // 1MB target
|
||||
maxAttempts := 10 // Limit iterations
|
||||
|
||||
for attempt := 0; attempt < maxAttempts && totalBytes < targetBytes; attempt++ {
|
||||
for _, endpoint := range endpoints {
|
||||
if totalBytes >= targetBytes {
|
||||
break
|
||||
}
|
||||
|
||||
url := serverURL + endpoint
|
||||
|
||||
reqCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
req, err := http.NewRequestWithContext(reqCtx, "GET", url, nil)
|
||||
if err != nil {
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
cancel()
|
||||
continue
|
||||
}
|
||||
|
||||
n, _ := io.Copy(io.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
cancel()
|
||||
|
||||
duration := time.Since(start)
|
||||
|
||||
if n > 0 {
|
||||
totalBytes += n
|
||||
totalDuration += duration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if totalBytes == 0 || totalDuration == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Calculate speed in Mbps
|
||||
seconds := totalDuration.Seconds()
|
||||
if seconds == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
bytesPerSecond := float64(totalBytes) / seconds
|
||||
mbps := (bytesPerSecond * 8) / (1024 * 1024)
|
||||
|
||||
return float64(int(mbps*100)) / 100
|
||||
}
|
||||
|
||||
// FormatBandwidth formats bandwidth for display
|
||||
func FormatBandwidth(mbps float64) string {
|
||||
if mbps == 0 {
|
||||
return "Unknown"
|
||||
}
|
||||
if mbps >= 1000 {
|
||||
return fmt.Sprintf("%.1f Gbps", mbps/1000)
|
||||
}
|
||||
return fmt.Sprintf("%.1f Mbps", mbps)
|
||||
}
|
||||
409
internal/pkg/envcheck/capabilities.go
Normal file
409
internal/pkg/envcheck/capabilities.go
Normal file
@@ -0,0 +1,409 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package envcheck
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
// DiskInfo holds disk space information
|
||||
type DiskInfo struct {
|
||||
Total uint64 `json:"total_bytes"`
|
||||
Free uint64 `json:"free_bytes"`
|
||||
Used uint64 `json:"used_bytes"`
|
||||
UsedPercent float64 `json:"used_percent"`
|
||||
}
|
||||
|
||||
// DistroInfo holds Linux distribution information
|
||||
type DistroInfo struct {
|
||||
ID string `json:"id,omitempty"` // e.g., "ubuntu", "debian", "fedora"
|
||||
VersionID string `json:"version_id,omitempty"` // e.g., "24.04", "12"
|
||||
PrettyName string `json:"pretty_name,omitempty"` // e.g., "Ubuntu 24.04 LTS"
|
||||
}
|
||||
|
||||
// RunnerCapabilities represents the capabilities of a runner for AI consumption
|
||||
type RunnerCapabilities struct {
|
||||
OS string `json:"os"`
|
||||
Arch string `json:"arch"`
|
||||
Distro *DistroInfo `json:"distro,omitempty"`
|
||||
Docker bool `json:"docker"`
|
||||
DockerCompose bool `json:"docker_compose"`
|
||||
ContainerRuntime string `json:"container_runtime,omitempty"`
|
||||
Shell []string `json:"shell,omitempty"`
|
||||
Tools map[string][]string `json:"tools,omitempty"`
|
||||
Features *CapabilityFeatures `json:"features,omitempty"`
|
||||
Limitations []string `json:"limitations,omitempty"`
|
||||
Disk *DiskInfo `json:"disk,omitempty"`
|
||||
Bandwidth *BandwidthInfo `json:"bandwidth,omitempty"`
|
||||
SuggestedLabels []string `json:"suggested_labels,omitempty"`
|
||||
}
|
||||
|
||||
// CapabilityFeatures represents feature support flags
|
||||
type CapabilityFeatures struct {
|
||||
ArtifactsV4 bool `json:"artifacts_v4"`
|
||||
Cache bool `json:"cache"`
|
||||
Services bool `json:"services"`
|
||||
CompositeActions bool `json:"composite_actions"`
|
||||
}
|
||||
|
||||
// DetectCapabilities detects the runner's capabilities
|
||||
func DetectCapabilities(ctx context.Context, dockerHost string) *RunnerCapabilities {
|
||||
cap := &RunnerCapabilities{
|
||||
OS: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Tools: make(map[string][]string),
|
||||
Shell: detectShells(),
|
||||
Features: &CapabilityFeatures{
|
||||
ArtifactsV4: false, // Gitea doesn't support v4 artifacts
|
||||
Cache: true,
|
||||
Services: true,
|
||||
CompositeActions: true,
|
||||
},
|
||||
Limitations: []string{
|
||||
"actions/upload-artifact@v4 not supported (use v3 or direct API upload)",
|
||||
"actions/download-artifact@v4 not supported (use v3)",
|
||||
},
|
||||
}
|
||||
|
||||
// Detect Linux distribution
|
||||
if runtime.GOOS == "linux" {
|
||||
cap.Distro = detectLinuxDistro()
|
||||
}
|
||||
|
||||
// Detect Docker
|
||||
cap.Docker, cap.ContainerRuntime = detectDocker(ctx, dockerHost)
|
||||
if cap.Docker {
|
||||
cap.DockerCompose = detectDockerCompose(ctx)
|
||||
cap.Features.Services = true
|
||||
}
|
||||
|
||||
// Detect common tools
|
||||
detectTools(ctx, cap)
|
||||
|
||||
// Detect disk space
|
||||
cap.Disk = detectDiskSpace()
|
||||
|
||||
// Generate suggested labels based on detected capabilities
|
||||
cap.SuggestedLabels = generateSuggestedLabels(cap)
|
||||
|
||||
return cap
|
||||
}
|
||||
|
||||
// detectLinuxDistro reads /etc/os-release to get distribution info
|
||||
func detectLinuxDistro() *DistroInfo {
|
||||
file, err := os.Open("/etc/os-release")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
distro := &DistroInfo{}
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "ID=") {
|
||||
distro.ID = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
|
||||
} else if strings.HasPrefix(line, "VERSION_ID=") {
|
||||
distro.VersionID = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), "\"")
|
||||
} else if strings.HasPrefix(line, "PRETTY_NAME=") {
|
||||
distro.PrettyName = strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"")
|
||||
}
|
||||
}
|
||||
|
||||
if distro.ID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return distro
|
||||
}
|
||||
|
||||
// generateSuggestedLabels creates industry-standard labels based on capabilities
|
||||
func generateSuggestedLabels(cap *RunnerCapabilities) []string {
|
||||
labels := []string{}
|
||||
seen := make(map[string]bool)
|
||||
|
||||
addLabel := func(label string) {
|
||||
if label != "" && !seen[label] {
|
||||
seen[label] = true
|
||||
labels = append(labels, label)
|
||||
}
|
||||
}
|
||||
|
||||
// OS labels
|
||||
switch cap.OS {
|
||||
case "linux":
|
||||
addLabel("linux")
|
||||
addLabel("linux-latest")
|
||||
case "windows":
|
||||
addLabel("windows")
|
||||
addLabel("windows-latest")
|
||||
case "darwin":
|
||||
addLabel("macos")
|
||||
addLabel("macos-latest")
|
||||
}
|
||||
|
||||
// Distro labels (Linux only)
|
||||
if cap.Distro != nil && cap.Distro.ID != "" {
|
||||
distro := strings.ToLower(cap.Distro.ID)
|
||||
addLabel(distro)
|
||||
addLabel(distro + "-latest")
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
|
||||
// ToJSON converts capabilities to JSON string for transmission
|
||||
func (c *RunnerCapabilities) ToJSON() string {
|
||||
data, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return "{}"
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func detectShells() []string {
|
||||
shells := []string{}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
if _, err := exec.LookPath("pwsh"); err == nil {
|
||||
shells = append(shells, "pwsh")
|
||||
}
|
||||
if _, err := exec.LookPath("powershell"); err == nil {
|
||||
shells = append(shells, "powershell")
|
||||
}
|
||||
shells = append(shells, "cmd")
|
||||
case "darwin":
|
||||
if _, err := exec.LookPath("zsh"); err == nil {
|
||||
shells = append(shells, "zsh")
|
||||
}
|
||||
if _, err := exec.LookPath("bash"); err == nil {
|
||||
shells = append(shells, "bash")
|
||||
}
|
||||
shells = append(shells, "sh")
|
||||
default: // linux and others
|
||||
if _, err := exec.LookPath("bash"); err == nil {
|
||||
shells = append(shells, "bash")
|
||||
}
|
||||
shells = append(shells, "sh")
|
||||
}
|
||||
|
||||
return shells
|
||||
}
|
||||
|
||||
func detectDocker(ctx context.Context, dockerHost string) (bool, string) {
|
||||
opts := []client.Opt{client.FromEnv}
|
||||
if dockerHost != "" {
|
||||
opts = append(opts, client.WithHost(dockerHost))
|
||||
}
|
||||
|
||||
cli, err := client.NewClientWithOpts(opts...)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
defer cli.Close()
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
_, err = cli.Ping(timeoutCtx)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Check if it's podman or docker
|
||||
info, err := cli.Info(timeoutCtx)
|
||||
if err == nil {
|
||||
if strings.Contains(strings.ToLower(info.Name), "podman") {
|
||||
return true, "podman"
|
||||
}
|
||||
}
|
||||
|
||||
return true, "docker"
|
||||
}
|
||||
|
||||
func detectDockerCompose(ctx context.Context) bool {
|
||||
// Check for docker compose v2 (docker compose)
|
||||
cmd := exec.CommandContext(ctx, "docker", "compose", "version")
|
||||
if err := cmd.Run(); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for docker-compose v1
|
||||
if _, err := exec.LookPath("docker-compose"); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func detectTools(ctx context.Context, cap *RunnerCapabilities) {
|
||||
toolDetectors := map[string]func(context.Context) []string{
|
||||
"node": detectNodeVersions,
|
||||
"go": detectGoVersions,
|
||||
"python": detectPythonVersions,
|
||||
"java": detectJavaVersions,
|
||||
"dotnet": detectDotnetVersions,
|
||||
"rust": detectRustVersions,
|
||||
}
|
||||
|
||||
for tool, detector := range toolDetectors {
|
||||
if versions := detector(ctx); len(versions) > 0 {
|
||||
cap.Tools[tool] = versions
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func detectNodeVersions(ctx context.Context) []string {
|
||||
return detectToolVersion(ctx, "node", "--version", "v")
|
||||
}
|
||||
|
||||
func detectGoVersions(ctx context.Context) []string {
|
||||
return detectToolVersion(ctx, "go", "version", "go")
|
||||
}
|
||||
|
||||
func detectPythonVersions(ctx context.Context) []string {
|
||||
versions := []string{}
|
||||
|
||||
// Try python3 first
|
||||
if v := detectToolVersion(ctx, "python3", "--version", "Python "); len(v) > 0 {
|
||||
versions = append(versions, v...)
|
||||
}
|
||||
|
||||
// Also try python
|
||||
if v := detectToolVersion(ctx, "python", "--version", "Python "); len(v) > 0 {
|
||||
// Avoid duplicates
|
||||
for _, ver := range v {
|
||||
found := false
|
||||
for _, existing := range versions {
|
||||
if existing == ver {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
versions = append(versions, ver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return versions
|
||||
}
|
||||
|
||||
func detectJavaVersions(ctx context.Context) []string {
|
||||
cmd := exec.CommandContext(ctx, "java", "-version")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Java version output goes to stderr and looks like: openjdk version "17.0.1" or java version "1.8.0_301"
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "version") {
|
||||
// Extract version from quotes
|
||||
start := strings.Index(line, "\"")
|
||||
end := strings.LastIndex(line, "\"")
|
||||
if start != -1 && end > start {
|
||||
version := line[start+1 : end]
|
||||
// Simplify version (e.g., "17.0.1" -> "17")
|
||||
parts := strings.Split(version, ".")
|
||||
if len(parts) > 0 {
|
||||
if parts[0] == "1" && len(parts) > 1 {
|
||||
return []string{parts[1]} // Java 8 style: 1.8 -> 8
|
||||
}
|
||||
return []string{parts[0]}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectDotnetVersions(ctx context.Context) []string {
|
||||
cmd := exec.CommandContext(ctx, "dotnet", "--list-sdks")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
versions := []string{}
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
// Format: "8.0.100 [/path/to/sdk]"
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) > 0 {
|
||||
version := parts[0]
|
||||
// Simplify to major version
|
||||
major := strings.Split(version, ".")[0]
|
||||
// Avoid duplicates
|
||||
found := false
|
||||
for _, v := range versions {
|
||||
if v == major {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
versions = append(versions, major)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return versions
|
||||
}
|
||||
|
||||
func detectRustVersions(ctx context.Context) []string {
|
||||
return detectToolVersion(ctx, "rustc", "--version", "rustc ")
|
||||
}
|
||||
|
||||
func detectToolVersion(ctx context.Context, cmd string, args string, prefix string) []string {
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
c := exec.CommandContext(timeoutCtx, cmd, args)
|
||||
output, err := c.Output()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(string(output))
|
||||
if prefix != "" {
|
||||
if idx := strings.Index(line, prefix); idx != -1 {
|
||||
line = line[idx+len(prefix):]
|
||||
}
|
||||
}
|
||||
|
||||
// Get just the version number
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) > 0 {
|
||||
version := parts[0]
|
||||
// Clean up version string
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
// Return major.minor or just major
|
||||
vparts := strings.Split(version, ".")
|
||||
if len(vparts) >= 2 {
|
||||
return []string{vparts[0] + "." + vparts[1]}
|
||||
}
|
||||
return []string{vparts[0]}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
32
internal/pkg/envcheck/disk_unix.go
Normal file
32
internal/pkg/envcheck/disk_unix.go
Normal file
@@ -0,0 +1,32 @@
|
||||
//go:build unix
|
||||
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package envcheck
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// detectDiskSpace detects disk space on the root filesystem (Unix version)
|
||||
func detectDiskSpace() *DiskInfo {
|
||||
var stat unix.Statfs_t
|
||||
|
||||
err := unix.Statfs("/", &stat)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
total := stat.Blocks * uint64(stat.Bsize)
|
||||
free := stat.Bavail * uint64(stat.Bsize)
|
||||
used := total - free
|
||||
usedPercent := float64(used) / float64(total) * 100
|
||||
|
||||
return &DiskInfo{
|
||||
Total: total,
|
||||
Free: free,
|
||||
Used: used,
|
||||
UsedPercent: usedPercent,
|
||||
}
|
||||
}
|
||||
31
internal/pkg/envcheck/disk_windows.go
Normal file
31
internal/pkg/envcheck/disk_windows.go
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build windows
|
||||
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package envcheck
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// detectDiskSpace detects disk space on the C: drive (Windows version)
|
||||
func detectDiskSpace() *DiskInfo {
|
||||
var freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64
|
||||
|
||||
path := windows.StringToUTF16Ptr("C:\\")
|
||||
err := windows.GetDiskFreeSpaceEx(path, &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
used := totalNumberOfBytes - totalNumberOfFreeBytes
|
||||
usedPercent := float64(used) / float64(totalNumberOfBytes) * 100
|
||||
|
||||
return &DiskInfo{
|
||||
Total: totalNumberOfBytes,
|
||||
Free: totalNumberOfFreeBytes,
|
||||
Used: used,
|
||||
UsedPercent: usedPercent,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user