feat: full macOS (Darwin) compatibility fixes for Ansible playbooks

This commit is contained in:
Haitao Pan 2026-06-18 16:26:51 +08:00
parent c7784f2063
commit 044a264256
31 changed files with 670 additions and 74 deletions

27
api.plist.j2 Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>plus.svc.xworkspace.api</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
source "{{ xworkspace_console_config_dir }}/portal.env"
exec {{ xworkspace_console_api_exec }}
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ xworkspace_console_api_working_dir }}</string>
<key>StandardOutPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/api.log</string>
<key>StandardErrorPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/api.err.log</string>
</dict>
</plist>

27
console.plist.j2 Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>plus.svc.xworkspace.console</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
exec /usr/bin/env npm run preview -- --host 127.0.0.1 --port {{ xworkspace_console_port }}
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ xworkspace_console_dashboard_dir }}</string>
<key>StandardOutPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/console.log</string>
<key>StandardErrorPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/console.err.log</string>
</dict>
</plist>

View File

@ -9,3 +9,4 @@
DEBIAN_FRONTEND: noninteractive
APT_LISTCHANGES_FRONTEND: none
become: true
when: ansible_os_family != 'Darwin'

View File

@ -9,3 +9,4 @@
DEBIAN_FRONTEND: noninteractive
APT_LISTCHANGES_FRONTEND: none
become: true
when: ansible_os_family != 'Darwin'

View File

@ -1,9 +1,9 @@
---
- name: Assert AI agent runtime is only supported on Debian family
- name: Assert AI agent runtime is supported on Debian or Darwin family
ansible.builtin.assert:
that:
- ansible_facts.os_family == "Debian"
fail_msg: "roles/ai_agent_runtime currently supports Debian-based hosts only."
- ansible_facts.os_family in ["Debian", "Darwin"]
fail_msg: "roles/ai_agent_runtime currently supports Debian-based and Darwin hosts only."
- name: Install AI agent runtime base packages
ansible.builtin.apt:
@ -15,6 +15,7 @@
DEBIAN_FRONTEND: noninteractive
APT_LISTCHANGES_FRONTEND: none
become: true
when: ansible_os_family != 'Darwin'
- name: Configure Node.js runtime
ansible.builtin.include_tasks: nodejs.yml

View File

@ -7,18 +7,25 @@
install_yarn: "{{ ai_agent_runtime_install_yarn }}"
yarn_version: "{{ ai_agent_runtime_yarn_version }}"
- name: Ensure user local bin directory exists on macOS
ansible.builtin.file:
path: "{{ ansible_env.HOME }}/.local/bin"
state: directory
mode: "0755"
when: ansible_os_family == 'Darwin'
- name: Install npm global package manager helper
ansible.builtin.copy:
src: manage_npm_global_package.sh
dest: /usr/local/sbin/ai-workspace-manage-npm-global-package
owner: root
group: root
dest: "{{ '/usr/local/sbin' if ansible_os_family != 'Darwin' else ansible_env.HOME + '/.local/bin' }}/ai-workspace-manage-npm-global-package"
owner: "{{ 'root' if ansible_os_family != 'Darwin' else xworkspace_console_user }}"
group: "{{ 'root' if ansible_os_family != 'Darwin' else ('staff' if ansible_os_family == 'Darwin' else xworkspace_console_user) }}"
mode: "0755"
become: true
become: "{{ ansible_os_family != 'Darwin' }}"
- name: Install global npm packages for AI runtime
ansible.builtin.command:
cmd: "/usr/local/sbin/ai-workspace-manage-npm-global-package {{ ai_agent_runtime_npm_global_package_action }} {{ item }}"
cmd: "{{ '/usr/local/sbin' if ansible_os_family != 'Darwin' else ansible_env.HOME + '/.local/bin' }}/ai-workspace-manage-npm-global-package {{ ai_agent_runtime_npm_global_package_action }} {{ item }}"
loop: "{{ ai_agent_runtime_npm_global_packages }}"
register: ai_agent_runtime_npm_global_install
changed_when: "'changed=1' in ai_agent_runtime_npm_global_install.stdout"
@ -26,7 +33,7 @@
- name: Install pinned Playwright package for AI runtime
ansible.builtin.command:
cmd: "/usr/local/sbin/ai-workspace-manage-npm-global-package {{ ai_agent_runtime_npm_global_package_action }} playwright@{{ ai_agent_runtime_playwright_version }}"
cmd: "{{ '/usr/local/sbin' if ansible_os_family != 'Darwin' else ansible_env.HOME + '/.local/bin' }}/ai-workspace-manage-npm-global-package {{ ai_agent_runtime_npm_global_package_action }} playwright@{{ ai_agent_runtime_playwright_version }}"
register: ai_agent_runtime_playwright_install
changed_when: "'changed=1' in ai_agent_runtime_playwright_install.stdout"
when:

View File

@ -12,7 +12,7 @@ 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"
acp_gemini_bridge_local_binary_path: "{{ acp_gemini_bridge_local_build_dir }}/xworkmate-go-core"
acp_gemini_bridge_build_goos: linux
acp_gemini_bridge_build_goos: "{{ 'darwin' if ansible_os_family == 'Darwin' else 'linux' }}"
acp_gemini_bridge_build_goarch: "{{ 'arm64' if ansible_architecture in ['aarch64', 'arm64'] else 'amd64' }}"
acp_gemini_bridge_binary_path: /usr/local/bin/xworkmate-go-core
acp_gemini_bridge_use_prebuilt: "{{ lookup('ansible.builtin.env', 'AI_WORKSPACE_USE_PREBUILT_BRIDGE') | default('false', true) | bool }}"

View File

@ -1,8 +1,23 @@
---
- name: Reload caddy
ansible.builtin.service:
name: caddy
state: reloaded
when: ansible_os_family != 'Darwin'
- name: Restart acp gemini
ansible.builtin.systemd:
ansible.builtin.service:
name: "{{ acp_gemini_service_name }}"
state: restarted
daemon_reload: true
when:
- not ansible_check_mode
when: ansible_os_family != 'Darwin'
- name: Restart acp gemini on macOS
ansible.builtin.command: "launchctl stop plus.svc.xworkspace.acp.gemini"
register: launchctl_stop
failed_when: false
changed_when: false
notify: Start acp gemini on macOS
- name: Start acp gemini on macOS
ansible.builtin.command: "launchctl start plus.svc.xworkspace.acp.gemini"
changed_when: false

