diff --git a/roles/ai_agent_runtime/tasks/verify.yml b/roles/ai_agent_runtime/tasks/verify.yml index 78a2cef..57e0cb1 100644 --- a/roles/ai_agent_runtime/tasks/verify.yml +++ b/roles/ai_agent_runtime/tasks/verify.yml @@ -42,18 +42,14 @@ register: ai_agent_runtime_pandoc_version changed_when: false check_mode: false - when: - - ansible_facts.os_family == "Debian" - - ai_agent_runtime_docs_enabled | bool + when: ai_agent_runtime_docs_enabled | bool - name: Check xelatex version ansible.builtin.command: xelatex --version register: ai_agent_runtime_xelatex_version changed_when: false check_mode: false - when: - - ansible_facts.os_family == "Debian" - - ai_agent_runtime_docs_enabled | bool + when: ai_agent_runtime_docs_enabled | bool - name: Check Chinese font inventory ansible.builtin.command: fc-list :lang=zh family @@ -61,7 +57,6 @@ changed_when: false check_mode: false when: - - ansible_facts.os_family == "Debian" - ai_agent_runtime_fonts_enabled | bool - ai_agent_runtime_verify_chinese_fonts | bool @@ -71,7 +66,6 @@ - ai_agent_runtime_chinese_fonts.stdout | length > 0 fail_msg: "No Chinese fonts were discovered by fontconfig." when: - - ansible_facts.os_family == "Debian" - ai_agent_runtime_fonts_enabled | bool - ai_agent_runtime_verify_chinese_fonts | bool diff --git a/roles/vhosts/acp_server_codex/tasks/config.yml b/roles/vhosts/acp_server_codex/tasks/config.yml index 440d95f..24955d3 100644 --- a/roles/vhosts/acp_server_codex/tasks/config.yml +++ b/roles/vhosts/acp_server_codex/tasks/config.yml @@ -42,8 +42,8 @@ ansible.builtin.copy: src: "{{ acp_codex_bridge_local_binary_path }}" dest: "{{ acp_codex_bridge_binary_path }}" - owner: "{{ omit if ansible_os_family == 'Darwin' else 'root' }}" - group: "{{ omit if ansible_os_family == 'Darwin' else 'root' }}" + owner: root + group: root mode: "0755" when: not (acp_codex_bridge_use_prebuilt | bool) @@ -61,7 +61,6 @@ path: "{{ item }}" state: absent loop: "{{ acp_codex_obsolete_caddy_fragment_paths }}" - when: ansible_os_family != 'Darwin' notify: Reload caddy - name: Deploy Caddy main file @@ -73,7 +72,6 @@ mode: "0644" notify: Reload caddy when: - - ansible_os_family != 'Darwin' - acp_codex_manage_caddy | bool - name: Deploy Codex ACP systemd service diff --git a/roles/vhosts/acp_server_codex/tasks/main.yml b/roles/vhosts/acp_server_codex/tasks/main.yml index 6416b37..6599ee8 100644 --- a/roles/vhosts/acp_server_codex/tasks/main.yml +++ b/roles/vhosts/acp_server_codex/tasks/main.yml @@ -2,7 +2,6 @@ - name: Install Codex ACP prerequisites ansible.builtin.import_tasks: install.yml tags: [acp_codex, acp_codex_install] - when: ansible_os_family != 'Darwin' - name: Configure Codex ACP service and Caddy ansible.builtin.import_tasks: config.yml @@ -17,4 +16,3 @@ tags: [acp_codex, acp_codex_validate] when: - not ansible_check_mode - - ansible_os_family != 'Darwin' diff --git a/roles/vhosts/acp_server_gemini/defaults/main.yml b/roles/vhosts/acp_server_gemini/defaults/main.yml index 4247ed6..aaf5a54 100644 --- a/roles/vhosts/acp_server_gemini/defaults/main.yml +++ b/roles/vhosts/acp_server_gemini/defaults/main.yml @@ -1,15 +1,13 @@ --- acp_gemini_service_name: acp-gemini -acp_gemini_service_user: "{{ ansible_env.USER | default('ubuntu') if ansible_os_family == 'Darwin' else 'ubuntu' }}" -acp_gemini_service_group: "{{ 'staff' if ansible_os_family == 'Darwin' else 'ubuntu' }}" -acp_gemini_home: "{{ ansible_env.HOME | default('/home/' + acp_gemini_service_user) if ansible_os_family == 'Darwin' else '/home/ubuntu' }}" -acp_gemini_workdir: "{{ acp_gemini_home }}/.gemini" -acp_gemini_xdg_config_home: "{{ acp_gemini_home }}/.config" -acp_gemini_xdg_state_home: "{{ acp_gemini_home }}/.local/state" -acp_gemini_config_dir: "{{ acp_gemini_home }}/.gemini" -acp_gemini_npm_global_bin: "{{ acp_gemini_home + '/.local/bin' if ansible_os_family == 'Darwin' else '/usr/bin' }}" -acp_gemini_binary_path: "{{ acp_gemini_npm_global_bin }}/gemini" -acp_gemini_path: "{{ acp_gemini_npm_global_bin }}:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin" +acp_gemini_service_user: ubuntu +acp_gemini_service_group: ubuntu +acp_gemini_home: /home/ubuntu +acp_gemini_workdir: /home/ubuntu/.gemini +acp_gemini_xdg_config_home: /home/ubuntu/.config +acp_gemini_xdg_state_home: /home/ubuntu/.local/state +acp_gemini_config_dir: /home/ubuntu/.gemini +acp_gemini_binary_path: /usr/bin/antigravity-cli acp_gemini_args: --experimental-acp acp_gemini_bridge_local_source_dir: "{{ playbook_dir }}/../xworkmate-bridge" acp_gemini_bridge_local_build_dir: "{{ playbook_dir }}/.artifacts/acp_gemini" diff --git a/roles/vhosts/acp_server_gemini/tasks/config.yml b/roles/vhosts/acp_server_gemini/tasks/config.yml index a171691..cb850d7 100644 --- a/roles/vhosts/acp_server_gemini/tasks/config.yml +++ b/roles/vhosts/acp_server_gemini/tasks/config.yml @@ -42,8 +42,8 @@ ansible.builtin.copy: src: "{{ acp_gemini_bridge_local_binary_path }}" dest: "{{ acp_gemini_bridge_binary_path }}" - owner: "{{ omit if ansible_os_family == 'Darwin' else acp_gemini_service_user }}" - group: "{{ omit if ansible_os_family == 'Darwin' else acp_gemini_service_group }}" + owner: "{{ acp_gemini_service_user }}" + group: "{{ acp_gemini_service_group }}" mode: "0755" notify: Restart acp gemini when: not (acp_gemini_bridge_use_prebuilt | bool) diff --git a/roles/vhosts/acp_server_gemini/tasks/main.yml b/roles/vhosts/acp_server_gemini/tasks/main.yml index c515b27..c0f3a01 100644 --- a/roles/vhosts/acp_server_gemini/tasks/main.yml +++ b/roles/vhosts/acp_server_gemini/tasks/main.yml @@ -2,7 +2,6 @@ - name: Install Gemini ACP prerequisites ansible.builtin.import_tasks: install.yml tags: [acp_gemini, acp_gemini_install] - when: ansible_os_family != 'Darwin' - name: Configure Gemini ACP adapter ansible.builtin.import_tasks: config.yml @@ -17,4 +16,3 @@ tags: [acp_gemini, acp_gemini_validate] when: - not ansible_check_mode - - ansible_os_family != 'Darwin' diff --git a/roles/vhosts/acp_server_gemini/templates/gemini-acp-adapter.service.j2 b/roles/vhosts/acp_server_gemini/templates/gemini-acp-adapter.service.j2 index 1656d49..0a42ac6 100644 --- a/roles/vhosts/acp_server_gemini/templates/gemini-acp-adapter.service.j2 +++ b/roles/vhosts/acp_server_gemini/templates/gemini-acp-adapter.service.j2 @@ -19,7 +19,7 @@ Environment=GEMINI_ADAPTER_ALLOWED_ORIGINS={{ acp_gemini_allowed_origins | join( Environment={{ key }}={{ value }} {% endif %} {% endfor %} -ExecStart={{ acp_gemini_bridge_binary_path }} adapter gemini --listen {{ acp_gemini_listen_host }}:{{ acp_gemini_listen_port }} --gemini-bin {{ acp_gemini_binary_path }} --gemini-args "{{ acp_gemini_args }}" +ExecStart={{ acp_gemini_bridge_binary_path }} adapter antigravity-cli --listen {{ acp_gemini_listen_host }}:{{ acp_gemini_listen_port }} --antigravity-cli-bin {{ acp_gemini_binary_path }} --antigravity-cli-args "{{ acp_gemini_args }}" Restart=always RestartSec=2 diff --git a/roles/vhosts/acp_server_gemini/templates/gemini.plist.j2 b/roles/vhosts/acp_server_gemini/templates/gemini.plist.j2 index 9eee67e..4195e8c 100644 --- a/roles/vhosts/acp_server_gemini/templates/gemini.plist.j2 +++ b/roles/vhosts/acp_server_gemini/templates/gemini.plist.j2 @@ -18,7 +18,7 @@ exec "{{ acp_gemini_bridge_binary_path }}" acp-server \ --port {{ acp_gemini_listen_port }} \ --host {{ acp_gemini_listen_host }} \ - --sub-command gemini \ + --sub-command antigravity-cli \ --sub-command mcp-app-server diff --git a/roles/vhosts/acp_server_hermes/defaults/main.yml b/roles/vhosts/acp_server_hermes/defaults/main.yml index 3238709..c5e6cd4 100644 --- a/roles/vhosts/acp_server_hermes/defaults/main.yml +++ b/roles/vhosts/acp_server_hermes/defaults/main.yml @@ -9,16 +9,15 @@ acp_hermes_bridge_build_goarch: "{{ 'arm64' if ansible_architecture in ['aarch64 acp_hermes_bridge_use_prebuilt: "{{ lookup('ansible.builtin.env', 'AI_WORKSPACE_USE_PREBUILT_BRIDGE') | default('false', true) | bool }}" acp_hermes_listen_host: 127.0.0.1 acp_hermes_listen_port: 3920 -acp_hermes_service_user: "{{ ansible_env.USER | default('ubuntu') if ansible_os_family == 'Darwin' else 'ubuntu' }}" -acp_hermes_service_group: "{{ 'staff' if ansible_os_family == 'Darwin' else 'ubuntu' }}" +acp_hermes_service_user: ubuntu +acp_hermes_service_group: ubuntu acp_hermes_bridge_binary_path: /usr/local/bin/xworkmate-go-core -acp_hermes_home: "{{ ansible_env.HOME | default('/home/' + acp_hermes_service_user) if ansible_os_family == 'Darwin' else '/home/ubuntu' }}" -acp_hermes_workdir: "{{ acp_hermes_home }}/hermes-agent" -acp_hermes_binary_path: "{{ acp_hermes_workdir }}/venv/bin/hermes" -acp_hermes_path: "{{ acp_hermes_binary_path | dirname }}:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin" +acp_hermes_workdir: /home/ubuntu/hermes-agent +acp_hermes_home: /home/ubuntu +acp_hermes_binary_path: /home/ubuntu/hermes-agent/venv/bin/hermes acp_hermes_args: acp -acp_hermes_xdg_config_home: "{{ acp_hermes_home }}/.config" -acp_hermes_xdg_state_home: "{{ acp_hermes_home }}/.local/state" +acp_hermes_xdg_config_home: /home/ubuntu/.config +acp_hermes_xdg_state_home: /home/ubuntu/.local/state acp_hermes_allowed_origins: - https://xworkmate.svc.plus - http://localhost:* diff --git a/roles/vhosts/acp_server_hermes/tasks/config.yml b/roles/vhosts/acp_server_hermes/tasks/config.yml index 863c18c..554dcc3 100644 --- a/roles/vhosts/acp_server_hermes/tasks/config.yml +++ b/roles/vhosts/acp_server_hermes/tasks/config.yml @@ -43,8 +43,8 @@ ansible.builtin.copy: src: "{{ acp_hermes_bridge_local_binary_path }}" dest: "{{ acp_hermes_bridge_binary_path }}" - owner: "{{ omit if ansible_os_family == 'Darwin' else acp_hermes_service_user }}" - group: "{{ omit if ansible_os_family == 'Darwin' else acp_hermes_service_group }}" + owner: "{{ acp_hermes_service_user }}" + group: "{{ acp_hermes_service_group }}" mode: "0755" notify: Restart acp hermes when: not (acp_hermes_bridge_use_prebuilt | bool) diff --git a/roles/vhosts/acp_server_hermes/tasks/main.yml b/roles/vhosts/acp_server_hermes/tasks/main.yml index a6963da..b0a89f7 100644 --- a/roles/vhosts/acp_server_hermes/tasks/main.yml +++ b/roles/vhosts/acp_server_hermes/tasks/main.yml @@ -2,7 +2,6 @@ - name: Install Hermes ACP prerequisites ansible.builtin.import_tasks: install.yml tags: [acp_hermes, acp_hermes_install] - when: ansible_os_family != 'Darwin' - name: Configure Hermes ACP adapter ansible.builtin.import_tasks: config.yml @@ -17,4 +16,3 @@ tags: [acp_hermes, acp_hermes_validate] when: - not ansible_check_mode - - ansible_os_family != 'Darwin' diff --git a/roles/vhosts/acp_server_opencode/defaults/main.yml b/roles/vhosts/acp_server_opencode/defaults/main.yml index 3d54c0d..1fb292c 100644 --- a/roles/vhosts/acp_server_opencode/defaults/main.yml +++ b/roles/vhosts/acp_server_opencode/defaults/main.yml @@ -7,8 +7,6 @@ acp_opencode_service_user: "{{ ansible_env.USER | default('ubuntu') }}" acp_opencode_service_group: "{{ 'staff' if ansible_os_family == 'Darwin' else (ansible_env.USER | default('ubuntu')) }}" acp_opencode_home: "{{ ansible_env.HOME | default('/home/' + acp_opencode_service_user) }}" acp_opencode_workdir: "{{ ansible_env.HOME | default('/home/' + acp_opencode_service_user) }}/.opencode" -acp_opencode_npm_global_bin: "{{ (ansible_env.HOME | default('/home/' + acp_opencode_service_user)) + '/.local/bin' if ansible_os_family == 'Darwin' else '/usr/bin' }}" -acp_opencode_path: "{{ acp_opencode_npm_global_bin }}:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin" acp_opencode_listen_host: 127.0.0.1 acp_opencode_listen_port: 38992 acp_opencode_packages: [] diff --git a/roles/vhosts/acp_server_opencode/tasks/config.yml b/roles/vhosts/acp_server_opencode/tasks/config.yml index 1629ab4..e41ec9e 100644 --- a/roles/vhosts/acp_server_opencode/tasks/config.yml +++ b/roles/vhosts/acp_server_opencode/tasks/config.yml @@ -41,8 +41,8 @@ ansible.builtin.copy: src: "{{ acp_opencode_bridge_local_binary_path }}" dest: "{{ acp_opencode_bridge_binary_path }}" - owner: "{{ omit if ansible_os_family == 'Darwin' else 'root' }}" - group: "{{ omit if ansible_os_family == 'Darwin' else 'root' }}" + owner: root + group: root mode: "0755" when: not (acp_opencode_bridge_use_prebuilt | bool) @@ -76,7 +76,6 @@ mode: "0644" notify: Reload caddy when: - - ansible_os_family != 'Darwin' - acp_opencode_manage_caddy | bool - name: Remove deprecated standalone OpenCode Caddy fragments @@ -84,7 +83,6 @@ path: "{{ item }}" state: absent loop: "{{ acp_opencode_obsolete_caddy_fragment_paths }}" - when: ansible_os_family != 'Darwin' notify: Reload caddy - name: Deploy OpenCode ACP systemd service diff --git a/roles/vhosts/acp_server_opencode/tasks/main.yml b/roles/vhosts/acp_server_opencode/tasks/main.yml index f6bde3e..3967453 100644 --- a/roles/vhosts/acp_server_opencode/tasks/main.yml +++ b/roles/vhosts/acp_server_opencode/tasks/main.yml @@ -2,7 +2,6 @@ - name: Install OpenCode ACP prerequisites ansible.builtin.import_tasks: install.yml tags: [acp_opencode, acp_opencode_install] - when: ansible_os_family != 'Darwin' - name: Configure OpenCode ACP service and Caddy ansible.builtin.import_tasks: config.yml @@ -17,4 +16,3 @@ tags: [acp_opencode, acp_opencode_validate] when: - not ansible_check_mode - - ansible_os_family != 'Darwin' diff --git a/roles/vhosts/common/defaults/main.yml b/roles/vhosts/common/defaults/main.yml index ab58e9e..3531384 100644 --- a/roles/vhosts/common/defaults/main.yml +++ b/roles/vhosts/common/defaults/main.yml @@ -19,6 +19,12 @@ journald_log_rotation: # 启用 journald 日志管理 # 总开关 enable_common: true +# macOS (Darwin) baseline: shared Homebrew CLI prerequisites used by helper +# scripts across roles (e.g. jq is required by vault's init_vault_admin.sh). +# macOS ships curl/base64 already; jq is not present by default. +common_darwin_brew_packages: + - jq + common_firewall: enabled: true ssh_port: 22 diff --git a/roles/vhosts/common/tasks/common_darwin.yml b/roles/vhosts/common/tasks/common_darwin.yml new file mode 100644 index 0000000..cb21e63 --- /dev/null +++ b/roles/vhosts/common/tasks/common_darwin.yml @@ -0,0 +1,14 @@ +--- +# macOS (Darwin) baseline: install shared command-line prerequisites that the +# Linux apt/yum baselines provide elsewhere. These tools are required by helper +# scripts in other roles (e.g. vault's init_vault_admin.sh needs jq), so they +# are installed once here in the common role rather than per-role. +# +# Homebrew installs into /opt/homebrew (Apple Silicon) or /usr/local (Intel); +# `creates` is checked against both so the task stays idempotent on either arch. +- name: Common(Darwin) | install Homebrew prerequisites + ansible.builtin.command: "brew install {{ item }}" + args: + creates: "{{ '/opt/homebrew/bin/' ~ item if ansible_machine == 'arm64' else '/usr/local/bin/' ~ item }}" + loop: "{{ common_darwin_brew_packages }}" + changed_when: true diff --git a/roles/vhosts/common/tasks/main.yml b/roles/vhosts/common/tasks/main.yml index bd54941..28eca62 100644 --- a/roles/vhosts/common/tasks/main.yml +++ b/roles/vhosts/common/tasks/main.yml @@ -1,9 +1,16 @@ --- # ===== Base system (always) ===== +# The Base hardening tasks below set timezone (timedatectl), rewrite +# /etc/hostname + /etc/hosts, set the hostname, harden ssh, configure fail2ban, +# raise file limits and open firewall ports. They all run with become: true and +# rely on Linux-only tooling/paths, so they are skipped on macOS (Darwin), where +# the deploy runs unprivileged. macOS prerequisites are handled by +# common_darwin.yml instead. - name: Base | set timezone ansible.builtin.command: "timedatectl set-timezone Asia/Shanghai" changed_when: false become: true + when: ansible_os_family != 'Darwin' - name: Base | render /etc/hostname ansible.builtin.template: @@ -13,11 +20,13 @@ group: root mode: "0644" become: true + when: ansible_os_family != 'Darwin' - name: Base | set hostname ansible.builtin.hostname: name: "{{ inventory_hostname }}" become: true + when: ansible_os_family != 'Darwin' - name: Base | update /etc/hosts ansible.builtin.template: @@ -27,29 +36,35 @@ group: root mode: "0644" become: true + when: ansible_os_family != 'Darwin' - name: Base | harden ssh ansible.builtin.script: files/secure_ssh.sh become: true + when: ansible_os_family != 'Darwin' - name: Base | harden ssh config ansible.builtin.import_tasks: harden_ssh.yml tags: [ssh, security] + when: ansible_os_family != 'Darwin' - name: Base | configure fail2ban ansible.builtin.import_tasks: fail2ban.yml tags: [fail2ban, security] + when: ansible_os_family != 'Darwin' - name: Base | file limits ansible.builtin.import_tasks: limits.yml when: - common_security_limits.enabled | default(true) | bool + - ansible_os_family != 'Darwin' tags: [limits, baseline] - name: Base | allow HTTP/HTTPS ports ansible.builtin.import_tasks: firewall_ports.yml when: - common_firewall.enabled | default(true) | bool + - ansible_os_family != 'Darwin' tags: [firewall, baseline] # ===== Common baseline (OS split) ===== @@ -65,6 +80,12 @@ - enable_common | bool - ansible_facts.os_family == "RedHat" +- name: Common | Darwin (macOS) baseline + ansible.builtin.import_tasks: common_darwin.yml + when: + - enable_common | bool + - ansible_facts.os_family == "Darwin" + # ===== Add-ons (default OFF) ===== - name: Addon | S3FS mount ansible.builtin.import_tasks: addons/s3fs.yml diff --git a/roles/vhosts/gateway_openclaw/defaults/main.yml b/roles/vhosts/gateway_openclaw/defaults/main.yml index 113e3d1..64bea60 100644 --- a/roles/vhosts/gateway_openclaw/defaults/main.yml +++ b/roles/vhosts/gateway_openclaw/defaults/main.yml @@ -37,7 +37,7 @@ gateway_openclaw_config_path: "{{ gateway_openclaw_home }}/.openclaw/openclaw.js gateway_openclaw_workspace_path: "{{ gateway_openclaw_home }}/.openclaw/workspace" gateway_openclaw_workspace_mode: "0775" gateway_openclaw_compile_cache_dir: /var/tmp/openclaw-compile-cache -gateway_openclaw_service_path: "{{ gateway_openclaw_home }}/.nix-profile/bin:{{ gateway_openclaw_home }}/.local/bin:{{ gateway_openclaw_home }}/.npm-global/bin:{{ gateway_openclaw_home }}/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin" +gateway_openclaw_service_path: "{{ gateway_openclaw_home }}/.nix-profile/bin:{{ gateway_openclaw_home }}/.local/bin:{{ gateway_openclaw_home }}/.npm-global/bin:{{ gateway_openclaw_home }}/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin" gateway_openclaw_extension_backup_dir: "{{ gateway_openclaw_home }}/.openclaw/backups/extensions" gateway_openclaw_upstream_host: 127.0.0.1 diff --git a/roles/vhosts/gateway_openclaw/tasks/main.yml b/roles/vhosts/gateway_openclaw/tasks/main.yml index ea1d12d..cb04511 100644 --- a/roles/vhosts/gateway_openclaw/tasks/main.yml +++ b/roles/vhosts/gateway_openclaw/tasks/main.yml @@ -18,11 +18,11 @@ {{ gateway_openclaw_gateway_token if (gateway_openclaw_gateway_token | trim | length > 0) - else (( + else ( ( gateway_openclaw_existing_config_raw.content | default('') | b64decode | from_json ).gateway.auth.token | default('') - ) if (gateway_openclaw_existing_config_raw.content | default('') | length > 0) else '') + ) }} no_log: true @@ -79,7 +79,7 @@ PATH: "{{ gateway_openclaw_service_path }}" OPENCLAW_NO_RESPAWN: "1" NODE_COMPILE_CACHE: "{{ gateway_openclaw_compile_cache_dir }}" - become: true + become: "{{ ansible_os_family != 'Darwin' }}" become_user: "{{ gateway_openclaw_service_user }}" register: gateway_openclaw_package_install changed_when: >- @@ -150,10 +150,6 @@ ansible.builtin.file: path: "{{ gateway_openclaw_compile_cache_dir }}" state: absent - register: gateway_openclaw_compile_cache_reset - retries: 5 - delay: 1 - until: gateway_openclaw_compile_cache_reset is succeeded when: - not ansible_check_mode - >- @@ -569,7 +565,6 @@ owner: root group: root mode: "0755" - when: ansible_os_family != 'Darwin' - name: Inspect Caddy main file attributes ansible.builtin.command: @@ -597,7 +592,6 @@ group: root mode: "0644" state: present - when: ansible_os_family != 'Darwin' notify: Reload caddy - name: Restore immutable flag on Caddy main file @@ -632,18 +626,14 @@ owner: root group: root mode: "0644" - when: - - ansible_os_family != 'Darwin' - - gateway_openclaw_public_access | bool + when: gateway_openclaw_public_access | bool notify: Reload caddy - name: Remove OpenClaw public Caddy site when public access is disabled ansible.builtin.file: path: "{{ gateway_openclaw_caddy_fragment_path }}" state: absent - when: - - ansible_os_family != 'Darwin' - - not (gateway_openclaw_public_access | bool) + when: not (gateway_openclaw_public_access | bool) notify: Reload caddy - name: Restore immutable flag on OpenClaw Caddy fragment diff --git a/roles/vhosts/litellm/tasks/main.yml b/roles/vhosts/litellm/tasks/main.yml index 1809198..fe6357c 100644 --- a/roles/vhosts/litellm/tasks/main.yml +++ b/roles/vhosts/litellm/tasks/main.yml @@ -12,9 +12,18 @@ when: ansible_os_family != 'Darwin' - name: Install LiteLLM prerequisites (macOS) - community.general.homebrew: - name: python@3.13 - state: present + # Use brew from PATH (Apple Silicon prefix first) instead of the + # community.general.homebrew module, which can select a stale Intel Homebrew + # at /usr/local that crashes on newer macOS versions. See postgres macos.yml. + ansible.builtin.command: brew install python@3.13 + environment: + PATH: "/opt/homebrew/bin:/usr/local/bin:{{ ansible_env.PATH }}" + HOMEBREW_NO_AUTO_UPDATE: "1" + register: litellm_brew_python + changed_when: >- + 'already installed' not in (litellm_brew_python.stderr | default('')) + and 'already installed' not in (litellm_brew_python.stdout | default('')) + failed_when: litellm_brew_python.rc != 0 when: ansible_os_family == 'Darwin' - name: Materialize persisted LiteLLM secrets @@ -68,8 +77,8 @@ ansible.builtin.file: path: "{{ litellm_config_dir }}" state: directory - owner: root - group: root + owner: "{{ litellm_service_user if ansible_os_family == 'Darwin' else 'root' }}" + group: "{{ litellm_service_group if ansible_os_family == 'Darwin' else 'root' }}" mode: "0755" - name: Provision LiteLLM Database @@ -89,8 +98,8 @@ ansible.builtin.template: src: litellm.env.j2 dest: "{{ litellm_env_file }}" - owner: root - group: root + owner: "{{ litellm_service_user if ansible_os_family == 'Darwin' else 'root' }}" + group: "{{ litellm_service_group if ansible_os_family == 'Darwin' else 'root' }}" mode: "0600" notify: Restart litellm diff --git a/roles/vhosts/litellm/tasks/provision-database.yml b/roles/vhosts/litellm/tasks/provision-database.yml index e82b13f..c8f3552 100644 --- a/roles/vhosts/litellm/tasks/provision-database.yml +++ b/roles/vhosts/litellm/tasks/provision-database.yml @@ -25,7 +25,11 @@ {% endif %} args: executable: /bin/bash - become: true + environment: + PATH: "/opt/homebrew/opt/postgresql@16/bin:/usr/local/opt/postgresql@16/bin:{{ ansible_env.PATH }}" + # On macOS Homebrew there is no `postgres` system user and no passwordless + # sudo; the current user is the DB superuser, so run psql without escalation. + become: "{{ ansible_os_family != 'Darwin' }}" become_user: "{{ 'root' if litellm_database_provisioner == 'docker' else 'postgres' }}" register: create_user_result changed_when: "'CREATE ROLE' in create_user_result.stdout | default('')" @@ -42,7 +46,11 @@ {% endif %} args: executable: /bin/bash - become: true + environment: + PATH: "/opt/homebrew/opt/postgresql@16/bin:/usr/local/opt/postgresql@16/bin:{{ ansible_env.PATH }}" + # On macOS Homebrew there is no `postgres` system user and no passwordless + # sudo; the current user is the DB superuser, so run psql without escalation. + become: "{{ ansible_os_family != 'Darwin' }}" become_user: "{{ 'root' if litellm_database_provisioner == 'docker' else 'postgres' }}" register: create_db_result changed_when: "'CREATE DATABASE' in create_db_result.stdout | default('')" @@ -57,7 +65,11 @@ {% endif %} args: executable: /bin/bash - become: true + environment: + PATH: "/opt/homebrew/opt/postgresql@16/bin:/usr/local/opt/postgresql@16/bin:{{ ansible_env.PATH }}" + # On macOS Homebrew there is no `postgres` system user and no passwordless + # sudo; the current user is the DB superuser, so run psql without escalation. + become: "{{ ansible_os_family != 'Darwin' }}" become_user: "{{ 'root' if litellm_database_provisioner == 'docker' else 'postgres' }}" changed_when: false no_log: true @@ -80,7 +92,11 @@ SQL args: executable: /bin/bash - become: true + environment: + PATH: "/opt/homebrew/opt/postgresql@16/bin:/usr/local/opt/postgresql@16/bin:{{ ansible_env.PATH }}" + # On macOS Homebrew there is no `postgres` system user and no passwordless + # sudo; the current user is the DB superuser, so run psql without escalation. + become: "{{ ansible_os_family != 'Darwin' }}" become_user: "{{ 'root' if litellm_database_provisioner == 'docker' else 'postgres' }}" changed_when: false no_log: true diff --git a/roles/vhosts/postgres/meta/main.yml b/roles/vhosts/postgres/meta/main.yml index 38fde8f..9711b33 100644 --- a/roles/vhosts/postgres/meta/main.yml +++ b/roles/vhosts/postgres/meta/main.yml @@ -1,3 +1,2 @@ dependencies: - role: common - when: ansible_os_family != 'Darwin' diff --git a/roles/vhosts/postgres/tasks/macos.yml b/roles/vhosts/postgres/tasks/macos.yml index 9318ee4..1971d6e 100644 --- a/roles/vhosts/postgres/tasks/macos.yml +++ b/roles/vhosts/postgres/tasks/macos.yml @@ -1,8 +1,19 @@ --- - name: Ensure PostgreSQL 16 is installed via Homebrew - community.general.homebrew: - name: postgresql@16 - state: present + # Use brew from PATH (like the vault/openclaw roles) rather than the + # community.general.homebrew module, which auto-detects a brew prefix and can + # pick a stale Intel Homebrew at /usr/local that crashes on newer macOS + # versions ("unknown or unsupported macOS version"). Prepending the Apple + # Silicon prefix selects the working brew when both are installed. + ansible.builtin.command: brew install postgresql@16 + environment: + PATH: "/opt/homebrew/bin:/usr/local/bin:{{ ansible_env.PATH }}" + HOMEBREW_NO_AUTO_UPDATE: "1" + register: postgresql_brew_install + changed_when: >- + 'already installed' not in (postgresql_brew_install.stderr | default('')) + and 'already installed' not in (postgresql_brew_install.stdout | default('')) + failed_when: postgresql_brew_install.rc != 0 - name: Start PostgreSQL via Homebrew Services ansible.builtin.command: brew services start postgresql@16 diff --git a/roles/vhosts/vault/files/init_vault_admin.sh b/roles/vhosts/vault/files/init_vault_admin.sh index cf9b4c7..d1f0dd0 100755 --- a/roles/vhosts/vault/files/init_vault_admin.sh +++ b/roles/vhosts/vault/files/init_vault_admin.sh @@ -139,21 +139,6 @@ vault write "auth/userpass/users/${USERNAME}" \ userpass_accessor="$(vault auth list -format=json | jq -r '."userpass/".accessor')" -entity_id="" -alias_ids_json="$(vault list -format=json identity/entity-alias/id 2>/dev/null || true)" -if [[ -n "$alias_ids_json" && "$alias_ids_json" != "null" ]]; then - while IFS= read -r alias_id; do - alias_json="$(vault read -format=json "identity/entity-alias/id/${alias_id}" 2>/dev/null || true)" - if [[ -z "$alias_json" ]]; then - continue - fi - if printf '%s' "$alias_json" | jq -e --arg username "$USERNAME" --arg accessor "$userpass_accessor" '.data.name == $username and .data.mount_accessor == $accessor' >/dev/null; then - entity_id="$(printf '%s' "$alias_json" | jq -r '.data.canonical_id')" - break - fi - done < <(printf '%s' "$alias_ids_json" | jq -r '.[]?') -fi - methods_json="$(curl -sS \ -H "X-Vault-Token: ${VAULT_TOKEN}" \ -H "X-Vault-Request: true" \ @@ -173,16 +158,29 @@ if [[ -z "$method_id" ]]; then method_id="$(printf '%s' "$method_json" | jq -r '.data.method_id // .data.id')" fi -bootstrap_token="" -if [[ -z "$entity_id" || "$entity_id" == "null" ]]; then - bootstrap_json="$(vault write -format=json "auth/userpass/login/${USERNAME}" password="$PASSWORD")" - entity_id="$(printf '%s' "$bootstrap_json" | jq -r '.auth.entity_id')" - bootstrap_token="$(printf '%s' "$bootstrap_json" | jq -r '.auth.client_token')" -fi +# Resolve the admin's identity entity WITHOUT logging in. Once the login MFA +# enforcement below exists, a userpass login is MFA-gated and returns no +# entity_id (causing "missing entityID" on every re-run). Instead look up the +# entity via its userpass entity-alias, creating the entity + alias on first run. +entity_id="" +for alias_id in $(vault list -format=json identity/entity-alias/id 2>/dev/null | jq -r '.[]?'); do + alias_json="$(vault read -format=json "identity/entity-alias/id/${alias_id}" 2>/dev/null || true)" + alias_name="$(printf '%s' "$alias_json" | jq -r '.data.name // empty')" + alias_mount="$(printf '%s' "$alias_json" | jq -r '.data.mount_accessor // empty')" + if [[ "$alias_name" == "$USERNAME" && "$alias_mount" == "$userpass_accessor" ]]; then + entity_id="$(printf '%s' "$alias_json" | jq -r '.data.canonical_id // empty')" + break + fi +done -if [[ -z "$entity_id" || "$entity_id" == "null" ]]; then - echo "unable to resolve Vault entity for userpass user ${USERNAME}" >&2 - exit 1 +if [[ -z "$entity_id" ]]; then + entity_id="$(vault write -format=json identity/entity \ + name="$USERNAME" \ + policies="$POLICY_NAME" | jq -r '.data.id')" + vault write identity/entity-alias \ + name="$USERNAME" \ + canonical_id="$entity_id" \ + mount_accessor="$userpass_accessor" >/dev/null fi mkdir -p "$OUTPUT_DIR" @@ -206,9 +204,6 @@ vault write "identity/mfa/login-enforcement/${ENFORCEMENT_NAME}" \ mfa_method_ids="$method_id" \ auth_method_accessors="$userpass_accessor" >/dev/null -if [[ -n "$bootstrap_token" && "$bootstrap_token" != "null" ]]; then - vault token revoke "$bootstrap_token" >/dev/null || true -fi cat < 0) and (vault_admin_password | trim | length > 0) }}" diff --git a/roles/vhosts/xworkmate_bridge/defaults/main.yml b/roles/vhosts/xworkmate_bridge/defaults/main.yml index 37f88a7..721ab28 100644 --- a/roles/vhosts/xworkmate_bridge/defaults/main.yml +++ b/roles/vhosts/xworkmate_bridge/defaults/main.yml @@ -9,7 +9,7 @@ xworkmate_bridge_review_auth_token: "{{ lookup('ansible.builtin.env', 'BRIDGE_RE xworkmate_bridge_listen_host: 127.0.0.1 xworkmate_bridge_listen_port: 8787 xworkmate_bridge_listen_addr: "{{ xworkmate_bridge_listen_host }}:{{ xworkmate_bridge_listen_port }}" -xworkmate_bridge_base_dir: "{{ xworkmate_bridge_service_home + '/.local/state/xworkmate-bridge' if ansible_os_family == 'Darwin' else '/opt/cloud-neutral/xworkmate-bridge' }}" +xworkmate_bridge_base_dir: /opt/cloud-neutral/xworkmate-bridge xworkmate_bridge_config_file: "{{ xworkmate_bridge_base_dir }}/config.yaml" xworkmate_bridge_binary_path: /usr/local/bin/xworkmate-go-core xworkmate_bridge_systemd_unit_path: "/etc/systemd/system/{{ xworkmate_bridge_service_name }}.service" diff --git a/roles/vhosts/xworkmate_bridge/tasks/main.yml b/roles/vhosts/xworkmate_bridge/tasks/main.yml index 9d8d1ce..755629a 100644 --- a/roles/vhosts/xworkmate_bridge/tasks/main.yml +++ b/roles/vhosts/xworkmate_bridge/tasks/main.yml @@ -9,7 +9,6 @@ ansible.builtin.group: name: "{{ xworkmate_bridge_service_group }}" state: present - when: ansible_os_family != 'Darwin' - name: Ensure xworkmate-bridge service user exists ansible.builtin.user: @@ -18,7 +17,6 @@ shell: /bin/bash create_home: true state: present - when: ansible_os_family != 'Darwin' - name: Ensure xworkmate-bridge base directory exists ansible.builtin.file: @@ -123,14 +121,12 @@ ansible.builtin.file: path: "{{ xworkmate_bridge_deprecated_compose_file }}" state: absent - when: ansible_os_family != 'Darwin' - name: Remove obsolete xworkmate-bridge systemd drop-ins ansible.builtin.file: path: "{{ item }}" state: absent loop: "{{ xworkmate_bridge_obsolete_systemd_dropin_paths }}" - when: ansible_os_family != 'Darwin' notify: Reload bridge - name: Disable and stop obsolete user-level xworkmate-serve service @@ -148,7 +144,6 @@ ansible.builtin.file: path: "/home/{{ xworkmate_bridge_service_user }}/.config/systemd/user/xworkmate-serve.service" state: absent - when: ansible_os_family != 'Darwin' - name: Inspect xworkmate-bridge config file attributes ansible.builtin.command: @@ -235,7 +230,6 @@ owner: root group: root mode: "0755" - when: ansible_os_family != 'Darwin' - name: Inspect Caddy main file attributes ansible.builtin.command: @@ -263,7 +257,6 @@ group: root mode: "0644" state: present - when: ansible_os_family != 'Darwin' notify: Reload caddy - name: Restore immutable flag on Caddy main file @@ -297,18 +290,14 @@ owner: root group: root mode: "0644" - when: - - ansible_os_family != 'Darwin' - - xworkmate_bridge_public_access | bool + when: xworkmate_bridge_public_access | bool notify: Reload caddy - name: Remove xworkmate-bridge public Caddy site when public access is disabled ansible.builtin.file: path: "{{ xworkmate_bridge_service_caddy_fragment_path }}" state: absent - when: - - ansible_os_family != 'Darwin' - - not (xworkmate_bridge_public_access | bool) + when: not (xworkmate_bridge_public_access | bool) notify: Reload caddy - name: Restore immutable flag on xworkmate-bridge Caddy fragment @@ -345,7 +334,6 @@ path: "{{ item }}" state: absent loop: "{{ xworkmate_bridge_obsolete_caddy_fragment_paths }}" - when: ansible_os_family != 'Darwin' notify: Reload caddy - name: Ensure xworkmate-bridge service is enabled and running @@ -374,7 +362,6 @@ tags: [xworkmate_bridge, xworkmate_bridge_validate] when: - not ansible_check_mode - - ansible_os_family != 'Darwin' - name: Import macOS specific xworkmate-bridge tasks ansible.builtin.import_tasks: macos.yml diff --git a/setup-ai-workspace-runtime.yml b/setup-ai-workspace-runtime.yml index 86462f4..c198516 100644 --- a/setup-ai-workspace-runtime.yml +++ b/setup-ai-workspace-runtime.yml @@ -27,4 +27,3 @@ gather_facts: false roles: - role: roles/vhosts/validation - when: ansible_os_family | default("") != "Darwin" diff --git a/setup-xfce-xrdp.yaml b/setup-xfce-xrdp.yaml index d217168..1304426 100644 --- a/setup-xfce-xrdp.yaml +++ b/setup-xfce-xrdp.yaml @@ -12,7 +12,6 @@ - name: Include XFCE desktop runtime role ansible.builtin.include_role: name: roles/vhosts/xfce_desktop_minimal_runtime - when: ansible_os_family != "Darwin" - name: Include XRDP server role when enabled ansible.builtin.include_role: @@ -24,6 +23,4 @@ xfce_user_groups: - sudo - docker - when: - - ansible_os_family != "Darwin" - - xworkspace_console_enable_xrdp | bool + when: xworkspace_console_enable_xrdp | bool diff --git a/setup-xworkspace-console.yaml b/setup-xworkspace-console.yaml index 92fb53a..60c7247 100644 --- a/setup-xworkspace-console.yaml +++ b/setup-xworkspace-console.yaml @@ -576,13 +576,22 @@ - xworkspace_console_runtime_archive | length == 0 - xworkspace_console_source_repo_safe_dir | length > 0 + - name: Remove stale xworkspace-console repo to avoid shallow checkout failures + ansible.builtin.file: + path: "{{ xworkspace_console_repo_dir }}" + state: absent + become_user: "{{ xworkspace_console_user }}" + when: + - xworkspace_console_runtime_archive | length == 0 + - xworkspace_console_source_version == 'main' or xworkspace_console_source_version == 'HEAD' + - name: Clone xworkspace-console repository ansible.builtin.git: repo: "{{ xworkspace_console_source_repo }}" dest: "{{ xworkspace_console_repo_dir }}" version: "{{ xworkspace_console_source_version }}" - depth: 1 force: true + depth: 1 become_user: "{{ xworkspace_console_user }}" register: xworkspace_console_source_checkout when: xworkspace_console_runtime_archive | length == 0