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
7 changes: 7 additions & 0 deletions playbooks/macos_actions_runner.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
# A macOS Actions runner is a host process, not an ARC runner pod: Xcode and macOS archive jobs cannot run in a Linux container. The role registers it with a short-lived GitHub App token and supervises its persistent process through launchd.
- name: Install managed macOS Actions runners
hosts: "{{ github_runner_macos_actions_runner_hosts | default(github_runner_cluster_group | default('github_runner_cluster')) }}"
gather_facts: true
roles:
- exadev.github_runner.github_runner_macos_actions_runner
21 changes: 21 additions & 0 deletions roles/github_runner_macos_actions_runner/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
# A persistent macOS runner is deliberately separate from ARC: runner pods require Linux containers, while Xcode and macOS archive jobs must run on the host OS. Restrict it to one repository whose workflows are equally trusted, because a persistent native runner is not a disposable cross-repository execution environment. The registration credential is minted only while Ansible configures it; launchd runs the already-registered process without any GitHub credential.
github_runner_macos_actions_runner_enabled: false
github_runner_macos_actions_runner_organization: ""
github_runner_macos_actions_runner_repository: ""
github_runner_macos_actions_runner_app_id: ""
github_runner_macos_actions_runner_installation_id: ""
github_runner_macos_actions_runner_private_key: ""
github_runner_macos_actions_runner_private_key_op_reference: ""
github_runner_macos_actions_runner_label: exadev-macos
github_runner_macos_actions_runner_name: "{{ inventory_hostname }}-macos"
github_runner_macos_actions_runner_user: github-runner
github_runner_macos_actions_runner_uid: 401
github_runner_macos_actions_runner_version: "2.338.2"
github_runner_macos_actions_runner_sha256: 25a065af409ffa317ab4d5a3b614dd15fc9c40f2172bc1b35a1b1258e919a388
# Repository identity is part of the directory. Changing the repository therefore creates a new unregistered runner rather than leaving a runner enrolled in the old scope.
github_runner_macos_actions_runner_root: "/var/lib/github-actions-runner/{{ github_runner_macos_actions_runner_organization }}-{{ github_runner_macos_actions_runner_repository }}"
github_runner_macos_actions_runner_work_dir: "{{ github_runner_macos_actions_runner_root }}/_work"
github_runner_macos_actions_runner_plist: "/Library/LaunchDaemons/io.exadev.github-actions-runner.{{ github_runner_macos_actions_runner_name }}.plist"
github_runner_macos_actions_runner_launchd_label: "io.exadev.github-actions-runner.{{ github_runner_macos_actions_runner_name }}"
github_runner_macos_actions_runner_path: /opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin
13 changes: 13 additions & 0 deletions roles/github_runner_macos_actions_runner/handlers/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
- name: Restart macOS Actions runner
become: true
block:
- name: Stop the previous runner launch daemon
ansible.builtin.command:
argv: [launchctl, bootout, "system/{{ github_runner_macos_actions_runner_launchd_label }}"]
failed_when: false
changed_when: false
- name: Start the runner launch daemon
ansible.builtin.command:
argv: [launchctl, bootstrap, system, "{{ github_runner_macos_actions_runner_plist }}"]
changed_when: true
61 changes: 61 additions & 0 deletions roles/github_runner_macos_actions_runner/meta/argument_specs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
argument_specs:
main:
short_description: Registers a macOS runner as a launchd-managed ephemeral GitHub Actions runner.
options:
github_runner_macos_actions_runner_enabled:
type: bool
default: false
github_runner_macos_actions_runner_organization:
type: str
default: ""
github_runner_macos_actions_runner_repository:
type: str
default: ""
github_runner_macos_actions_runner_app_id:
type: str
default: ""
github_runner_macos_actions_runner_installation_id:
type: str
default: ""
github_runner_macos_actions_runner_private_key:
type: str
default: ""
no_log: true
github_runner_macos_actions_runner_private_key_op_reference:
type: str
default: ""
no_log: true
github_runner_macos_actions_runner_label:
type: str
default: exadev-macos
github_runner_macos_actions_runner_name:
type: str
default: ""
github_runner_macos_actions_runner_user:
type: str
default: github-runner
github_runner_macos_actions_runner_uid:
type: int
default: 401
github_runner_macos_actions_runner_version:
type: str
default: "2.338.2"
github_runner_macos_actions_runner_sha256:
type: str
default: 25a065af409ffa317ab4d5a3b614dd15fc9c40f2172bc1b35a1b1258e919a388
github_runner_macos_actions_runner_root:
type: path
default: ""
github_runner_macos_actions_runner_work_dir:
type: path
default: ""
github_runner_macos_actions_runner_plist:
type: path
default: ""
github_runner_macos_actions_runner_launchd_label:
type: str
default: ""
github_runner_macos_actions_runner_path:
type: str
default: /opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin
10 changes: 10 additions & 0 deletions roles/github_runner_macos_actions_runner/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
galaxy_info:
author: ExaDev
description: Registers a managed macOS GitHub Actions runner through launchd.
license: MIT
min_ansible_version: "2.15"
platforms:
- name: MacOSX
versions: [all]
dependencies: []
252 changes: 252 additions & 0 deletions roles/github_runner_macos_actions_runner/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
---
- name: Read the host platform and connecting user
ansible.builtin.setup:
gather_subset:
- "!all"
- "!min"
- platform
- user
when: "'system' not in ansible_facts or 'user_id' not in ansible_facts"

