feat: support macos runtime deployment

This commit is contained in:
Haitao Pan 2026-06-18 14:48:04 +08:00
parent 0e1f8ab7cf
commit dbbce5ff49
14 changed files with 412 additions and 10 deletions

View File

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

View File

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

View File

@ -219,11 +219,13 @@
register: gateway_openclaw_config_attrs
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from OpenClaw gateway JSON config when present
ansible.builtin.command:
cmd: chattr -i "{{ gateway_openclaw_config_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (gateway_openclaw_config_attrs.stdout | default(''))"
changed_when: true
@ -301,6 +303,7 @@
ansible.builtin.command:
cmd: chattr +i "{{ gateway_openclaw_config_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (gateway_openclaw_config_attrs.stdout | default(''))"
- not ansible_check_mode
changed_when: true
@ -398,6 +401,7 @@
owner: "{{ gateway_openclaw_service_user }}"
group: "{{ gateway_openclaw_service_group }}"
mode: "0755"
when: ansible_os_family != 'Darwin'
- name: Deploy OpenClaw user systemd unit
ansible.builtin.template:
@ -407,6 +411,7 @@
group: "{{ gateway_openclaw_service_group }}"
mode: "0644"
register: gateway_openclaw_user_service_unit
when: ansible_os_family != 'Darwin'
- name: Deploy OpenClaw user systemd shell environment
ansible.builtin.template:
@ -415,6 +420,7 @@
owner: root
group: root
mode: "0644"
when: ansible_os_family != 'Darwin'
- name: Enable OpenClaw service user linger
ansible.builtin.command:
@ -422,6 +428,7 @@
creates: "/var/lib/systemd/linger/{{ gateway_openclaw_service_user }}"
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Resolve OpenClaw service user uid
ansible.builtin.command:
@ -429,6 +436,7 @@
register: gateway_openclaw_resolved_service_uid
changed_when: false
check_mode: false
when: ansible_os_family != 'Darwin'
- name: Set OpenClaw effective service uid
ansible.builtin.set_fact:
@ -436,8 +444,9 @@
{{
gateway_openclaw_service_uid
if (gateway_openclaw_service_uid | string | trim | length > 0)
else gateway_openclaw_resolved_service_uid.stdout
else (gateway_openclaw_resolved_service_uid.stdout | default(''))
}}
when: ansible_os_family != 'Darwin'
- name: Ensure OpenClaw service user manager is running
ansible.builtin.systemd:
@ -445,6 +454,7 @@
state: started
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Stop and disable stale root-managed OpenClaw gateway service
ansible.builtin.systemd:
@ -454,6 +464,7 @@
failed_when: false
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Inspect stale OpenClaw gateway root systemd unit attributes
ansible.builtin.command:
@ -461,11 +472,13 @@
register: gateway_openclaw_unit_attrs
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from stale OpenClaw gateway root systemd unit when present
ansible.builtin.command:
cmd: chattr -i "{{ gateway_openclaw_service_unit_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (gateway_openclaw_unit_attrs.stdout | default(''))"
changed_when: true
@ -474,6 +487,7 @@
path: "{{ gateway_openclaw_service_unit_path }}"
state: absent
register: gateway_openclaw_removed_root_service_unit
when: ansible_os_family != 'Darwin'
- name: Reload root systemd after removing stale OpenClaw gateway unit
ansible.builtin.systemd:
@ -481,6 +495,7 @@
when:
- gateway_openclaw_removed_root_service_unit.changed | default(false)
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Reload OpenClaw user systemd manager
ansible.builtin.shell: |
@ -498,6 +513,7 @@
changed_when: false
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Ensure OpenClaw user gateway service is enabled and running
ansible.builtin.shell: |
@ -518,6 +534,7 @@
'Created symlink' in (gateway_openclaw_user_service_enable.stderr | default(''))
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Restart OpenClaw user gateway service after unit changes
ansible.builtin.shell: |
@ -535,6 +552,11 @@
when:
- gateway_openclaw_user_service_unit.changed | default(false)
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Import macOS specific service tasks
ansible.builtin.import_tasks: macos.yml
when: ansible_os_family == 'Darwin'
- name: Ensure Caddy fragment directory exists
ansible.builtin.file:
@ -550,11 +572,13 @@
register: gateway_openclaw_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 "{{ gateway_openclaw_caddyfile_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (gateway_openclaw_caddyfile_attrs.stdout | default(''))"
changed_when: true
@ -574,6 +598,7 @@
ansible.builtin.command:
cmd: chattr +i "{{ gateway_openclaw_caddyfile_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (gateway_openclaw_caddyfile_attrs.stdout | default(''))"
- not ansible_check_mode
changed_when: true
@ -584,11 +609,13 @@
register: gateway_openclaw_caddy_fragment_attrs
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Remove immutable flag from OpenClaw Caddy fragment when present
ansible.builtin.command:
cmd: chattr -i "{{ gateway_openclaw_caddy_fragment_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (gateway_openclaw_caddy_fragment_attrs.stdout | default(''))"
changed_when: true
@ -613,6 +640,7 @@
ansible.builtin.command:
cmd: chattr +i "{{ gateway_openclaw_caddy_fragment_path }}"
when:
- ansible_os_family != 'Darwin'
- "'i' in (gateway_openclaw_caddy_fragment_attrs.stdout | default(''))"
- not ansible_check_mode
changed_when: true
@ -624,3 +652,4 @@
state: started
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'

