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
3 changes: 2 additions & 1 deletion roles/github_runner_macos_host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ A MacBook with its lid closed still sleeps with `sleep 0`, because that setting

## Tailscale

The role installs the Homebrew formula `tailscale` and runs `tailscaled install-system-daemon`, which copies `tailscaled` to `/usr/local/bin`, writes the launchd daemon `/Library/LaunchDaemons/com.tailscale.tailscaled.plist` and starts it, so the daemon runs from boot, before anyone logs in. A host that already has that file is not installed on again, whether this role or a person installed it, and a daemon without the Homebrew formula behind it (one built by hand) is never replaced; what the role does for a host that has the formula is in Keeping Tailscale current below.
The role installs the Homebrew formula `tailscale` and runs `tailscaled install-system-daemon`, which copies `tailscaled` to `/usr/local/bin`, writes the launchd daemon `/Library/LaunchDaemons/com.tailscale.tailscaled.plist` and starts it, so the daemon runs from boot, before anyone logs in. A host that already has that file is not installed on again, whether this role or a person installed it, and neither is a host that has Tailscale's standalone app (`/Applications/Tailscale.app`), which is its own Tailscale client in a system network extension and would conflict with a second daemon; and a daemon without the Homebrew formula behind it (one built by hand) is never replaced; what the role does for a host that has the formula is in Keeping Tailscale current below.