View File

@ -33,6 +33,7 @@
ansible.builtin.command:
cmd: chattr -i "{{ acp_gemini_bridge_binary_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (acp_gemini_bridge_binary_attrs.stdout | default(''))"
changed_when: true
become: true
@ -51,50 +52,70 @@
ansible.builtin.command:
cmd: chattr +i "{{ acp_gemini_bridge_binary_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (acp_gemini_bridge_binary_attrs.stdout | default(''))"
changed_when: true
become: true
- name: Deploy Gemini ACP adapter service
- name: Deploy Gemini ACP systemd service
ansible.builtin.command:
cmd: lsattr "/etc/systemd/system/{{ acp_gemini_service_name }}.service"
register: acp_gemini_service_attrs
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from Gemini ACP systemd service when present
ansible.builtin.command:
cmd: chattr -i "/etc/systemd/system/{{ acp_gemini_service_name }}.service"
when:
- ansible_os_family != 'Darwin'
- "'i' in (acp_gemini_service_attrs.stdout | default(''))"
changed_when: true
become: true
- name: Deploy Gemini ACP adapter service
- name: Deploy Gemini ACP systemd service
ansible.builtin.template:
src: gemini-acp-adapter.service.j2
src: gemini-acp.service.j2
dest: "/etc/systemd/system/{{ acp_gemini_service_name }}.service"
owner: root
group: root
mode: "0644"
notify: Restart acp gemini
when: ansible_os_family != 'Darwin'
- name: Restore immutable flag on Gemini ACP systemd service
ansible.builtin.command:
cmd: chattr +i "/etc/systemd/system/{{ acp_gemini_service_name }}.service"
when:
- ansible_os_family != 'Darwin'
- "'i' in (acp_gemini_service_attrs.stdout | default(''))"
changed_when: true
become: true
- name: Reload systemd manager configuration for Gemini ACP
- name: Reload systemd manager configuration
ansible.builtin.systemd:
daemon_reload: true
when: ansible_os_family != 'Darwin'
- name: Ensure Gemini ACP adapter service is enabled and running
- name: Ensure Caddy is enabled and running
ansible.builtin.systemd:
name: caddy
enabled: true
state: started
when:
- ansible_os_family != 'Darwin'
- acp_gemini_manage_caddy | bool
- name: Ensure Gemini ACP service is enabled and running
ansible.builtin.systemd:
name: "{{ acp_gemini_service_name }}"
enabled: true
state: started
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Import macOS specific Gemini ACP tasks
ansible.builtin.import_tasks: macos.yml
when: ansible_os_family == 'Darwin'

View File

@ -0,0 +1,13 @@
---
- name: Create launchd plist template for Gemini ACP
ansible.builtin.template:
src: gemini.plist.j2
dest: "{{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.acp.gemini.plist"
mode: "0644"
notify: Restart acp gemini on macOS
- name: Reload launchd agent for Gemini ACP
ansible.builtin.command: "launchctl load -w {{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.acp.gemini.plist"
register: launchctl_result
changed_when: false
failed_when: launchctl_result.rc != 0 and 'already loaded' not in launchctl_result.stderr

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>plus.svc.xworkspace.acp.gemini</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
export PATH="{{ acp_gemini_path }}"
export LITELLM_MASTER_KEY="{{ acp_gemini_auth_token }}"
{% for key, value in acp_gemini_environment.items() %}
export {{ key }}="{{ value }}"
{% endfor %}
exec "{{ acp_gemini_bridge_binary_path }}" acp-server \
--port {{ acp_gemini_listen_port }} \
--host {{ acp_gemini_listen_host }} \
--sub-command gemini \
--sub-command mcp-app-server
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ acp_gemini_workdir }}</string>
<key>StandardOutPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/acp.gemini.log</string>
<key>StandardErrorPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/acp.gemini.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>{{ acp_gemini_workdir }}</string>
</dict>
</dict>
</plist>

View File

@ -4,7 +4,7 @@ acp_hermes_version: "0.15"
acp_hermes_bridge_local_source_dir: "{{ playbook_dir }}/../xworkmate-bridge"
acp_hermes_bridge_local_build_dir: "{{ playbook_dir }}/.artifacts/acp_hermes"
acp_hermes_bridge_local_binary_path: "{{ acp_hermes_bridge_local_build_dir }}/xworkmate-go-core"
acp_hermes_bridge_build_goos: linux
acp_hermes_bridge_build_goos: "{{ 'darwin' if ansible_os_family == 'Darwin' else 'linux' }}"
acp_hermes_bridge_build_goarch: "{{ 'arm64' if ansible_architecture in ['aarch64', 'arm64'] else 'amd64' }}"
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

View File

@ -1,8 +1,23 @@
---
- name: Reload caddy
ansible.builtin.service:
name: caddy
state: reloaded
when: ansible_os_family != 'Darwin'
- name: Restart acp hermes
ansible.builtin.systemd:
ansible.builtin.service:
name: "{{ acp_hermes_service_name }}"
state: restarted
daemon_reload: true
when:
- not ansible_check_mode
when: ansible_os_family != 'Darwin'
- name: Restart acp hermes on macOS
ansible.builtin.command: "launchctl stop plus.svc.xworkspace.acp.hermes"
register: launchctl_stop
failed_when: false
changed_when: false
notify: Start acp hermes on macOS
- name: Start acp hermes on macOS
ansible.builtin.command: "launchctl start plus.svc.xworkspace.acp.hermes"
changed_when: false

View File