View File

@ -0,0 +1,35 @@
<?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.openclaw</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
export PATH="{{ gateway_openclaw_service_path }}"
export NODE_COMPILE_CACHE="{{ gateway_openclaw_compile_cache_dir }}"
export OPENCLAW_NO_RESPAWN=1
exec "{{ gateway_openclaw_binary_path }}" gateway --json-config "{{ gateway_openclaw_config_path }}"
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ gateway_openclaw_home }}</string>
<key>StandardOutPath</key>
<string>{{ gateway_openclaw_home }}/.local/state/xworkspace/openclaw.log</string>
<key>StandardErrorPath</key>
<string>{{ gateway_openclaw_home }}/.local/state/xworkspace/openclaw.err.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>HOME</key>
<string>{{ gateway_openclaw_home }}</string>
</dict>
</dict>
</plist>

View File

@ -34,6 +34,10 @@ litellm_ui_password: "{{ litellm_master_key }}"
litellm_deepseek_api_key: "{{ lookup('ansible.builtin.env', 'DEEPSEEK_API_KEY') | default('', true) }}"
litellm_openai_api_key: "{{ lookup('ansible.builtin.env', 'OPENAI_API_KEY') | default('', true) }}"
litellm_nvidia_api_key: "{{ lookup('ansible.builtin.env', 'NVIDIA_API_KEY') | default('', true) }}"
litellm_gemini_api_key: "{{ lookup('ansible.builtin.env', 'GEMINI_API_KEY') | default('', true) }}"
litellm_anthropic_api_key: "{{ lookup('ansible.builtin.env', 'ANTHROPIC_API_KEY') | default('', true) }}"
litellm_ollama_api_key: "{{ lookup('ansible.builtin.env', 'OLLAMA_API_KEY') | default('', true) }}"
litellm_caddyfile_path: /etc/caddy/Caddyfile
litellm_caddy_conf_dir: /etc/caddy/conf.d

View File

