Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f92e50f35b | ||
|
|
a792b47b41 | ||
|
|
68ec7efde0 | ||
|
|
f314ffb036 | ||
|
|
b303a83a77 | ||
|
|
66d0b1e608 | ||
|
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- 'v*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
goreleaser:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: linux-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:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
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
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- uses: actions/setup-go@v4
|
||||||
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:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
go-version-file: 'go.mod'
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
cache: false
|
||||||
|
|
||||||
- name: Get Meta
|
- name: Build
|
||||||
id: meta
|
|
||||||
run: |
|
run: |
|
||||||
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
|
VERSION=${GITHUB_REF#refs/tags/v}
|
||||||
echo REPO_VERSION=${GITHUB_REF_NAME#v} >> $GITHUB_OUTPUT
|
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
|
- name: Upload artifact
|
||||||
uses: docker/build-push-action@v5
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
name: act_runner-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
file: ./Dockerfile
|
path: act_runner-*
|
||||||
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: Build and push dind
|
release:
|
||||||
uses: docker/build-push-action@v5
|
needs: build
|
||||||
with:
|
runs-on: linux-latest
|
||||||
context: .
|
steps:
|
||||||
file: ./Dockerfile
|
- uses: actions/checkout@v4
|
||||||
target: dind
|
|
||||||
platforms: |
|
|
||||||
linux/amd64
|
|
||||||
linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-dind
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind
|
|
||||||
|
|
||||||
- name: Build and push dind-rootless
|
- name: Download all artifacts
|
||||||
uses: docker/build-push-action@v5
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
path: artifacts
|
||||||
file: ./Dockerfile
|
|
||||||
target: dind-rootless
|
- name: Prepare release files
|
||||||
platforms: |
|
run: |
|
||||||
linux/amd64
|
mkdir -p release
|
||||||
linux/arm64
|
find artifacts -type f -name 'act_runner-*' -exec mv {} release/ \;
|
||||||
push: true
|
cd release && sha256sum * > checksums.txt
|
||||||
tags: |
|
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}-dind-rootless
|
- name: Create Release
|
||||||
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}-dind-rootless
|
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:
|
on:
|
||||||
- push
|
push:
|
||||||
- pull_request
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
build-and-test:
|
||||||
name: check and test
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: 'go.mod'
|
go-version-file: 'go.mod'
|
||||||
- name: vet checks
|
cache: false
|
||||||
|
|
||||||
|
- name: Vet
|
||||||
run: make vet
|
run: make vet
|
||||||
- name: build
|
env:
|
||||||
|
GOPRIVATE: git.marketally.com
|
||||||
|
|
||||||
|
- name: Build
|
||||||
run: make build
|
run: make build
|
||||||
- name: test
|
env:
|
||||||
|
GOPRIVATE: git.marketally.com
|
||||||
|
|
||||||
|
- name: Test
|
||||||
run: make test
|
run: make test
|
||||||
|
env:
|
||||||
|
GOPRIVATE: git.marketally.com
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
|
- loong64
|
||||||
- s390x
|
- s390x
|
||||||
- riscv64
|
- riscv64
|
||||||
goarm:
|
goarm:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ RUN make clean && make build
|
|||||||
### DIND VARIANT
|
### DIND VARIANT
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
FROM docker:dind AS dind
|
FROM docker:28-dind AS dind
|
||||||
|
|
||||||
RUN apk add --no-cache s6 bash git tzdata
|
RUN apk add --no-cache s6 bash git tzdata
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ ENTRYPOINT ["s6-svscan","/etc/s6"]
|
|||||||
### DIND-ROOTLESS VARIANT
|
### DIND-ROOTLESS VARIANT
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
FROM docker:dind-rootless AS dind-rootless
|
FROM docker:28-dind-rootless AS dind-rootless
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
RUN apk add --no-cache s6 bash git tzdata
|
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 --from=builder /opt/src/act_runner/act_runner /usr/local/bin/act_runner
|
||||||
COPY scripts/run.sh /usr/local/bin/run.sh
|
COPY scripts/run.sh /usr/local/bin/run.sh
|
||||||
|
|
||||||
VOLUME /var/run/docker.sock
|
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini","--","run.sh"]
|
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
|
DIST := dist
|
||||||
EXECUTABLE := act_runner
|
EXECUTABLE := act_runner
|
||||||
GOFMT ?= gofumpt -l
|
GOFMT ?= gofumpt -l
|
||||||
DIST := dist
|
|
||||||
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
DIST_DIRS := $(DIST)/binaries $(DIST)/release
|
||||||
GO ?= go
|
GO ?= go
|
||||||
SHASUM ?= shasum -a 256
|
SHASUM ?= shasum -a 256
|
||||||
@@ -21,6 +20,8 @@ DOCKER_TAG ?= nightly
|
|||||||
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
DOCKER_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)
|
||||||
DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless
|
DOCKER_ROOTLESS_REF := $(DOCKER_IMAGE):$(DOCKER_TAG)-dind-rootless
|
||||||
|
|
||||||
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1
|
||||||
|
|
||||||
ifneq ($(shell uname), Darwin)
|
ifneq ($(shell uname), Darwin)
|
||||||
EXTLDFLAGS = -extldflags "-static" $(null)
|
EXTLDFLAGS = -extldflags "-static" $(null)
|
||||||
else
|
else
|
||||||
@@ -102,7 +103,15 @@ fmt-check:
|
|||||||
exit 1; \
|
exit 1; \
|
||||||
fi;
|
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
|
@$(GO) test -v -cover -coverprofile coverage.txt ./... && echo "\n==>\033[32m Ok\033[m\n" || exit 1
|
||||||
|
|
||||||
.PHONY: vet
|
.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
|
## 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 from [Releases](https://git.marketally.com/gitcaddy/act_runner/releases):
|
||||||
|
|
||||||
### Download pre-built binary
|
|
||||||
|
|
||||||
Visit [here](https://dl.gitea.com/act_runner/) and download the right version for your platform.
|
|
||||||
|
|
||||||
### Build from source
|
|
||||||
|
|
||||||
```bash
|
```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
|
make build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Build a docker image
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
### 1. Enable Actions in Gitea
|
||||||
make docker
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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
|
```ini
|
||||||
[actions]
|
[actions]
|
||||||
ENABLED=true
|
ENABLED = true
|
||||||
```
|
```
|
||||||
|
|
||||||
### Register
|
### 2. Register the Runner
|
||||||
|
|
||||||
```bash
|
```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:
|
### 3. Start the Runner
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./act_runner daemon
|
./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
|
## Capability Detection
|
||||||
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
|
|
||||||
|
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.
|
Create a config file or use command-line flags:
|
||||||
The configuration file is a YAML file, you can generate a sample configuration file with `./act_runner generate-config`.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./act_runner generate-config > config.yaml
|
./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
|
```bash
|
||||||
./act_runner -c config.yaml register # register with config file
|
docker run -d \
|
||||||
./act_runner -c config.yaml daemon # run with config file
|
--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.
|
||||||
|
|||||||
BIN
act_runner-darwin-amd64
Executable file
BIN
act_runner-darwin-amd64
Executable file
Binary file not shown.
BIN
act_runner-darwin-arm64
Executable file
BIN
act_runner-darwin-arm64
Executable file
Binary file not shown.
BIN
act_runner-linux-amd64
Executable file
BIN
act_runner-linux-amd64
Executable file
Binary file not shown.
BIN
act_runner-windows-amd64.exe
Executable file
BIN
act_runner-windows-amd64.exe
Executable file
Binary file not shown.
BIN
act_runner_test
Executable file
BIN
act_runner_test
Executable file
Binary file not shown.
47
go.mod
47
go.mod
@@ -1,39 +1,44 @@
|
|||||||
module gitea.com/gitea/act_runner
|
module gitea.com/gitea/act_runner
|
||||||
|
|
||||||
go 1.24
|
go 1.24.0
|
||||||
|
|
||||||
|
toolchain go1.24.11
|
||||||
|
|
||||||
require (
|
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
|
code.gitea.io/gitea-vet v0.2.3
|
||||||
connectrpc.com/connect v1.16.2
|
connectrpc.com/connect v1.16.2
|
||||||
github.com/avast/retry-go/v4 v4.6.0
|
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/joho/godotenv v1.5.1
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/nektos/act v0.0.0 // will be replaced
|
github.com/nektos/act v0.0.0 // will be replaced
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
golang.org/x/term v0.31.0
|
golang.org/x/term v0.36.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.12.0
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.35.2
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gotest.tools/v3 v3.5.1
|
gotest.tools/v3 v3.5.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require golang.org/x/sys v0.37.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
cyphar.com/go-pathrs v0.2.1 // indirect
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
dario.cat/mergo v1.0.0 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||||
github.com/cloudflare/circl v1.6.1 // 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/containerd/log v0.1.0 // indirect
|
||||||
github.com/creack/pty v1.1.21 // 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/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/cli v25.0.3+incompatible // indirect
|
||||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.8.2 // 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/buildkit v0.12.5 // indirect
|
||||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
github.com/moby/sys/sequential v0.5.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/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||||
github.com/opencontainers/selinux v1.11.0 // indirect
|
github.com/opencontainers/selinux v1.13.0 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // 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 v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||||
golang.org/x/crypto v0.37.0 // indirect
|
golang.org/x/crypto v0.43.0 // indirect
|
||||||
golang.org/x/net v0.39.0 // indirect
|
golang.org/x/net v0.45.0 // indirect
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
|
||||||
golang.org/x/tools v0.23.0 // indirect
|
golang.org/x/tools v0.23.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // 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
|
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 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
|
||||||
code.gitea.io/gitea-vet v0.2.3/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE=
|
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 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
|
||||||
connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
|
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 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
gitea.com/gitea/act v0.261.7 h1:0tX0EdWo5uZUgsN/iJGyEAtagqYURrbOuWxyx+LV1Wk=
|
git.marketally.com/gitcaddy/actions-proto-go v0.5.7 h1:RUbafr3Vkw2l4WfSwa+oF+Ihakbm05W0FlAmXuQrDJc=
|
||||||
gitea.com/gitea/act v0.261.7/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
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 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
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=
|
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.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
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/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
|
||||||
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
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 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
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=
|
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/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 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
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.29 h1:90fWABQsaN9mJhGkoVnuzEY+o1XDPbg9BTC9QTAHnuE=
|
||||||
github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4=
|
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 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
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/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 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
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.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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/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 h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
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.13+incompatible h1:YeBrkUd3q0ZoRDNoEzuopwCLU+uD8GZahDHwBdsTnkU=
|
||||||
github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
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 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||||
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
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=
|
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/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 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
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.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
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 h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
|
||||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
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/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 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
|
github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84=
|
||||||
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
|
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 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
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.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.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.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
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 h1:zjNCuOOhh1TKRU0Ru3PPPJt80z7eReswCao91gBLk00=
|
||||||
github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928/go.mod h1:PCFYfAEfKT+Nd6zWvUpsXduMR1bXFLf0uGSlEF05MCI=
|
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=
|
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/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 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
|
||||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
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.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
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 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
||||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
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=
|
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-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-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.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.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
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 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
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.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.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.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
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-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-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-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-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.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
|
||||||
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
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-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-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.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.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.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
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.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
|
||||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/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.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
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-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-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=
|
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-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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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 v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||||
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
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 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
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"
|
"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 {
|
func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) func(cmd *cobra.Command, args []string) error {
|
||||||
return func(cmd *cobra.Command, args []string) error {
|
return func(cmd *cobra.Command, args []string) error {
|
||||||
cfg, err := config.LoadDefault(*configFile)
|
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)
|
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, cfg.Container.WorkdirParent)
|
||||||
|
// 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
|
// 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 {
|
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")
|
log.Errorf("Your Gitea version is too old to support runner declare, please upgrade to v1.21 or later")
|
||||||
return err
|
return err
|
||||||
@@ -149,7 +185,24 @@ func runDaemon(ctx context.Context, daemArgs *daemonArgs, configFile *string) fu
|
|||||||
resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels)
|
resp.Msg.Runner.Name, resp.Msg.Runner.Version, resp.Msg.Runner.Labels)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start periodic capabilities update goroutine
|
||||||
|
go periodicCapabilitiesUpdate(ctx, runner, ls.Names(), dockerHost, cfg.Container.WorkdirParent)
|
||||||
|
// Start periodic stale job cache cleanup (every hour, remove caches older than 2 hours)
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(1 * time.Hour)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
runner.CleanStaleJobCaches(2 * time.Hour)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
poller := poll.New(cfg, cli, runner)
|
poller := poll.New(cfg, cli, runner)
|
||||||
|
poller.SetBandwidthManager(bandwidthManager)
|
||||||
|
|
||||||
if daemArgs.Once || reg.Ephemeral {
|
if daemArgs.Once || reg.Ephemeral {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
@@ -183,6 +236,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, workingDir 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, workingDir)
|
||||||
|
|
||||||
|
// 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 {
|
type daemonArgs struct {
|
||||||
Once bool
|
Once bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,15 @@ import (
|
|||||||
"gitea.com/gitea/act_runner/internal/app/run"
|
"gitea.com/gitea/act_runner/internal/app/run"
|
||||||
"gitea.com/gitea/act_runner/internal/pkg/client"
|
"gitea.com/gitea/act_runner/internal/pkg/client"
|
||||||
"gitea.com/gitea/act_runner/internal/pkg/config"
|
"gitea.com/gitea/act_runner/internal/pkg/config"
|
||||||
|
"gitea.com/gitea/act_runner/internal/pkg/envcheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Poller struct {
|
type Poller struct {
|
||||||
client client.Client
|
client client.Client
|
||||||
runner *run.Runner
|
runner *run.Runner
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
tasksVersion atomic.Int64 // tasksVersion used to store the version of the last task fetched from the Gitea.
|
tasksVersion atomic.Int64 // tasksVersion used to store the version of the last task fetched from the Gitea.
|
||||||
|
bandwidthManager *envcheck.BandwidthManager
|
||||||
|
|
||||||
pollingCtx context.Context
|
pollingCtx context.Context
|
||||||
shutdownPolling context.CancelFunc
|
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() {
|
func (p *Poller) Poll() {
|
||||||
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
|
limiter := rate.NewLimiter(rate.Every(p.cfg.Runner.FetchInterval), 1)
|
||||||
wg := &sync.WaitGroup{}
|
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)
|
reqCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.FetchTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
// Detect capabilities including current disk space
|
||||||
|
caps := envcheck.DetectCapabilities(ctx, p.cfg.Container.DockerHost, p.cfg.Container.WorkdirParent)
|
||||||
|
|
||||||
|
// 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.
|
// Load the version value that was in the cache when the request was sent.
|
||||||
v := p.tasksVersion.Load()
|
v := p.tasksVersion.Load()
|
||||||
resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(&runnerv1.FetchTaskRequest{
|
fetchReq := &runnerv1.FetchTaskRequest{
|
||||||
TasksVersion: v,
|
TasksVersion: v,
|
||||||
}))
|
CapabilitiesJson: capsJson,
|
||||||
|
}
|
||||||
|
resp, err := p.client.FetchTask(reqCtx, connect.NewRequest(fetchReq))
|
||||||
if errors.Is(err, context.DeadlineExceeded) {
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
@@ -174,6 +193,18 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
|
|||||||
return nil, false
|
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 {
|
if resp.Msg.TasksVersion > v {
|
||||||
p.tasksVersion.CompareAndSwap(v, resp.Msg.TasksVersion)
|
p.tasksVersion.CompareAndSwap(v, resp.Msg.TasksVersion)
|
||||||
}
|
}
|
||||||
@@ -182,7 +213,7 @@ func (p *Poller) fetchTask(ctx context.Context) (*runnerv1.Task, bool) {
|
|||||||
return nil, false
|
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)
|
p.tasksVersion.CompareAndSwap(resp.Msg.TasksVersion, 0)
|
||||||
|
|
||||||
return resp.Msg.Task, true
|
return resp.Msg.Task, true
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -41,6 +42,48 @@ type Runner struct {
|
|||||||
runningTasks sync.Map
|
runningTasks sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getJobCacheDir returns a job-isolated cache directory
|
||||||
|
func (r *Runner) getJobCacheDir(taskID int64) string {
|
||||||
|
return filepath.Join(r.cfg.Host.WorkdirParent, "jobs", fmt.Sprintf("%d", taskID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupJobCache removes the job-specific cache directory after completion
|
||||||
|
func (r *Runner) cleanupJobCache(taskID int64) {
|
||||||
|
jobCacheDir := r.getJobCacheDir(taskID)
|
||||||
|
if err := os.RemoveAll(jobCacheDir); err != nil {
|
||||||
|
log.Warnf("failed to cleanup job cache %s: %v", jobCacheDir, err)
|
||||||
|
} else {
|
||||||
|
log.Infof("cleaned up job cache: %s", jobCacheDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanStaleJobCaches removes job cache directories older than maxAge
|
||||||
|
func (r *Runner) CleanStaleJobCaches(maxAge time.Duration) {
|
||||||
|
jobsDir := filepath.Join(r.cfg.Host.WorkdirParent, "jobs")
|
||||||
|
entries, err := os.ReadDir(jobsDir)
|
||||||
|
if err != nil {
|
||||||
|
return // directory may not exist yet
|
||||||
|
}
|
||||||
|
|
||||||
|
cutoff := time.Now().Add(-maxAge)
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !entry.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if info.ModTime().Before(cutoff) {
|
||||||
|
jobPath := filepath.Join(jobsDir, entry.Name())
|
||||||
|
if err := os.RemoveAll(jobPath); err != nil {
|
||||||
|
log.Warnf("failed to remove stale job cache %s: %v", jobPath, err)
|
||||||
|
} else {
|
||||||
|
log.Infof("evicted stale job cache: %s", jobPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner {
|
func NewRunner(cfg *config.Config, reg *config.Registration, cli client.Client) *Runner {
|
||||||
ls := labels.Labels{}
|
ls := labels.Labels{}
|
||||||
for _, v := range reg.Labels {
|
for _, v := range reg.Labels {
|
||||||
@@ -95,6 +138,7 @@ func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error {
|
|||||||
}
|
}
|
||||||
r.runningTasks.Store(task.Id, struct{}{})
|
r.runningTasks.Store(task.Id, struct{}{})
|
||||||
defer r.runningTasks.Delete(task.Id)
|
defer r.runningTasks.Delete(task.Id)
|
||||||
|
defer r.cleanupJobCache(task.Id)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout)
|
ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -197,19 +241,30 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
|||||||
maxLifetime = time.Until(deadline)
|
maxLifetime = time.Until(deadline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create job-specific environment with isolated cache directories
|
||||||
|
jobCacheDir := r.getJobCacheDir(task.Id)
|
||||||
|
jobEnvs := make(map[string]string, len(r.envs)+2)
|
||||||
|
for k, v := range r.envs {
|
||||||
|
jobEnvs[k] = v
|
||||||
|
}
|
||||||
|
// Isolate golangci-lint cache to prevent parallel job conflicts
|
||||||
|
jobEnvs["GOLANGCI_LINT_CACHE"] = filepath.Join(jobCacheDir, "golangci-lint")
|
||||||
|
// Set XDG_CACHE_HOME to isolate other tools that respect it
|
||||||
|
jobEnvs["XDG_CACHE_HOME"] = jobCacheDir
|
||||||
|
|
||||||
runnerConfig := &runner.Config{
|
runnerConfig := &runner.Config{
|
||||||
// On Linux, Workdir will be like "/<parent_directory>/<owner>/<repo>"
|
// On Linux, Workdir will be like "/<parent_directory>/<owner>/<repo>"
|
||||||
// On Windows, Workdir will be like "\<parent_directory>\<owner>\<repo>"
|
// On Windows, Workdir will be like "\<parent_directory>\<owner>\<repo>"
|
||||||
Workdir: filepath.FromSlash(fmt.Sprintf("/%s/%s", strings.TrimLeft(r.cfg.Container.WorkdirParent, "/"), preset.Repository)),
|
Workdir: filepath.FromSlash(fmt.Sprintf("/%s/%s", strings.TrimLeft(r.cfg.Container.WorkdirParent, "/"), preset.Repository)),
|
||||||
BindWorkdir: false,
|
BindWorkdir: false,
|
||||||
ActionCacheDir: filepath.FromSlash(r.cfg.Host.WorkdirParent),
|
ActionCacheDir: filepath.FromSlash(jobCacheDir),
|
||||||
|
|
||||||
ReuseContainers: false,
|
ReuseContainers: false,
|
||||||
ForcePull: r.cfg.Container.ForcePull,
|
ForcePull: r.cfg.Container.ForcePull,
|
||||||
ForceRebuild: r.cfg.Container.ForceRebuild,
|
ForceRebuild: r.cfg.Container.ForceRebuild,
|
||||||
LogOutput: true,
|
LogOutput: true,
|
||||||
JSONLogger: false,
|
JSONLogger: false,
|
||||||
Env: r.envs,
|
Env: jobEnvs,
|
||||||
Secrets: task.Secrets,
|
Secrets: task.Secrets,
|
||||||
GitHubInstance: strings.TrimSuffix(r.client.Address(), "/"),
|
GitHubInstance: strings.TrimSuffix(r.client.Address(), "/"),
|
||||||
AutoRemove: true,
|
AutoRemove: true,
|
||||||
@@ -249,9 +304,10 @@ func (r *Runner) run(ctx context.Context, task *runnerv1.Task, reporter *report.
|
|||||||
return execErr
|
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{
|
return r.client.Declare(ctx, connect.NewRequest(&runnerv1.DeclareRequest{
|
||||||
Version: ver.Version(),
|
Version: ver.Version(),
|
||||||
Labels: labels,
|
Labels: labels,
|
||||||
|
CapabilitiesJson: capabilitiesJson,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ runner:
|
|||||||
env_file: .env
|
env_file: .env
|
||||||
# The timeout for a job to be finished.
|
# The timeout for a job to be finished.
|
||||||
# Please note that the Gitea instance also has a timeout (3h by default) for the job.
|
# 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
|
timeout: 3h
|
||||||
# The timeout for the runner to wait for running jobs to finish when shutting down.
|
# 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.
|
# Any running jobs that haven't finished after this timeout will be cancelled.
|
||||||
@@ -39,13 +39,13 @@ runner:
|
|||||||
github_mirror: ''
|
github_mirror: ''
|
||||||
# The labels of a runner are used to determine which jobs the runner can run, and how to run them.
|
# 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"
|
# 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 registering, it will ask for inputting labels.
|
||||||
# If it's empty when execute `daemon`, will use labels in `.runner` file.
|
# If it's empty when execute `daemon`, will use labels in `.runner` file.
|
||||||
labels:
|
labels:
|
||||||
- "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest"
|
- "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-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04"
|
||||||
- "ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04"
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
# Enable cache server to use actions/cache.
|
# Enable cache server to use actions/cache.
|
||||||
@@ -72,7 +72,7 @@ container:
|
|||||||
network: ""
|
network: ""
|
||||||
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
|
# Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker).
|
||||||
privileged: false
|
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:
|
options:
|
||||||
# The parent directory of a job's working directory.
|
# 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.
|
# 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:
|
||||||
# - '**'
|
# - '**'
|
||||||
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 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 "-", 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.
|
# 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)
|
||||||
|
}
|
||||||
889
internal/pkg/envcheck/capabilities.go
Normal file
889
internal/pkg/envcheck/capabilities.go
Normal file
@@ -0,0 +1,889 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package envcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiskInfo holds disk space information
|
||||||
|
type DiskInfo struct {
|
||||||
|
Path string `json:"path,omitempty"` // Path being checked (working directory)
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
// XcodeInfo holds Xcode and iOS development information
|
||||||
|
type XcodeInfo struct {
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
|
Build string `json:"build,omitempty"`
|
||||||
|
SDKs []string `json:"sdks,omitempty"` // e.g., ["iOS 17.0", "macOS 14.0"]
|
||||||
|
Simulators []string `json:"simulators,omitempty"` // Available iOS simulators
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
Xcode *XcodeInfo `json:"xcode,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"`
|
||||||
|
BuildTools []string `json:"build_tools,omitempty"` // Available build/installer tools
|
||||||
|
PackageManagers []string `json:"package_managers,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
|
||||||
|
// workingDir is the directory where builds will run (for disk space detection)
|
||||||
|
func DetectCapabilities(ctx context.Context, dockerHost string, workingDir string) *RunnerCapabilities {
|
||||||
|
cap := &RunnerCapabilities{
|
||||||
|
OS: runtime.GOOS,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
Tools: make(map[string][]string),
|
||||||
|
BuildTools: []string{},
|
||||||
|
PackageManagers: []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 macOS Xcode/iOS
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
cap.Xcode = detectXcode(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 build tools
|
||||||
|
detectBuildTools(ctx, cap)
|
||||||
|
|
||||||
|
// Detect package managers
|
||||||
|
detectPackageManagers(ctx, cap)
|
||||||
|
|
||||||
|
// Detect disk space on the working directory's filesystem
|
||||||
|
cap.Disk = detectDiskSpace(workingDir)
|
||||||
|
|
||||||
|
// Generate suggested labels based on detected capabilities
|
||||||
|
cap.SuggestedLabels = generateSuggestedLabels(cap)
|
||||||
|
|
||||||
|
return cap
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectXcode detects Xcode and iOS development capabilities on macOS
|
||||||
|
func detectXcode(ctx context.Context) *XcodeInfo {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Check for xcodebuild
|
||||||
|
cmd := exec.CommandContext(timeoutCtx, "xcodebuild", "-version")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
xcode := &XcodeInfo{}
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "Xcode ") {
|
||||||
|
xcode.Version = strings.TrimPrefix(line, "Xcode ")
|
||||||
|
} else if strings.HasPrefix(line, "Build version ") {
|
||||||
|
xcode.Build = strings.TrimPrefix(line, "Build version ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get available SDKs
|
||||||
|
cmd = exec.CommandContext(timeoutCtx, "xcodebuild", "-showsdks")
|
||||||
|
output, err = cmd.Output()
|
||||||
|
if err == nil {
|
||||||
|
lines = strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
// Look for SDK lines like "-sdk iphoneos17.0" or "iOS 17.0"
|
||||||
|
if strings.Contains(line, "SDK") || strings.HasPrefix(line, "-sdk") {
|
||||||
|
continue // Skip header lines
|
||||||
|
}
|
||||||
|
if strings.Contains(line, "iOS") || strings.Contains(line, "macOS") ||
|
||||||
|
strings.Contains(line, "watchOS") || strings.Contains(line, "tvOS") ||
|
||||||
|
strings.Contains(line, "visionOS") || strings.Contains(line, "xrOS") {
|
||||||
|
// Extract SDK name
|
||||||
|
if idx := strings.Index(line, "-sdk"); idx != -1 {
|
||||||
|
sdkPart := strings.TrimSpace(line[:idx])
|
||||||
|
if sdkPart != "" {
|
||||||
|
xcode.SDKs = append(xcode.SDKs, sdkPart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get available simulators
|
||||||
|
cmd = exec.CommandContext(timeoutCtx, "xcrun", "simctl", "list", "devices", "available", "-j")
|
||||||
|
output, err = cmd.Output()
|
||||||
|
if err == nil {
|
||||||
|
var simData struct {
|
||||||
|
Devices map[string][]struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
State string `json:"state"`
|
||||||
|
} `json:"devices"`
|
||||||
|
}
|
||||||
|
if json.Unmarshal(output, &simData) == nil {
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for runtime, devices := range simData.Devices {
|
||||||
|
if strings.Contains(runtime, "iOS") {
|
||||||
|
for _, dev := range devices {
|
||||||
|
key := dev.Name
|
||||||
|
if !seen[key] {
|
||||||
|
seen[key] = true
|
||||||
|
xcode.Simulators = append(xcode.Simulators, dev.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if xcode.Version == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return xcode
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Xcode/iOS labels (macOS only)
|
||||||
|
if cap.Xcode != nil {
|
||||||
|
addLabel("xcode")
|
||||||
|
// Check for SDKs
|
||||||
|
for _, sdk := range cap.Xcode.SDKs {
|
||||||
|
sdkLower := strings.ToLower(sdk)
|
||||||
|
if strings.Contains(sdkLower, "ios") {
|
||||||
|
addLabel("ios")
|
||||||
|
}
|
||||||
|
if strings.Contains(sdkLower, "visionos") || strings.Contains(sdkLower, "xros") {
|
||||||
|
addLabel("visionos")
|
||||||
|
}
|
||||||
|
if strings.Contains(sdkLower, "watchos") {
|
||||||
|
addLabel("watchos")
|
||||||
|
}
|
||||||
|
if strings.Contains(sdkLower, "tvos") {
|
||||||
|
addLabel("tvos")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If simulators available, add simulator label
|
||||||
|
if len(cap.Xcode.Simulators) > 0 {
|
||||||
|
addLabel("ios-simulator")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool-based labels
|
||||||
|
if _, ok := cap.Tools["dotnet"]; ok {
|
||||||
|
addLabel("dotnet")
|
||||||
|
}
|
||||||
|
if _, ok := cap.Tools["java"]; ok {
|
||||||
|
addLabel("java")
|
||||||
|
}
|
||||||
|
if _, ok := cap.Tools["node"]; ok {
|
||||||
|
addLabel("node")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build tool labels
|
||||||
|
for _, tool := range cap.BuildTools {
|
||||||
|
switch tool {
|
||||||
|
case "msbuild":
|
||||||
|
addLabel("msbuild")
|
||||||
|
case "visual-studio":
|
||||||
|
addLabel("vs2022") // or detect actual version
|
||||||
|
case "inno-setup":
|
||||||
|
addLabel("inno-setup")
|
||||||
|
case "nsis":
|
||||||
|
addLabel("nsis")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
"ruby": detectRubyVersions,
|
||||||
|
"php": detectPHPVersions,
|
||||||
|
"swift": detectSwiftVersions,
|
||||||
|
"kotlin": detectKotlinVersions,
|
||||||
|
"flutter": detectFlutterVersions,
|
||||||
|
"dart": detectDartVersions,
|
||||||
|
"powershell": detectPowerShellVersions,
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool, detector := range toolDetectors {
|
||||||
|
if versions := detector(ctx); len(versions) > 0 {
|
||||||
|
cap.Tools[tool] = versions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect additional tools that just need presence check
|
||||||
|
simpleTools := map[string]string{
|
||||||
|
"git": "git",
|
||||||
|
"cmake": "cmake",
|
||||||
|
"make": "make",
|
||||||
|
"ninja": "ninja",
|
||||||
|
"gradle": "gradle",
|
||||||
|
"maven": "mvn",
|
||||||
|
"npm": "npm",
|
||||||
|
"yarn": "yarn",
|
||||||
|
"pnpm": "pnpm",
|
||||||
|
"cargo": "cargo",
|
||||||
|
"pip": "pip3",
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, cmd := range simpleTools {
|
||||||
|
if v := detectSimpleToolVersion(ctx, cmd); v != "" {
|
||||||
|
cap.Tools[name] = []string{v}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectBuildTools(ctx context.Context, cap *RunnerCapabilities) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
detectWindowsBuildTools(ctx, cap)
|
||||||
|
case "darwin":
|
||||||
|
detectMacOSBuildTools(ctx, cap)
|
||||||
|
case "linux":
|
||||||
|
detectLinuxBuildTools(ctx, cap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectWindowsBuildTools(ctx context.Context, cap *RunnerCapabilities) {
|
||||||
|
// Check for Visual Studio via vswhere
|
||||||
|
vswherePaths := []string{
|
||||||
|
`C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe`,
|
||||||
|
`C:\Program Files\Microsoft Visual Studio\Installer\vswhere.exe`,
|
||||||
|
}
|
||||||
|
for _, vswhere := range vswherePaths {
|
||||||
|
if _, err := os.Stat(vswhere); err == nil {
|
||||||
|
cmd := exec.CommandContext(ctx, vswhere, "-latest", "-property", "displayName")
|
||||||
|
if output, err := cmd.Output(); err == nil && len(output) > 0 {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "visual-studio")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for MSBuild
|
||||||
|
msbuildPaths := []string{
|
||||||
|
`C:\Program Files\Microsoft Visual Studio\2022\Enterprise\MSBuild\Current\Bin\MSBuild.exe`,
|
||||||
|
`C:\Program Files\Microsoft Visual Studio\2022\Professional\MSBuild\Current\Bin\MSBuild.exe`,
|
||||||
|
`C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe`,
|
||||||
|
`C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe`,
|
||||||
|
`C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\MSBuild\Current\Bin\MSBuild.exe`,
|
||||||
|
`C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe`,
|
||||||
|
}
|
||||||
|
for _, msbuild := range msbuildPaths {
|
||||||
|
if _, err := os.Stat(msbuild); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "msbuild")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Inno Setup
|
||||||
|
innoSetupPaths := []string{
|
||||||
|
`C:\Program Files (x86)\Inno Setup 6\ISCC.exe`,
|
||||||
|
`C:\Program Files\Inno Setup 6\ISCC.exe`,
|
||||||
|
`C:\Program Files (x86)\Inno Setup 5\ISCC.exe`,
|
||||||
|
`C:\Program Files\Inno Setup 5\ISCC.exe`,
|
||||||
|
}
|
||||||
|
for _, iscc := range innoSetupPaths {
|
||||||
|
if _, err := os.Stat(iscc); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "inno-setup")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Also check PATH
|
||||||
|
if _, err := exec.LookPath("iscc"); err == nil {
|
||||||
|
if !contains(cap.BuildTools, "inno-setup") {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "inno-setup")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for NSIS
|
||||||
|
nsisPaths := []string{
|
||||||
|
`C:\Program Files (x86)\NSIS\makensis.exe`,
|
||||||
|
`C:\Program Files\NSIS\makensis.exe`,
|
||||||
|
}
|
||||||
|
for _, nsis := range nsisPaths {
|
||||||
|
if _, err := os.Stat(nsis); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "nsis")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("makensis"); err == nil {
|
||||||
|
if !contains(cap.BuildTools, "nsis") {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "nsis")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for WiX Toolset
|
||||||
|
wixPaths := []string{
|
||||||
|
`C:\Program Files (x86)\WiX Toolset v3.11\bin\candle.exe`,
|
||||||
|
`C:\Program Files (x86)\WiX Toolset v3.14\bin\candle.exe`,
|
||||||
|
}
|
||||||
|
for _, wix := range wixPaths {
|
||||||
|
if _, err := os.Stat(wix); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "wix")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for signtool (Windows SDK)
|
||||||
|
signtoolPaths, _ := filepath.Glob(`C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe`)
|
||||||
|
if len(signtoolPaths) > 0 {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "signtool")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectMacOSBuildTools(ctx context.Context, cap *RunnerCapabilities) {
|
||||||
|
// Check for xcpretty
|
||||||
|
if _, err := exec.LookPath("xcpretty"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "xcpretty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for fastlane
|
||||||
|
if _, err := exec.LookPath("fastlane"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "fastlane")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for CocoaPods
|
||||||
|
if _, err := exec.LookPath("pod"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "cocoapods")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Carthage
|
||||||
|
if _, err := exec.LookPath("carthage"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "carthage")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for SwiftLint
|
||||||
|
if _, err := exec.LookPath("swiftlint"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "swiftlint")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for create-dmg or similar
|
||||||
|
if _, err := exec.LookPath("create-dmg"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "create-dmg")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Packages (packagesbuild)
|
||||||
|
if _, err := exec.LookPath("packagesbuild"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "packages")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for pkgbuild (built-in)
|
||||||
|
if _, err := exec.LookPath("pkgbuild"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "pkgbuild")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for codesign (built-in)
|
||||||
|
if _, err := exec.LookPath("codesign"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "codesign")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for notarytool (built-in with Xcode)
|
||||||
|
if _, err := exec.LookPath("notarytool"); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, "notarytool")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectLinuxBuildTools(ctx context.Context, cap *RunnerCapabilities) {
|
||||||
|
// Check for common Linux build tools
|
||||||
|
tools := []string{
|
||||||
|
"gcc", "g++", "clang", "clang++",
|
||||||
|
"autoconf", "automake", "libtool",
|
||||||
|
"pkg-config", "meson",
|
||||||
|
"dpkg-deb", "rpmbuild", "fpm",
|
||||||
|
"appimage-builder", "linuxdeploy",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tool := range tools {
|
||||||
|
if _, err := exec.LookPath(tool); err == nil {
|
||||||
|
cap.BuildTools = append(cap.BuildTools, tool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectPackageManagers(ctx context.Context, cap *RunnerCapabilities) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
if _, err := exec.LookPath("choco"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "chocolatey")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("scoop"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "scoop")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("winget"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "winget")
|
||||||
|
}
|
||||||
|
case "darwin":
|
||||||
|
if _, err := exec.LookPath("brew"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "homebrew")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("port"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "macports")
|
||||||
|
}
|
||||||
|
case "linux":
|
||||||
|
if _, err := exec.LookPath("apt"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "apt")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("yum"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "yum")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("dnf"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "dnf")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("pacman"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "pacman")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("zypper"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "zypper")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("apk"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "apk")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("snap"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "snap")
|
||||||
|
}
|
||||||
|
if _, err := exec.LookPath("flatpak"); err == nil {
|
||||||
|
cap.PackageManagers = append(cap.PackageManagers, "flatpak")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
for _, ver := range v {
|
||||||
|
if !contains(versions, ver) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, "version") {
|
||||||
|
start := strings.Index(line, "\"")
|
||||||
|
end := strings.LastIndex(line, "\"")
|
||||||
|
if start != -1 && end > start {
|
||||||
|
version := line[start+1 : end]
|
||||||
|
parts := strings.Split(version, ".")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
if parts[0] == "1" && len(parts) > 1 {
|
||||||
|
return []string{parts[1]}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
parts := strings.Split(line, " ")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
version := parts[0]
|
||||||
|
major := strings.Split(version, ".")[0]
|
||||||
|
if !contains(versions, major) {
|
||||||
|
versions = append(versions, major)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return versions
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectRustVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "rustc", "--version", "rustc ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectRubyVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "ruby", "--version", "ruby ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectPHPVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "php", "--version", "PHP ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectSwiftVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "swift", "--version", "Swift version ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectKotlinVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "kotlin", "-version", "Kotlin version ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectFlutterVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "flutter", "--version", "Flutter ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectDartVersions(ctx context.Context) []string {
|
||||||
|
return detectToolVersion(ctx, "dart", "--version", "Dart SDK version: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectPowerShellVersions(ctx context.Context) []string {
|
||||||
|
versions := []string{}
|
||||||
|
|
||||||
|
// Check for pwsh (PowerShell Core / PowerShell 7+)
|
||||||
|
if v := detectPwshVersion(ctx, "pwsh"); v != "" {
|
||||||
|
versions = append(versions, "pwsh:"+v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for powershell (Windows PowerShell 5.x)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
if v := detectPwshVersion(ctx, "powershell"); v != "" {
|
||||||
|
versions = append(versions, "powershell:"+v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return versions
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectPwshVersion(ctx context.Context, cmd string) string {
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Use -Command to get version
|
||||||
|
var c *exec.Cmd
|
||||||
|
if cmd == "pwsh" {
|
||||||
|
c = exec.CommandContext(timeoutCtx, cmd, "-Command", "$PSVersionTable.PSVersion.ToString()")
|
||||||
|
} else {
|
||||||
|
c = exec.CommandContext(timeoutCtx, cmd, "-Command", "$PSVersionTable.PSVersion.ToString()")
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := c.Output()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
version := strings.TrimSpace(string(output))
|
||||||
|
// Return major.minor
|
||||||
|
parts := strings.Split(version, ".")
|
||||||
|
if len(parts) >= 2 {
|
||||||
|
return parts[0] + "." + parts[1]
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectSimpleToolVersion(ctx context.Context, cmd string) string {
|
||||||
|
if _, err := exec.LookPath(cmd); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c := exec.CommandContext(timeoutCtx, cmd, "--version")
|
||||||
|
output, err := c.Output()
|
||||||
|
if err != nil {
|
||||||
|
// Try without --version for tools that don't support it
|
||||||
|
return "installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(strings.Split(string(output), "\n")[0])
|
||||||
|
// Extract version number if possible
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
for _, part := range parts {
|
||||||
|
// Look for something that looks like a version
|
||||||
|
if len(part) > 0 && (part[0] >= '0' && part[0] <= '9' || part[0] == 'v') {
|
||||||
|
return strings.TrimPrefix(part, "v")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
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):]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Fields(line)
|
||||||
|
if len(parts) > 0 {
|
||||||
|
version := parts[0]
|
||||||
|
version = strings.TrimPrefix(version, "v")
|
||||||
|
vparts := strings.Split(version, ".")
|
||||||
|
if len(vparts) >= 2 {
|
||||||
|
return []string{vparts[0] + "." + vparts[1]}
|
||||||
|
}
|
||||||
|
return []string{vparts[0]}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(slice []string, item string) bool {
|
||||||
|
for _, s := range slice {
|
||||||
|
if s == item {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
43
internal/pkg/envcheck/disk_unix.go
Normal file
43
internal/pkg/envcheck/disk_unix.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
//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 specified path's filesystem (Unix version)
|
||||||
|
// If path is empty, defaults to "/"
|
||||||
|
func detectDiskSpace(path string) *DiskInfo {
|
||||||
|
if path == "" {
|
||||||
|
path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
var stat unix.Statfs_t
|
||||||
|
|
||||||
|
err := unix.Statfs(path, &stat)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback to root if the path doesn't exist
|
||||||
|
err = unix.Statfs("/", &stat)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
path = "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
total := stat.Blocks * uint64(stat.Bsize)
|
||||||
|
free := stat.Bavail * uint64(stat.Bsize)
|
||||||
|
used := total - free
|
||||||
|
usedPercent := float64(used) / float64(total) * 100
|
||||||
|
|
||||||
|
return &DiskInfo{
|
||||||
|
Path: path,
|
||||||
|
Total: total,
|
||||||
|
Free: free,
|
||||||
|
Used: used,
|
||||||
|
UsedPercent: usedPercent,
|
||||||
|
}
|
||||||
|
}
|
||||||
57
internal/pkg/envcheck/disk_windows.go
Normal file
57
internal/pkg/envcheck/disk_windows.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package envcheck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// detectDiskSpace detects disk space on the specified path's drive (Windows version)
|
||||||
|
// If path is empty, defaults to "C:\"
|
||||||
|
func detectDiskSpace(path string) *DiskInfo {
|
||||||
|
if path == "" {
|
||||||
|
path = "C:\\"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve to absolute path
|
||||||
|
absPath, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
absPath = "C:\\"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract drive letter (e.g., "D:\" from "D:\builds\runner")
|
||||||
|
drivePath := filepath.VolumeName(absPath) + "\\"
|
||||||
|
if drivePath == "\\" {
|
||||||
|
drivePath = "C:\\"
|
||||||
|
}
|
||||||
|
|
||||||
|
var freeBytesAvailable, totalNumberOfBytes, totalNumberOfFreeBytes uint64
|
||||||
|
|
||||||
|
pathPtr := windows.StringToUTF16Ptr(drivePath)
|
||||||
|
err = windows.GetDiskFreeSpaceEx(pathPtr, &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)
|
||||||
|
if err != nil {
|
||||||
|
// Fallback to C: drive
|
||||||
|
pathPtr = windows.StringToUTF16Ptr("C:\\")
|
||||||
|
err = windows.GetDiskFreeSpaceEx(pathPtr, &freeBytesAvailable, &totalNumberOfBytes, &totalNumberOfFreeBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
drivePath = "C:\\"
|
||||||
|
}
|
||||||
|
|
||||||
|
used := totalNumberOfBytes - totalNumberOfFreeBytes
|
||||||
|
usedPercent := float64(used) / float64(totalNumberOfBytes) * 100
|
||||||
|
|
||||||
|
return &DiskInfo{
|
||||||
|
Path: drivePath,
|
||||||
|
Total: totalNumberOfBytes,
|
||||||
|
Free: totalNumberOfFreeBytes,
|
||||||
|
Used: used,
|
||||||
|
UsedPercent: usedPercent,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user