The role never joins or re-authenticates a tailnet on its own, because joining needs an auth key, a secret only the tailnet's owner can create. With `github_runner_macos_host_tailscale_auth_key` set, the role reads the daemon's login state and, for a daemon that needs a login, runs `tailscale up` with the key on standard input (so it is in no process listing and, with `no_log`, in no output), plus `github_runner_macos_host_tailscale_up_args`. A daemon that is already logged in is not touched, and a join that failed is retried by the next run. It applies only to a host whose Homebrew formula the role installed (the formula's `tailscale` is in the Homebrew prefix), so a hand-built daemon is never joined or logged in again.

Expand All @@ -57,6 +57,7 @@ Replacing the daemon restarts it, so connections over the host's tailnet, an SSH
- `github_runner_macos_host_tailscale_auth_key` (default empty), `github_runner_macos_host_tailscale_up_args` (default empty list): see Tailscale above.
- `github_runner_macos_host_tailscale_plist`: the launchd definition `tailscaled install-system-daemon` writes. Not a setting; a variable only so the tests can point it at a stand-in.
- `github_runner_macos_host_tailscale_update` (default true): keep the formula, its daemon and the standalone app current, as under Keeping Tailscale current above. Part of the Tailscale part, so `github_runner_macos_host_tailscale` false skips it too.
- `github_runner_macos_host_tailscale_app_bundle`: where Tailscale's standalone app lives. A host that has it gets no daemon installed beside it. Not a setting; a variable only so the tests can point it at a stand-in.
- `github_runner_macos_host_tailscale_daemon_binary`, `github_runner_macos_host_tailscale_app_cask`: where `tailscaled install-system-daemon` puts the daemon the launchd definition starts, and the Homebrew cask of the standalone app. Not settings; variables only so the tests can point them at stand-ins.
- `github_runner_macos_host_path`: the `PATH` the Homebrew tasks run with, both Homebrew prefixes and the system directories.

Expand Down
2 changes: 2 additions & 0 deletions roles/github_runner_macos_host/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ github_runner_macos_host_tailscale_update: true
# Not settings: where tailscaled install-system-daemon puts the daemon that the launchd definition starts, and the Homebrew cask of Tailscale's standalone app. Variables only so the tests can point them at stand-ins.
github_runner_macos_host_tailscale_daemon_binary: /usr/local/bin/tailscaled
github_runner_macos_host_tailscale_app_cask: tailscale-app
# Where the standalone Tailscale app lives. A host that has it is its own Tailscale client (a system network extension), so the role installs no daemon beside it. Not a setting: a variable only so the tests can point it at a stand-in.
github_runner_macos_host_tailscale_app_bundle: /Applications/Tailscale.app
# A Tailscale auth key (a secret only the tailnet's owner can create, from any source Ansible reads), empty by default. Set, the role runs tailscale up with it on a host whose daemon the role manages (the Homebrew formula's tailscale is installed) and whose tailnet login is still needed; empty, the role never joins or re-authenticates a tailnet.
github_runner_macos_host_tailscale_auth_key: ""
# Further arguments for tailscale up, such as --hostname=example or --advertise-tags=tag:example. Used only when the auth key is set.
Expand Down
4 changes: 4 additions & 0 deletions roles/github_runner_macos_host/meta/argument_specs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ argument_specs:
type: str
default: tailscale-app
description: "The Homebrew cask of Tailscale's standalone app, kept current only when it is installed. Not a setting, and a variable only so the tests can point it at a stand-in."
github_runner_macos_host_tailscale_app_bundle:
type: path
default: /Applications/Tailscale.app
description: "Where Tailscale's standalone app lives. A host that has it is its own Tailscale client, so the role installs no daemon beside it. Not a setting, and a variable only so the tests can point it at a stand-in."
github_runner_macos_host_tailscale_auth_key:
type: str
default: ""
Expand Down
13 changes: 11 additions & 2 deletions roles/github_runner_macos_host/tasks/tailscale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,18 @@
path: "{{ github_runner_macos_host_tailscale_plist }}"
register: github_runner_macos_host_tailscale_daemon

# A host that already has the definition, installed by this role earlier or by hand, is left alone: nothing below runs, so a hand-built daemon is never replaced by the formula's.
- name: Look for the standalone Tailscale app
# The app carries its own client in a system network extension, which the formula's daemon would conflict with (both want the host's one tailnet connection), so a host that has the app is its own Tailscale client already.
ansible.builtin.stat:
path: "{{ github_runner_macos_host_tailscale_app_bundle }}"
get_checksum: false
register: github_runner_macos_host_tailscale_app_bundle_stat

# A host that already has the definition, installed by this role earlier or by hand, or the standalone app, is left alone: nothing below runs, so a hand-built daemon is never replaced by the formula's and the app is never given a second client.
- name: Install the daemon
when: not github_runner_macos_host_tailscale_daemon.stat.exists
when:
- not github_runner_macos_host_tailscale_daemon.stat.exists
- not github_runner_macos_host_tailscale_app_bundle_stat.stat.exists
block:
- name: Install the Tailscale Homebrew formula
community.general.homebrew:
Expand Down
12 changes: 12 additions & 0 deletions tests/unit/test_macos_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ def setUp(self) -> None:
self.plist = self.stub / "LaunchDaemons" / PLIST_NAME
# Where tailscaled install-system-daemon puts the daemon it installs.
self.daemon_binary = self.stub / "usr-local-bin" / "tailscaled"
# Where the standalone app would be; absent unless a test creates it.
self.app_bundle = self.stub / "Applications" / "Tailscale.app"
self.set_power(sleepdisabled="0", sleep="1", displaysleep="10", disksleep="10")

def set_power(self, sleepdisabled: str | None = "1", **overrides: str) -> None:
Expand Down Expand Up @@ -493,6 +495,7 @@ def run_role(self, *extra: str, system: str = "Darwin", machine: str = "arm64",
"github_runner_macos_host_homebrew_prefix": str(self.prefix),
"github_runner_macos_host_tailscale_plist": str(self.plist),
"github_runner_macos_host_tailscale_daemon_binary": str(self.daemon_binary),
"github_runner_macos_host_tailscale_app_bundle": str(self.app_bundle),
# The install and join tests are about those, and a stand-in formula that is installed would otherwise be upgraded on their second runs: the update has tests of its own, which turn it on.
"github_runner_macos_host_tailscale_update": False,
**variables,
Expand Down Expand Up @@ -738,6 +741,15 @@ def test_a_host_that_already_has_the_daemon_is_left_entirely_alone(self) -> None
self.assertEqual(self.calls(), READS)
self.assertEqual(self.recap(result)["changed"], 0)

def test_a_host_running_the_standalone_app_gets_no_daemon_beside_it(self) -> None:
self.set_power()
self.app_bundle.mkdir(parents=True)
result = self.run_role()
self.ok(result)
self.assertEqual(self.calls(), READS)
self.assertFalse(self.plist.exists())
self.assertEqual(self.recap(result)["changed"], 0)

def test_a_hand_built_daemon_stays_alone_even_with_an_auth_key_and_no_formula(self) -> None:
self.set_power()
self.hand_built_daemon()
Expand Down
Loading