@ -0,0 +1,130 @@
#!/usr/bin/env bash
set -euo pipefail
# Configurable endpoints and tokens
LITELLM_URL="${LITELLM_URL:-http://127.0.0.1:4000}"
LITELLM_TOKEN="${LITELLM_TOKEN:-}"
if [ -z "$LITELLM_TOKEN" ] && [ -f "$HOME/.ai_workspace_auth_token" ]; then
LITELLM_TOKEN="$(cat "$HOME/.ai_workspace_auth_token")"
fi
if [ -z "$LITELLM_TOKEN" ]; then
echo "Error: LITELLM_TOKEN is not set and could not be read from ~/.ai_workspace_auth_token."
exit 1
fi
echo "[INFO] Using LiteLLM URL: $LITELLM_URL"
# Function to add a model
add_model() {
local alias_name="$1"
local litellm_provider_model="$2"
local api_key_env_var="$3"
local api_base="${4:-}"
echo "Adding model: $alias_name -> $litellm_provider_model"
local payload
if [ -n "$api_base" ]; then
payload=$(cat <<EOF
{
"model_name": "$alias_name",
"litellm_params": {
"model": "$litellm_provider_model",
"api_key": "os.environ/$api_key_env_var",
"api_base": "$api_base"
},
"model_info": {
"id": "$alias_name",
"mode": "chat"
}
}
EOF
)
else
payload=$(cat <<EOF
{
"model_name": "$alias_name",
"litellm_params": {
"model": "$litellm_provider_model",
"api_key": "os.environ/$api_key_env_var"
},
"model_info": {
"id": "$alias_name",
"mode": "chat"
}
}
EOF
)
fi
local response
local http_code
response=$(curl -s -w "\nHTTP_CODE:%{http_code}" -X POST "$LITELLM_URL/model/new" \
-H "Authorization: Bearer $LITELLM_TOKEN" \
-H "Content-Type: application/json" \
-d "$payload")
http_code=$(echo "$response" | grep -Eo 'HTTP_CODE:[0-9]{3}' | cut -d':' -f2 || echo "000")
if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
echo "[SUCCESS] Model $alias_name added."
else
echo "[ERROR] Failed to add model $alias_name. HTTP Code: $http_code"
echo "Response: $response"
fi
}
echo "========================================="
echo "Registering DeepSeek Models..."
echo "========================================="
add_model "deepseek-chat" "deepseek/deepseek-chat" "DEEPSEEK_API_KEY"
add_model "deepseek-reasoner" "deepseek/deepseek-reasoner" "DEEPSEEK_API_KEY"
add_model "deepseek-v4-flash" "deepseek/deepseek-v4-flash" "DEEPSEEK_API_KEY"
add_model "deepseek-v4-pro" "deepseek/deepseek-v4-pro" "DEEPSEEK_API_KEY"
echo "========================================="
echo "Registering NVIDIA Build Models..."
echo "========================================="
# For NVIDIA NIM models, you can use openai format with custom base, or nvidia_nim/ provider
add_model "nvidia/deepseek-r1" "openai/deepseek-ai/deepseek-r1" "NVIDIA_API_KEY" "https://integrate.api.nvidia.com/v1"
add_model "nvidia/minimax-text-01" "openai/minimax/minimax-text-01" "NVIDIA_API_KEY" "https://integrate.api.nvidia.com/v1"
add_model "nvidia/glm-4" "openai/thudm/glm-4-9b-chat" "NVIDIA_API_KEY" "https://integrate.api.nvidia.com/v1"
add_model "nvidia/glm-5" "openai/thudm/glm-5" "NVIDIA_API_KEY" "https://integrate.api.nvidia.com/v1"
echo "========================================="
echo "Registering Gemini Models..."
echo "========================================="
add_model "gemini-2.5-pro" "gemini/gemini-2.5-pro" "GEMINI_API_KEY"
add_model "gemini-2.5-flash" "gemini/gemini-2.5-flash" "GEMINI_API_KEY"
add_model "gemini-1.5-pro" "gemini/gemini-1.5-pro" "GEMINI_API_KEY"
echo "========================================="
echo "Registering GPT Models..."
echo "========================================="
add_model "gpt-5.5" "openai/gpt-5.5" "OPENAI_API_KEY"
add_model "gpt-5.4" "openai/gpt-5.4" "OPENAI_API_KEY"
add_model "gpt-5.4-mini" "openai/gpt-5.4-mini" "OPENAI_API_KEY"
echo "========================================="
echo "Registering Claude Models..."
echo "========================================="
add_model "claude-3.5-sonnet" "anthropic/claude-3-5-sonnet-20241022" "ANTHROPIC_API_KEY"
add_model "claude-3.5-haiku" "anthropic/claude-3-5-haiku-20241022" "ANTHROPIC_API_KEY"
add_model "claude-3-opus" "anthropic/claude-3-opus-20240229" "ANTHROPIC_API_KEY"
echo "========================================="
echo "Registering Zhipu (GLM) using OLLAMA_API_KEY..."
echo "========================================="
add_model "glm-4" "openai/glm-4" "OLLAMA_API_KEY" "https://open.bigmodel.cn/api/paas/v4"
add_model "glm-5" "openai/glm-5" "OLLAMA_API_KEY" "https://open.bigmodel.cn/api/paas/v4"
echo "========================================="
echo "Registering OLLAMA Cloud Models..."
echo "========================================="
# Assuming OLLAMA API is exposed via a cloud endpoint or an OpenAI proxy
OLLAMA_API_BASE="${OLLAMA_API_BASE:-https://api.ollama.cloud/v1}"
add_model "ollama-llama3" "openai/llama3" "OLLAMA_API_KEY" "$OLLAMA_API_BASE"
add_model "ollama-qwen" "openai/qwen" "OLLAMA_API_KEY" "$OLLAMA_API_BASE"
echo "All models requested have been registered."
echo "You can check them at $LITELLM_URL/ui/?page=models"

