This project provides a GitHub action for running GitHub Actions workflows on multiple platforms, including platforms that GitHub Actions doesn't currently natively support.
[!IMPORTANT] This readme documents the
masterbranch, which may describe features and operating system versions that have not been released yet. For the documentation matching the latest release, see the readme for the latest release.
FeaturesSome of the features that this action supports include:
UsageHere's a sample workflow file which will run the given commands on FreeBSD 15.1.
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
shell: cpa.sh {0}
steps:
- uses: actions/checkout@v6
- name: Start VM
uses: cross-platform-actions/action@v1.4.0
with:
operating_system: freebsd
version: '15.1'
- name: Test
run: |
uname -a
echo $SHELL
pwd
ls -lah
whoami
env | sort
Here's a sample workflow file which will set up a matrix resulting in nine jobs. One which will run on FreeBSD 15.1, one which runs OpenBSD 7.9, one which runs NetBSD 11.0, one which runs OpenBSD 7.9 on ARM64, one which runs NetBSD 11.0 on ARM64, one which runs DragonFly BSD 6.4.2, one which runs MidnightBSD 4.0.4, one which runs Haiku R1/beta5 and one which runs OmniOS r151056.
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
shell: cpa.sh {0}
strategy:
matrix:
os:
- name: freebsd
architecture: x86-64
version: '15.1'
- name: openbsd
architecture: x86-64
version: '7.9'
- name: openbsd
architecture: arm64
version: '7.9'
- name: netbsd
architecture: x86-64
version: '11.0'
- name: netbsd
architecture: arm64
version: '11.0'
- name: dragonflybsd
architecture: x86-64
version: '6.4.2'
- name: midnightbsd
architecture: x86-64
version: '4.0.4'
- name: haiku
architecture: x86-64
version: 'r1beta5'
- name: omnios
architecture: x86-64
version: 'r151056'
steps:
- uses: actions/checkout@v6
- name: Start VM on ${{ matrix.os.name }}
uses: cross-platform-actions/action@v1.4.0
env:
MY_ENV1: MY_ENV1
MY_ENV2: MY_ENV2
with:
environment_variables: MY_ENV1 MY_ENV2
operating_system: ${{ matrix.os.name }}
architecture: ${{ matrix.os.architecture }}
version: ${{ matrix.os.version }}
shell: bash
memory: 5G
cpu_count: 4
- name: Test 1 on ${{ matrix.os.name }}
run: |
uname -a
echo $SHELL
pwd
- name: Test 2 on ${{ matrix.os.name }}
run: |
ls -lah
whoami
env | sort
Different platforms need to run on different runners, so see the Runners section below.
When the action starts the VM, the cpa.sh helper is added to the runner's
PATH and can be used in subsequent steps. It runs commands inside the VM
and can also synchronize files or reboot the VM:
cpa.sh FILE [POST_FLAGS...] # Run FILE inside the VM
cpa.sh --sync-files [DIRECTION] # Synchronize files between the runner and the VM
cpa.sh --reboot # Reboot the VM and wait until it's reachable again
When FILE is given (typically via shell: cpa.sh {0}), files are
synchronized automatically: runner-to-vm before the file is executed and
vm-to-runner after. Pass --sync-files DIRECTION as a POST_FLAG to change
this. DIRECTION accepts:
both (default) — sync runner-to-vm before and vm-to-runner afternone — skip syncing entirelyrunner-to-vm — only sync runner-to-vm before the file runsvm-to-runner — only sync vm-to-runner after the file runs--reboot may also be given as a POST_FLAG to reboot the VM after the file
has run (and after the post-sync, if any).
--environment-variables NAME1 NAME2 ... may be given as a POST_FLAG to
forward additional environment variables to the VM for that step, on top of the
ones declared via the action's environment_variables input. List
the names separated by spaces and do not quote them (GitHub Actions splits the
shell line on spaces without honoring quotes). The named variables are read
from the step's environment (set them with env:), so they don't need to be
listed on the Start VM step.
- name: Run a command inside the VM
shell: cpa.sh {0}
run: uname -a
- name: Run a command without syncing files
shell: cpa.sh {0} --sync-files none
run: uname -a
- name: Run a command and reboot afterwards
shell: cpa.sh {0} --reboot
run: sysctl -w some.setting=1
- name: Run a command with extra environment variables
shell: cpa.sh {0} --environment-variables MY_ENV1 MY_ENV2
env:
MY_ENV1: value1
MY_ENV2: value2
run: echo "$MY_ENV1 $MY_ENV2"
Use the standalone forms when you don't need to run a command. In standalone
mode --sync-files defaults to both and runs the sync as a one-shot:
- name: Sync files from the runner to the VM
run: cpa.sh --sync-files runner-to-vm
- name: Reboot the VM
run: cpa.sh --reboot
This section lists the available inputs for the action.
| Input | Required | Default Value | Type | Description |
|---|---|---|---|---|
run | ❌ | "" | string | Deprecated. Runs command-line programs using the operating system's shell. This will be executed inside the virtual machine. Prefer the cpa.sh custom shell (shell: cpa.sh {0}) in subsequent steps instead. |
operating_system | ✅ | ❌ | string | The type of operating system to run the job on. See Supported Platforms. |
architecture | ❌ | x86-64 | string | The architecture of the operating system. See Supported Platforms. |
version | ✅ | ❌ | string | The version of the operating system to use. See Supported Platforms. |
shell | ❌ | default | string | The shell to use to execute the commands. Defaults to the default shell for the given operating system. Allowed values are: default, sh and bash |
environment_variables | ❌ | "" | string | A list of environment variables to forward to the virtual machine. The list should be separated with spaces. The CI and any environment variables starting with GITHUB_ are forwarded automatically. |
memory | ❌ | 6G | string | The amount of memory for the virtual machine. |
cpu_count | ❌ | 2 | integer | The number of CPU cores for the virtual machine. |
variant | ❌ | default | string | Which variant of the operating system to run. See Variants. Valid values are default and, where the platform has it, microvm. |
image_url | ❌ | ❌ | string | URL a custom VM image that should be used in place of the default ones. |
sync_files | ❌ | true | string | Specifies if the local files should be synchronized to the virtual machine and in which direction. Valid values are true, false, runner-to-vm and vm-to-runner. true synchronizes files in both directions. false disables file synchronization. |
shutdown_vm | ❌ | conditional | boolean | Deprecated. Specifies if the VM should be shutdown after the action has been run. If unset, defaults to true when run is provided and false otherwise. There is no replacement. |
All inputs are expected to be of the specified type. It's especially important
that you specify version as a string, using single or
double quotes. Otherwise YAML might interpet the value as a numeric value
instead of a string, which leads to some unexpected behavior. If the
version is specified as version: 13.0, YAML will interpet 13.0 as a
floating point number, drop the fraction part (because 13 and 13.0 are the
same) and the GitHub action will only see 13 instead of 13.0. The solution
is to explicitly state that a string is required by using quotes: version: '13.0'.
variant)A variant is a named configuration of a platform, not a set of independent options. Asking for a variant a platform doesn't have is an error listing the ones it does, rather than a silent fallback.
| Variant | Available on | Description |
|---|---|---|
default | everything | The platform as it has always booted: firmware, boot loader, then the kernel. |
microvm | NetBSD 11 on x86-64 | Boots the kernel directly on QEMU's microvm machine type, which has no boot loader, no PCI bus and no ACPI. |
- uses: cross-platform-actions/action@v1
with:
operating_system: netbsd
version: '11.0'
variant: microvm
The microvm variant reaches a usable guest in roughly half the time, but it
is opt-in because it changes the hardware the guest sees, and a job that
inspects hardware will notice:
ld0 (virtio-blk), not sd0 (vioscsi)pcictl and anything reading ACPI differMICROVM rather than GENERIC, which uname -v reportsIt also needs an image new enough to ship a kernel beside its disk. Asking for it with an older image is an error rather than a slow boot.
image_url)With the image_url input it's possible to specify a custom virtual machine
image. The main reason for this feature is to do additional custom
provisioning, like installing additional packages. This allows to pre-install
everything that is needed for a CI job beforhand, which can save time later
when the job is run.
Only existing operating systems, architectures and versions are supported.
resources/custom.sh script. Don't
remove any existing provisioning scripts.image_url inputSupported PlatformsThis sections lists the currently supported platforms by operating system. Each operating system will list which versions are supported.
openbsd)| Version | x86-64 | arm64 |
|---|---|---|
| 7.9 | ✅ | ✅ |
| 7.8 | ✅ | ✅ |
| 7.7 | ✅ | ✅ |
| 7.6 | ✅ | ✅ |
| 7.5 | ✅ | ✅ |
| 7.4 | ✅ | ✅ |
| 7.3 | ✅ | ✅ |
| 7.2 | ✅ | ✅ |
| 7.1 | ✅ | ✅ |
| 6.9 | ✅ | ✅ |
| 6.8 | ✅ | ❌ |
freebsd)| Version | x86-64 | arm64 | riscv64 |
|---|---|---|---|
| 15.1 | ✅ | ✅ | ✅ |
| 15.0 | ✅ | ✅ | ✅ |
| 14.4 | ✅ | ✅ | ❌ |
| 14.3 | ✅ | ✅ | ❌ |
| 14.2 | ✅ | ✅ | ❌ |
| 14.1 | ✅ | ✅ | ❌ |
| 14.0 | ✅ | ✅ | ❌ |
| 13.5 | ✅ | ✅ | ❌ |
| 13.4 | ✅ | ✅ | ❌ |
| 13.3 | ✅ | ✅ | ❌ |
| 13.2 | ✅ | ✅ | ❌ |
| 13.1 | ✅ | ✅ | ❌ |
| 13.0 | ✅ | ✅ | ❌ |
| 12.4 | ✅ | ✅ | ❌ |
| 12.2 | ✅ | ❌ | ❌ |
netbsd)| Version | x86-64 | arm64 | vax |
|---|---|---|---|
| 11.0 | ✅ | ✅ | ✅ |
| 10.1 | ✅ | ✅ | ✅ |
| 10.0 | ✅ | ✅ | ❌ |
| 9.4 | ✅ | ❌ | ❌ |
| 9.3 | ✅ | ❌ | ❌ |
| 9.2 | ✅ | ❌ | ❌ |
Note, the VAX architecture runs on the SIMH simulator (MicroVAX 3900) instead of QEMU and comes with a couple of limitations:
memory input is rounded down to the largest supported size
(16M, 32M, 64M, 128M, 256M or 512M) and the cpu_count input is ignored.cpa.sh --reboot) is not supported: the emulated KA655
firmware self-test is unreliable when the machine is restarted inside the
same simulator process.dragonflybsd)| Version | x86-64 |
|---|---|
| 6.4.2 | ✅ |
midnightbsd)| Version | x86-64 |
|---|---|
| 4.0.4 | ✅ |
haiku)Note, Haiku is a single user system. That means the user that runs the the job
is the default (and only) user, user, instead of runner, as for the other
operating systems.
| Version | x86-64 |
|---|---|
| r1beta5 | ✅ |
omnios)| Version | x86-64 |
|---|---|
| r151058 | ✅ |
| r151056 | ✅ |
This section lists the supported architectures and any aliases. All the names are case insensitive. For a combination of supported architectures and operating systems, see the sections for each operating system above.
| Architecture | Aliases |
|---|---|
arm64 | aarch64 |
vax | |
x86-64 | x86_64, x64 |
riscv64 | riscv, rv64 |
This section lists the available hypervisors, which platforms they can run and which runners they can run on.
| Hypervisor | Linux Runner | FreeBSD | OpenBSD | Other Platforms |
|---|---|---|---|---|
qemu | ✅ | ✅ | ✅ | ✅ |
The VAX architecture always runs on the SIMH simulator.
This section lists the different combinations of platforms and on which runners they can run.
| Runner | OpenBSD | FreeBSD | NetBSD | DragonFly BSD | MidnightBSD | ARM64 |
|---|---|---|---|---|---|---|
| Linux | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Linux on Non-x86 ArchitecturesThere are currently no plans to add support for Linux. Instead it's very easy to support Linux on non-x86 architectures using the QEMU support in Docker with the docker/setup-qemu-action action:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/riscv64
- name: Run Command in Docker
run: |
docker run \
--rm \
-v $(pwd):/${{ github.workspace }} \
-w ${{ github.workspace }} \
--platform linux/riscv64 \
debian:unstable-slim \
<command to run>
For those not familiar with Docker, here's an explanation of the above command:
run - Runs a Docker container--rm - Removes the container after it exits-v - Mounts a local directory into the container. In this case the current
directory is mounted to the same path in the container-w - Specifies the working directory inside the container--platform - Specifies the platform/architecturedebian:unstable-slim - Specifies with image to create the container from.
Basically the Linux distribution to use<command to run> - The command you want to run inside the containerCommon IssuesWhen installing packages on FreeBSD you might see an error related to mismatching of operating system or kernel version. This occurs because FreeBSD only supports one minor version of the previous major version. Therefore FreeBSD only has one package repository for each major version, not each minor version. When a new minor version is released, all packages in the repository are rebuilt targeting this new minor version. If you're on an older minor version of the operating system the package manager will give you an error.
For more information, see: https://www.freebsd.org/security/#sup and https://www.freebsd.org/releases.
The best solution is to upgrade to the latest supported minor version.
If Alternative 1 is not possible, you can ignore the operating system version
mismatch by setting the IGNORE_OSVERSION environment variable with the value
yes. Ignoring the operating system version mismatch can lead to runtime
issues if the package depends on features or libraries only present in the
newer operating system version. Example:
env IGNORE_OSVERSION=yes pkg install <package>
Where <package> is the name of the package to install.
To report a security vulnerability, follow the guidelines described in the
security.md document.
Under the HoodGitHub Actions currently only support macOS, Linux, and Windows. To be able to run other platforms, this GitHub action runs the commands inside a virtual machine (VM). If the host platform is macOS or Linux the hypervisor can take advantage of nested virtualization.
All platforms run on the QEMU hypervisor. QEMU is a general purpose hypervisor and emulator that runs on most host platforms and supports most guest systems.
The VM images running inside the hypervisor are built using Packer. It's a tool for automatically creating VM images, installing the guest operating system and doing any final provisioning.
The GitHub action uses SSH to communicate and execute commands inside the VM. It uses rsync to share files between the guest VM and the host. To authenticate the SSH connection a unique key pair is used. This pair is generated each time the action is run. The public key is added to the VM image and the host stores the private key. A secondary hard drive, which is backed by a file, is created. The public key is stored on this hard drive, which the VM then mounts. At boot time, the secondary hard drive will be identified and the public key will be copied to the appropriate location.
To reduce the time it takes for the GitHub action to start executing the commands specified by the user, it aims to boot the guest operating systems as fast as possible. This is achieved in a couple of ways:
By downloading resources, like the hypervisor and a few other tools, instead of installing them through a package manager
The resources that are downloaded use no compression. The size is small enough anyway and it's faster to download the uncompressed data than it is to download compressed data and then uncompress it.
It leverages async/await to perform tasks asynchronously. Like
downloading the VM image and other resources at the same time
It performs as much as possible of the setup ahead of time when the VM image is provisioned
ContributingThe changelog is maintained in the changelog.md file, following the Keep a Changelog format. The changelog is updated incrementally. That is, for every new feature or bugfix, add an entry to the changelog. New entries are added below the Unreleased section, with an appropriate sub header.
To add support for a new version of an existing operating system follow the steps below:
First, make the necessary changes in the appropriate *-builder repository:
Add a new subdirectory under the var_files directory
Name the subdirectory after the new version, i.e. 14.3
Add a .pkrvars.hcl file for each supported architecture
The content of the above files should be:
checksum = "<hash>"
Where <hash> should be replaced with the hash of the install media. Make
sure the hash value is prefixed with the hashing algorithm, example:
checksum = "sha256:1c41fdca8fcd8c746eb05f31a82a4bff2ad866c5fde1808f16b822a6df3f0de5"
The hash of the install media can usually be found at a nearby URL where the
install media is downloaded from. Exactly where to find this depends on the
operating system. The URL for the install media can be found the main Packer
configuration file in the *-builder repository.'
Add a new entry to the table in the readme containing supported versions and architectures.
Update the GitHub Action workflow with a new entry in the existing matrix of versions.
If there's a changelog file, update that as well
Create a pull request with the changes
Update the src/version.ts file with the new tag from the previous step
Compile the TypeScript by running npm run all
Update the .github/workflows/ci.yml file by adding the new version to the existing
matrix of versions of the operating system
Update the readme.md file and add new entry in the version and
architecture table of the operating system in the
Supported Platforms
section
If the new version is the latest version available of the operating system,
update Full Example
section in the readme.md, both the description and the
example usage of the GitHub action
Update the changelog: changelog.md
Create a pull request with all the changes
In addition to the steps below, a new builder repository for the operating system needs to be added and implemented.
To add support for a new operating system, follow these steps:
Create the Operating System Implementation:
src/operating_systems with the name of the
operating system.qemu_vm.ts: Define the virtual machine class for the operating system.<os_name>.ts: Implement the operating system class extending the Qemu
class.factory.ts: Implement the factory class for the operating system.Write Tests:
spec/operating_systems with the
name of the operating system.Update the Main Entry Point:
src/main.ts.Update the CI Configuration:
.github/workflows/ci.yml.Update the readme.md:
Supported Platforms section.Test the Implementation:
Submit a Pull Request:
By following these steps, you can ensure the new operating system is integrated seamlessly into the project.
Releases are cut with relog, driven by the [Unreleased] section of
changelog.md. The repo ships a release.conf that configures relog for this
project (master branch, origin remote, and a pre-commit hook that bumps the
cross-platform-actions/action@vX.Y.Z reference in this readme).
These steps need to be performed by a maintainer:
Make sure the [Unreleased] section in changelog.md describes everything
going into the release. Cross-Platform Action follows the
semantic versioning scheme; relog derives the next version from
the sub-headers under [Unreleased]:
### Fixed only → patch bump### Added, ### Changed, ### Deprecated → minor bump### Removed (or "Breaking" anywhere in the section) → major bumpFrom a clean master working tree, run relog (optionally pass an explicit
version like relog 2.0.0, or --dry-run to preview). It rewrites the
changelog, updates this readme, commits, creates an annotated vX.Y.Z tag,
and prompts before pushing.
Pushing the tag triggers the Release workflow, which creates a draft
release on GitHub with the newly added changelog section as release notes.
Review the draft release on GitHub and publish it. Publishing fires the
Update Major Tag workflow, which force-updates the matching major tag
(e.g. v2) to point at the published release.
Local DevelopmentInstall the above prerequisites
Clone the repository by running:
git clone https://github.com/cross-platform-actions/action
Navigate to the newly cloned repository: cd action
Install the dependencies by running: npm install
Run any of the below npm commands
The following npm commands are available:
build - Build the GitHub actionformat - Reformat the codelint - Lint the codepackage - Package the GitHub action for distribution and end to end testingtest - Run unit testsall - Will run all of the above commandsThe end to end tests can be run locally by running it through Act. By default, resources and VM images will be downloaded from github.com. By running a local HTTP server it's possible to point the GitHub action to local resources.
Install the above prerequisites
Copy test/workflows/ci.yml.example to
test/workflows/ci.yml
Make any changes you like to test/workflows/ci.yml, this is file ignored by
Git
Build the GitHub action by running: npm run build
Package the GitHub action by running: npm run package
Run the GitHub action by running: act --privileged -W test/workflows
The GitHub action includes a development dependency on a HTTP server. The
test/http directory contains a skeleton of a directory structure
which matches the URLs that the GitHub action uses to download resources. All
files within the test/http are ignore by Git.
Add resources as necessary to the test/http directory
In one shell, run the following command to start the HTTP server:
./node_modules/http-server/bin/http-server test/http -a 127.0.0.1
The -a flag configures the HTTP server to only listen for incoming
connections from localhost, no external computers will be able to connect.
In another shell, run the GitHub action by running:
act --privileged -W test/workflows --env CPA_RESOURCE_URL=<url>
Where <url> is the URL inside Docker that points to localhost of the host
machine, for macOS, this is http://host.docker.internal:8080. By default,
the HTTP server is listening on port 8080.
TypeScript
99.1%
This project provides a GitHub action for running GitHub Actions workflows on multiple platforms, including platforms that GitHub Actions doesn't currently natively support.
[!IMPORTANT] This readme documents the
masterbranch, which may describe features and operating system versions that have not been released yet. For the documentation matching the latest release, see the readme for the latest release.
FeaturesSome of the features that this action supports include:
UsageHere's a sample workflow file which will run the given commands on FreeBSD 15.1.
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
shell: cpa.sh {0}
steps:
- uses: actions/checkout@v6
- name: Start VM
uses: cross-platform-actions/action@v1.4.0
with:
operating_system: freebsd
version: '15.1'
- name: Test
run: |
uname -a
echo $SHELL
pwd
ls -lah
whoami
env | sort
Here's a sample workflow file which will set up a matrix resulting in nine jobs. One which will run on FreeBSD 15.1, one which runs OpenBSD 7.9, one which runs NetBSD 11.0, one which runs OpenBSD 7.9 on ARM64, one which runs NetBSD 11.0 on ARM64, one which runs DragonFly BSD 6.4.2, one which runs MidnightBSD 4.0.4, one which runs Haiku R1/beta5 and one which runs OmniOS r151056.
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
shell: cpa.sh {0}
strategy:
matrix:
os:
- name: freebsd
architecture: x86-64
version: '15.1'
- name: openbsd
architecture: x86-64
version: '7.9'
- name: openbsd
architecture: arm64
version: '7.9'
- name: netbsd
architecture: x86-64
version: '11.0'
- name: netbsd
architecture: arm64
version: '11.0'
- name: dragonflybsd
architecture: x86-64
version: '6.4.2'
- name: midnightbsd
architecture: x86-64
version: '4.0.4'
- name: haiku
architecture: x86-64
version: 'r1beta5'
- name: omnios
architecture: x86-64
version: 'r151056'
steps:
- uses: actions/checkout@v6
- name: Start VM on ${{ matrix.os.name }}
uses: cross-platform-actions/action@v1.4.0
env:
MY_ENV1: MY_ENV1
MY_ENV2: MY_ENV2
with:
environment_variables: MY_ENV1 MY_ENV2
operating_system: ${{ matrix.os.name }}
architecture: ${{ matrix.os.architecture }}
version: ${{ matrix.os.version }}
shell: bash
memory: 5G
cpu_count: 4
- name: Test 1 on ${{ matrix.os.name }}
run: |
uname -a
echo $SHELL
pwd
- name: Test 2 on ${{ matrix.os.name }}
run: |
ls -lah
whoami
env | sort
Different platforms need to run on different runners, so see the Runners section below.
When the action starts the VM, the cpa.sh helper is added to the runner's
PATH and can be used in subsequent steps. It runs commands inside the VM
and can also synchronize files or reboot the VM:
cpa.sh FILE [POST_FLAGS...] # Run FILE inside the VM
cpa.sh --sync-files [DIRECTION] # Synchronize files between the runner and the VM
cpa.sh --reboot # Reboot the VM and wait until it's reachable again
When FILE is given (typically via shell: cpa.sh {0}), files are
synchronized automatically: runner-to-vm before the file is executed and
vm-to-runner after. Pass --sync-files DIRECTION as a POST_FLAG to change
this. DIRECTION accepts:
both (default) — sync runner-to-vm before and vm-to-runner afternone — skip syncing entirelyrunner-to-vm — only sync runner-to-vm before the file runsvm-to-runner — only sync vm-to-runner after the file runs--reboot may also be given as a POST_FLAG to reboot the VM after the file
has run (and after the post-sync, if any).
--environment-variables NAME1 NAME2 ... may be given as a POST_FLAG to
forward additional environment variables to the VM for that step, on top of the
ones declared via the action's environment_variables input. List
the names separated by spaces and do not quote them (GitHub Actions splits the
shell line on spaces without honoring quotes). The named variables are read
from the step's environment (set them with env:), so they don't need to be
listed on the Start VM step.
- name: Run a command inside the VM
shell: cpa.sh {0}
run: uname -a
- name: Run a command without syncing files
shell: cpa.sh {0} --sync-files none
run: uname -a
- name: Run a command and reboot afterwards
shell: cpa.sh {0} --reboot
run: sysctl -w some.setting=1
- name: Run a command with extra environment variables
shell: cpa.sh {0} --environment-variables MY_ENV1 MY_ENV2
env:
MY_ENV1: value1
MY_ENV2: value2
run: echo "$MY_ENV1 $MY_ENV2"
Use the standalone forms when you don't need to run a command. In standalone
mode --sync-files defaults to both and runs the sync as a one-shot:
- name: Sync files from the runner to the VM
run: cpa.sh --sync-files runner-to-vm
- name: Reboot the VM
run: cpa.sh --reboot
This section lists the available inputs for the action.
| Input | Required | Default Value | Type | Description |
|---|---|---|---|---|
run | ❌ | "" | string | Deprecated. Runs command-line programs using the operating system's shell. This will be executed inside the virtual machine. Prefer the cpa.sh custom shell (shell: cpa.sh {0}) in subsequent steps instead. |
operating_system | ✅ | ❌ | string | The type of operating system to run the job on. See Supported Platforms. |
architecture | ❌ | x86-64 | string | The architecture of the operating system. See Supported Platforms. |
version | ✅ | ❌ | string | The version of the operating system to use. See Supported Platforms. |
shell | ❌ | default | string | The shell to use to execute the commands. Defaults to the default shell for the given operating system. Allowed values are: default, sh and bash |
environment_variables | ❌ | "" | string | A list of environment variables to forward to the virtual machine. The list should be separated with spaces. The CI and any environment variables starting with GITHUB_ are forwarded automatically. |
memory | ❌ | 6G | string | The amount of memory for the virtual machine. |
cpu_count | ❌ | 2 | integer | The number of CPU cores for the virtual machine. |
variant | ❌ | default | string | Which variant of the operating system to run. See Variants. Valid values are default and, where the platform has it, microvm. |
image_url | ❌ | ❌ | string | URL a custom VM image that should be used in place of the default ones. |
sync_files | ❌ | true | string | Specifies if the local files should be synchronized to the virtual machine and in which direction. Valid values are true, false, runner-to-vm and vm-to-runner. true synchronizes files in both directions. false disables file synchronization. |
shutdown_vm | ❌ | conditional | boolean | Deprecated. Specifies if the VM should be shutdown after the action has been run. If unset, defaults to true when run is provided and false otherwise. There is no replacement. |
All inputs are expected to be of the specified type. It's especially important
that you specify version as a string, using single or
double quotes. Otherwise YAML might interpet the value as a numeric value
instead of a string, which leads to some unexpected behavior. If the
version is specified as version: 13.0, YAML will interpet 13.0 as a
floating point number, drop the fraction part (because 13 and 13.0 are the
same) and the GitHub action will only see 13 instead of 13.0. The solution
is to explicitly state that a string is required by using quotes: version: '13.0'.
variant)A variant is a named configuration of a platform, not a set of independent options. Asking for a variant a platform doesn't have is an error listing the ones it does, rather than a silent fallback.
| Variant | Available on | Description |
|---|---|---|
default | everything | The platform as it has always booted: firmware, boot loader, then the kernel. |
microvm | NetBSD 11 on x86-64 | Boots the kernel directly on QEMU's microvm machine type, which has no boot loader, no PCI bus and no ACPI. |
- uses: cross-platform-actions/action@v1
with:
operating_system: netbsd
version: '11.0'
variant: microvm
The microvm variant reaches a usable guest in roughly half the time, but it
is opt-in because it changes the hardware the guest sees, and a job that
inspects hardware will notice:
ld0 (virtio-blk), not sd0 (vioscsi)pcictl and anything reading ACPI differMICROVM rather than GENERIC, which uname -v reportsIt also needs an image new enough to ship a kernel beside its disk. Asking for it with an older image is an error rather than a slow boot.
image_url)With the image_url input it's possible to specify a custom virtual machine
image. The main reason for this feature is to do additional custom
provisioning, like installing additional packages. This allows to pre-install
everything that is needed for a CI job beforhand, which can save time later
when the job is run.
Only existing operating systems, architectures and versions are supported.
resources/custom.sh script. Don't
remove any existing provisioning scripts.image_url inputSupported PlatformsThis sections lists the currently supported platforms by operating system. Each operating system will list which versions are supported.
openbsd)| Version | x86-64 | arm64 |
|---|---|---|
| 7.9 | ✅ | ✅ |
| 7.8 | ✅ | ✅ |
| 7.7 | ✅ | ✅ |
| 7.6 | ✅ | ✅ |
| 7.5 | ✅ | ✅ |
| 7.4 | ✅ | ✅ |
| 7.3 | ✅ | ✅ |
| 7.2 | ✅ | ✅ |
| 7.1 | ✅ | ✅ |
| 6.9 | ✅ | ✅ |
| 6.8 | ✅ | ❌ |
freebsd)| Version | x86-64 | arm64 | riscv64 |
|---|---|---|---|
| 15.1 | ✅ | ✅ | ✅ |
| 15.0 | ✅ | ✅ | ✅ |
| 14.4 | ✅ | ✅ | ❌ |
| 14.3 | ✅ | ✅ | ❌ |
| 14.2 | ✅ | ✅ | ❌ |
| 14.1 | ✅ | ✅ | ❌ |
| 14.0 | ✅ | ✅ | ❌ |
| 13.5 | ✅ | ✅ | ❌ |
| 13.4 | ✅ | ✅ | ❌ |
| 13.3 | ✅ | ✅ | ❌ |
| 13.2 | ✅ | ✅ | ❌ |
| 13.1 | ✅ | ✅ | ❌ |
| 13.0 | ✅ | ✅ | ❌ |
| 12.4 | ✅ | ✅ | ❌ |
| 12.2 | ✅ | ❌ | ❌ |
netbsd)| Version | x86-64 | arm64 | vax |
|---|---|---|---|
| 11.0 | ✅ | ✅ | ✅ |
| 10.1 | ✅ | ✅ | ✅ |
| 10.0 | ✅ | ✅ | ❌ |
| 9.4 | ✅ | ❌ | ❌ |
| 9.3 | ✅ | ❌ | ❌ |
| 9.2 | ✅ | ❌ | ❌ |
Note, the VAX architecture runs on the SIMH simulator (MicroVAX 3900) instead of QEMU and comes with a couple of limitations:
memory input is rounded down to the largest supported size
(16M, 32M, 64M, 128M, 256M or 512M) and the cpu_count input is ignored.cpa.sh --reboot) is not supported: the emulated KA655
firmware self-test is unreliable when the machine is restarted inside the
same simulator process.dragonflybsd)| Version | x86-64 |
|---|---|
| 6.4.2 | ✅ |
midnightbsd)| Version | x86-64 |
|---|---|
| 4.0.4 | ✅ |
haiku)Note, Haiku is a single user system. That means the user that runs the the job
is the default (and only) user, user, instead of runner, as for the other
operating systems.
| Version | x86-64 |
|---|---|
| r1beta5 | ✅ |
omnios)| Version | x86-64 |
|---|---|
| r151058 | ✅ |
| r151056 | ✅ |
This section lists the supported architectures and any aliases. All the names are case insensitive. For a combination of supported architectures and operating systems, see the sections for each operating system above.
| Architecture | Aliases |
|---|---|
arm64 | aarch64 |
vax | |
x86-64 | x86_64, x64 |
riscv64 | riscv, rv64 |
This section lists the available hypervisors, which platforms they can run and which runners they can run on.
| Hypervisor | Linux Runner | FreeBSD | OpenBSD | Other Platforms |
|---|---|---|---|---|
qemu | ✅ | ✅ | ✅ | ✅ |
The VAX architecture always runs on the SIMH simulator.
This section lists the different combinations of platforms and on which runners they can run.
| Runner | OpenBSD | FreeBSD | NetBSD | DragonFly BSD | MidnightBSD | ARM64 |
|---|---|---|---|---|---|---|
| Linux | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Linux on Non-x86 ArchitecturesThere are currently no plans to add support for Linux. Instead it's very easy to support Linux on non-x86 architectures using the QEMU support in Docker with the docker/setup-qemu-action action:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
with:
platforms: linux/riscv64
- name: Run Command in Docker
run: |
docker run \
--rm \
-v $(pwd):/${{ github.workspace }} \
-w ${{ github.workspace }} \
--platform linux/riscv64 \
debian:unstable-slim \
<command to run>
For those not familiar with Docker, here's an explanation of the above command:
run - Runs a Docker container--rm - Removes the container after it exits-v - Mounts a local directory into the container. In this case the current
directory is mounted to the same path in the container-w - Specifies the working directory inside the container--platform - Specifies the platform/architecturedebian:unstable-slim - Specifies with image to create the container from.
Basically the Linux distribution to use<command to run> - The command you want to run inside the containerCommon IssuesWhen installing packages on FreeBSD you might see an error related to mismatching of operating system or kernel version. This occurs because FreeBSD only supports one minor version of the previous major version. Therefore FreeBSD only has one package repository for each major version, not each minor version. When a new minor version is released, all packages in the repository are rebuilt targeting this new minor version. If you're on an older minor version of the operating system the package manager will give you an error.
For more information, see: https://www.freebsd.org/security/#sup and https://www.freebsd.org/releases.
The best solution is to upgrade to the latest supported minor version.
If Alternative 1 is not possible, you can ignore the operating system version
mismatch by setting the IGNORE_OSVERSION environment variable with the value
yes. Ignoring the operating system version mismatch can lead to runtime
issues if the package depends on features or libraries only present in the
newer operating system version. Example:
env IGNORE_OSVERSION=yes pkg install <package>
Where <package> is the name of the package to install.
To report a security vulnerability, follow the guidelines described in the
security.md document.
Under the HoodGitHub Actions currently only support macOS, Linux, and Windows. To be able to run other platforms, this GitHub action runs the commands inside a virtual machine (VM). If the host platform is macOS or Linux the hypervisor can take advantage of nested virtualization.
All platforms run on the QEMU hypervisor. QEMU is a general purpose hypervisor and emulator that runs on most host platforms and supports most guest systems.
The VM images running inside the hypervisor are built using Packer. It's a tool for automatically creating VM images, installing the guest operating system and doing any final provisioning.
The GitHub action uses SSH to communicate and execute commands inside the VM. It uses rsync to share files between the guest VM and the host. To authenticate the SSH connection a unique key pair is used. This pair is generated each time the action is run. The public key is added to the VM image and the host stores the private key. A secondary hard drive, which is backed by a file, is created. The public key is stored on this hard drive, which the VM then mounts. At boot time, the secondary hard drive will be identified and the public key will be copied to the appropriate location.
To reduce the time it takes for the GitHub action to start executing the commands specified by the user, it aims to boot the guest operating systems as fast as possible. This is achieved in a couple of ways:
By downloading resources, like the hypervisor and a few other tools, instead of installing them through a package manager
The resources that are downloaded use no compression. The size is small enough anyway and it's faster to download the uncompressed data than it is to download compressed data and then uncompress it.
It leverages async/await to perform tasks asynchronously. Like
downloading the VM image and other resources at the same time
It performs as much as possible of the setup ahead of time when the VM image is provisioned
ContributingThe changelog is maintained in the changelog.md file, following the Keep a Changelog format. The changelog is updated incrementally. That is, for every new feature or bugfix, add an entry to the changelog. New entries are added below the Unreleased section, with an appropriate sub header.
To add support for a new version of an existing operating system follow the steps below:
First, make the necessary changes in the appropriate *-builder repository:
Add a new subdirectory under the var_files directory
Name the subdirectory after the new version, i.e. 14.3
Add a .pkrvars.hcl file for each supported architecture
The content of the above files should be:
checksum = "<hash>"
Where <hash> should be replaced with the hash of the install media. Make
sure the hash value is prefixed with the hashing algorithm, example:
checksum = "sha256:1c41fdca8fcd8c746eb05f31a82a4bff2ad866c5fde1808f16b822a6df3f0de5"
The hash of the install media can usually be found at a nearby URL where the
install media is downloaded from. Exactly where to find this depends on the
operating system. The URL for the install media can be found the main Packer
configuration file in the *-builder repository.'
Add a new entry to the table in the readme containing supported versions and architectures.
Update the GitHub Action workflow with a new entry in the existing matrix of versions.
If there's a changelog file, update that as well
Create a pull request with the changes
Update the src/version.ts file with the new tag from the previous step
Compile the TypeScript by running npm run all
Update the .github/workflows/ci.yml file by adding the new version to the existing
matrix of versions of the operating system
Update the readme.md file and add new entry in the version and
architecture table of the operating system in the
Supported Platforms
section
If the new version is the latest version available of the operating system,
update Full Example
section in the readme.md, both the description and the
example usage of the GitHub action
Update the changelog: changelog.md
Create a pull request with all the changes
In addition to the steps below, a new builder repository for the operating system needs to be added and implemented.
To add support for a new operating system, follow these steps:
Create the Operating System Implementation:
src/operating_systems with the name of the
operating system.qemu_vm.ts: Define the virtual machine class for the operating system.<os_name>.ts: Implement the operating system class extending the Qemu
class.factory.ts: Implement the factory class for the operating system.Write Tests:
spec/operating_systems with the
name of the operating system.Update the Main Entry Point:
src/main.ts.Update the CI Configuration:
.github/workflows/ci.yml.Update the readme.md:
Supported Platforms section.Test the Implementation:
Submit a Pull Request:
By following these steps, you can ensure the new operating system is integrated seamlessly into the project.
Releases are cut with relog, driven by the [Unreleased] section of
changelog.md. The repo ships a release.conf that configures relog for this
project (master branch, origin remote, and a pre-commit hook that bumps the
cross-platform-actions/action@vX.Y.Z reference in this readme).
These steps need to be performed by a maintainer:
Make sure the [Unreleased] section in changelog.md describes everything
going into the release. Cross-Platform Action follows the
semantic versioning scheme; relog derives the next version from
the sub-headers under [Unreleased]:
### Fixed only → patch bump### Added, ### Changed, ### Deprecated → minor bump### Removed (or "Breaking" anywhere in the section) → major bumpFrom a clean master working tree, run relog (optionally pass an explicit
version like relog 2.0.0, or --dry-run to preview). It rewrites the
changelog, updates this readme, commits, creates an annotated vX.Y.Z tag,
and prompts before pushing.
Pushing the tag triggers the Release workflow, which creates a draft
release on GitHub with the newly added changelog section as release notes.
Review the draft release on GitHub and publish it. Publishing fires the
Update Major Tag workflow, which force-updates the matching major tag
(e.g. v2) to point at the published release.
Local DevelopmentInstall the above prerequisites
Clone the repository by running:
git clone https://github.com/cross-platform-actions/action
Navigate to the newly cloned repository: cd action
Install the dependencies by running: npm install
Run any of the below npm commands
The following npm commands are available:
build - Build the GitHub actionformat - Reformat the codelint - Lint the codepackage - Package the GitHub action for distribution and end to end testingtest - Run unit testsall - Will run all of the above commandsThe end to end tests can be run locally by running it through Act. By default, resources and VM images will be downloaded from github.com. By running a local HTTP server it's possible to point the GitHub action to local resources.
Install the above prerequisites
Copy test/workflows/ci.yml.example to
test/workflows/ci.yml
Make any changes you like to test/workflows/ci.yml, this is file ignored by
Git
Build the GitHub action by running: npm run build
Package the GitHub action by running: npm run package
Run the GitHub action by running: act --privileged -W test/workflows
The GitHub action includes a development dependency on a HTTP server. The
test/http directory contains a skeleton of a directory structure
which matches the URLs that the GitHub action uses to download resources. All
files within the test/http are ignore by Git.
Add resources as necessary to the test/http directory
In one shell, run the following command to start the HTTP server:
./node_modules/http-server/bin/http-server test/http -a 127.0.0.1
The -a flag configures the HTTP server to only listen for incoming
connections from localhost, no external computers will be able to connect.
In another shell, run the GitHub action by running:
act --privileged -W test/workflows --env CPA_RESOURCE_URL=<url>
Where <url> is the URL inside Docker that points to localhost of the host
machine, for macOS, this is http://host.docker.internal:8080. By default,
the HTTP server is listening on port 8080.
TypeScript
99.1%