Skip to content
Open
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
88 changes: 70 additions & 18 deletions ansible/macos/macos/base.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
name: all_packages
tags: always

- name: Load cask to app name mappings
include_vars:
file: "{{ dev_env_dir }}/config/packages/cask-app-names.yml"
name: cask_mappings
tags: always

- name: Collect all package lists
tags: brew_packages
set_fact:
Expand Down Expand Up @@ -104,31 +110,77 @@
when: all_removed_homebrew | length > 0
become: false

- name: Docker and container runtime installation
tags: [docker]
- name: Preflight check for cask packages
tags: cask
block:
- name: Check if Docker Desktop is already installed
shell: brew list --cask docker-desktop >/dev/null 2>&1 && echo yes || echo no
register: docker_desktop_installed
- name: Get all installed casks from Homebrew (single call optimization)
shell: brew list --cask 2>/dev/null || true
register: all_installed_casks
changed_when: false
become: false

- name: Remove Docker Desktop from install list if already installed
set_fact:
all_cask_packages: "{{ all_cask_packages | difference(['docker-desktop']) }}"
when: "'docker-desktop' in all_cask_packages and docker_desktop_installed.stdout == 'yes'"
- name: Check which cask packages are already installed via Homebrew
shell: |
# Use cached list of installed casks
installed_casks="{{ all_installed_casks.stdout }}"
for pkg in {{ all_cask_packages | map('quote') | join(' ') }}; do
if echo "$installed_casks" | grep -qx "$pkg"; then
echo "$pkg"
fi
done
Comment on lines +123 to +131
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Homebrew cask check is redundant. The script checks if packages are in all_installed_casks.stdout, which was already obtained by running brew list --cask. Since we have this data, we could filter all_cask_packages directly in Ansible using intersect instead of executing another shell command. This would eliminate the need for shell execution and improve maintainability.

Copilot uses AI. Check for mistakes.
register: homebrew_cask_check
changed_when: false
become: false

- name: Google Chrome installation
tags: [chrome]
block:
- name: Check if Google Chrome is already installed
shell: brew list --cask google-chrome >/dev/null 2>&1 && echo yes || echo no
register: google_chrome_installed
- name: Check which cask packages are manually installed
shell: |
# Use cached list of installed casks
installed_casks="{{ all_installed_casks.stdout }}"
{% for cask in all_cask_packages %}
{% if cask in cask_mappings.cask_to_app_mapping %}
if [ -d "/Applications/{{ cask_mappings.cask_to_app_mapping[cask] }}" ]; then
if ! echo "$installed_casks" | grep -qx "{{ cask }}"; then
echo "{{ cask }}"
fi
fi
{% endif %}
{% endfor %}
register: manual_cask_check
changed_when: false
become: false

Comment on lines +136 to +153
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manual cask check generates a shell script with Jinja2 templating that iterates through all cask packages. This creates a large shell script when many casks are configured. Consider using Ansible's native stat module with a loop to check for application existence, which would be more maintainable and easier to debug.

Suggested change
- name: Check which cask packages are manually installed
shell: |
# Use cached list of installed casks
installed_casks="{{ all_installed_casks.stdout }}"
{% for cask in all_cask_packages %}
{% if cask in cask_mappings.cask_to_app_mapping %}
if [ -d "/Applications/{{ cask_mappings.cask_to_app_mapping[cask] }}" ]; then
if ! echo "$installed_casks" | grep -qx "{{ cask }}"; then
echo "{{ cask }}"
fi
fi
{% endif %}
{% endfor %}
register: manual_cask_check
changed_when: false
become: false
- name: Check which cask packages are manually installed (stat loop)
stat:
path: "/Applications/{{ cask_mappings.cask_to_app_mapping[item] | default('') }}"
loop: "{{ all_cask_packages }}"
loop_control:
label: "{{ item }}"
register: manual_cask_stat_results
when: item in cask_mappings.cask_to_app_mapping
changed_when: false
become: false
- name: Set manually_installed_casks fact
set_fact:
manually_installed_casks: >-
{{
manual_cask_stat_results.results
| selectattr('stat.exists', 'defined')
| selectattr('stat.exists')
| map(attribute='item')
| select('match', '^(?!.*(' ~ (all_installed_casks.stdout | regex_replace('\n', '|')) ~ ')).*$')
| list
}}

Copilot uses AI. Check for mistakes.
- name: Parse installation status
set_fact:
homebrew_installed_casks: "{{ homebrew_cask_check.stdout_lines }}"
manually_installed_casks: "{{ manual_cask_check.stdout_lines }}"

- name: Display warning for manually installed applications
debug:
msg:
- "⚠️ WARNING: The following applications are installed manually (not via Homebrew):"
- "{{ manually_installed_casks | join(', ') }}"
- ""
- "These will be SKIPPED during installation to avoid conflicts."
- ""
- "📋 Recommendation: For better package management, consider:"
- " 1. Uninstalling the manually installed applications"
- " 2. Re-running sparkdock to install them via Homebrew"
- ""
- "This ensures all applications are managed consistently through Homebrew."
when: manually_installed_casks | length > 0

- name: Remove Google Chrome from install list if already installed
- name: Remove already-installed casks from installation list
set_fact:
all_cask_packages: "{{ all_cask_packages | difference(['google-chrome']) }}"
when: "'google-chrome' in all_cask_packages and google_chrome_installed.stdout == 'yes'"
all_cask_packages: "{{ all_cask_packages | difference(homebrew_installed_casks) | difference(manually_installed_casks) }}"

- name: Display casks to be installed
debug:
msg:
- "📦 Cask packages to be installed: {{ all_cask_packages | length }}"
- "{{ all_cask_packages | join(', ') if all_cask_packages | length > 0 else 'None (all already installed)' }}"
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional check if all_cask_packages | length > 0 is redundant since the outer when condition already guards against empty lists at line 171. The else branch 'None (all already installed)' will never execute. Simplify to just {{ all_cask_packages | join(', ') }}.

Copilot uses AI. Check for mistakes.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

when: ansible_verbosity >= 0
Copy link

Copilot AI Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition ansible_verbosity >= 0 will always be true since Ansible verbosity starts at 0 by default. This effectively means the debug message will always display. If the intent is to always show this message, remove the when condition. If conditional display is needed, use a higher verbosity threshold (e.g., >= 1).

Suggested change
when: ansible_verbosity >= 0

Copilot uses AI. Check for mistakes.

- name: Install cask packages
tags: cask
Expand Down
20 changes: 20 additions & 0 deletions config/packages/cask-app-names.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Mapping of Homebrew cask package names to their Application bundle names
# This file is used by the preflight-check to detect manually installed applications
# Format: cask_package_name: "Application Name.app"
#
# Only include casks that:
# 1. Are commonly installed manually by users
# 2. Cause installation conflicts when already present
# 3. Have standard installation paths in /Applications
#
# Do NOT include casks that are rarely installed manually or have no known conflict issues.
# Example: Do not include utility casks that are typically installed as dependencies and do not conflict with manual installations.

cask_to_app_mapping:
google-chrome: "Google Chrome.app"
docker-desktop: "Docker.app"
visual-studio-code: "Visual Studio Code.app"
slack: "Slack.app"
zoom: "zoom.us.app"
iterm2: "iTerm.app"
ghostty: "Ghostty.app"
Loading