View File

@ -2,8 +2,21 @@
ansible.builtin.service:
name: caddy
state: reloaded
when: ansible_os_family != 'Darwin'
- name: Restart litellm
ansible.builtin.service:
name: "{{ litellm_service_name }}"
state: restarted
state: restarted
when: ansible_os_family != 'Darwin'
- name: Restart litellm on macOS
ansible.builtin.command: "launchctl stop plus.svc.xworkspace.litellm"
register: launchctl_stop
failed_when: false
changed_when: false
notify: Start litellm on macOS
- name: Start litellm on macOS
ansible.builtin.command: "launchctl start plus.svc.xworkspace.litellm"
changed_when: false

View File

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

View File

@ -1,7 +1,7 @@
---
# Provision the litellm database and user BEFORE litellm starts.
# Only runs when litellm_database_host is set.
- name: Install LiteLLM prerequisites
- name: Install LiteLLM prerequisites (Linux)
ansible.builtin.package:
name:
- python3
@ -9,6 +9,13 @@
- python3-venv
- python3-psycopg2
state: present
when: ansible_os_family != 'Darwin'
- name: Install LiteLLM prerequisites (macOS)
community.general.homebrew:
name: python@3.13
state: present
when: ansible_os_family == 'Darwin'
- name: Materialize persisted LiteLLM secrets
ansible.builtin.assert:
@ -42,7 +49,9 @@
ansible.builtin.group:
name: "{{ litellm_service_group }}"
state: present
when: litellm_service_group != 'root'
when:
- litellm_service_group != 'root'
- ansible_os_family != 'Darwin'
- name: Ensure litellm service user exists
ansible.builtin.user:
@ -51,7 +60,9 @@
shell: /bin/bash
create_home: true
state: present
when: litellm_service_user != 'root'
when:
- litellm_service_user != 'root'
- ansible_os_family != 'Darwin'
- name: Ensure litellm config directory exists
ansible.builtin.file:
@ -133,11 +144,14 @@
group: root
mode: "0644"
notify: Restart litellm
when: ansible_os_family != 'Darwin'
- name: Reload systemd after unit changes
ansible.builtin.systemd:
daemon_reload: true
when: not ansible_check_mode
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Ensure Caddy fragment directory exists
ansible.builtin.file:
@ -209,7 +223,13 @@
name: "{{ litellm_service_name }}"
enabled: true
state: started
when: not ansible_check_mode
when:
- not ansible_check_mode
- ansible_os_family != 'Darwin'
- name: Import macOS specific service tasks
ansible.builtin.import_tasks: macos.yml
when: ansible_os_family == 'Darwin'
- name: Ensure Caddy is enabled and running
ansible.builtin.systemd:
@ -219,6 +239,7 @@
when:
- not ansible_check_mode
- litellm_caddy_config_enabled | bool
- ansible_os_family != 'Darwin'
- name: Apply litellm and Caddy changes before validation
ansible.builtin.meta: flush_handlers
@ -243,3 +264,24 @@
owner: "{{ litellm_service_user }}"
group: "{{ litellm_service_group }}"
mode: "0600"
- name: Register mainstream models to LiteLLM
ansible.builtin.script: files/register_mainstream_models.sh
environment:
LITELLM_URL: "http://{{ litellm_listen_host }}:{{ litellm_listen_port }}"
LITELLM_TOKEN: "{{ litellm_master_key }}"
DEEPSEEK_API_KEY: "{{ litellm_deepseek_api_key }}"
OPENAI_API_KEY: "{{ litellm_openai_api_key }}"
NVIDIA_API_KEY: "{{ litellm_nvidia_api_key }}"
GEMINI_API_KEY: "{{ litellm_gemini_api_key }}"
ANTHROPIC_API_KEY: "{{ litellm_anthropic_api_key }}"
OLLAMA_API_KEY: "{{ litellm_ollama_api_key }}"
when: >
not ansible_check_mode and
((litellm_deepseek_api_key | length > 0) or
(litellm_openai_api_key | length > 0) or
(litellm_nvidia_api_key | length > 0) or
(litellm_gemini_api_key | length > 0) or
(litellm_anthropic_api_key | length > 0) or
(litellm_ollama_api_key | length > 0))
changed_when: false