- name: End the role on a non-macOS host
ansible.builtin.meta: end_role
when: ansible_facts['system'] != 'Darwin'

- name: Read the macOS runner App key from 1Password
delegate_to: localhost
ansible.builtin.command:
argv: [op, read, "{{ github_runner_macos_actions_runner_private_key_op_reference }}"]
register: github_runner_macos_actions_runner_op_key
changed_when: false
no_log: true
when:
- github_runner_macos_actions_runner_enabled | bool
- github_runner_macos_actions_runner_private_key | length == 0
- github_runner_macos_actions_runner_private_key_op_reference | length > 0

- name: Set the macOS runner App key
ansible.builtin.set_fact:
github_runner_macos_actions_runner_private_key: "{{ github_runner_macos_actions_runner_op_key.stdout }}"
no_log: true
when: github_runner_macos_actions_runner_op_key is not skipped

- name: Require the macOS runner settings
ansible.builtin.assert:
that:
- github_runner_macos_actions_runner_organization | length > 0
- github_runner_macos_actions_runner_repository | length > 0
- github_runner_macos_actions_runner_app_id | string | regex_search('^[0-9]+$') is not none
- github_runner_macos_actions_runner_installation_id | string | regex_search('^[0-9]+$') is not none
- github_runner_macos_actions_runner_private_key | length > 0
- github_runner_macos_actions_runner_label | regex_search('^[A-Za-z0-9_.-]+$') is not none
quiet: true
when: github_runner_macos_actions_runner_enabled | bool
no_log: true

- name: Stop and remove a disabled macOS runner
become: true
when: not (github_runner_macos_actions_runner_enabled | bool)
block:
- name: Remove the runner launch daemon
ansible.builtin.file:
path: "{{ github_runner_macos_actions_runner_plist }}"
state: absent
- name: Stop the disabled runner service
ansible.builtin.command:
argv: [launchctl, bootout, "system/{{ github_runner_macos_actions_runner_launchd_label }}"]
failed_when: false
changed_when: false

- name: Configure the enabled macOS runner
when: github_runner_macos_actions_runner_enabled | bool
block:
- name: Read the runner account
ansible.builtin.command:
argv: [dscl, ., -read, "/Users/{{ github_runner_macos_actions_runner_user }}", UniqueID]
register: github_runner_macos_actions_runner_account
changed_when: false
failed_when: false
check_mode: false

