Bumps `github.com/avast/retry-go` v4.7.0 -> v5.0.0, `golangci-lint` v2.11.4 -> v2.12.2 (aligns with gitea/gitea), and pins `govulncheck` to v1.3.0.
- `retry-go` v5 replaces the package-level `retry.Do(fn, opts...)` with a builder API `retry.New(opts...).Do(fn)`. The single call site in `internal/pkg/report/reporter.go` was migrated.
- `golangci-lint` v2.12.2 surfaces three new findings in `act/` (modernize/slicesbackward, govet/inline): one backward loop now uses `slices.Backward`, and the deprecated `reflect.Ptr` alias is replaced with `reflect.Pointer`.
- `go.mod`: the two direct-`require` blocks are merged into one, and a stray `gopkg.in/yaml.v3 // indirect` is moved into the indirect block. Purely cosmetic; `go.sum` is unchanged.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/965
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
Fixes: https://gitea.com/gitea/runner/issues/859
Migration approach mirrors [actions-oss/act-cli#154](https://github.com/actions-oss/act-cli/pull/154).
### Dependency changes
- `github.com/docker/docker` v25.0.15 → **removed** (v29 doesn't exist as docker/docker; the project moved to moby/moby)
- `github.com/docker/cli` v25.0.7 → v29.4.3
- `github.com/docker/go-connections` v0.6.0 → v0.7.0
- `github.com/docker/docker-credential-helpers` v0.9.5 → v0.9.6
- `github.com/moby/go-archive` added at v0.2.0
- `github.com/moby/moby/api` added at v1.54.2
- `github.com/moby/moby/client` added at v0.4.1
- `github.com/moby/buildkit` removed (only used `dockerignore.ReadAll`, swapped for `moby/patternmatcher/ignorefile.ReadAll` directly)
- `github.com/containerd/errdefs` v0.3.0 → v1.0.0
### Migration
- v28: type aliases moved to their subpackages (`types.{Container,Image,Network,Exec}*` → `container/image/network/...`); deprecated APIs replaced (`ImageInspectWithRaw`, `client.IsErrNotFound`, `archive.CanonicalTarNameForPath`, `opts.ValidateMACAddress`, `ListOpts.GetAll`)
- v29: structural client redesign — every `cli.X(ctx, ...)` call switched to options-everywhere/Result-typed signatures, `ContainerExec*` → `Exec*`, `ContainerWait` returns a struct with `Result`/`Error` channels, `Tty`→`TTY`, `Copy*Container` takes options struct, `client.NewClientWithOpts` → `client.New`. `pkg/stdcopy` moved to `moby/moby/api/pkg/stdcopy`. The vendored copy of `cli/command/container/opts.go` was refreshed from cli v29 (now uses `netip.Addr` for IPs, port-set conversion helpers). A small local `parsePlatform` helper centralises the `os/arch[/variant]` parsing previously inlined into multiple call sites.
### Behaviour preservation
The migration introduced several behavioural shifts vs the v25 client; all were caught in review and reverted/fixed in follow-up commits:
- `GetDockerClient`: cli v29's `Ping(NegotiateAPIVersion: true)` returns errors that the old `NegotiateAPIVersion` silently swallowed. Restored best-effort behaviour (warn-log + continue) so daemons with blocked `_ping` or API < 1.40 keep working. The SSH-helper `client.New` call no longer inherits `client.FromEnv`, matching the old `NewClientWithOpts(WithHost, WithDialContext)` so `DOCKER_API_VERSION`/`DOCKER_TLS_VERIFY` don't leak into the SSH-tunneled client
- `parsePlatform`: malformed input now returns an explicit error instead of silently dropping to "no platform constraint" and pulling the host-default architecture. Single-segment (`"linux"`), 4+-segment (`"linux/arm/v7/extra"`), and trailing-slash (`"linux/arm/"`) inputs are all rejected
- `LoadDockerAuthConfig`/`LoadDockerAuthConfigs`: `config.LoadDefaultConfigFile(nil)` panics on a malformed config file (it does `fmt.Fprintln` on the nil `io.Writer`). Switched to `config.Load(config.Dir())` so load errors reach the logger and the panic path is gone. Restored the old behaviour of returning `config.Load` and `GetAuthConfig` errors to the caller (the v29 refactor had silently downgraded them to warn-only). A `reference.ParseNormalizedNamed` failure on the image string falls through to the `docker.io` default rather than aborting, since the old string-based hostname extraction was infallible
Test assertions also updated for two upstream error-message string shifts (`go-connections` port-range parser; `cli/opts` envfile BOM check). Added unit-test coverage for the new `parsePlatform` helper, locking in the intentional limits (single-segment, 4+-segment, and trailing-slash platforms rejected).
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/943
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
At v1.0.0 the `github.com/imdario/mergo` module was relocated to `dario.cat/mergo`, so a plain version bump (as in https://gitea.com/gitea/runner/pulls/951) leaves the import path pointing at the old, unmaintained location. This PR updates the import in `act/container/docker_run.go` and adjusts `go.mod` accordingly. The public API (`mergo.Merge`, `mergo.WithOverride`, `mergo.WithAppendSlice`) is unchanged.
Supersedes https://gitea.com/gitea/runner/pulls/951.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/runner/pulls/954
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
The `replace github.com/distribution/reference v0.6.0 => v0.5.0` was added defensively in [c4b57fbc](c4b57fbcb2) (#775) against `github.com/docker/distribution v2.8.3`, whose `reference_deprecated.go` calls the now-removed `reference.SplitHostname`. However, nothing in act_runner's build graph imports `github.com/docker/distribution/reference`, so the file is never compiled and the replace has no effect.
Verified locally: removing the directive followed by `go mod tidy` keeps `distribution/reference` at v0.6.0, drops `docker/distribution` from the graph entirely, and `go build ./...` / `go vet ./...` both pass.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/858
Reviewed-by: Nicolas <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
Merges the `gitea.com/gitea/act` fork into this repository as the `act/`
directory and consumes it as a local package. The `replace github.com/nektos/act
=> gitea.com/gitea/act` directive is removed; act's dependencies are merged
into the root `go.mod`.
- Imports rewritten: `github.com/nektos/act/pkg/...` → `gitea.com/gitea/act_runner/act/...`
(flattened — `pkg/` boundary dropped to match the layout forgejo-runner adopted).
- Dropped act's CLI (`cmd/`, `main.go`) and all upstream project files; kept
the library tree + `LICENSE`.
- Added `// Copyright <year> The Gitea Authors ...` / `// Copyright <year> nektos`
headers to 104 `.go` files.
- Pre-existing act lint violations annotated inline with
`//nolint:<linter> // pre-existing issue from nektos/act`.
`.golangci.yml` is unchanged vs `main`.
- Makefile test target: `-race -short` (matches forgejo-runner).
- Pre-existing integration test failures fixed: race in parallel executor
(atomic counters); TestSetupEnv / command_test / expression_test /
run_context_test updated to match gitea fork runtime; TestJobExecutor and
TestActionCache gated on `testing.Short()`.
Full `gitea/act` commit history is reachable via the second parent.
Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
## What
Add an optional Prometheus `/metrics` HTTP endpoint to `act_runner` so operators can observe runner health, polling behavior, job outcomes, and RPC latency without scraping logs.
New surface:
- `internal/pkg/metrics/metrics.go` — metric definitions, custom `Registry`, static Go/process collectors, label constants, `ResultToStatusLabel` helper.
- `internal/pkg/metrics/server.go` — hardened `http.Server` serving `/metrics` and `/healthz` with Slowloris-safe timeouts (`ReadHeaderTimeout` 5s, `ReadTimeout`/`WriteTimeout` 10s, `IdleTimeout` 60s) and a 5s graceful shutdown.
- `daemon.go` wires it up behind `cfg.Metrics.Enabled` (disabled by default).
- `poller.go` / `reporter.go` / `runner.go` instrument their existing hot paths with counters/histograms/gauges — no behavior change.
Metrics exported (namespace `act_runner_`):
| Subsystem | Metric | Type | Labels |
|---|---|---|---|
| — | `info` | Gauge | `version`, `name` |
| — | `capacity`, `uptime_seconds` | Gauge | — |
| `poll` | `fetch_total`, `client_errors_total` | Counter | `result` / `method` |
| `poll` | `fetch_duration_seconds`, `backoff_seconds` | Histogram / Gauge | — |
| `job` | `total` | Counter | `status` |
| `job` | `duration_seconds`, `running`, `capacity_utilization_ratio` | Histogram / GaugeFunc | — |
| `report` | `log_total`, `state_total` | Counter | `result` |
| `report` | `log_duration_seconds`, `state_duration_seconds` | Histogram | — |
| `report` | `log_buffer_rows` | Gauge | — |
| — | `go_*`, `process_*` | standard collectors | — |
All label values are predefined constants — **no high-cardinality labels** (no task IDs, repo URLs, branches, tokens, or secrets) so scraping is safe and bounded.
## Why
Teams self-hosting Gitea + `act_runner` at scale need to answer basic SRE questions that are currently invisible:
- How often are RPCs failing? Which RPC? (`act_runner_client_errors_total`)
- Are runners saturated? (`act_runner_job_capacity_utilization_ratio`, `act_runner_job_running`)
- How long do jobs take? (`act_runner_job_duration_seconds`)
- Is polling backing off? (`act_runner_poll_backoff_seconds`, `act_runner_poll_fetch_total{result=\"error\"}`)
- Are log/state reports slow? (`act_runner_report_{log,state}_duration_seconds`)
- Is the log buffer draining? (`act_runner_report_log_buffer_rows`)
Today operators have to grep logs. This PR makes all of the above first-class metrics so they can feed dashboards and alerts (`rate(act_runner_client_errors_total[5m]) > 0.1`, capacity saturation alerts, etc.).
The endpoint is **disabled by default** and binds to `127.0.0.1:9101` when enabled, so it's opt-in and safe for existing deployments.
## How
### Config
```yaml
metrics:
enabled: false # opt-in
addr: 127.0.0.1:9101 # change to 0.0.0.0:9101 only behind a reverse proxy
```
`config.example.yaml` documents both fields plus a security note about binding externally without auth.
### Wiring
1. `daemon.go` calls `metrics.Init()` (guarded by `sync.Once`), sets `act_runner_info`, `act_runner_capacity`, registers uptime + running-jobs GaugeFuncs, then starts the server goroutine with the daemon context — it shuts down cleanly on `ctx.Done()`.
2. `poller.fetchTask` observes RPC latency / result / error counters. `DeadlineExceeded` (long-poll idle) is treated as an empty result and **not** observed into the histogram so the 5s timeout doesn't swamp the buckets.
3. `poller.pollOnce` reports `poll_backoff_seconds` using the pre-jitter base interval (the true backoff level), and only when it changes — prevents noisy no-op gauge updates at the `FetchIntervalMax` plateau.
4. `reporter.ReportLog` / `ReportState` record duration histograms and success/error counters; `log_buffer_rows` is updated only when the value changes, guarded by the already-held `clientM`.
5. `runner.Run` observes `job_duration_seconds` and increments `job_total` by outcome via `metrics.ResultToStatusLabel`.
### Safety / security review
- All timeouts set; Slowloris-safe.
- Custom `prometheus.NewRegistry()` — no global registration side-effects.
- No sensitive data in labels (reviewed every instrumentation site).
- Single new dependency: `github.com/prometheus/client_golang v1.23.2`.
- Endpoint is unauthenticated by design and documented as such; default localhost bind mitigates exposure. Operators exposing externally should front it with a reverse proxy.
## Verification
### Unit tests
\`\`\`bash
go build ./...
go vet ./...
go test ./...
\`\`\`
### Manual smoke test
1. Enable metrics in `config.yaml`:
\`\`\`yaml
metrics:
enabled: true
addr: 127.0.0.1:9101
\`\`\`
2. Start the runner against a Gitea instance: \`./act_runner daemon\`.
3. Scrape the endpoint:
\`\`\`bash
curl -s http://127.0.0.1:9101/metrics | grep '^act_runner_'
curl -s http://127.0.0.1:9101/healthz # → ok
\`\`\`
4. Confirm the static series appear immediately: \`act_runner_info\`, \`act_runner_capacity\`, \`act_runner_uptime_seconds\`, \`act_runner_job_running\`, \`act_runner_job_capacity_utilization_ratio\`.
5. Trigger a workflow and confirm counters increment: \`act_runner_poll_fetch_total{result=\"task\"}\`, \`act_runner_job_total{status=\"success\"}\`, \`act_runner_report_log_total{result=\"success\"}\`.
6. Leave the runner idle and confirm \`act_runner_poll_backoff_seconds\` settles (and does **not** churn on every poll).
7. Ctrl-C and confirm a clean \"metrics server shutdown\" log line (no port-in-use error on restart within 5s).
### Prometheus integration
Add to \`prometheus.yml\`:
\`\`\`yaml
scrape_configs:
- job_name: act_runner
static_configs:
- targets: ['127.0.0.1:9101']
\`\`\`
Sample alert to try:
\`\`\`
sum(rate(act_runner_client_errors_total[5m])) by (method) > 0.1
\`\`\`
## Out of scope (follow-ups)
- TLS and auth on the metrics endpoint (mitigated today by localhost default; add when operators need external scraping).
- Per-task labels (intentionally avoided for cardinality safety).
---
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/820
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
## Summary
- Bumps Go version from 1.24.0 (toolchain go1.24.11) to 1.26.0
- Fixes CI `govulncheck` failures caused by three standard library vulnerabilities in go1.24.11:
- GO-2026-4341: Memory exhaustion in `net/url` query parameter parsing
- GO-2026-4340: Handshake messages at incorrect encryption level in `crypto/tls`
- GO-2026-4337: Unexpected session resumption in `crypto/tls`
## Test plan
- [x] `make vet` passes
- [x] `make build` passes
- [x] `make test` passes (includes `govulncheck` and all unit tests)
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/787
Reviewed-by: ChristopherHX <christopherhx@noreply.gitea.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>