View File

@ -5,6 +5,10 @@ UI_PASSWORD={{ litellm_ui_password }}
LITELLM_DB_PASSWORD={{ litellm_database_password }}
DEEPSEEK_API_KEY={{ litellm_deepseek_api_key }}
OPENAI_API_KEY={{ litellm_openai_api_key }}
NVIDIA_API_KEY={{ litellm_nvidia_api_key }}
GEMINI_API_KEY={{ litellm_gemini_api_key }}
ANTHROPIC_API_KEY={{ litellm_anthropic_api_key }}
OLLAMA_API_KEY={{ litellm_ollama_api_key }}
{% if litellm_database_url | trim | length > 0 %}
DATABASE_URL={{ litellm_database_url }}
{% endif %}

View File

@ -0,0 +1,39 @@
<?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.litellm</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>
export PATH="/opt/homebrew/bin:/usr/local/bin:{{ litellm_service_home }}/.local/bin:$PATH"
export DATABASE_URL="{{ litellm_database_url }}"
export LITELLM_MASTER_KEY="{{ litellm_master_key }}"
export LITELLM_SALT_KEY="{{ litellm_salt_key }}"
export UI_USERNAME="{{ litellm_ui_username }}"
export UI_PASSWORD="{{ litellm_ui_password }}"
export DEEPSEEK_API_KEY="{{ litellm_deepseek_api_key }}"
export OPENAI_API_KEY="{{ litellm_openai_api_key }}"
export NVIDIA_API_KEY="{{ litellm_nvidia_api_key }}"
export OLLAMA_API_KEY="{{ litellm_ollama_api_key }}"
export GEMINI_API_KEY="{{ litellm_gemini_api_key }}"
export ANTHROPIC_API_KEY="{{ litellm_anthropic_api_key }}"
exec "{{ litellm_proxy_dir | default(litellm_service_home ~ '/.local/lib/python3.13/site-packages/litellm/proxy') }}/../../../../bin/litellm" --host {{ litellm_listen_host }} --port {{ litellm_listen_port }} --config "{{ litellm_config_file }}" --use_prisma_db_push
</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>WorkingDirectory</key>
<string>{{ litellm_service_home }}</string>
<key>StandardOutPath</key>
<string>{{ litellm_service_home }}/.local/state/xworkspace/litellm.log</string>
<key>StandardErrorPath</key>
<string>{{ litellm_service_home }}/.local/state/xworkspace/litellm.err.log</string>
</dict>
</plist>

View File

@ -0,0 +1,57 @@
---
- name: Ensure PostgreSQL 16 is installed via Homebrew
community.general.homebrew:
name: postgresql@16
state: present
- name: Start PostgreSQL via Homebrew Services
ansible.builtin.command: brew services start postgresql@16
register: brew_services_output
changed_when: "'Successfully started' in brew_services_output.stdout or 'started' in brew_services_output.stdout"
failed_when: brew_services_output.rc != 0 and 'already started' not in brew_services_output.stderr and 'already started' not in brew_services_output.stdout
- name: Wait for PostgreSQL to become ready
ansible.builtin.wait_for:
host: "{{ postgresql_listen_addresses }}"
port: "{{ postgresql_port }}"
timeout: 60
- name: Ensure the database user exists
ansible.builtin.shell: |
set -e
# Run the SQL via psql as the current user (which Homebrew configures as superuser)
psql -h "{{ postgresql_listen_addresses }}" -p "{{ postgresql_port }}" -d postgres -v ON_ERROR_STOP=1 <<SQL
DO \$\$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '{{ postgresql_admin_user }}') THEN
CREATE ROLE "{{ postgresql_admin_user }}" LOGIN PASSWORD '{{ postgresql_admin_password }}';
ELSE
ALTER ROLE "{{ postgresql_admin_user }}" LOGIN PASSWORD '{{ postgresql_admin_password }}';
END IF;
END
\$\$;
SQL
environment:
PATH: "/opt/homebrew/opt/postgresql@16/bin:/usr/local/opt/postgresql@16/bin:{{ ansible_env.PATH }}"
no_log: true
changed_when: true # Idempotent SQL
- name: Ensure the database exists and belongs to the user
ansible.builtin.shell: |
set -e
psql -h "{{ postgresql_listen_addresses }}" -p "{{ postgresql_port }}" -d postgres -v ON_ERROR_STOP=1 <<SQL
SELECT format('CREATE DATABASE %I OWNER %I', '{{ postgresql_database }}', '{{ postgresql_admin_user }}')
WHERE NOT EXISTS (SELECT 1 FROM pg_database WHERE datname = '{{ postgresql_database }}') \gexec
ALTER DATABASE "{{ postgresql_database }}" OWNER TO "{{ postgresql_admin_user }}";
SQL
environment:
PATH: "/opt/homebrew/opt/postgresql@16/bin:/usr/local/opt/postgresql@16/bin:{{ ansible_env.PATH }}"
changed_when: true # Idempotent SQL
- name: Verify PostgreSQL connection
ansible.builtin.shell: |
PGPASSWORD="{{ postgresql_admin_password }}" psql -h "{{ postgresql_listen_addresses }}" -p "{{ postgresql_port }}" -U "{{ postgresql_admin_user }}" -d "{{ postgresql_database }}" -v ON_ERROR_STOP=1 -Atc 'select 1'
environment:
PATH: "/opt/homebrew/opt/postgresql@16/bin:/usr/local/opt/postgresql@16/bin:{{ ansible_env.PATH }}"
no_log: true
changed_when: false