@ -34,6 +34,7 @@
ansible.builtin.command:
cmd: chattr -i "{{ acp_hermes_bridge_binary_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (acp_hermes_bridge_binary_attrs.stdout | default(''))"
changed_when: true
become: true
@ -52,6 +53,7 @@
ansible.builtin.command:
cmd: chattr +i "{{ acp_hermes_bridge_binary_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (acp_hermes_bridge_binary_attrs.stdout | default(''))"
changed_when: true
become: true
@ -124,11 +126,13 @@
changed_when: false
failed_when: false
check_mode: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from Hermes ACP systemd service when present
ansible.builtin.command:
cmd: chattr -i "/etc/systemd/system/{{ acp_hermes_service_name }}.service"
when:
- ansible_os_family != 'Darwin'
- "'i' in (acp_hermes_service_attrs.stdout | default(''))"
changed_when: true
become: true
@ -147,6 +151,7 @@
failed_when: false
no_log: true
check_mode: false
when: ansible_os_family != 'Darwin'
- name: Resolve Hermes ACP auth token
ansible.builtin.set_fact:
@ -166,11 +171,13 @@
group: root
mode: "0644"
notify: Restart acp hermes
when: ansible_os_family != 'Darwin'
- name: Restore immutable flag on Hermes ACP systemd service
ansible.builtin.command:
cmd: chattr +i "/etc/systemd/system/{{ acp_hermes_service_name }}.service"
when:
- ansible_os_family != 'Darwin'
- "'i' in (acp_hermes_service_attrs.stdout | default(''))"
changed_when: true
become: true
@ -178,6 +185,7 @@
- name: Reload systemd manager configuration for Hermes ACP
ansible.builtin.systemd:
daemon_reload: true
when: ansible_os_family != 'Darwin'
- name: Ensure Hermes ACP adapter service is enabled and running
ansible.builtin.systemd:
@ -186,3 +194,8 @@
state: started
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Import macOS specific Hermes ACP tasks
ansible.builtin.import_tasks: macos.yml
when: ansible_os_family == 'Darwin'

View File

@ -0,0 +1,13 @@
---
- name: Create launchd plist template for Hermes ACP
ansible.builtin.template:
src: hermes.plist.j2
dest: "{{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.acp.hermes.plist"
mode: "0644"
notify: Restart acp hermes on macOS
- name: Reload launchd agent for Hermes ACP
ansible.builtin.command: "launchctl load -w {{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.acp.hermes.plist"
register: launchctl_result
changed_when: false
failed_when: launchctl_result.rc != 0 and 'already loaded' not in launchctl_result.stderr

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>plus.svc.xworkspace.acp.hermes</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
export PATH="{{ acp_hermes_path }}"
export LITELLM_MASTER_KEY="{{ acp_hermes_auth_token }}"
export HERMES_ADAPTER_AUTH_TOKEN="{{ acp_hermes_effective_auth_token }}"
{% for key, value in acp_hermes_environment.items() %}
export {{ key }}="{{ value }}"
{% endfor %}
exec "{{ acp_hermes_bridge_binary_path }}" acp-server \
--port {{ acp_hermes_listen_port }} \
--host {{ acp_hermes_listen_host }} \
--sub-command hermes \
--sub-command mcp-app-server
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ acp_hermes_workdir }}</string>
<key>StandardOutPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/acp.hermes.log</string>
<key>StandardErrorPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/acp.hermes.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>{{ acp_hermes_workdir }}</string>
</dict>
</dict>
</plist>

View File

@ -37,12 +37,14 @@
- gnupg
- ca-certificates
state: present
when: ansible_os_family != 'Darwin'
- name: Ensure apt keyrings directory exists
file:
path: /etc/apt/keyrings
state: directory
mode: '0755'
when: ansible_os_family != 'Darwin'
- name: Remove old NodeSource repository list files
file:
@ -55,6 +57,7 @@
when:
- nodejs_needs_install | default(true)
- not nodejs_offline_active
- ansible_os_family != 'Darwin'
- name: Add NodeSource GPG key
get_url:
@ -65,6 +68,7 @@
when:
- nodejs_needs_install | default(true)
- not nodejs_offline_active
- ansible_os_family != 'Darwin'
- name: Add NodeSource repository
apt_repository:
@ -75,6 +79,7 @@
when:
- nodejs_needs_install | default(true)
- not nodejs_offline_active
- ansible_os_family != 'Darwin'
- name: Update apt cache for NodeSource repository
apt:
@ -84,6 +89,7 @@
- nodejs_needs_install | default(true)
- not nodejs_offline_active
- nodesource_key.changed or nodesource_repo.changed
- ansible_os_family != 'Darwin'
- name: Install Node.js (exact version)
apt:
@ -92,7 +98,9 @@
state: present
allow_downgrade: true
update_cache: yes
when: nodejs_needs_install | default(true)
when:
- nodejs_needs_install | default(true)
- ansible_os_family != 'Darwin'
- name: Verify npm is available
command: npm --version
@ -123,6 +131,7 @@
- install_yarn | default(true)
- yarn_desired_version | length == 0
- not nodejs_offline_active
- ansible_os_family != 'Darwin'
- name: Add Yarn repository
apt_repository:
@ -133,6 +142,7 @@
- install_yarn | default(true)
- yarn_desired_version | length == 0
- not nodejs_offline_active
- ansible_os_family != 'Darwin'
- name: Install Yarn
apt:
@ -142,6 +152,7 @@
when:
- install_yarn | default(true)
- yarn_desired_version | length == 0
- ansible_os_family != 'Darwin'
- name: Enable Corepack
command: corepack enable
@ -160,7 +171,7 @@
- name: Set npm to use version tags by default
shell: npm config set save-exact true
args:
creates: /root/.npmrc
creates: "{{ ansible_env.HOME }}/.npmrc"
- name: Create npm global directory
file:
@ -173,7 +184,9 @@
src: npm_global.sh.j2
dest: /etc/profile.d/npm_global.sh
mode: '0644'
when: add_npm_to_path | default(true)
when:
- add_npm_to_path | default(true)
- ansible_os_family != 'Darwin'
- name: Verify installations
debug:

View File

