From 5c018bfeecfdf760999438b55805fd37f72382e6 Mon Sep 17 00:00:00 2001 From: Christopher Maher Date: Fri, 6 Nov 2026 12:43:25 -0800 Subject: [PATCH 1/3] feat(brew): stop installing cursor, trust the hashicorp and twilio taps, add ghostty Adds a trusted_taps list to the developer and home profiles and a task that runs `brew tap --trust` style trust through community.general.homebrew_tap, because Homebrew 6.0.0 requires a non-official tap to be trusted before its formulae load. Drops the cursor cask from both profiles and adds the ghostty cask to them. Nothing uninstalls an existing Cursor.app; the role only installs. Decisions: - cursor stop being installable: changed, removed from developer and home cask lists, issue, tests/assert-package-lists.yml (casks tag) - existing Cursor.app removal: unchanged, the role has no removal task, base tasks/darwin.yml is install-only, tests/assert-package-lists.yml - taps trusted: hashicorp/tap and twilio/brew only, issue plus vars/main.yml taps list, tests/assert-package-lists.yml (taps tag) - aws/tap trusted: unchanged, not trusted, issue names two taps, tests/assert-package-lists.yml - lightweight taps and trust: unchanged, no taps so no trust task, base vars/main.yml lightweight map, tests/assert-package-lists.yml - profiles getting ghostty: developer and home only, issue plus install.sh option mapping, tests/assert-package-lists.yml (casks tag) - ghostty form: plain cask list entry like the other casks, plan answer from Chris, tests/assert-package-lists.yml - twilio and hashicorp formulae: unchanged, vars/main.yml, tests/assert-package-lists.yml (parity tag) Behavior changes only for: macOS developer and home runs (cursor no longer installed, ghostty installed, hashicorp/tap and twilio/brew trusted). Files in scope: defilan-osconfig/roles/defilan-osconfig/vars/main.yml, defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml, defilan-osconfig/requirements.yml, README.md, defilan-osconfig/roles/defilan-osconfig/README.md, defilan-osconfig/tests/assert-package-lists.yml Signed-off-by: Christopher Maher --- README.md | 13 +++-- defilan-osconfig/requirements.yml | 2 +- .../roles/defilan-osconfig/README.md | 2 +- .../roles/defilan-osconfig/tasks/darwin.yml | 9 ++++ .../roles/defilan-osconfig/vars/main.yml | 10 +++- .../tests/assert-package-lists.yml | 52 +++++++++++++++++++ 6 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 defilan-osconfig/tests/assert-package-lists.yml diff --git a/README.md b/README.md index 4e14c89..4d2105a 100644 --- a/README.md +++ b/README.md @@ -39,18 +39,25 @@ The `install.sh` script performs the following actions: ### What does the Ansible playbook do? #### The Ansible playbook installs a variety of applications and performs some customizations to the MacOS or Debian environment. Here's a brief overview: -1. Taps into several Homebrew repositories, including HashiCorp, AWS, and Homebrew's own font cask. +1. Taps into several Homebrew repositories, including HashiCorp, AWS, and Twilio. +2. Trusts the HashiCorp and Twilio taps, which Homebrew requires before it loads a non-official tap. #### For MacOS: - Installs a variety of Homebrew packages, including `neovim`, `tmux`, `git-extras`, `thefuck`, `go`, `kubectl`, `skaffold`, `awscli`, `terraform`, `packer`, `aws-sam-cli`, `node`, `redis`, and `molecule`. -- Installs several Homebrew cask packages, including `arc`, `iterm2`, `visual-studio-code`, `1password`, `rectangle`, `font-hack-nerd-font`, `jetbrains-toolbox`, `nordvpn`, `switchresx`, `discord`, `istat-menus`, `signal`, `podman`, `bruno`, `gimp`, `obs`, and `krisp`. +- Installs `ghostty` and the casks below, selected by profile: + +| Profile | Casks | +|---------|-------| +| Lightweight | `arc`, `iterm2`, `1password`, `rectangle`, `istat-menus` | +| Developer | the lightweight casks, plus `visual-studio-code`, `font-hack-nerd-font`, `jetbrains-toolbox`, `switchresx`, `bruno`, `obs` | +| Home | the developer casks, plus `nordvpn`, `discord`, `signal`, `gimp`, `krisp` | #### For Debian: - Installs a variety of packages using the package manager, including `neovim`, `tmux`, `git-extras`, `thefuck`, `go`, `kubectl`, `skaffold`, `awscli`, `terraform`, `packer`, `aws-sam-cli`, `node`, `redis`, and `molecule`. -2. Creates directories at `~/stuffy`, `~/stuffy/code`, and `~/stuffy/playground`. +3. Creates directories at `~/stuffy`, `~/stuffy/code`, and `~/stuffy/playground`. ## Notes diff --git a/defilan-osconfig/requirements.yml b/defilan-osconfig/requirements.yml index 3c194cf..c5f39a0 100644 --- a/defilan-osconfig/requirements.yml +++ b/defilan-osconfig/requirements.yml @@ -1,4 +1,4 @@ --- collections: - name: community.general - version: ">=8.0.0" + version: ">=13.3.0" diff --git a/defilan-osconfig/roles/defilan-osconfig/README.md b/defilan-osconfig/roles/defilan-osconfig/README.md index 65db92b..2bfb700 100644 --- a/defilan-osconfig/roles/defilan-osconfig/README.md +++ b/defilan-osconfig/roles/defilan-osconfig/README.md @@ -9,7 +9,7 @@ This role supports three configuration profiles, selected via the `config` varia | Profile | Description | |---------|-------------| | `lightweight` | Essentials only -- basic cask apps (Arc, iTerm2, 1Password, Rectangle, iStat Menus) | -| `developer` | Full developer setup -- CLI tools, languages, containers, and dev-focused cask apps | +| `developer` | Full developer setup -- CLI tools, languages, containers, and dev-focused cask apps (including Ghostty) | | `home` | Productivity/full config -- everything in developer plus personal apps (NordVPN, Discord, Signal, GIMP, Krisp) | ## Requirements diff --git a/defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml b/defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml index b38f59e..4fdb4d5 100644 --- a/defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml +++ b/defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml @@ -10,6 +10,15 @@ - packages - homebrew +- name: Trust non-official Homebrew taps + community.general.homebrew_tap: + name: "{{ config_packages[config].trusted_taps }}" + trust: true + when: config_packages[config].trusted_taps is defined + tags: + - packages + - homebrew + - name: Install brew packages community.general.homebrew: name: "{{ config_packages[config].homebrew_packages }}" diff --git a/defilan-osconfig/roles/defilan-osconfig/vars/main.yml b/defilan-osconfig/roles/defilan-osconfig/vars/main.yml index 5426797..c062381 100644 --- a/defilan-osconfig/roles/defilan-osconfig/vars/main.yml +++ b/defilan-osconfig/roles/defilan-osconfig/vars/main.yml @@ -8,6 +8,10 @@ _shared_homebrew_taps: &shared_taps - aws/tap - twilio/brew +_shared_trusted_taps: &shared_trusted_taps + - hashicorp/tap + - twilio/brew + _shared_homebrew_packages: &shared_homebrew - neovim - tmux @@ -50,6 +54,7 @@ config_packages: - istat-menus developer: homebrew_taps: *shared_taps + trusted_taps: *shared_trusted_taps homebrew_packages: *shared_homebrew homebrew_cask_packages: - arc @@ -63,10 +68,11 @@ config_packages: - istat-menus - bruno - obs - - cursor + - ghostty apt_packages: *shared_apt home: homebrew_taps: *shared_taps + trusted_taps: *shared_trusted_taps homebrew_packages: *shared_homebrew homebrew_cask_packages: - arc @@ -85,7 +91,7 @@ config_packages: - gimp - obs - krisp - - cursor + - ghostty apt_packages: *shared_apt defilan_directories: diff --git a/defilan-osconfig/tests/assert-package-lists.yml b/defilan-osconfig/tests/assert-package-lists.yml new file mode 100644 index 0000000..0a01651 --- /dev/null +++ b/defilan-osconfig/tests/assert-package-lists.yml @@ -0,0 +1,52 @@ +--- +# Asserts the package lists in roles/defilan-osconfig/vars/main.yml. +# Run from the defilan-osconfig directory. +- name: Package list assertions + hosts: localhost + connection: local + gather_facts: false + vars_files: + - ../roles/defilan-osconfig/vars/main.yml + tasks: + - name: Trusted taps are HashiCorp and Twilio only + ansible.builtin.assert: + that: + - config_packages.developer.trusted_taps | default([]) | sort == ['hashicorp/tap', 'twilio/brew'] + - config_packages.home.trusted_taps | default([]) | sort == ['hashicorp/tap', 'twilio/brew'] + fail_msg: "trusted_taps: developer={{ config_packages.developer.trusted_taps | default('MISSING') }} home={{ config_packages.home.trusted_taps | default('MISSING') }}" + tags: [taps] + + - name: Lightweight profile has no trusted taps + ansible.builtin.assert: + that: + - config_packages.lightweight.trusted_taps is not defined + fail_msg: "lightweight has trusted_taps={{ config_packages.lightweight.trusted_taps | default('MISSING') }}" + tags: [taps] + + - name: Ghostty is a cask for developer and home + ansible.builtin.assert: + that: + - "'ghostty' in config_packages.developer.homebrew_cask_packages" + - "'ghostty' in config_packages.home.homebrew_cask_packages" + tags: [casks] + + - name: Cursor is a cask for neither developer nor home + ansible.builtin.assert: + that: + - "'cursor' not in config_packages.developer.homebrew_cask_packages" + - "'cursor' not in config_packages.home.homebrew_cask_packages" + tags: [casks] + + - name: Lightweight profile casks are unchanged + ansible.builtin.assert: + that: + - config_packages.lightweight.homebrew_cask_packages == ['arc', 'iterm2', '1password', 'rectangle', 'istat-menus'] + tags: [parity] + + - name: Shared Homebrew formulae are unchanged + ansible.builtin.assert: + that: + - "'twilio' in config_packages.developer.homebrew_packages" + - "'hashicorp/tap/terraform' in config_packages.developer.homebrew_packages" + - "'hashicorp/tap/packer' in config_packages.developer.homebrew_packages" + tags: [parity] From e23def2589ce2ff0b30cba97e4dfbd6e411e1938 Mon Sep 17 00:00:00 2001 From: Christopher Maher Date: Fri, 6 Nov 2026 12:57:16 -0800 Subject: [PATCH 2/3] test(brew): pin the darwin trust task and the full formula list The vars-only assertions stayed green when the darwin trust task was deleted, its loop removed, or trust flipped to false, and the shared formula parity check asserted three memberships instead of the list. This reads the darwin and main task files and asserts there is exactly one trust task, imported from main.yml, looping per tap with trust true, and compares the full shared formula list. Review round 1. Settled: declined the qwen nit to add tests/ and the assert playbook to .github/workflows/shell_lint.yml, because the plan's Files in scope omit workflow files and the plan records that gap as a note; the test is manual and run from defilan-osconfig. The Ubuntu dry-run jobs already exercise the changed tasks. Evidence: plan Files in scope line, plan Consumers note on the CI dry run. Signed-off-by: Christopher Maher --- .../roles/defilan-osconfig/tasks/darwin.yml | 3 +- .../tests/assert-package-lists.yml | 36 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml b/defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml index 4fdb4d5..4a57f32 100644 --- a/defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml +++ b/defilan-osconfig/roles/defilan-osconfig/tasks/darwin.yml @@ -12,8 +12,9 @@ - name: Trust non-official Homebrew taps community.general.homebrew_tap: - name: "{{ config_packages[config].trusted_taps }}" + name: "{{ item }}" trust: true + loop: "{{ config_packages[config].trusted_taps }}" when: config_packages[config].trusted_taps is defined tags: - packages diff --git a/defilan-osconfig/tests/assert-package-lists.yml b/defilan-osconfig/tests/assert-package-lists.yml index 0a01651..2b81d8d 100644 --- a/defilan-osconfig/tests/assert-package-lists.yml +++ b/defilan-osconfig/tests/assert-package-lists.yml @@ -46,7 +46,37 @@ - name: Shared Homebrew formulae are unchanged ansible.builtin.assert: that: - - "'twilio' in config_packages.developer.homebrew_packages" - - "'hashicorp/tap/terraform' in config_packages.developer.homebrew_packages" - - "'hashicorp/tap/packer' in config_packages.developer.homebrew_packages" + - config_packages.developer.homebrew_packages | sort == ['aws-sam-cli', 'awscli', 'battery', 'exercism', 'git-extras', 'go', 'hashicorp/tap/packer', 'hashicorp/tap/terraform', 'helm', 'kubectl', 'kubectx', 'minikube', 'molecule', 'neovim', 'node', 'podman', 'redis', 'skaffold', 'thefuck', 'tmux', 'twilio', 'yarn'] tags: [parity] + +- name: Darwin task file trusts each tap it lists + hosts: localhost + connection: local + gather_facts: false + vars_files: + - ../roles/defilan-osconfig/vars/main.yml + tasks: + - name: Read the darwin task file + tags: [trust-task] + ansible.builtin.set_fact: + darwin_tasks_raw: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/../roles/defilan-osconfig/tasks/darwin.yml') | from_yaml | selectattr('name', 'match', '^Trust ') | list }}" + main_tasks: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/../roles/defilan-osconfig/tasks/main.yml') | from_yaml }}" + + - name: Read the trust task body + tags: [trust-task] + ansible.builtin.set_fact: + trust_task: "{{ (darwin_tasks_raw | first) if darwin_tasks_raw | length else {} }}" + + - name: The import task includes darwin.yml + ansible.builtin.assert: + that: + - main_tasks | map('dict2items') | map('selectattr', 'key', 'equalto', 'ansible.builtin.import_tasks') | map('map', attribute='value') | flatten | select('equalto', 'darwin.yml') | list | length == 1 + + - name: There is one trust task, looping over trusted_taps with trust true + ansible.builtin.assert: + that: + - darwin_tasks_raw | length == 1 + - trust_task['community.general.homebrew_tap'].trust + - "'trusted_taps' in (trust_task.loop | default(''))" + - "'homebrew_tap' in trust_task | to_json" + tags: [trust-task] From bacfea95c3c2e80fa4fecd047da7d753bd0a53b1 Mon Sep 17 00:00:00 2001 From: Christopher Maher Date: Fri, 6 Nov 2026 13:12:45 -0800 Subject: [PATCH 3/3] test(brew): assert the trust task guard and restate the cask table The trust-task play did not read the task's when guard, so deleting the guard (which breaks a default lightweight macOS run) left the file green. It now asserts the guard, and the three-tap loop is one assertion per property so each failure names the property. The README table moves ghostty into the Developer row instead of claiming it for every profile. Review round 2. Settled: the round-1 Settled claim "The Ubuntu dry-run jobs already exercise the changed tasks" was false, since main.yml imports darwin.yml only when os_family is Darwin and the macOS dry run is continue-on-error. Replaced, not relied on. Evidence: tasks/main.yml macOS import guard, .github/workflows/shell_lint.yml macOS job. Declined [earlier]-tagged nit on the trust task name over-promising (aws/tap non-official but not trusted): the name matches the task's effect on the two taps it lists, and the plan's Comments asked for it. Signed-off-by: Christopher Maher --- README.md | 4 +- .../tests/assert-package-lists.yml | 62 +++++++++++++------ 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 4d2105a..be415e5 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,12 @@ The `install.sh` script performs the following actions: #### For MacOS: - Installs a variety of Homebrew packages, including `neovim`, `tmux`, `git-extras`, `thefuck`, `go`, `kubectl`, `skaffold`, `awscli`, `terraform`, `packer`, `aws-sam-cli`, `node`, `redis`, and `molecule`. -- Installs `ghostty` and the casks below, selected by profile: +- Installs Homebrew casks selected by profile: | Profile | Casks | |---------|-------| | Lightweight | `arc`, `iterm2`, `1password`, `rectangle`, `istat-menus` | -| Developer | the lightweight casks, plus `visual-studio-code`, `font-hack-nerd-font`, `jetbrains-toolbox`, `switchresx`, `bruno`, `obs` | +| Developer | the lightweight casks, plus `visual-studio-code`, `font-hack-nerd-font`, `jetbrains-toolbox`, `switchresx`, `bruno`, `obs`, `ghostty` | | Home | the developer casks, plus `nordvpn`, `discord`, `signal`, `gimp`, `krisp` | #### For Debian: diff --git a/defilan-osconfig/tests/assert-package-lists.yml b/defilan-osconfig/tests/assert-package-lists.yml index 2b81d8d..67546da 100644 --- a/defilan-osconfig/tests/assert-package-lists.yml +++ b/defilan-osconfig/tests/assert-package-lists.yml @@ -1,6 +1,8 @@ --- -# Asserts the package lists in roles/defilan-osconfig/vars/main.yml. -# Run from the defilan-osconfig directory. +# Asserts the package lists in roles/defilan-osconfig/vars/main.yml and the +# Homebrew trust task in roles/defilan-osconfig/tasks/darwin.yml. +# Run from the defilan-osconfig directory: +# ansible-playbook tests/assert-package-lists.yml - name: Package list assertions hosts: localhost connection: local @@ -46,37 +48,59 @@ - name: Shared Homebrew formulae are unchanged ansible.builtin.assert: that: - - config_packages.developer.homebrew_packages | sort == ['aws-sam-cli', 'awscli', 'battery', 'exercism', 'git-extras', 'go', 'hashicorp/tap/packer', 'hashicorp/tap/terraform', 'helm', 'kubectl', 'kubectx', 'minikube', 'molecule', 'neovim', 'node', 'podman', 'redis', 'skaffold', 'thefuck', 'tmux', 'twilio', 'yarn'] + - config_packages.developer.homebrew_packages | sort == expected_formulae + vars: + expected_formulae: + - aws-sam-cli + - awscli + - battery + - exercism + - git-extras + - go + - hashicorp/tap/packer + - hashicorp/tap/terraform + - helm + - kubectl + - kubectx + - minikube + - molecule + - neovim + - node + - podman + - redis + - skaffold + - thefuck + - tmux + - twilio + - yarn tags: [parity] - name: Darwin task file trusts each tap it lists hosts: localhost connection: local gather_facts: false - vars_files: - - ../roles/defilan-osconfig/vars/main.yml tasks: - - name: Read the darwin task file - tags: [trust-task] + - name: Read the darwin and main task files + vars: + darwin_file: "{{ playbook_dir }}/../roles/defilan-osconfig/tasks/darwin.yml" ansible.builtin.set_fact: - darwin_tasks_raw: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/../roles/defilan-osconfig/tasks/darwin.yml') | from_yaml | selectattr('name', 'match', '^Trust ') | list }}" + darwin_tasks_raw: "{{ (lookup('ansible.builtin.file', darwin_file) | from_yaml) | selectattr('name', 'match', '^Trust ') | list }}" main_tasks: "{{ lookup('ansible.builtin.file', playbook_dir ~ '/../roles/defilan-osconfig/tasks/main.yml') | from_yaml }}" - - name: Read the trust task body - tags: [trust-task] - ansible.builtin.set_fact: - trust_task: "{{ (darwin_tasks_raw | first) if darwin_tasks_raw | length else {} }}" - - - name: The import task includes darwin.yml + - name: Homebrew trust runs on darwin only ansible.builtin.assert: that: - - main_tasks | map('dict2items') | map('selectattr', 'key', 'equalto', 'ansible.builtin.import_tasks') | map('map', attribute='value') | flatten | select('equalto', 'darwin.yml') | list | length == 1 + - (main_tasks | map('dict2items') + | map('selectattr', 'key', 'equalto', 'ansible.builtin.import_tasks') + | map('map', attribute='value') | flatten + | select('equalto', 'darwin.yml') | list | length) == 1 - name: There is one trust task, looping over trusted_taps with trust true ansible.builtin.assert: that: - darwin_tasks_raw | length == 1 - - trust_task['community.general.homebrew_tap'].trust - - "'trusted_taps' in (trust_task.loop | default(''))" - - "'homebrew_tap' in trust_task | to_json" - tags: [trust-task] + - (darwin_tasks_raw | first)['community.general.homebrew_tap'].trust + - "'trusted_taps' in ((darwin_tasks_raw | first).loop | default(''))" + - ((darwin_tasks_raw | first).when | default('')) == when_trusted_taps_defined + vars: + when_trusted_taps_defined: "config_packages[config].trusted_taps is defined"