View File

@ -9,6 +9,14 @@
ansible.builtin.import_tasks: compose.yml
when: postgresql_deploy_mode == 'compose'
- name: Deploy PostgreSQL natively
- name: Deploy PostgreSQL natively (Linux)
ansible.builtin.import_tasks: native.yml
when: postgresql_deploy_mode == 'native'
when:
- postgresql_deploy_mode == 'native'
- ansible_os_family != 'Darwin'
- name: Deploy PostgreSQL natively (macOS)
ansible.builtin.import_tasks: macos.yml
when:
- postgresql_deploy_mode == 'native'
- ansible_os_family == 'Darwin'

View File

@ -4,7 +4,7 @@ xworkmate_bridge_service_user: ubuntu
xworkmate_bridge_service_group: ubuntu
xworkmate_bridge_service_home: "/home/{{ xworkmate_bridge_service_user }}"
ai_workspace_auth_token: "{{ lookup('ansible.builtin.env', 'AI_WORKSPACE_AUTH_TOKEN') | default('', true) }}"
xworkmate_bridge_auth_token: "{{ lookup('ansible.builtin.env', 'BRIDGE_AUTH_TOKEN') | default(lookup('ansible.builtin.env', 'XWORKMATE_BRIDGE_AUTH_TOKEN') | default(lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default(ai_workspace_auth_token, true), true), true) }}"
xworkmate_bridge_auth_token: "{{ lookup('ansible.builtin.env', 'BRIDGE_AUTH_TOKEN') | default(lookup('ansible.builtin.env', 'XWORKMATE_BRIDGE_AUTH_TOKEN') | default(ai_workspace_auth_token, true), true) }}"
xworkmate_bridge_review_auth_token: "{{ lookup('ansible.builtin.env', 'BRIDGE_REVIEW_AUTH_TOKEN') | default('', true) }}"
xworkmate_bridge_listen_host: 127.0.0.1
xworkmate_bridge_listen_port: 8787
@ -19,6 +19,7 @@ xworkmate_bridge_deprecated_compose_file: "{{ xworkmate_bridge_base_dir }}/docke
xworkmate_bridge_obsolete_systemd_dropin_paths:
- "/etc/systemd/system/{{ xworkmate_bridge_service_name }}.service.d/20-distributed-forward.conf"
xworkmate_bridge_service_environment:
AI_WORKSPACE_AUTH_TOKEN: "{{ ai_workspace_auth_token }}"
BRIDGE_AUTH_TOKEN: "{{ xworkmate_bridge_effective_auth_token | default(xworkmate_bridge_auth_token) }}"
BRIDGE_REVIEW_AUTH_TOKEN: "{{ xworkmate_bridge_effective_review_auth_token | default(xworkmate_bridge_review_auth_token) }}"
BRIDGE_CONFIG_PATH: "{{ xworkmate_bridge_config_file }}"