@ -0,0 +1,11 @@
---
- name: Restart QMD on macOS
ansible.builtin.command: "launchctl stop plus.svc.xworkspace.qmd"
register: launchctl_stop
failed_when: false
changed_when: false
notify: Start QMD on macOS
- name: Start QMD on macOS
ansible.builtin.command: "launchctl start plus.svc.xworkspace.qmd"
changed_when: false

View File

@ -0,0 +1,13 @@
---
- name: Create launchd plist template for QMD
ansible.builtin.template:
src: qmd.plist.j2
dest: "{{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.qmd.plist"
mode: "0644"
notify: Restart QMD on macOS
- name: Reload launchd agent for QMD
ansible.builtin.command: "launchctl load -w {{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.qmd.plist"
register: launchctl_result
changed_when: false
failed_when: launchctl_result.rc != 0 and 'already loaded' not in launchctl_result.stderr

View File

@ -1,9 +1,9 @@
---
- name: Assert QMD is only supported on Debian family
- name: Assert QMD is supported on Debian or Darwin family
ansible.builtin.assert:
that:
- ansible_facts.os_family == "Debian"
fail_msg: "roles/vhosts/qmd currently supports Debian-based hosts only."
- ansible_facts.os_family in ["Debian", "Darwin"]
fail_msg: "roles/vhosts/qmd currently supports Debian-based and Darwin hosts only."
- name: Ensure QMD config and cache directories exist
ansible.builtin.file:
@ -27,10 +27,12 @@
cmd: "id -u {{ qmd_user }}"
register: qmd_service_uid_result
changed_when: false
when: ansible_os_family != 'Darwin'
- name: Set QMD service user uid
ansible.builtin.set_fact:
qmd_service_uid: "{{ qmd_service_uid_result.stdout | trim }}"
when: ansible_os_family != 'Darwin'
- name: Deploy QMD collection index config
ansible.builtin.template:
@ -221,6 +223,7 @@
group: "{{ qmd_group }}"
mode: "0644"
register: qmd_mcp_user_service_unit
when: ansible_os_family != 'Darwin'
- name: Enable QMD service user linger
ansible.builtin.command:
@ -228,6 +231,7 @@
creates: "/var/lib/systemd/linger/{{ qmd_user }}"
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Ensure QMD service user manager is running
ansible.builtin.systemd:
@ -235,6 +239,7 @@
state: started
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Reload QMD user systemd manager
ansible.builtin.command:
@ -248,6 +253,7 @@
changed_when: false
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Ensure QMD MCP daemon is enabled and running
ansible.builtin.command:
@ -267,6 +273,7 @@
'Created symlink' in (qmd_mcp_service_enable.stderr | default(''))
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Restart QMD MCP daemon after unit changes
ansible.builtin.command:
@ -280,6 +287,11 @@
when:
- qmd_mcp_user_service_unit.changed | default(false)
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Import macOS specific QMD tasks
ansible.builtin.import_tasks: macos.yml
when: ansible_os_family == 'Darwin'
- name: Validate QMD status
ansible.builtin.command:

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>plus.svc.xworkspace.qmd</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
source "{{ qmd_env_path }}"
exec "{{ qmd_binary_path }}" mcp --http --port {{ qmd_mcp_port }}
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ qmd_home }}</string>
<key>StandardOutPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/qmd.log</string>
<key>StandardErrorPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/qmd.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>{{ qmd_home }}</string>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:{{ ansible_env.HOME }}/.nvm/versions/node/{{ nodejs_version }}/bin</string>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,30 @@
---
- name: Install HashiCorp Tap
ansible.builtin.command: brew tap hashicorp/tap
changed_when: false
- name: Install Vault via Homebrew
ansible.builtin.command: brew install hashicorp/tap/vault
args:
creates: /opt/homebrew/bin/vault
changed_when: true
- name: Create symlink for Vault binary to match Linux path
ansible.builtin.file:
src: /opt/homebrew/bin/vault
dest: /usr/local/bin/vault
state: link
become: true
ignore_errors: true
- name: Create launchd plist template for Vault
ansible.builtin.template:
src: vault.plist.j2
dest: "{{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.vault.plist"
mode: "0644"
- name: Start Vault on macOS
ansible.builtin.command: "launchctl load -w {{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.vault.plist"
register: launchctl_result
changed_when: false
failed_when: launchctl_result.rc != 0 and 'already loaded' not in launchctl_result.stderr

View File

@ -23,6 +23,7 @@
update_cache: true
when:
- vault_deploy_mode == "standalone"
- ansible_os_family != 'Darwin'
- name: Check standalone Vault binary
ansible.builtin.command: "{{ vault_binary_path }} version"
@ -31,6 +32,7 @@
failed_when: false
when:
- vault_deploy_mode == "standalone"
- ansible_os_family != 'Darwin'
- name: Download standalone Vault release
ansible.builtin.unarchive:
@ -40,6 +42,7 @@
mode: "0755"
when:
- vault_deploy_mode == "standalone"
- ansible_os_family != 'Darwin'
- vault_binary_check.rc != 0 or (vault_binary_check.stdout | default('')) is not search(vault_version)
- name: Ensure standalone Vault directories exist
@ -81,6 +84,7 @@
no_log: true
when:
- vault_deploy_mode == "standalone"
- ansible_os_family != 'Darwin'
- name: Start standalone Vault service
ansible.builtin.systemd:
@ -90,6 +94,11 @@
daemon_reload: true
when:
- vault_deploy_mode == "standalone"
- ansible_os_family != 'Darwin'
- name: Import macOS specific Vault tasks
ansible.builtin.import_tasks: macos.yml
when: ansible_os_family == 'Darwin'
- name: Wait for standalone Vault API
ansible.builtin.uri:

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>plus.svc.xworkspace.vault</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
export VAULT_DEV_ROOT_TOKEN_ID="{{ vault_server_root_access_token }}"
exec "{{ vault_binary_path }}" server -dev -dev-listen-address={{ vault_listen_addr }} -dev-root-token-id={{ vault_server_root_access_token }}
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ vault_data_dir }}</string>
<key>StandardOutPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/vault.log</string>
<key>StandardErrorPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/vault.err.log</string>
</dict>
</plist>

View File