- name: Create the unprivileged runner account
become: true
ansible.builtin.command:
argv:
- sysadminctl
- -addUser
- "{{ github_runner_macos_actions_runner_user }}"
- -fullName
- GitHub Actions Runner
- -UID
- "{{ github_runner_macos_actions_runner_uid }}"
- -shell
- /usr/bin/false
- -home
- "{{ github_runner_macos_actions_runner_root }}"
- -password
- '*'
no_log: true
changed_when: true
when: github_runner_macos_actions_runner_account.rc != 0

- name: Require the runner account's expected identity
ansible.builtin.assert:
that:
- "github_runner_macos_actions_runner_account.rc != 0 or github_runner_macos_actions_runner_account.stdout is regex('UniqueID: ' ~ (github_runner_macos_actions_runner_uid | string) ~ '$')"
quiet: true
when: not ansible_check_mode

- name: Create the runner directory
become: true
ansible.builtin.file:
path: "{{ github_runner_macos_actions_runner_root }}"
state: directory
owner: "{{ github_runner_macos_actions_runner_user }}"
mode: "0750"

- name: Check the installed runner version
become: true
become_user: "{{ github_runner_macos_actions_runner_user }}"
ansible.builtin.command:
argv: ["{{ github_runner_macos_actions_runner_root }}/bin/Runner.Listener", --version]
register: github_runner_macos_actions_runner_installed_version
changed_when: false
failed_when: false
check_mode: false

- name: Download the pinned Apple Silicon runner package
become: true
ansible.builtin.get_url:
url: "https://github.com/actions/runner/releases/download/v{{ github_runner_macos_actions_runner_version }}/actions-runner-osx-arm64-{{ github_runner_macos_actions_runner_version }}.tar.gz"
dest: "{{ github_runner_macos_actions_runner_root }}-{{ github_runner_macos_actions_runner_version }}.tar.gz"
checksum: "sha256:{{ github_runner_macos_actions_runner_sha256 }}"
mode: "0644"
when: github_runner_macos_actions_runner_installed_version.stdout | trim != github_runner_macos_actions_runner_version

- name: Stop the runner before replacing its installation
become: true
ansible.builtin.command:
argv: [launchctl, bootout, "system/{{ github_runner_macos_actions_runner_launchd_label }}"]
failed_when: false
changed_when: false
when: github_runner_macos_actions_runner_installed_version.stdout | trim != github_runner_macos_actions_runner_version

- name: Remove the prior runner installation
ansible.builtin.file:
path: "{{ github_runner_macos_actions_runner_root }}"
state: absent
when: github_runner_macos_actions_runner_installed_version.stdout | trim != github_runner_macos_actions_runner_version

- name: Recreate the runner directory
become: true
ansible.builtin.file:
path: "{{ github_runner_macos_actions_runner_root }}"
state: directory
owner: "{{ github_runner_macos_actions_runner_user }}"
mode: "0750"
when: github_runner_macos_actions_runner_installed_version.stdout | trim != github_runner_macos_actions_runner_version

- name: Install the pinned Apple Silicon runner package
become: true
ansible.builtin.unarchive:
src: "{{ github_runner_macos_actions_runner_root }}-{{ github_runner_macos_actions_runner_version }}.tar.gz"
dest: "{{ github_runner_macos_actions_runner_root }}"
remote_src: true
owner: "{{ github_runner_macos_actions_runner_user }}"
when: github_runner_macos_actions_runner_installed_version.stdout | trim != github_runner_macos_actions_runner_version

- name: Check whether the runner is registered
ansible.builtin.stat:
path: "{{ github_runner_macos_actions_runner_root }}/.runner"
register: github_runner_macos_actions_runner_registration
Comment thread
Mearman marked this conversation as resolved.
Resolved

- name: Register the runner through the GitHub App
delegate_to: localhost
check_mode: false
no_log: true
when: not github_runner_macos_actions_runner_registration.stat.exists
block:
- name: Create a temporary App key file
ansible.builtin.tempfile:
state: file
suffix: ".pem"
register: github_runner_macos_actions_runner_key_file

