Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Bug report
description: Report reproducible behavior that differs from the documentation or expected result.
title: "bug: "
labels:
- bug
body:
- type: markdown
attributes:
value: |
Thank you for reporting a bug. Do not include credentials, private keys, tokens, or other secrets. For a security vulnerability, use the private reporting path in docs/security.md instead.
- type: textarea
id: summary
attributes:
label: What happened?
description: Describe the observed behavior and the expected result.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Include the smallest safe configuration and commands needed to reproduce the issue.
placeholder: |
1. ...
2. ...
3. ...
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Include EPAR version or commit, provider, host operating system, and Docker or WSL version when relevant.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Sanitized logs
description: Include relevant logs after removing secrets and private infrastructure details.
25 changes: 25 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Feature request
description: Suggest an improvement to EPAR.
title: "feature: "
labels:
- enhancement
body:
- type: textarea
id: problem
attributes:
label: What problem does this solve?
description: Describe the workflow or limitation you are trying to address.
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution
description: Explain the change you would like to see and any alternatives you considered.
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Include relevant provider, operating-system, or GitHub Actions details.
14 changes: 14 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Summary

<!-- What problem does this pull request solve, and what changed? -->

## Validation

<!-- List the commands, workflow runs, or manual checks you performed. -->

## Checklist

- [ ] I kept credentials, private keys, tokens, and machine-specific configuration out of this pull request.
- [ ] I added or updated tests where behavior changed.
- [ ] I updated relevant documentation.
- [ ] I read and followed the contributing guide and code of conduct.
4 changes: 4 additions & 0 deletions .github/workflows/core-runner-verification.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
branches:
- develop
- main
workflow_dispatch:

permissions:
contents: read
Expand All @@ -20,6 +21,7 @@ concurrency:
jobs:
controller:
name: Core runner controller
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
environment: epar-live-ci
# Leaves time for a cold image build and bounded cleanup around the
Expand Down Expand Up @@ -83,6 +85,7 @@ jobs:

canary-1:
name: Core canary 1
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on:
group: epar-ci-canary
labels: epar-core-${{ github.run_id }}-${{ github.run_attempt }}
Expand Down Expand Up @@ -128,6 +131,7 @@ jobs:
canary-2:
name: Core canary 2
needs: canary-1
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on:
group: epar-ci-canary
labels: epar-core-${{ github.run_id }}-${{ github.run_attempt }}
Expand Down
162 changes: 162 additions & 0 deletions .github/workflows/hosted-runner-docker-architecture.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
name: Hosted runner Docker architecture proof

on:
pull_request:
branches:
- develop
- main
paths:
- .github/workflows/hosted-runner-docker-architecture.yml
workflow_dispatch:

permissions:
contents: read

