Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ vault-password.txt
# pixi environments
.pixi/*
!.pixi/config.toml
*.DS_Store
18 changes: 18 additions & 0 deletions macOS/github-runner/ansible/github-runner.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
- name: Deploy native GitHub runner for Mantid builds (macOS)
hosts: all
vars:
organization: mantidproject
repository: mantid
github_token: "{{ lookup('ansible.builtin.env', 'GITHUB_TOKEN') }}"
runner_version: "latest"
runner_arch: "osx-arm64"

pre_tasks:
- name: Fail if github token is missing
fail:
msg: "GITHUB_TOKEN is not set"
when: github_token == ""

roles:
- role: runner
tags: "runner"
3 changes: 3 additions & 0 deletions macOS/github-runner/ansible/requirements.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
collections:
- name: geerlingguy.mac
103 changes: 103 additions & 0 deletions macOS/github-runner/ansible/roles/runner/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
---
- name: Fail if agent_name is not set for this host
ansible.builtin.assert:
that: agent_name is defined
fail_msg: >-
agent_name is not set for {{ inventory_hostname }}. Add 'agent_name=<name>' to this
host's line in inventory.txt, e.g.:
{{ inventory_hostname }} agent_name=<NAME_OF_RUNNER>

- name: Check whether the runner is already configured
ansible.builtin.stat:
path: "{{ ansible_env.HOME }}/actions-runner/.runner"
register: runner_configured

- name: Fetch GitHub Actions runner registration token
ansible.builtin.uri:
url: "https://api.github.com/repos/{{ organization }}/{{ repository }}/actions/runners/registration-token"
method: POST
headers:
Accept: "application/vnd.github+json"
Authorization: "Bearer {{ github_token }}"
X-GitHub-Api-Version: "2022-11-28"
return_content: yes
status_code: 201
register: runner_token
when: not runner_configured.stat.exists

- name: Create the actions-runner directory
ansible.builtin.file:
path: "{{ ansible_env.HOME }}/actions-runner"
state: directory
mode: "0755"
when: not runner_configured.stat.exists

# runner_version may be a pinned version (e.g. "2.319.1") or "latest". The download
# URL always needs a real version number, so resolve "latest" via the GitHub API first.
- name: Look up the latest GitHub Actions runner release
ansible.builtin.uri:
url: "https://api.github.com/repos/actions/runner/releases/latest"
headers:
Accept: "application/vnd.github+json"
return_content: yes
register: latest_runner_release
when: not runner_configured.stat.exists and runner_version == "latest"

- name: Resolve the runner version to download
ansible.builtin.set_fact:
resolved_runner_version: >-
{{ (latest_runner_release.json.tag_name | regex_replace('^v', ''))
if runner_version == "latest" else runner_version }}
when: not runner_configured.stat.exists

- name: Download the GitHub Actions runner package
ansible.builtin.get_url:
url: "https://github.com/actions/runner/releases/download/v{{ resolved_runner_version }}/actions-runner-{{ runner_arch }}-{{ resolved_runner_version }}.tar.gz"
dest: "{{ ansible_env.HOME }}/actions-runner/actions-runner-{{ runner_arch }}-{{ resolved_runner_version }}.tar.gz"
mode: "0644"
when: not runner_configured.stat.exists

- name: Extract the GitHub Actions runner package
ansible.builtin.unarchive:
src: "{{ ansible_env.HOME }}/actions-runner/actions-runner-{{ runner_arch }}-{{ resolved_runner_version }}.tar.gz"
dest: "{{ ansible_env.HOME }}/actions-runner"
remote_src: yes
when: not runner_configured.stat.exists

- name: Clear the quarantine attribute so macOS Gatekeeper doesn't block the binaries
ansible.builtin.shell: xattr -dr com.apple.quarantine "{{ ansible_env.HOME }}/actions-runner"
when: not runner_configured.stat.exists

- name: Configure the runner
ansible.builtin.command:
argv:
- ./config.sh
- --unattended
- --url
- "https://github.com/{{ organization }}/{{ repository }}"
- --token
- "{{ runner_token.json.token }}"
- --name
- "{{ agent_name }}"
- --labels
- "{{ agent_name }}"
- --replace
chdir: "{{ ansible_env.HOME }}/actions-runner"
when: not runner_configured.stat.exists

- name: Check whether the runner service is already installed
ansible.builtin.stat:
path: "{{ ansible_env.HOME }}/actions-runner/.service"
register: runner_service_installed

# Note: unlike Linux, installing/starting the launchd service does not need sudo.
- name: Install the runner as a launchd service
ansible.builtin.command:
cmd: ./svc.sh install
chdir: "{{ ansible_env.HOME }}/actions-runner"
when: not runner_service_installed.stat.exists

- name: Start the runner service
ansible.builtin.command:
cmd: ./svc.sh start
chdir: "{{ ansible_env.HOME }}/actions-runner"
106 changes: 106 additions & 0 deletions macOS/github-runner/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Native GitHub self-hosted runner for macOS

This describes how to register a macOS machine as a GitHub self-hosted runner in the `mantidproject/mantid` repository.

The macOS runner is installed **natively** on the machine, following the same physical-machine model already used for the [macOS Jenkins node](../jenkins-node/README.md), with the GitHub Actions runner application registered and run as a `launchd` service instead of a Docker container.

## Prerequisites

- A [fine-grained GitHub token](https://github.com/settings/personal-access-tokens/new) with:
- resource owner: `mantidproject`
- repository access: Only select repositories (select `mantidproject/mantid`)
- permissions: Administration (Read and write)

See [here](https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-a-repository--fine-grained-access-tokens) for reference and instructions for generating a registration token.

## Manual machine setup

If the Mac has not been used as a build node before, do the following first (identical to the Jenkins node setup, see [macOS/jenkins-node/README.md](../jenkins-node/README.md#manual-setup) for full detail):

- Log in to the provided administrator account and create a `mantidbuilder` user (`System Settings -> Users & Groups`).
- Enable `Remote Login` and `Remote Management` under `System Settings -> General -> Sharing` for all users, and note the hostname used after the `@` in the SSH login.
- Under `System Settings -> Privacy & Security`:
- Untick "Require password after sleep or screensaver begins".
- Turn off FileVault (otherwise the machine won't accept SSH connections until someone logs in locally after a reboot).
- Install the Xcode Command Line Tools:

```sh
xcode-select --install
```

- Add your SSH key to the machine:

```sh
ssh-copy-id mantidbuilder@<HOST>
```

## Registering the runner

The `ansible/` directory automates the same steps: it generates a registration token via the GitHub API and configures/installs the runner as a service.

1. Clone the [`dockerfiles`](https://github.com/mantidproject/dockerfiles) repo and navigate to `macOS/github-runner/ansible`.

2. Create and activate a conda environment for Ansible (or reuse the one from `macOS/jenkins-node`):

```sh
mamba create --prefix ./condaenv ansible
mamba activate ./condaenv
```

3. Install the required collections:

```sh
ansible-galaxy install -r requirements.yml --force
```

4. Create an `inventory.txt` file with one line per machine:

```ini
[all]
<IP_ADDRESS_OR_HOSTNAME_1> runner_name=<NAME_OF_RUNNER_1>
<IP_ADDRESS_OR_HOSTNAME_2> runner_name=<NAME_OF_RUNNER_2>
```

5. Add your SSH key to each host if you haven't already:

```sh
ssh-copy-id mantidbuilder@<HOSTNAME>
```

6. Export the fine-grained PAT from the prerequisites and run the playbook:

```sh
export GITHUB_TOKEN=<github_token>
ansible-playbook -i inventory.txt github-runner.yml -u mantidbuilder -K
```

`-K` prompts for the `mantidbuilder` account password, needed to install the `launchd` service.

7. Confirm the runner(s) appear at `https://github.com/mantidproject/mantid/settings/actions/runners` within a minute or two.

`runner_version` in `github-runner.yml` defaults to `latest`, which is resolved to the current release via the GitHub API at deploy time. Pin it to a specific version (e.g. `2.319.1`) if you need reproducible deploys.

## Removing a runner

1. Mark the runner offline/idle on GitHub first (don't remove while a job is running).
2. On the machine:

```sh
cd ~/actions-runner
./svc.sh stop
./svc.sh uninstall
./config.sh remove --token <removal_token>
```

The removal token is shown on the runner's page under `Settings -> Actions -> Runners -> <runner> -> Remove`, or can be generated via the [remove-token API endpoint](https://docs.github.com/en/rest/actions/self-hosted-runners#create-a-remove-token-for-a-repository).

## Troubleshooting

- If `ansible-playbook` can't connect, you may need to log in locally or via VNC once first (FileVault can block SSH until the machine is unlocked). From another Mac: Finder -> `Cmd+K` -> `vnc://<HOSTNAME>`, using the `mantidbuilder` login.
- If macOS blocks the downloaded runner binaries with a Gatekeeper/quarantine warning, clear the quarantine attribute before running `config.sh`:

```sh
xattr -d com.apple.quarantine ~/actions-runner/bin/*
```

- `./svc.sh status` and `~/actions-runner/_diag/` logs are the first places to check if the runner shows as offline.