@ -6,6 +6,18 @@
daemon_reload: true
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Restart bridge on macOS
ansible.builtin.command: "launchctl stop plus.svc.xworkspace.bridge"
register: launchctl_stop
failed_when: false
changed_when: false
notify: Start bridge on macOS
- name: Start bridge on macOS
ansible.builtin.command: "launchctl start plus.svc.xworkspace.bridge"
changed_when: false
- name: Reload caddy
ansible.builtin.systemd:
@ -13,3 +25,4 @@
state: reloaded
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'

View File

@ -0,0 +1,13 @@
---
- name: Create launchd plist template for XWorkMate Bridge
ansible.builtin.template:
src: bridge.plist.j2
dest: "{{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.bridge.plist"
mode: "0644"
notify: Restart bridge on macOS
- name: Reload launchd agent for XWorkMate Bridge
ansible.builtin.command: "launchctl load -w {{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.bridge.plist"
register: launchctl_result
changed_when: false
failed_when: launchctl_result.rc != 0 and 'already loaded' not in launchctl_result.stderr

View File

@ -3,6 +3,7 @@
ansible.builtin.package:
name: "{{ xworkmate_bridge_packages }}"
state: present
when: ansible_os_family != 'Darwin'
- name: Ensure xworkmate-bridge service group exists
ansible.builtin.group:
@ -43,6 +44,7 @@
changed_when: false
failed_when: false
no_log: true
when: ansible_os_family != 'Darwin'
- name: Read existing xworkmate-bridge review auth token from systemd units
ansible.builtin.shell: |
@ -62,6 +64,7 @@
changed_when: false
failed_when: false
no_log: true
when: ansible_os_family != 'Darwin'
- name: Resolve xworkmate-bridge auth token
ansible.builtin.set_fact:
@ -112,6 +115,7 @@
failed_when: false
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Remove deprecated Docker bridge compose file
ansible.builtin.file:
@ -134,6 +138,7 @@
failed_when: false
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Remove obsolete user-level xworkmate-serve service file
ansible.builtin.file:
@ -146,11 +151,13 @@
register: xworkmate_bridge_config_attrs
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from xworkmate-bridge config file when present
ansible.builtin.command:
cmd: chattr -i "{{ xworkmate_bridge_config_file }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (xworkmate_bridge_config_attrs.stdout | default(''))"
changed_when: true
@ -169,6 +176,7 @@
changed_when: true
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Inspect xworkmate-bridge systemd unit attributes
ansible.builtin.command:
@ -176,11 +184,13 @@
register: xworkmate_bridge_unit_attrs
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from xworkmate-bridge systemd unit when present
ansible.builtin.command:
cmd: chattr -i "{{ xworkmate_bridge_systemd_unit_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (xworkmate_bridge_unit_attrs.stdout | default(''))"
changed_when: true
@ -195,6 +205,7 @@
no_log: true
register: xworkmate_bridge_systemd_unit
notify: Reload bridge
when: ansible_os_family != 'Darwin'
- name: Restore immutable flag on xworkmate-bridge systemd unit
ansible.builtin.command:
@ -202,6 +213,7 @@
changed_when: true
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Reload systemd after xworkmate-bridge unit changes
ansible.builtin.systemd:
@ -209,6 +221,7 @@
when:
- xworkmate_bridge_systemd_unit.changed | default(false)
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Ensure Caddy fragment directory exists
ansible.builtin.file:
@ -224,11 +237,13 @@
register: xworkmate_bridge_caddyfile_attrs
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from Caddy main file when present
ansible.builtin.command:
cmd: chattr -i "{{ xworkmate_bridge_caddyfile_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (xworkmate_bridge_caddyfile_attrs.stdout | default(''))"
changed_when: true
@ -248,6 +263,7 @@
ansible.builtin.command:
cmd: chattr +i "{{ xworkmate_bridge_caddyfile_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (xworkmate_bridge_caddyfile_attrs.stdout | default(''))"
changed_when: true
@ -257,11 +273,13 @@
register: xworkmate_bridge_site_fragment_attrs
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from xworkmate-bridge Caddy fragment when present
ansible.builtin.command:
cmd: chattr -i "{{ xworkmate_bridge_service_caddy_fragment_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (xworkmate_bridge_site_fragment_attrs.stdout | default(''))"
changed_when: true
@ -288,6 +306,7 @@
changed_when: true
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- xworkmate_bridge_public_access | bool
- name: Inspect deprecated ACP Caddy fragment attributes
@ -297,11 +316,13 @@
changed_when: false
failed_when: false
loop: "{{ xworkmate_bridge_obsolete_caddy_fragment_paths }}"
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from deprecated ACP Caddy fragments when present
ansible.builtin.command:
cmd: chattr -i "{{ item.item }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (item.stdout | default(''))"
changed_when: true
loop: "{{ xworkmate_bridge_obsolete_fragment_attrs.results }}"
@ -322,6 +343,7 @@
state: started
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Ensure Caddy is enabled and running
ansible.builtin.systemd:
@ -330,6 +352,7 @@
state: started
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Apply xworkmate-bridge service and Caddy changes before validation
ansible.builtin.meta: flush_handlers
@ -339,3 +362,7 @@
tags: [xworkmate_bridge, xworkmate_bridge_validate]
when:
- not ansible_check_mode
- name: Import macOS specific xworkmate-bridge tasks
ansible.builtin.import_tasks: macos.yml
when: ansible_os_family == 'Darwin'

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>plus.svc.xworkspace.bridge</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
{% for key, value in xworkmate_bridge_service_environment.items() %}
export {{ key }}="{{ value }}"
{% endfor %}
exec "{{ xworkmate_bridge_binary_path }}" server \
--port {{ xworkmate_bridge_listen_port }} \
--host {{ xworkmate_bridge_listen_host }}
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ xworkmate_bridge_base_dir }}</string>
<key>StandardOutPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/bridge.log</string>
<key>StandardErrorPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/bridge.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>{{ xworkmate_bridge_base_dir }}</string>
</dict>
</dict>
</plist>

View File

