diff --git a/.gitignore b/.gitignore index a5c968d..b548d47 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ vault-password.txt # pixi environments .pixi/* !.pixi/config.toml +*.DS_Store diff --git a/macOS/github-runner/ansible/github-runner.yml b/macOS/github-runner/ansible/github-runner.yml new file mode 100644 index 0000000..b990b19 --- /dev/null +++ b/macOS/github-runner/ansible/github-runner.yml @@ -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" diff --git a/macOS/github-runner/ansible/requirements.yml b/macOS/github-runner/ansible/requirements.yml new file mode 100644 index 0000000..cb9538a --- /dev/null +++ b/macOS/github-runner/ansible/requirements.yml @@ -0,0 +1,3 @@ +--- +collections: + - name: geerlingguy.mac diff --git a/macOS/github-runner/ansible/roles/runner/tasks/main.yml b/macOS/github-runner/ansible/roles/runner/tasks/main.yml new file mode 100644 index 0000000..68b3f8f --- /dev/null +++ b/macOS/github-runner/ansible/roles/runner/tasks/main.yml @@ -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=' to this + host's line in inventory.txt, e.g.: + {{ inventory_hostname }} agent_name= + +- 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" diff --git a/macOS/github-runner/readme.md b/macOS/github-runner/readme.md new file mode 100644 index 0000000..ab33edc --- /dev/null +++ b/macOS/github-runner/readme.md @@ -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@ + ``` + +## 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] + runner_name= + runner_name= + ``` + +5. Add your SSH key to each host if you haven't already: + + ```sh + ssh-copy-id mantidbuilder@ + ``` + +6. Export the fine-grained PAT from the prerequisites and run the playbook: + + ```sh + export 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 + ``` + + The removal token is shown on the runner's page under `Settings -> Actions -> Runners -> -> 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://`, 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.