2
0

Compare commits

...

6 Commits

Author SHA1 Message Date
GitCaddy
2c66de4df2 fix: run make fmt for code formatting
Some checks failed
CI / build-and-test (push) Successful in 8s
Release / build (amd64, darwin) (push) Successful in 14s
Release / build (amd64, linux) (push) Successful in 13s
Release / build (amd64, windows) (push) Failing after 17s
Release / build (arm64, darwin) (push) Successful in 21s
Release / build (arm64, linux) (push) Successful in 5s
Release / release (push) Has been skipped
2026-01-11 07:11:49 +00:00
GitCaddy
9de33d4252 Send runner capabilities with FetchTask poll
Some checks failed
CI / build-and-test (push) Failing after 8s
- Add envcheck import and capability detection to poller.go
- Send capabilities JSON with every FetchTask request
- Use GitCaddy actions-proto-go v0.5.6 with CapabilitiesJson field
2026-01-11 07:03:54 +00:00
GitCaddy
ff8375c6e1 Add disk space reporting to runner capabilities
Some checks failed
CI / build-and-test (push) Failing after 4s
- Add DiskInfo struct with total, free, used bytes and usage percentage
- Detect disk space using unix.Statfs on startup
- Add periodic capabilities updates every 5 minutes
- Add disk space warnings at 85% (warning) and 95% (critical) thresholds
- Send updated capabilities to Gitea server periodically

This helps monitor runners that are running low on disk space.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-11 05:16:49 +00:00
3e22214bbd ci: Disable Go cache to prevent hanging on Gitea runners
Some checks failed
Release / build (amd64, darwin) (push) Successful in 15s
Release / build (arm64, darwin) (push) Successful in 5s
CI / build-and-test (push) Failing after 3s
Release / build (arm64, linux) (push) Successful in 15s
Release / release (push) Successful in 11s
Release / build (amd64, linux) (push) Successful in 9s
Release / build (amd64, windows) (push) Successful in 5s
The actions/setup-go@v5 cache feature tries to upload to GitHub's
cache infrastructure, which doesn't exist on self-hosted Gitea
runners. This causes jobs to hang indefinitely during the post step.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 05:30:00 -05:00
b4e90db372 Update actions-proto-go to v0.5.2
Some checks failed
CI / build-and-test (push) Waiting to run
Release / build (arm64, darwin) (push) Has been cancelled
Release / build (arm64, linux) (push) Has been cancelled
Release / release (push) Has been cancelled
Release / build (amd64, darwin) (push) Has been cancelled
Release / build (amd64, windows) (push) Has been cancelled
Release / build (amd64, linux) (push) Has been cancelled
Use corrected module path version.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 05:15:40 -05:00
c6b9e0c2d1 Add professional README for GitCaddy fork
Some checks are pending
CI / build-and-test (push) Waiting to run
Document capability detection, installation, configuration,
and GitCaddy integration with architecture diagram.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 05:03:21 -05:00
8 changed files with 280 additions and 80 deletions

View File

@@ -29,6 +29,7 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: false
- name: Build
run: |

View File

@@ -15,6 +15,7 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache: false
- name: Vet
run: make vet

221
README.md
View File

@@ -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.

7
go.mod
View File

@@ -5,7 +5,7 @@ go 1.24.0
toolchain go1.24.11
require (
code.gitea.io/actions-proto-go v0.5.0
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
@@ -23,6 +23,8 @@ require (
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
@@ -96,7 +98,6 @@ require (
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/sys v0.37.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
@@ -110,4 +111,4 @@ replace github.com/go-git/go-git/v5 => github.com/go-git/go-git/v5 v5.16.2
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.0
replace code.gitea.io/actions-proto-go => git.marketally.com/gitcaddy/actions-proto-go v0.5.6

4
go.sum
View File

@@ -6,8 +6,8 @@ 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=
git.marketally.com/gitcaddy/actions-proto-go v0.5.0 h1:D2loMnqTXiaJL6TMfOOUJz4/3Vpv0AnMDSJVuiqMNrM=
git.marketally.com/gitcaddy/actions-proto-go v0.5.0/go.mod h1:li5RzZsj1sV8a0SXzXWsGNwv0dYw7Wj829AgloZqF5o=
git.marketally.com/gitcaddy/actions-proto-go v0.5.6 h1:G7T0vpx8HyCFWd0YMJ9sp8rCsWtzFrCJK4BMdOFJa1A=
git.marketally.com/gitcaddy/actions-proto-go v0.5.6/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=

View File

@@ -30,6 +30,15 @@ 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
)
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)
@@ -147,6 +156,9 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
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(), capabilitiesJson)
if err != nil && connect.CodeOf(err) == connect.CodeUnimplemented {
@@ -160,6 +172,9 @@ 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)
if daemArgs.Once || reg.Ephemeral {
@@ -194,6 +209,53 @@ 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
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")
return
case <-ticker.C:
// Detect updated capabilities (disk space changes over time)
capabilities := envcheck.DetectCapabilities(ctx, dockerHost)
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 {
log.Debugf("capabilities updated: disk %.1f%% used, %.2f GB free",
capabilities.Disk.UsedPercent,
float64(capabilities.Disk.Free)/(1024*1024*1024))
}
}
}
}
type daemonArgs struct {
Once bool
}

View File

@@ -18,6 +18,7 @@ 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 {
@@ -157,11 +158,17 @@ 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)
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
}
@@ -182,7 +189,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

View File

@@ -12,8 +12,17 @@ import (
"time"
"github.com/docker/docker/client"
"golang.org/x/sys/unix"
)
// 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"`
}
// RunnerCapabilities represents the capabilities of a runner for AI consumption
type RunnerCapabilities struct {
OS string `json:"os"`
@@ -25,6 +34,7 @@ type RunnerCapabilities struct {
Tools map[string][]string `json:"tools,omitempty"`
Features *CapabilityFeatures `json:"features,omitempty"`
Limitations []string `json:"limitations,omitempty"`
Disk *DiskInfo `json:"disk,omitempty"`
}
// CapabilityFeatures represents feature support flags
@@ -38,10 +48,10 @@ type CapabilityFeatures struct {
// 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(),
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,
@@ -64,9 +74,40 @@ func DetectCapabilities(ctx context.Context, dockerHost string) *RunnerCapabilit
// Detect common tools
detectTools(ctx, cap)
// Detect disk space
cap.Disk = detectDiskSpace()
return cap
}
// detectDiskSpace detects disk space on the root filesystem
func detectDiskSpace() *DiskInfo {
var stat unix.Statfs_t
// Get stats for root filesystem (or current working directory)
path := "/"
if runtime.GOOS == "windows" {
path = "C:\\"
}
err := unix.Statfs(path, &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,
}
}
// ToJSON converts capabilities to JSON string for transmission
func (c *RunnerCapabilities) ToJSON() string {
data, err := json.Marshal(c)