@ -10,9 +10,9 @@
xworkspace_console_user: ubuntu
xworkspace_console_public_access: false
xworkspace_console_domain: workspace.svc.plus
xworkspace_console_home: /home/ubuntu
xworkspace_console_root: /home/ubuntu/xworkspace
xworkspace_console_repo_dir: /home/ubuntu/xworkspace-console
xworkspace_console_home: "{{ ansible_env.HOME | default('/home/ubuntu') }}"
xworkspace_console_root: "{{ xworkspace_console_home }}/.local/state/ai-workspace"
xworkspace_console_repo_dir: "{{ xworkspace_console_home }}/xworkspace-console"
xworkspace_console_source_repo: "https://github.com/ai-workspace-lab/xworkspace-console.git"
xworkspace_console_source_version: "main"
xworkspace_console_source_repo_safe_dir: >-
@ -23,16 +23,16 @@
}}
xworkspace_console_runtime_archive: "{{ lookup('ansible.builtin.env', 'XWORKSPACE_CONSOLE_RUNTIME_ARCHIVE') | default('', true) }}"
ai_workspace_prebuilt_components_required: "{{ lookup('ansible.builtin.env', 'AI_WORKSPACE_PREBUILT_COMPONENTS_REQUIRED') | default('false', true) | bool }}"
xworkspace_console_dashboard_dir: /home/ubuntu/xworkspace-console/dashboard
xworkspace_console_api_dir: /home/ubuntu/xworkspace-console/api
xworkspace_console_api_binary: /home/ubuntu/xworkspace-console/bin/xworkspace-api
xworkspace_console_runtime_marker: /home/ubuntu/xworkspace-console/.runtime-archive-sha256
xworkspace_console_dashboard_dir: "{{ xworkspace_console_repo_dir }}/dashboard"
xworkspace_console_api_dir: "{{ xworkspace_console_repo_dir }}/api"
xworkspace_console_api_binary: "{{ xworkspace_console_repo_dir }}/bin/xworkspace-api"
xworkspace_console_runtime_marker: "{{ xworkspace_console_repo_dir }}/.runtime-archive-sha256"
xworkspace_console_api_working_dir: >-
{{ xworkspace_console_repo_dir if xworkspace_console_runtime_archive | length > 0 else xworkspace_console_api_dir }}
xworkspace_console_api_exec: >-
{{ xworkspace_console_api_binary if xworkspace_console_runtime_archive | length > 0 else '/usr/bin/env go run .' }}
xworkspace_console_scripts_dir: /home/ubuntu/xworkspace/scripts
xworkspace_console_config_dir: /home/ubuntu/.config/xworkspace
xworkspace_console_scripts_dir: "{{ xworkspace_console_root }}/scripts"
xworkspace_console_config_dir: "{{ xworkspace_console_home }}/.config/xworkspace"
xworkspace_console_url: http://127.0.0.1:17000
xworkspace_console_port: 17000
xworkspace_console_api_port: 8788
@ -111,6 +111,7 @@
state: present
install_recommends: false
update_cache: true
when: ansible_os_family != 'Darwin'
- name: Ensure Google Chrome apt keyring directory exists
ansible.builtin.file:
@ -119,6 +120,7 @@
owner: root
group: root
mode: "0755"
when: ansible_os_family != 'Darwin'
- name: Install Google Linux signing key
ansible.builtin.shell: |
@ -134,6 +136,7 @@
when:
- ansible_architecture in ['x86_64', 'amd64']
- not ai_workspace_offline_active
- ansible_os_family != 'Darwin'
- name: Configure Google Chrome apt repository
ansible.builtin.copy:
@ -145,6 +148,7 @@
when:
- ansible_architecture in ['x86_64', 'amd64']
- not ai_workspace_offline_active
- ansible_os_family != 'Darwin'
- name: Refresh apt cache after Google Chrome repository changes
ansible.builtin.apt:
@ -152,6 +156,7 @@
when:
- ansible_architecture in ['x86_64', 'amd64']
- not ai_workspace_offline_active
- ansible_os_family != 'Darwin'
- name: Install AI Agentic Workspace runtime packages
ansible.builtin.apt:
@ -162,6 +167,7 @@
+ ([xworkspace_console_browser_package] if xworkspace_console_browser_package | length > 0 else [])
}}
state: present
when: ansible_os_family != 'Darwin'
- name: Ensure ttyd binary target directory exists
ansible.builtin.file:
@ -170,6 +176,7 @@
owner: root
group: root
mode: "0755"
when: ansible_os_family != 'Darwin'
- name: Set ttyd binary download metadata
ansible.builtin.set_fact:
@ -191,16 +198,31 @@
owner: root
group: root
force: false
when: not ai_workspace_offline_active
when:
- not ai_workspace_offline_active
- ansible_os_family != 'Darwin'
- name: Verify ttyd binary
ansible.builtin.command: "{{ xworkspace_console_ttyd_binary_path }} --version"
register: xworkspace_console_ttyd_version
changed_when: false
register: ttyd_version_check
failed_when: ttyd_version_check.rc != 0
when: ansible_os_family != 'Darwin'
- name: Find ttyd path on macOS
ansible.builtin.command: which ttyd
register: ttyd_macos_path
changed_when: false
when: ansible_os_family == 'Darwin'
- name: Set ttyd binary path for macOS
ansible.builtin.set_fact:
xworkspace_console_ttyd_binary_path: "{{ ttyd_macos_path.stdout }}"
when: ansible_os_family == 'Darwin'
- name: Show ttyd binary version
ansible.builtin.debug:
var: xworkspace_console_ttyd_version.stdout
msg: "{{ ttyd_version_check.stdout | default('ttyd path: ' + ttyd_macos_path.stdout | default('Unknown')) }}"
- name: Ensure AI Agentic Workspace user exists
ansible.builtin.user:
@ -208,32 +230,36 @@
state: present
create_home: true
shell: /bin/bash
when: xworkspace_console_user != 'root'
when:
- xworkspace_console_user != 'root'
- ansible_os_family != 'Darwin'
- name: Ensure AI Agentic Workspace directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0755"
loop:
- "{{ xworkspace_console_root }}"
- "{{ xworkspace_console_scripts_dir }}"
- "{{ xworkspace_console_repo_dir }}"
- "{{ xworkspace_console_home }}/.config"
- "{{ xworkspace_console_config_dir }}"
- "{{ xworkspace_console_home }}/.config/autostart"
- "{{ xworkspace_console_home }}/.config/systemd"
- "{{ xworkspace_console_home }}/.config/systemd/user"
- "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants"
- "{{ xworkspace_console_home }}/.config/systemd/user/timers.target.wants"
loop: "{{ _directories | reject('search', 'systemd') | list if ansible_os_family == 'Darwin' else _directories }}"
vars:
_directories:
- "{{ xworkspace_console_root }}"
- "{{ xworkspace_console_scripts_dir }}"
- "{{ xworkspace_console_repo_dir }}"
- "{{ xworkspace_console_home }}/.config"
- "{{ xworkspace_console_config_dir }}"
- "{{ xworkspace_console_home }}/.config/autostart"
- "{{ xworkspace_console_home }}/.config/systemd"
- "{{ xworkspace_console_home }}/.config/systemd/user"
- "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants"
- "{{ xworkspace_console_home }}/.config/systemd/user/timers.target.wants"
- name: Deploy AI Agentic Workspace status generator
ansible.builtin.copy:
dest: "{{ xworkspace_console_scripts_dir }}/generate-status.py"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0755"
content: |
#!/usr/bin/env python3
@ -365,7 +391,7 @@
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-status.service"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0644"
content: |
[Unit]
@ -376,12 +402,13 @@
[Service]
Type=oneshot
ExecStart={{ xworkspace_console_scripts_dir }}/generate-status.py
when: ansible_os_family != 'Darwin'
- name: Deploy AI Agentic Workspace status timer
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-status.timer"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0644"
content: |
[Unit]
@ -395,12 +422,13 @@
[Install]
WantedBy=timers.target
when: ansible_os_family != 'Darwin'
- name: Deploy Xinit entrypoint for AI Agentic Workspace
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.xinitrc"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0755"
content: |
#!/usr/bin/env bash
@ -427,22 +455,24 @@
systemctl --user start xworkspace-console.service >/dev/null 2>&1 || true
exec dbus-launch --exit-with-session startxfce4
when: ansible_os_family != 'Darwin'
- name: Point Xsession to Xinit entrypoint
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.xsession"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0755"
content: |
#!/usr/bin/env bash
exec "$HOME/.xinitrc"
when: ansible_os_family != 'Darwin'
- name: Deploy AI Agentic Workspace console launcher script
ansible.builtin.copy:
dest: "{{ xworkspace_console_scripts_dir }}/start.sh"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0755"
content: |
#!/usr/bin/env bash
@ -453,7 +483,7 @@
ansible.builtin.copy:
dest: "{{ xworkspace_console_scripts_dir }}/chrome-app.sh"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0755"
content: |
#!/usr/bin/env bash
@ -495,7 +525,7 @@
dest: "{{ xworkspace_console_repo_dir | dirname }}"
remote_src: true
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
when:
- xworkspace_console_runtime_archive | length > 0
- xworkspace_console_runtime_archive_stat.stat.exists | default(false)
@ -509,7 +539,7 @@
ansible.builtin.copy:
dest: "{{ xworkspace_console_runtime_marker }}"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0644"
content: "{{ xworkspace_console_runtime_archive_stat.stat.checksum }}\n"
when:
@ -581,7 +611,7 @@
ansible.builtin.copy:
dest: "{{ xworkspace_console_config_dir }}/portal-services.json"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0644"
content: "{{ {'services': xworkspace_console_portal_services} | to_nice_json }}\n"
@ -589,7 +619,7 @@
ansible.builtin.copy:
dest: "{{ xworkspace_console_config_dir }}/auth-token"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0600"
content: "{{ xworkspace_console_auth_token }}\n"
no_log: true
@ -598,7 +628,7 @@
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.ai_workspace_auth_token"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0600"
content: "{{ xworkspace_console_auth_token }}\n"
no_log: true
@ -607,7 +637,7 @@
ansible.builtin.copy:
dest: "{{ xworkspace_console_config_dir }}/portal.env"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0600"
content: |
AI_WORKSPACE_AUTH_TOKEN={{ xworkspace_console_auth_token }}
@ -623,7 +653,7 @@
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-console.service"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0644"
content: |
[Unit]
@ -640,12 +670,13 @@
[Install]
WantedBy=default.target
when: ansible_os_family != 'Darwin'
- name: Deploy XWorkspace API service
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-api.service"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0644"
content: |
[Unit]
@ -663,12 +694,13 @@
[Install]
WantedBy=default.target
when: ansible_os_family != 'Darwin'
- name: Deploy AI Agentic Workspace ttyd service
ansible.builtin.copy:
dest: "{{ xworkspace_console_home }}/.config/systemd/user/xworkspace-ttyd.service"
owner: "{{ xworkspace_console_user }}"
group: "{{ xworkspace_console_user }}"
group: "{{ 'staff' if ansible_os_family == 'Darwin' else xworkspace_console_user }}"
mode: "0644"
content: |
[Unit]
@ -684,6 +716,7 @@
[Install]
WantedBy=default.target
when: ansible_os_family != 'Darwin'
- name: Enable XWorkspace Console service
ansible.builtin.file:
@ -691,6 +724,7 @@
dest: "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-console.service"
state: link
become_user: "{{ xworkspace_console_user }}"
when: ansible_os_family != 'Darwin'
- name: Enable XWorkspace API service
ansible.builtin.file:
@ -698,6 +732,7 @@
dest: "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-api.service"
state: link
become_user: "{{ xworkspace_console_user }}"
when: ansible_os_family != 'Darwin'
- name: Enable AI Agentic Workspace ttyd service
ansible.builtin.file:
@ -705,6 +740,7 @@
dest: "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-ttyd.service"
state: link
become_user: "{{ xworkspace_console_user }}"
when: ansible_os_family != 'Darwin'
- name: Enable AI Agentic Workspace LiteLLM service
ansible.builtin.file:
@ -713,6 +749,7 @@
state: link
force: true
become_user: "{{ xworkspace_console_user }}"
when: ansible_os_family != 'Darwin'
- name: Enable AI Agentic Workspace status timer
ansible.builtin.file:
@ -720,6 +757,7 @@
dest: "{{ xworkspace_console_home }}/.config/systemd/user/timers.target.wants/xworkspace-status.timer"
state: link
become_user: "{{ xworkspace_console_user }}"
when: ansible_os_family != 'Darwin'
- name: Kill legacy python http.server on port 7000
ansible.builtin.shell: |
@ -741,6 +779,7 @@
- "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-portal.service"
- "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-chrome.service"
- "{{ xworkspace_console_home }}/.config/systemd/user/default.target.wants/xworkspace-console.target"
when: ansible_os_family != 'Darwin'
- name: Remove legacy portal directory
ansible.builtin.file:
@ -754,6 +793,7 @@
systemctl start "user@${uid}.service" || true
runuser -u {{ xworkspace_console_user }} -- env XDG_RUNTIME_DIR="/run/user/${uid}" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${uid}/bus" systemctl --user daemon-reload
become: true
when: ansible_os_family != 'Darwin'
- name: Restart xworkspace-console service
ansible.builtin.shell: |
@ -762,6 +802,7 @@
systemctl start "user@${uid}.service" || true
runuser -u {{ xworkspace_console_user }} -- env XDG_RUNTIME_DIR="/run/user/${uid}" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${uid}/bus" systemctl --user restart xworkspace-console.service
become: true
when: ansible_os_family != 'Darwin'
- name: Restart xworkspace-api service
ansible.builtin.shell: |
@ -770,6 +811,7 @@
systemctl start "user@${uid}.service" || true
runuser -u {{ xworkspace_console_user }} -- env XDG_RUNTIME_DIR="/run/user/${uid}" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${uid}/bus" systemctl --user restart xworkspace-api.service
become: true
when: ansible_os_family != 'Darwin'
- name: Restart xworkspace-ttyd service
ansible.builtin.shell: |
@ -778,6 +820,7 @@
systemctl start "user@${uid}.service" || true
runuser -u {{ xworkspace_console_user }} -- env XDG_RUNTIME_DIR="/run/user/${uid}" DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/${uid}/bus" systemctl --user restart xworkspace-ttyd.service
become: true
when: ansible_os_family != 'Darwin'
- name: Hide XFCE desktop icons
ansible.builtin.command: xfconf-query -c xfce4-desktop -p /desktop-icons/style -t int -s 0 --create
@ -791,6 +834,7 @@
owner: root
group: root
mode: "0755"
when: ansible_os_family != 'Darwin'
- name: Deploy xworkspace-console public Caddy site
ansible.builtin.copy:
@ -802,19 +846,29 @@
{{ xworkspace_console_domain }} {
reverse_proxy 127.0.0.1:{{ xworkspace_console_port }}
}
when: xworkspace_console_public_access | bool
when:
- xworkspace_console_public_access | bool
- ansible_os_family != 'Darwin'
register: xworkspace_caddy_deploy
- name: Remove xworkspace-console public Caddy site when disabled
ansible.builtin.file:
path: "/etc/caddy/conf.d/{{ xworkspace_console_domain }}.caddy"
state: absent
when: not (xworkspace_console_public_access | bool)
when:
- not (xworkspace_console_public_access | bool)
- ansible_os_family != 'Darwin'
register: xworkspace_caddy_remove
- name: Reload Caddy if xworkspace-console proxy changed
ansible.builtin.service:
name: caddy
state: reloaded
when: (xworkspace_caddy_deploy.changed or xworkspace_caddy_remove.changed) and not ansible_check_mode
when:
- (xworkspace_caddy_deploy.changed or xworkspace_caddy_remove.changed) and not ansible_check_mode
- ansible_os_family != 'Darwin'
failed_when: false
- name: Import macOS specific XWorkspace console tasks
ansible.builtin.include_tasks: xworkspace_console_macos.yml
when: ansible_os_family == 'Darwin'