concurrency:
group: hosted-docker-architecture-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
capability:
name: Capability (${{ matrix.runner }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
runner:
- ubuntu-latest
- ubuntu-24.04-arm
- windows-latest
- windows-11-arm
- macos-latest
- macos-15-intel

steps:
- name: Record host and Docker capability
shell: pwsh
run: |
$ErrorActionPreference = 'Continue'
$summary = [System.Collections.Generic.List[string]]::new()
$summary.Add("## $env:RUNNER_OS/$env:RUNNER_ARCH on ``${{ matrix.runner }}``")
$summary.Add('')
$summary.Add("- Runner OS: ``$env:RUNNER_OS``")
$summary.Add("- Runner architecture: ``$env:RUNNER_ARCH``")

$dockerCommand = Get-Command docker -ErrorAction SilentlyContinue
if (-not $dockerCommand) {
$summary.Add('- Docker CLI: not installed')
$summary.Add('- Linux container test: unavailable')
} else {
$summary.Add("- Docker CLI: ``$($dockerCommand.Source)``")
$versionOutput = (& docker version 2>&1 | Out-String).Trim()
$versionStatus = $LASTEXITCODE
$summary.Add("- Docker daemon reachable: ``$($versionStatus -eq 0)``")

if ($versionStatus -eq 0) {
$serverOS = (& docker info --format '{{.OSType}}' 2>&1 | Out-String).Trim()
$serverArchitecture = (& docker info --format '{{.Architecture}}' 2>&1 | Out-String).Trim()
$summary.Add("- Docker server: ``$serverOS/$serverArchitecture``")
if ($serverOS -eq 'linux') {
$summary.Add('- Linux container test: available')
} else {
$summary.Add('- Linux container test: unavailable because the reachable daemon is not a Linux daemon')
}
} else {
$summary.Add('- Linux container test: unavailable because no Docker daemon is reachable')
$summary.Add('')
$summary.Add('<details><summary>docker version diagnostic</summary>')
$summary.Add('')
$summary.Add('```text')
$summary.Add($versionOutput)
$summary.Add('```')
$summary.Add('</details>')
}
}

$summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Encoding utf8 -Append
$summary | ForEach-Object { Write-Host $_ }

linux-cross-architecture:
name: Linux ${{ matrix.native }} runs ${{ matrix.foreign }} with explicit QEMU
runs-on: ${{ matrix.runner }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
native: amd64
native_platform: linux/amd64
native_output: x86_64
foreign: arm64
foreign_platform: linux/arm64
foreign_output: aarch64
- runner: ubuntu-24.04-arm
native: arm64
native_platform: linux/arm64
native_output: aarch64
foreign: amd64
foreign_platform: linux/amd64
foreign_output: x86_64

steps:
- name: Verify native container execution
shell: bash
run: |
set -euo pipefail
docker info --format 'Docker server: {{.OSType}}/{{.Architecture}}'
native_architecture="$(docker run --rm --platform '${{ matrix.native_platform }}' alpine:3.22 uname -m)"
echo "Native container reported: ${native_architecture}"
[[ "${native_architecture}" == '${{ matrix.native_output }}' ]]

- name: Observe foreign-architecture behavior before explicit QEMU setup
id: baseline
shell: bash
run: |
set -euo pipefail
set +e
baseline_output="$(docker run --rm --platform '${{ matrix.foreign_platform }}' alpine:3.22 uname -m 2>&1)"
baseline_status=$?
set -e

printf '%s\n' "${baseline_output}"
{
echo '## ${{ matrix.foreign }} container before explicit QEMU setup'
echo
echo "- Exit status: \`${baseline_status}\`"
echo
echo '```text'
printf '%s\n' "${baseline_output}"
echo '```'
} >>"${GITHUB_STEP_SUMMARY}"

if [[ ${baseline_status} -eq 0 ]]; then
[[ "${baseline_output}" == *'${{ matrix.foreign_output }}'* ]]
echo 'This hosted image already had a compatible foreign-architecture execution path before the workflow configured QEMU.' >>"${GITHUB_STEP_SUMMARY}"
elif [[ "${baseline_output}" == *"exec format error"* ]]; then
echo 'The baseline failed with the expected missing-emulation error.' >>"${GITHUB_STEP_SUMMARY}"
else
echo 'The baseline failed for a reason other than a recognized architecture mismatch.' >&2
exit 1
fi

- name: Set up foreign-architecture container emulation
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
with:
image: docker.io/tonistiigi/binfmt@sha256:400a4873b838d1b89194d982c45e5fb3cda4593fbfd7e08a02e76b03b21166f0
platforms: ${{ matrix.foreign }}

- name: Verify foreign-architecture container execution after QEMU setup
shell: bash
run: |
set -euo pipefail
foreign_architecture="$(docker run --rm --platform '${{ matrix.foreign_platform }}' alpine:3.22 uname -m)"
echo "Emulated container reported: ${foreign_architecture}"
[[ "${foreign_architecture}" == '${{ matrix.foreign_output }}' ]]
{
echo '## ${{ matrix.foreign }} container after explicit QEMU setup'
echo
echo "- Reported architecture: \`${foreign_architecture}\`"
echo '- Result: success'
} >>"${GITHUB_STEP_SUMMARY}"
17 changes: 17 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Code of Conduct

We are committed to a welcoming, respectful, and constructive community.

## Expected Behavior

- Be respectful and professional in issues, pull requests, discussions, and reviews.
- Focus feedback on ideas and technical work rather than people.
- Welcome good-faith questions and contributions from people with different backgrounds and experience levels.

## Unacceptable Behavior

Harassment, discrimination, personal attacks, threats, deliberate disruption, and sharing another person's private information without permission are not acceptable.

## Reporting

Report conduct concerns privately to a repository maintainer through GitHub. Do not open a public issue for a conduct report. Maintainers will review reports promptly and may remove content, limit participation, or take other action needed to protect the community.
27 changes: 27 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Contributing to EPAR

Thanks for taking the time to contribute.

## Before You Start

- Use GitHub issues to discuss bugs, documentation gaps, and proposed changes before starting substantial work.
- Do not report security vulnerabilities in public issues. Follow [Security](docs/security.md) instead.
- EPAR runs GitHub Actions jobs on trusted infrastructure. Changes that affect runners, credentials, container privileges, workflow permissions, or cleanup boundaries need clear security reasoning and tests.

## Development Workflow

1. Fork the repository and create a focused branch from `develop`.
2. Keep the change small and document any operational or security behavior that it changes.
3. Run the relevant tests locally. The baseline Go test suite is `go test ./...`.
4. Open a pull request targeting `develop` and complete the pull-request template.

Fork pull requests run the safe hosted verification workflow. The live EPAR canary is reserved for branches in this repository because it uses a protected environment and disposable privileged containers.

## Pull Request Expectations

- Explain the problem, the approach, and how you tested it.
- Add or update tests when behavior changes.
- Keep credentials, private keys, tokens, and machine-specific configuration out of commits.
- Update the relevant documentation when a user-visible or operational behavior changes.

By contributing, you agree to follow the [Code of Conduct](CODE_OF_CONDUCT.md).
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ That's it.

#### What Happens

EPAR initializes `.local/config.yml` for you if it does not exist. Docker-DinD is the default. The wizard asks whether new Docker-DinD runners should inherit the host's trusted TLS roots and defaults to yes. On native Windows, it also offers WSL2 when `wsl.exe --status` confirms default version 2; press Enter to keep Docker-DinD. Existing configs do not enable host trust inheritance automatically. You can customize the config afterward; see [Configuration](docs/configuration.md).
EPAR initializes `.local/config.yml` for you if it does not exist. Docker-DinD is the default. The wizard asks whether new Docker-DinD runners should inherit the host's trusted TLS roots and defaults to yes. On native Windows, it also offers WSL2 when `wsl.exe --status` confirms default version 2. On macOS, it offers experimental Tart mode when `tart --version` succeeds. Press Enter to keep Docker-DinD. Existing configs do not enable host trust inheritance automatically. You can customize the config afterward; see [Configuration](docs/configuration.md).

Then EPAR checks the configured runner image, builds or replaces it when needed, and starts the configured number of runners. The default config uses `pool.instances: 1`.

Expand Down Expand Up @@ -117,10 +117,12 @@ Docker-DinD is the default first choice. Other providers are available when they
| --- | --- |
| Docker-DinD | You have a Docker-compatible daemon on Windows, macOS, or Linux, and want a private Docker daemon per runner. |
| WSL2 | You are on Windows and want runners as disposable WSL distros. |
| Tart | You are on Apple Silicon macOS and want Linux VM runners; consider Docker-DinD first for Docker-heavy jobs because virtualization limits can affect compatibility. |
| Tart (experimental) | You are on Apple Silicon macOS and want to experiment with native ARM64 Linux VMs. The default Tart image is a basic Ubuntu OS image and does not include the normal GitHub-hosted runner dependency set. |

WSL2 also defaults to Catthehacker's full Ubuntu runner image, but it converts that Docker image into a WSL rootfs during `image build`.

Tart is not a ready-made substitute for GitHub's hosted Ubuntu runners. If you need that environment, build and maintain your own bootable Tart runner image by adapting the scripts from [actions/runner-images](https://github.com/actions/runner-images), then configure EPAR to use it. EPAR does not automate that conversion.

See [Usage](docs/usage.md) for WSL, Tart, source builds, custom configs, and advanced options.

## FAQ
Expand Down Expand Up @@ -154,12 +156,15 @@ GitHub also warns against using self-hosted runners with public repositories tha
- [GitHub App Setup](docs/github-app.md): required GitHub App permissions and fields.
- [Docker-DinD Provider](docs/providers/docker-dind.md): default Docker runner mode.
- [WSL Provider](docs/providers/wsl.md): Windows WSL2 runners.
- [Tart Provider](docs/providers/tart.md): Apple Silicon Linux VM runners.
- [Tart Provider (experimental)](docs/providers/tart.md): Apple Silicon ARM64 Linux VM runners and Rosetta compatibility limits.
- [Image Build](docs/image-build.md): image internals and customization.
- [Operations](docs/operations.md): logs, cleanup, and troubleshooting.
- [Troubleshooting](docs/troubleshooting.md): symptom-first diagnostics by host and provider.
- [Support](SUPPORT.md): where to start, what diagnostic information to collect, and where to ask for help.
- [Windows Startup](docs/advanced/windows-startup.md): start EPAR after Windows login.
- [macOS Startup](docs/advanced/macos-startup.md): start EPAR after macOS login.
- [Running EPAR Without Installing Go](docs/advanced/no-go-install.md): run from source with no local Go install.
- [Security](docs/security.md): trust boundaries and secret handling.
- [Security](docs/security.md): trust boundaries, secret handling, and private vulnerability reporting.
- [Contributing](CONTRIBUTING.md): how to propose and validate changes.
- [Code of Conduct](CODE_OF_CONDUCT.md): community expectations and reporting concerns.
- [Level 1 Core Runner Verification](docs/core-runner-verification.md): trusted live CI setup, canary behavior, and cleanup.
18 changes: 18 additions & 0 deletions SUPPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Support

Start with the [troubleshooting guide](docs/troubleshooting.md). It provides symptom-first diagnostics for Docker-DinD, WSL, Tart, host trust, image builds, storage, and cross-architecture containers.

Before asking for help, search the repository's existing [issues](https://github.com/solutionforest/ephemeral-action-runner/issues) and collect:

- the EPAR version or commit;
- the host operating system and architecture;
- the selected provider and runner image;
- the relevant configuration with credentials, private keys, tokens, certificate contents, organization names, and other sensitive values removed;
- the smallest reproducible workflow or command;
- the relevant controller, image-build, and guest logs with secrets removed.

For a reproducible bug or documentation gap, open a [GitHub issue](https://github.com/solutionforest/ephemeral-action-runner/issues/new/choose). For usage questions, include what you expected, what happened, and the diagnostics above so another contributor can reproduce the environment.

Do not report security vulnerabilities in a public issue. Follow the private reporting instructions in the [security policy](docs/security.md).

Community support is provided on a best-effort basis; there is no guaranteed response time or service-level agreement.
Loading
Loading