diff --git a/doc/gitea.md b/doc/gitea.md index a39ea99f5..870d067fa 100644 --- a/doc/gitea.md +++ b/doc/gitea.md @@ -186,7 +186,7 @@ After changing `app.ini`, restart Gitea for the setting to take effect. ## Differences from GitHub -- **Runner binary:** Gitea uses `act_runner` instead of the GitHub Actions runner. GARM handles the differences transparently. +- **Runner binary:** Gitea uses `gitea-runner` (formerly `act_runner`) instead of the GitHub Actions runner. GARM handles the differences transparently. - **Enterprise level:** Not available for Gitea - **Scale sets:** Not available for Gitea (GitHub-only feature) - **GitHub Apps:** Gitea uses PATs only diff --git a/internal/templates/userdata/gitea_linux_userdata.tmpl b/internal/templates/userdata/gitea_linux_userdata.tmpl index a7adefbe8..b64dec990 100644 --- a/internal/templates/userdata/gitea_linux_userdata.tmpl +++ b/internal/templates/userdata/gitea_linux_userdata.tmpl @@ -11,7 +11,7 @@ CALLBACK_URL="{{ .CallbackURL }}" METADATA_URL="{{ .MetadataURL }}" BEARER_TOKEN="{{ .CallbackToken }}" -RUN_HOME="/home/{{.RunnerUsername}}/act-runner" +RUN_HOME="/home/{{.RunnerUsername}}/gitea-runner" if [ -z "$METADATA_URL" ];then echo "no token is available and METADATA_URL is not set" @@ -87,7 +87,7 @@ log_file = "/var/log/garm-agent/garm-agent.log" work_dir = "$RUN_HOME" enable_shell = $AGENT_SHELL token = "$AGENT_TOKEN" -runner_cmdline = ["$RUN_HOME/act_runner", "daemon", "--once"] +runner_cmdline = ["$RUN_HOME/gitea-runner", "daemon", "--once"] state_db_path = "/etc/garm-agent/agent-state.db" EOF @@ -119,9 +119,9 @@ fi function downloadAndExtractRunner() { sendStatus "downloading tools from {{ .DownloadURL }}" mkdir -p "$RUN_HOME" || fail "failed to create actions-runner folder" - curl --retry 5 --retry-delay 5 --retry-connrefused --fail -L -o "$RUN_HOME/act_runner" "{{ .DownloadURL }}" || fail "failed to download tools" + curl --retry 5 --retry-delay 5 --retry-connrefused --fail -L -o "$RUN_HOME/gitea-runner" "{{ .DownloadURL }}" || fail "failed to download tools" chown {{ .RunnerUsername }}:{{ .RunnerGroup }} -R "$RUN_HOME"/ || fail "failed to change owner" - chmod +x "$RUN_HOME/act_runner" || fail "failed to set executable flag" + chmod +x "$RUN_HOME/gitea-runner" || fail "failed to set executable flag" } if [ ! -d "$RUN_HOME" ];then @@ -147,7 +147,7 @@ set +e attempt=1 while true; do ERROUT=$(mktemp) - ./act_runner register --ephemeral --no-interactive --instance "{{ .RepoURL }}" --token "$GITHUB_TOKEN" --name "{{ .RunnerName }}" --labels "{{ .RunnerLabels }}" 2>$ERROUT + ./gitea-runner register --ephemeral --no-interactive --instance "{{ .RepoURL }}" --token "$GITHUB_TOKEN" --name "{{ .RunnerName }}" --labels "{{ .RunnerLabels }}" 2>$ERROUT if [ $? -eq 0 ]; then rm $ERROUT || true sendStatus "runner successfully configured after $attempt attempt(s)" diff --git a/internal/templates/userdata/gitea_windows_userdata.tmpl b/internal/templates/userdata/gitea_windows_userdata.tmpl index 2ef13f7c7..e0146b09c 100644 --- a/internal/templates/userdata/gitea_windows_userdata.tmpl +++ b/internal/templates/userdata/gitea_windows_userdata.tmpl @@ -465,15 +465,15 @@ function Install-NSSM { rm -Recurse -Force "$runnerDir\nssm" rm -Force "$runnerDir\nssm.zip" $nssm="$runnerDir/nssm.exe" - & $nssm install GiteaActRunner "$runnerDir/act_runner.exe" - & $nssm set GiteaActRunner AppParameters daemon - & $nssm set GiteaActRunner AppStdout $runnerDir\stdout.log - & $nssm set GiteaActRunner AppStderr $runnerDir\stderr.log - & $nssm set GiteaActRunner AppStopMethodSkip 6 - & $nssm set GiteaActRunner AppStopMethodConsole 1000 - & $nssm set GiteaActRunner AppThrottle 5000 - & $nssm set GiteaActRunner ObjectName $username $password - & $nssm start GiteaActRunner + & $nssm install GiteaRunner "$runnerDir/gitea-runner.exe" + & $nssm set GiteaRunner AppParameters daemon + & $nssm set GiteaRunner AppStdout $runnerDir\stdout.log + & $nssm set GiteaRunner AppStderr $runnerDir\stderr.log + & $nssm set GiteaRunner AppStopMethodSkip 6 + & $nssm set GiteaRunner AppStopMethodConsole 1000 + & $nssm set GiteaRunner AppThrottle 5000 + & $nssm set GiteaRunner ObjectName $username $password + & $nssm start GiteaRunner } @@ -494,7 +494,7 @@ function Install-Runner() { Throw "missing metadata URL" } $runnerDir = "C:\actions-runner" - $runnerExecutable = Join-Path $runnerDir "act_runner.exe" + $runnerExecutable = Join-Path $runnerDir "gitea-runner.exe" # Create user with administrator rights to run service as $userPasswd = Get-RandomString -Length 10 diff --git a/runner/metadata.go b/runner/metadata.go index 0e47c810a..36177ce23 100644 --- a/runner/metadata.go +++ b/runner/metadata.go @@ -62,13 +62,13 @@ WantedBy=multi-user.target ` var giteaSystemdUnitTemplate = `[Unit] -Description=Act Runner ({{.ServiceName}}) +Description=Gitea Runner ({{.ServiceName}}) After=network.target [Service] -ExecStart=/home/{{.RunAsUser}}/act-runner/act_runner daemon --once +ExecStart=/home/{{.RunAsUser}}/gitea-runner/gitea-runner daemon --once User={{.RunAsUser}} -WorkingDirectory=/home/{{.RunAsUser}}/act-runner +WorkingDirectory=/home/{{.RunAsUser}}/gitea-runner KillMode=process KillSignal=SIGTERM TimeoutStopSec=5min diff --git a/runner/metadata_test.go b/runner/metadata_test.go index 8f72957e0..58a3432ec 100644 --- a/runner/metadata_test.go +++ b/runner/metadata_test.go @@ -453,7 +453,7 @@ func (s *MetadataTestSuite) TestGenerateSystemdUnitFile() { name: "Gitea with custom user", runAsUser: "gitea-user", forgeType: params.GiteaEndpointType, - expectedTemplate: "Act Runner", + expectedTemplate: "Gitea Runner", }, } @@ -1282,8 +1282,8 @@ func (s *MetadataTestSuite) TestGenerateSystemdUnitFileGiteaWithDefaultUser() { s.Require().Nil(err) s.Require().NotEmpty(unitFile) - s.Require().Contains(string(unitFile), "Act Runner") - s.Require().Contains(string(unitFile), "act_runner daemon --once") + s.Require().Contains(string(unitFile), "Gitea Runner") + s.Require().Contains(string(unitFile), "gitea-runner daemon --once") s.Require().Contains(string(unitFile), "Restart=always") } diff --git a/util/appdefaults/appdefaults.go b/util/appdefaults/appdefaults.go index b803f980d..faf93ad85 100644 --- a/util/appdefaults/appdefaults.go +++ b/util/appdefaults/appdefaults.go @@ -46,7 +46,7 @@ const ( // GiteaRunnerReleasesURL is the public API URL that returns a json of all Gitea runner releases. // By default it returns the last 10 releases, which is enough for our needs. - GiteaRunnerReleasesURL = "https://gitea.com/api/v1/repos/gitea/act_runner/releases" + GiteaRunnerReleasesURL = "https://gitea.com/api/v1/repos/gitea/runner/releases" // GiteaRunnerMinimumVersion is the minimum version we need in order to support ephemeral runners. GiteaRunnerMinimumVersion = "v0.2.12" diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 0be53e6ac..613a3ecb8 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -2881,9 +2881,9 @@ } }, "node_modules/basic-ftp": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz", - "integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", "dev": true, "license": "MIT", "engines": { @@ -4333,6 +4333,16 @@ "node": ">=8" } }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "dev": true, @@ -4480,11 +4490,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/jsbn": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, "node_modules/jsdom": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", @@ -5534,20 +5539,6 @@ "node": ">= 14" } }, - "node_modules/proxy-agent/node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/proxy-agent/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -5609,32 +5600,6 @@ "dev": true, "license": "MIT" }, - "node_modules/proxy-agent/node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/proxy-agent/node_modules/socks": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.6.tgz", - "integrity": "sha512-pe4Y2yzru68lXCb38aAqRf5gvN8YdjP1lok5o0J7BOHljkyCGKVz7H3vpVIXKD27rj2giOJ7DwVyk/GWrPHDWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/proxy-agent/node_modules/socks-proxy-agent": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", @@ -5650,13 +5615,6 @@ "node": ">= 14" } }, - "node_modules/proxy-agent/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -5979,6 +5937,32 @@ "node": ">=6" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", + "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "dev": true, diff --git a/webapp/src/lib/components/forms/EndpointForm.svelte b/webapp/src/lib/components/forms/EndpointForm.svelte index 18671216c..26ac36bb3 100644 --- a/webapp/src/lib/components/forms/EndpointForm.svelte +++ b/webapp/src/lib/components/forms/EndpointForm.svelte @@ -153,7 +153,7 @@
@@ -166,7 +166,7 @@ disabled={formData.use_internal_tools_metadata} autocomplete="off" class="w-full px-3 py-2 border rounded-md focus:outline-none transition-colors {formData.use_internal_tools_metadata ? 'bg-gray-100 dark:bg-gray-800 border-gray-300 dark:border-gray-700 text-gray-400 dark:text-gray-500 cursor-not-allowed' : 'border-gray-300 dark:border-gray-600 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white'}" - placeholder="https://gitea.com/api/v1/repos/gitea/act_runner/releases" + placeholder="https://gitea.com/api/v1/repos/gitea/runner/releases" />
@@ -184,7 +184,7 @@
diff --git a/webapp/src/routes/objects/page.integration.test.ts b/webapp/src/routes/objects/page.integration.test.ts index a9aaa9f92..7ab79aad4 100644 --- a/webapp/src/routes/objects/page.integration.test.ts +++ b/webapp/src/routes/objects/page.integration.test.ts @@ -3,6 +3,12 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/svelte'; import ObjectsPage from './+page.svelte'; import { createMockFileObject } from '../../test/factories.js'; +// Reset any component mocks that might be set by setup.ts +vi.unmock('$lib/components/PageHeader.svelte'); +vi.unmock('$lib/components/DataTable.svelte'); +vi.unmock('$lib/components/DeleteModal.svelte'); +vi.unmock('$lib/components/cells'); + // Only mock the GARM API vi.mock('$lib/api/client.js', () => ({ garmApi: { @@ -34,8 +40,12 @@ vi.mock('$app/paths', () => ({ })); vi.mock('$lib/utils/format', () => ({ - formatFileSize: vi.fn((size) => `${(size / 1024).toFixed(1)} KB`), - formatDateTime: vi.fn((date) => date || 'N/A') + formatFileSize: vi.fn((size: number) => `${(size / 1024).toFixed(1)} KB`), + formatDateTime: vi.fn((date: string) => date || 'N/A') +})); + +vi.mock('$lib/utils/apiError', () => ({ + extractAPIError: vi.fn((err: any) => err.message || 'Unknown error') })); const mockObject1 = createMockFileObject({ @@ -56,6 +66,21 @@ describe('Objects Page - Integration Tests', () => { beforeEach(async () => { vi.clearAllMocks(); + // Ensure localStorage is available (jsdom may not always provide it) + const mockLocalStorage = { + getItem: vi.fn().mockReturnValue(null), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), + length: 0, + key: vi.fn() + }; + Object.defineProperty(window, 'localStorage', { + value: mockLocalStorage, + writable: true, + configurable: true + }); + const { garmApi } = await import('$lib/api/client.js'); (garmApi.listFileObjects as any).mockResolvedValue({ results: [mockObject1, mockObject2], diff --git a/workers/cache/gitea_tools.go b/workers/cache/gitea_tools.go index e19dd8bd5..fa847de4d 100644 --- a/workers/cache/gitea_tools.go +++ b/workers/cache/gitea_tools.go @@ -40,22 +40,30 @@ var githubArchMapping = map[string]string{ "arm64": "arm64", } -var nightlyActRunner = GiteaEntityTool{ +// Known prefixes for Gitea runner asset names. The new name (gitea-runner) +// contains a hyphen, so we can't simply split on "-" to parse the asset name. +// We strip the prefix first, then split the remainder. +var giteaRunnerPrefixes = []string{ + "gitea-runner-", + "act_runner-", +} + +var nightlyGiteaRunner = GiteaEntityTool{ TagName: "nightly", Name: "nightly", - TarballURL: "https://gitea.com/gitea/act_runner/archive/main.tar.gz", + TarballURL: "https://gitea.com/gitea/runner/archive/main.tar.gz", Assets: []GiteaToolsAssets{ { - Name: "act_runner-nightly-linux-amd64", - DownloadURL: "https://dl.gitea.com/act_runner/nightly/act_runner-nightly-linux-amd64", + Name: "gitea-runner-nightly-linux-amd64", + DownloadURL: "https://dl.gitea.com/runner/nightly/gitea-runner-nightly-linux-amd64", }, { - Name: "act_runner-nightly-linux-arm64", - DownloadURL: "https://dl.gitea.com/act_runner/nightly/act_runner-nightly-linux-arm64", + Name: "gitea-runner-nightly-linux-arm64", + DownloadURL: "https://dl.gitea.com/runner/nightly/gitea-runner-nightly-linux-arm64", }, { - Name: "act_runner-nightly-windows-amd64.exe", - DownloadURL: "https://dl.gitea.com/act_runner/nightly/act_runner-nightly-windows-amd64.exe", + Name: "gitea-runner-nightly-windows-amd64.exe", + DownloadURL: "https://dl.gitea.com/runner/nightly/gitea-runner-nightly-windows-amd64.exe", }, }, } @@ -70,17 +78,35 @@ type GiteaToolsAssets struct { DownloadURL string `json:"browser_download_url"` } +// stripRunnerPrefix removes the known runner prefix from an asset name and +// returns the remainder (e.g. "nightly-linux-amd64"). It returns an error +// if the name doesn't match any known prefix. +func stripRunnerPrefix(name string) (string, error) { + for _, prefix := range giteaRunnerPrefixes { + if remainder, ok := strings.CutPrefix(name, prefix); ok { + return remainder, nil + } + } + return "", fmt.Errorf("asset name %q does not match any known runner prefix", name) +} + func (g GiteaToolsAssets) GetOS() (*string, error) { if g.Name == "" { return nil, fmt.Errorf("gitea tools name is empty") } - parts := strings.SplitN(g.Name, "-", 4) - if len(parts) != 4 { + remainder, err := stripRunnerPrefix(g.Name) + if err != nil { + return nil, fmt.Errorf("could not parse asset name: %w", err) + } + + // remainder is "{version}-{os}-{arch}[.exe]" + parts := strings.SplitN(remainder, "-", 3) + if len(parts) != 3 { return nil, fmt.Errorf("could not parse asset name") } - os := parts[2] + os := parts[1] return &os, nil } @@ -89,12 +115,18 @@ func (g GiteaToolsAssets) GetArch() (*string, error) { return nil, fmt.Errorf("gitea tools name is empty") } - parts := strings.SplitN(g.Name, "-", 4) - if len(parts) != 4 { + remainder, err := stripRunnerPrefix(g.Name) + if err != nil { + return nil, fmt.Errorf("could not parse asset name: %w", err) + } + + // remainder is "{version}-{os}-{arch}[.exe]" + parts := strings.SplitN(remainder, "-", 3) + if len(parts) != 3 { return nil, fmt.Errorf("could not parse asset name") } - archParts := strings.SplitN(parts[3], ".", 2) + archParts := strings.SplitN(parts[2], ".", 2) if len(archParts) == 0 { return nil, fmt.Errorf("unexpected asset name format") } @@ -166,7 +198,7 @@ func getReleasesFromURL(ctx context.Context, metadataURL string) (GiteaEntityToo latest, ok := tools.MinimumVersion() if !ok { slog.InfoContext(ctx, "failed to find tools, falling back to nightly") - latest = nightlyActRunner + latest = nightlyGiteaRunner } return latest, nil } @@ -178,7 +210,7 @@ func getTools(ctx context.Context, metadataURL string, useInternal bool) ([]comm var latest GiteaEntityTool var err error if useInternal { - latest = nightlyActRunner + latest = nightlyGiteaRunner } else { latest, err = getReleasesFromURL(ctx, metadataURL) if err != nil {