29
ttyd.plist.j2 Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>plus.svc.xworkspace.ttyd</string>
<key>ProgramArguments</key>
<array>
<string>{{ xworkspace_console_ttyd_binary_path }}</string>
<string>-i</string>
<string>lo0</string>
<string>-p</string>
<string>{{ xworkspace_console_ttyd_port }}</string>
<string>-O</string>
<string>login</string>
<string>bash</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ ansible_env.HOME }}</string>
<key>StandardOutPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/ttyd.log</string>
<key>StandardErrorPath</key>
<string>{{ ansible_env.HOME }}/.local/state/xworkspace/ttyd.err.log</string>
</dict>
</plist>

View File

@ -0,0 +1,37 @@
---
- name: Create launchd plist templates for XWorkspace
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.{{ item.name }}.plist"
mode: "0644"
loop:
- src: console.plist.j2
name: console
- src: api.plist.j2
name: api
- src: ttyd.plist.j2
name: ttyd
register: xworkspace_mac_plists
- name: Restart launchd agents for XWorkspace on template change
ansible.builtin.command: "launchctl stop plus.svc.xworkspace.{{ item.item.name }}"
loop: "{{ xworkspace_mac_plists.results }}"
when: item.changed
failed_when: false
changed_when: false
- name: Reload launchd agents for XWorkspace
ansible.builtin.command: "launchctl load -w {{ ansible_env.HOME }}/Library/LaunchAgents/plus.svc.xworkspace.{{ item.name }}.plist"
loop:
- { name: console }
- { name: api }
- { name: ttyd }
register: launchctl_result
changed_when: false
failed_when: launchctl_result.rc != 0 and 'already loaded' not in launchctl_result.stderr
- name: Start launchd agents for XWorkspace
ansible.builtin.command: "launchctl start plus.svc.xworkspace.{{ item.item.name }}"
loop: "{{ xworkspace_mac_plists.results }}"
when: item.changed
changed_when: false