Darwin.configure macos 26.yml

#!/usr/bin/env ansible-playbook
#
# Copyright © 2025 Florent Claerhout.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

- name: "macOS 26 Tahoe is configured"

  hosts: "all"

  vars:

    ##
    ## System
    ##
    # hostname:
    # domain_name:
    # mobile_config_rfile

    ##
    ## WindowManager
    ##
    wm_with_stage_manager: true
    wm_with_tiled_window_margins: false

    ##
    ## Dock
    ##
    dock_with_autohide: true
    # time the Dock waits before appearing after the mouse cursor moves to the screen edge
    dock_autohide_delay: 0
    # duration of the Dock's animation when it appears or disappears
    dock_autohide_time_modifier: 0
    dock_orientation: "bottom" # left|bottom|right

    ##
    ## Finder
    ##
    finder_with_show_all_extensions: true
    finder_with_show_all_files: true
    finder_with_extension_change_warning: false
    finder_with_warn_on_empty_trash: false

    ##
    ## Disk Utility
    ##
    diskutility_with_sidebar_show_all_devices: true

  handlers:

    - name: "Dock is restarted"
      listen: "Dock configuration has changed"
      command: "killall Dock"

    - name: "Finder is restarted"
      listen: "Finder configuration has changed"
      command: "killall Finder"

  force_handlers: true

  tasks:

    - block:

        # NOTICE!
        # ansible built-in hostname module does not handle the domain name properly

        - name: "HostName is assessed"
          changed_when: false
          failed_when: false # can fail with "HostName: not set"
          register: cmd
          command: "scutil --get HostName"

        - name: "Hostname is configured"
          when: cmd.stdout != (hostname + "." + domain_name)
          become: true
          command: "scutil --set HostName {{ hostname }}.{{ domain_name }}"

        - name: "ComputerName is assessed"
          changed_when: false
          register: cmd
          command: "scutil --get ComputerName"

        - name: "ComputerName is configured"
          when: cmd.stdout != hostname
          become: true
          command: "scutil --set ComputerName {{ hostname }}"

        - name: "LocalHostName is assessed"
          changed_when: false
          register: cmd
          command: "scutil --get LocalHostName"

        - name: "LocalHostName is configured"
          when: cmd.stdout != hostname
          become: true
          command: "scutil --set LocalHostName {{ hostname }}" # NOTICE! .local is added automatically

    - name: "WindowManager is configured"
      loop:

        - domain: "com.apple.WindowManager"
          key: "HasDisplayedShowDesktopEducation"
          type: "bool"
          value: true
          present: true

        - domain: "com.apple.WindowManager"
          key: "GloballyEnabled"
          type: "bool"
          value: "{{ wm_with_stage_manager }}"
          present: wm_with_stage_manager is defined

        - domain: "com.apple.WindowManager"
          key: "EnableTiledWindowMargins"
          type: "bool"
          value: "{{ wm_with_tiled_window_margins }}"
          present: wm_with_tiled_window_margins is defined

      community.general.osx_defaults:
        domain: "{{ item.domain }}"
        key: "{{ item.key }}"
        type: "{{ item.type }}"
        value: "{{ item.value }}"
        state: "{{ 'present' if item.present else 'absent' }}"

    - name: "Dock is configured"
      loop:

        - domain: "com.apple.dock"
          key: "autohide"
          type: "bool"
          value: "{{ dock_with_autohide }}"
          present: dock_with_autohide is defined

        - domain: "com.apple.dock"
          key: "orientation"
          type: "string"
          value: "{{ dock_orientation }}"
          present: dock_orientation is defined

        - domain: "com.apple.dock"
          key: "autohide-delay"
          type: "int"
          value: "{{ dock_autohide_delay }}"
          present: dock_autohide_delay is defined

        - domain: "com.apple.dock"
          key: "autohide-time-modifier"
          type: "int"
          value: "{{ dock_autohide_time_modifier }}"
          present: dock_autohide_time_modifier is defined

      community.general.osx_defaults:
        domain: "{{ item.domain }}"
        key: "{{ item.key }}"
        type: "{{ item.type }}"
        value: "{{ item.value }}"
        state: "{{ 'present' if item.present else 'absent' }}"

      notify: "Dock configuration has changed"

    - name: "Finder is configured"
      loop:

        - domain: "NSGlobalDomain"
          key: "AppleShowAllExtensions"
          type: "bool"
          value: "{{ finder_with_show_all_extensions }}"
          present: finder_with_show_all_extensions is defined

        - domain: "com.apple.finder"
          key: "AppleShowAllFiles"
          type: "bool"
          value: "{{ finder_with_show_all_files }}"
          present: finder_with_show_all_files is defined

        - domain: "com.apple.finder"
          key: "FXEnableExtensionChangeWarning"
          type: "bool"
          value: "{{ finder_with_extension_change_warning }}"
          present: finder_with_extension_change_warning is defined

        - domain: "com.apple.finder"
          key: "WarnOnEmptyTrash"
          type: "bool"
          value: "{{ finder_with_warn_on_empty_trash }}"
          present: finder_with_warn_on_empty_trash is defined

      community.general.osx_defaults:
        domain: "{{ item.domain }}"
        key: "{{ item.key }}"
        type: "{{ item.type }}"
        value: "{{ item.value }}"
        state: "present"

      notify: "Finder configuration has changed"

    - name: "Disk Utility is configured"
      loop:

        - domain: "com.apple.DiskUtility"
          key: "SidebarShowAllDevices"
          type: "bool"
          value: "{{ diskutility_with_sidebar_show_all_devices }}"
          present: diskutility_with_sidebar_show_all_devices is defined

      community.general.osx_defaults:
        domain: "{{ item.domain }}"
        key: "{{ item.key }}"
        type: "{{ item.type }}"
        value: "{{ item.value }}"
        state: "present"

    - when: mobile_config_rfile is defined
      block:

        - name: "MDM profile is assessed"
          changed_when: false
          failed_when: false
          register: cmd
          command: "python3 -B profiles.py is-installed '{{ mobile_config_rfile }}'"

        - name: "MDM profile is installed"
          when: ("profile is not installed" in cmd.stderr)
          command: "python3 -B profiles.py install '{{ mobile_config_rfile }}'"