Repository navigation
feat: provision managed macOS Actions runners #226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
f8f8d5a
feat: provision managed macOS Actions runners
Mearman 336ac0f
fix: isolate macOS runner deployment
Mearman 614119f
fix: stop runner before replacement
Mearman 6cefbeb
fix: restrict macOS runner to one repository
Mearman aac4cf8
fix: isolate macOS runner account and scope
Mearman 947d3a8
fix: keep runner registration token off argv
Mearman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
21
roles/github_runner_macos_actions_runner/defaults/main.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
13
roles/github_runner_macos_actions_runner/handlers/main.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
61
roles/github_runner_macos_actions_runner/meta/argument_specs.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
252
roles/github_runner_macos_actions_runner/tasks/main.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
| - 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 | ||
|
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
29
roles/github_runner_macos_actions_runner/templates/runner.plist.j2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.