- name: Write the GitHub App key
ansible.builtin.copy:
content: "{{ github_runner_macos_actions_runner_private_key }}"
dest: "{{ github_runner_macos_actions_runner_key_file.path }}"
mode: "0600"

- name: Sign the GitHub App JWT
ansible.builtin.script:
cmd: "{{ role_path }}/../github_runner_arc/files/generate_app_jwt.py {{ github_runner_macos_actions_runner_app_id | quote }} {{ github_runner_macos_actions_runner_key_file.path | quote }}"
executable: python3
changed_when: false
register: github_runner_macos_actions_runner_jwt

- name: Mint a self-hosted runner installation token
ansible.builtin.uri:
url: "https://api.github.com/app/installations/{{ github_runner_macos_actions_runner_installation_id }}/access_tokens"
method: POST
headers:
Authorization: "Bearer {{ github_runner_macos_actions_runner_jwt.stdout | trim }}"
Accept: application/vnd.github+json
X-GitHub-Api-Version: "2022-11-28"
body_format: json
body:
permissions:
administration: write
metadata: read
status_code: 201
register: github_runner_macos_actions_runner_installation_token

- name: Request a runner registration token
ansible.builtin.uri:
url: "https://api.github.com/repos/{{ github_runner_macos_actions_runner_organization }}/{{ github_runner_macos_actions_runner_repository }}/actions/runners/registration-token"
method: POST
headers:
Authorization: "Bearer {{ github_runner_macos_actions_runner_installation_token.json.token }}"
Accept: application/vnd.github+json
X-GitHub-Api-Version: "2022-11-28"
status_code: 201
register: github_runner_macos_actions_runner_registration_token
always:
- name: Remove the temporary App key file
ansible.builtin.file:
path: "{{ github_runner_macos_actions_runner_key_file.path }}"
state: absent
when: github_runner_macos_actions_runner_key_file.path is defined

- name: Configure the macOS runner
become: true
become_user: "{{ github_runner_macos_actions_runner_user }}"
ansible.builtin.command:
argv:
- "{{ github_runner_macos_actions_runner_root }}/config.sh"
- --unattended
- --url
- "https://github.com/{{ github_runner_macos_actions_runner_organization }}/{{ github_runner_macos_actions_runner_repository }}"
- --name
- "{{ github_runner_macos_actions_runner_name }}"
- --labels
- "{{ github_runner_macos_actions_runner_label }}"
- --work
- "{{ github_runner_macos_actions_runner_work_dir }}"
- --replace
Comment thread
Mearman marked this conversation as resolved.
Resolved
environment:
ACTIONS_RUNNER_INPUT_TOKEN: "{{ github_runner_macos_actions_runner_registration_token.json.token }}"
no_log: true
changed_when: true
when: not github_runner_macos_actions_runner_registration.stat.exists

- name: Install the runner launch daemon
become: true
ansible.builtin.template:
src: runner.plist.j2
dest: "{{ github_runner_macos_actions_runner_plist }}"
owner: root
group: wheel
mode: "0644"
validate: plutil -lint %s
notify: Restart macOS Actions runner
29 changes: 29 additions & 0 deletions roles/github_runner_macos_actions_runner/templates/runner.plist.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>{{ github_runner_macos_actions_runner_launchd_label }}</string>
<key>ProgramArguments</key>
<array>
<string>{{ github_runner_macos_actions_runner_root }}/run.sh</string>
</array>
<key>WorkingDirectory</key>
<string>{{ github_runner_macos_actions_runner_root }}</string>
<key>UserName</key>
<string>{{ github_runner_macos_actions_runner_user }}</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>{{ github_runner_macos_actions_runner_path }}</string>
</dict>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>{{ github_runner_macos_actions_runner_root }}/runner.stdout.log</string>
<key>StandardErrorPath</key>
<string>{{ github_runner_macos_actions_runner_root }}/runner.stderr.log</string>
</dict>
</plist>
Loading