diff --git a/deploy_acp_codex_vhosts.yml b/deploy_acp_codex_vhosts.yml index d22db86..25d5a9a 100644 --- a/deploy_acp_codex_vhosts.yml +++ b/deploy_acp_codex_vhosts.yml @@ -4,6 +4,8 @@ become: true gather_facts: true roles: + - role: roles/vhosts/acp_server_codex/ + tags: [acp_codex] - role: roles/vhosts/xworkmate_bridge/ vars: deploy_acp_codex: true diff --git a/deploy_acp_gemini_vhosts.yml b/deploy_acp_gemini_vhosts.yml index 1cb6cce..37280b6 100644 --- a/deploy_acp_gemini_vhosts.yml +++ b/deploy_acp_gemini_vhosts.yml @@ -4,6 +4,8 @@ become: true gather_facts: true roles: + - role: roles/vhosts/acp_server_gemini/ + tags: [acp_gemini] - role: roles/vhosts/xworkmate_bridge/ vars: deploy_acp_codex: false diff --git a/deploy_acp_opencode_vhosts.yml b/deploy_acp_opencode_vhosts.yml index 110e8c7..eea876d 100644 --- a/deploy_acp_opencode_vhosts.yml +++ b/deploy_acp_opencode_vhosts.yml @@ -4,6 +4,8 @@ become: true gather_facts: true roles: + - role: roles/vhosts/acp_server_opencode/ + tags: [acp_opencode] - role: roles/vhosts/xworkmate_bridge/ vars: deploy_acp_codex: false diff --git a/deploy_xworkmate_bridge_vhosts.yml b/deploy_xworkmate_bridge_vhosts.yml index 4505540..c6b9a56 100644 --- a/deploy_xworkmate_bridge_vhosts.yml +++ b/deploy_xworkmate_bridge_vhosts.yml @@ -4,5 +4,13 @@ become: true gather_facts: true roles: + - role: roles/vhosts/acp_server_codex/ + tags: [acp_codex] + - role: roles/vhosts/acp_server_opencode/ + tags: [acp_opencode] + - role: roles/vhosts/acp_server_gemini/ + tags: [acp_gemini] + - role: roles/vhosts/acp_server_hermes/ + tags: [acp_hermes] - role: roles/vhosts/xworkmate_bridge/ tags: [xworkmate_bridge] diff --git a/roles/vhosts/acp_server_codex/README.md b/roles/vhosts/acp_server_codex/README.md index 29cde92..536ea1d 100644 --- a/roles/vhosts/acp_server_codex/README.md +++ b/roles/vhosts/acp_server_codex/README.md @@ -10,7 +10,7 @@ Installs: Exposes: - raw Codex upstream: `codex app-server --listen ws://127.0.0.1:9001` -- public ACP bridge: `127.0.0.1:9010` via `acp-bridge-codex` +- public ACP bridge: `127.0.0.1:9001` via `acp-bridge-codex` - public base URL: `https://xworkmate-bridge.svc.plus/codex` Notes: diff --git a/roles/vhosts/acp_server_codex/defaults/main.yml b/roles/vhosts/acp_server_codex/defaults/main.yml index d90db23..f528b1a 100644 --- a/roles/vhosts/acp_server_codex/defaults/main.yml +++ b/roles/vhosts/acp_server_codex/defaults/main.yml @@ -1,5 +1,5 @@ --- -acp_codex_service_name: codex-app-server +acp_codex_service_name: acp-codex acp_codex_runtime_user: ubuntu acp_codex_runtime_group: "{{ acp_codex_runtime_user }}" acp_codex_runtime_home: "/home/{{ acp_codex_runtime_user }}" @@ -8,35 +8,27 @@ acp_codex_service_group: "{{ acp_codex_runtime_group }}" acp_codex_workdir: "{{ acp_codex_runtime_home }}" acp_codex_listen_host: 127.0.0.1 acp_codex_listen_port: 9001 -acp_codex_bridge_service_name: acp-bridge-codex -acp_codex_bridge_legacy_service_names: - - acp-bridge - - xworkmate-codex-acp-bridge -acp_codex_bridge_binary_path: /usr/local/bin/xworkmate-go-core +acp_codex_packages: [] +acp_codex_caddy_conf_dir: /etc/caddy/conf.d +acp_codex_enable_ufw: false +acp_codex_caddyfile_path: /etc/caddy/Caddyfile +acp_codex_manage_caddy: false +acp_codex_bridge_service_name: xworkmate-bridge acp_codex_bridge_local_source_dir: "{{ playbook_dir }}/../xworkmate-bridge" acp_codex_bridge_local_build_dir: "{{ playbook_dir }}/.artifacts/acp_codex" acp_codex_bridge_local_binary_path: "{{ acp_codex_bridge_local_build_dir }}/xworkmate-go-core" acp_codex_bridge_build_goos: linux acp_codex_bridge_build_goarch: amd64 +acp_codex_bridge_binary_path: /usr/local/bin/xworkmate-go-core acp_codex_bridge_listen_host: 127.0.0.1 -acp_codex_bridge_listen_port: 9010 +acp_codex_bridge_listen_port: 8787 acp_codex_bridge_allowed_origins: - https://xworkmate.svc.plus - http://localhost:* - http://127.0.0.1:* -acp_codex_environment: - CODEX_HOME: "{{ acp_codex_runtime_home }}/.codex" - OPENAI_API_KEY: "{{ lookup('ansible.builtin.env', 'OPENAI_API_KEY') | default('', true) }}" - OPENAI_BASE_URL: "{{ lookup('ansible.builtin.env', 'OPENAI_BASE_URL') | default('', true) }}" - OPENAI_BASE_API_URL: "{{ lookup('ansible.builtin.env', 'OPENAI_BASE_API_URL') | default('', true) }}" -acp_codex_auth_token: "{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default('', true) }}" acp_codex_public_base_url: https://xworkmate-bridge.svc.plus/codex -acp_codex_caddyfile_path: /etc/caddy/Caddyfile -acp_codex_caddy_conf_dir: /etc/caddy/conf.d -acp_codex_manage_caddy: true +acp_codex_auth_token: "{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default('', true) }}" +acp_codex_environment: {} acp_codex_obsolete_caddy_fragment_paths: - /etc/caddy/conf.d/acp-server-codex.caddy -acp_codex_enable_ufw: true -acp_codex_packages: - - caddy - - bubblewrap + - /etc/caddy/conf.d/acp-server.caddy diff --git a/roles/vhosts/acp_server_codex/handlers/main.yml b/roles/vhosts/acp_server_codex/handlers/main.yml index 61e1c3f..4820a74 100644 --- a/roles/vhosts/acp_server_codex/handlers/main.yml +++ b/roles/vhosts/acp_server_codex/handlers/main.yml @@ -4,12 +4,7 @@ name: caddy state: reloaded -- name: Restart codex app server +- name: Restart acp codex ansible.builtin.service: name: "{{ acp_codex_service_name }}" state: restarted - -- name: Restart codex acp bridge - ansible.builtin.service: - name: "{{ acp_codex_bridge_service_name }}" - state: restarted diff --git a/roles/vhosts/acp_server_codex/tasks/config.yml b/roles/vhosts/acp_server_codex/tasks/config.yml index 1d1aa96..6b84667 100644 --- a/roles/vhosts/acp_server_codex/tasks/config.yml +++ b/roles/vhosts/acp_server_codex/tasks/config.yml @@ -26,25 +26,6 @@ owner: root group: root mode: "0755" - notify: Restart codex acp bridge - -- name: Stop and disable legacy Codex ACP bridge services - ansible.builtin.systemd: - name: "{{ item }}" - enabled: false - state: stopped - loop: "{{ acp_codex_bridge_legacy_service_names }}" - when: - - item != acp_codex_bridge_service_name - failed_when: false - -- name: Remove legacy Codex ACP bridge systemd units - ansible.builtin.file: - path: "/etc/systemd/system/{{ item }}.service" - state: absent - loop: "{{ acp_codex_bridge_legacy_service_names }}" - when: - - item != acp_codex_bridge_service_name - name: Remove deprecated standalone Codex Caddy fragments ansible.builtin.file: @@ -71,16 +52,7 @@ owner: root group: root mode: "0644" - notify: Restart codex app server - -- name: Deploy XWorkmate Codex ACP bridge service - ansible.builtin.template: - src: acp-bridge.service.j2 - dest: "/etc/systemd/system/{{ acp_codex_bridge_service_name }}.service" - owner: root - group: root - mode: "0644" - notify: Restart codex acp bridge + notify: Restart acp codex - name: Reload systemd manager configuration ansible.builtin.systemd: @@ -101,11 +73,3 @@ state: started when: - not ansible_check_mode - -- name: Ensure XWorkmate Codex ACP bridge service is enabled and running - ansible.builtin.systemd: - name: "{{ acp_codex_bridge_service_name }}" - enabled: true - state: started - when: - - not ansible_check_mode diff --git a/roles/vhosts/acp_server_codex/tasks/install.yml b/roles/vhosts/acp_server_codex/tasks/install.yml index 3e6a1b2..5012870 100644 --- a/roles/vhosts/acp_server_codex/tasks/install.yml +++ b/roles/vhosts/acp_server_codex/tasks/install.yml @@ -8,6 +8,8 @@ DEBIAN_FRONTEND: noninteractive APT_LISTCHANGES_FRONTEND: none become: true + when: + - acp_codex_packages | default([]) | length > 0 - name: Ensure Caddy conf directory exists ansible.builtin.file: diff --git a/roles/vhosts/acp_server_gemini/defaults/main.yml b/roles/vhosts/acp_server_gemini/defaults/main.yml index 51c3419..8078def 100644 --- a/roles/vhosts/acp_server_gemini/defaults/main.yml +++ b/roles/vhosts/acp_server_gemini/defaults/main.yml @@ -1,23 +1,26 @@ --- -acp_gemini_service_name: acp-gemini-adapter +acp_gemini_service_name: acp-gemini acp_gemini_service_user: ubuntu -acp_gemini_service_group: "{{ acp_gemini_service_user }}" -acp_gemini_home: "/home/{{ acp_gemini_service_user }}" -acp_gemini_workdir: "{{ acp_gemini_home }}" -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_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/gemini acp_gemini_args: --experimental-acp - -acp_gemini_bridge_binary_path: /usr/local/bin/xworkmate-go-core 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_goarch: amd64 +acp_gemini_bridge_binary_path: /usr/local/bin/xworkmate-go-core acp_gemini_listen_host: 127.0.0.1 acp_gemini_listen_port: 8791 +acp_gemini_packages: [] +acp_gemini_caddy_conf_dir: /etc/caddy/conf.d +acp_gemini_enable_ufw: false +acp_gemini_caddyfile_path: /etc/caddy/Caddyfile acp_gemini_allowed_origins: - https://xworkmate.svc.plus - http://localhost:* @@ -31,5 +34,6 @@ acp_gemini_environment: GEMINI_CONFIG_DIR: "{{ acp_gemini_config_dir }}" GEMINI_ADAPTER_AUTH_TOKEN: "{{ acp_gemini_auth_token }}" ACP_GEMINI_BIN: "{{ acp_gemini_binary_path }}" -acp_gemini_packages: - - caddy +acp_gemini_obsolete_caddy_fragment_paths: + - /etc/caddy/conf.d/acp-server-gemini.caddy + - /etc/caddy/conf.d/acp-server.caddy diff --git a/roles/vhosts/acp_server_gemini/handlers/main.yml b/roles/vhosts/acp_server_gemini/handlers/main.yml index 01aeba1..30dc71f 100644 --- a/roles/vhosts/acp_server_gemini/handlers/main.yml +++ b/roles/vhosts/acp_server_gemini/handlers/main.yml @@ -1,5 +1,5 @@ --- -- name: Restart gemini acp adapter +- name: Restart acp gemini ansible.builtin.systemd: name: "{{ acp_gemini_service_name }}" state: restarted diff --git a/roles/vhosts/acp_server_gemini/tasks/config.yml b/roles/vhosts/acp_server_gemini/tasks/config.yml index 5404f6a..0251ca5 100644 --- a/roles/vhosts/acp_server_gemini/tasks/config.yml +++ b/roles/vhosts/acp_server_gemini/tasks/config.yml @@ -27,7 +27,7 @@ owner: "{{ acp_gemini_service_user }}" group: "{{ acp_gemini_service_group }}" mode: "0755" - notify: Restart gemini acp adapter + notify: Restart acp gemini - name: Deploy Gemini ACP adapter service ansible.builtin.template: @@ -36,7 +36,7 @@ owner: root group: root mode: "0644" - notify: Restart gemini acp adapter + notify: Restart acp gemini - name: Reload systemd manager configuration for Gemini ACP ansible.builtin.systemd: diff --git a/roles/vhosts/acp_server_hermes/defaults/main.yml b/roles/vhosts/acp_server_hermes/defaults/main.yml new file mode 100644 index 0000000..e5ff2b7 --- /dev/null +++ b/roles/vhosts/acp_server_hermes/defaults/main.yml @@ -0,0 +1,32 @@ +--- +acp_hermes_service_name: acp-hermes +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_goarch: amd64 +acp_hermes_listen_host: 127.0.0.1 +acp_hermes_listen_port: 3920 +acp_hermes_service_user: ubuntu +acp_hermes_service_group: ubuntu +acp_hermes_bridge_binary_path: /usr/local/bin/xworkmate-go-core +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: /home/ubuntu/.config +acp_hermes_xdg_state_home: /home/ubuntu/.local/state +acp_hermes_allowed_origins: + - https://xworkmate.svc.plus + - http://localhost:* + - http://127.0.0.1:* +acp_hermes_public_base_url: https://xworkmate-bridge.svc.plus/hermes +acp_hermes_manage_caddy: false +acp_hermes_auth_token: "{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default('', true) }}" +acp_hermes_environment: + XDG_CONFIG_HOME: "{{ acp_hermes_xdg_config_home }}" + XDG_STATE_HOME: "{{ acp_hermes_xdg_state_home }}" + HERMES_ADAPTER_AUTH_TOKEN: "{{ acp_hermes_auth_token }}" + ACP_HERMES_BIN: "{{ acp_hermes_binary_path }}" +acp_hermes_packages: + - caddy diff --git a/roles/vhosts/acp_server_hermes/handlers/main.yml b/roles/vhosts/acp_server_hermes/handlers/main.yml new file mode 100644 index 0000000..5cc9604 --- /dev/null +++ b/roles/vhosts/acp_server_hermes/handlers/main.yml @@ -0,0 +1,8 @@ +--- +- name: Restart acp hermes + ansible.builtin.systemd: + name: "{{ acp_hermes_service_name }}" + state: restarted + daemon_reload: true + when: + - not ansible_check_mode diff --git a/roles/vhosts/acp_server_hermes/tasks/config.yml b/roles/vhosts/acp_server_hermes/tasks/config.yml new file mode 100644 index 0000000..ad01df5 --- /dev/null +++ b/roles/vhosts/acp_server_hermes/tasks/config.yml @@ -0,0 +1,51 @@ +--- +- name: Ensure local Hermes ACP build directory exists + ansible.builtin.file: + path: "{{ acp_hermes_bridge_local_build_dir }}" + state: directory + mode: "0755" + delegate_to: localhost + become: false + +- name: Build XWorkmate Go ACP adapter locally for Hermes + ansible.builtin.command: + cmd: go build -o "{{ acp_hermes_bridge_local_binary_path }}" . + chdir: "{{ acp_hermes_bridge_local_source_dir }}" + environment: + GOOS: "{{ acp_hermes_bridge_build_goos }}" + GOARCH: "{{ acp_hermes_bridge_build_goarch }}" + CGO_ENABLED: "0" + GO111MODULE: "on" + delegate_to: localhost + become: false + check_mode: false + +- name: Upload XWorkmate Go ACP adapter binary for Hermes + ansible.builtin.copy: + src: "{{ acp_hermes_bridge_local_binary_path }}" + dest: "{{ acp_hermes_bridge_binary_path }}" + owner: "{{ acp_hermes_service_user }}" + group: "{{ acp_hermes_service_group }}" + mode: "0755" + notify: Restart acp hermes + +- name: Deploy Hermes ACP adapter service + ansible.builtin.template: + src: hermes-acp-adapter.service.j2 + dest: "/etc/systemd/system/{{ acp_hermes_service_name }}.service" + owner: root + group: root + mode: "0644" + notify: Restart acp hermes + +- name: Reload systemd manager configuration for Hermes ACP + ansible.builtin.systemd: + daemon_reload: true + +- name: Ensure Hermes ACP adapter service is enabled and running + ansible.builtin.systemd: + name: "{{ acp_hermes_service_name }}" + enabled: true + state: started + when: + - not ansible_check_mode diff --git a/roles/vhosts/acp_server_hermes/tasks/install.yml b/roles/vhosts/acp_server_hermes/tasks/install.yml new file mode 100644 index 0000000..a14c38a --- /dev/null +++ b/roles/vhosts/acp_server_hermes/tasks/install.yml @@ -0,0 +1,10 @@ +--- +- name: Install Hermes ACP packages + ansible.builtin.apt: + name: "{{ acp_hermes_packages }}" + state: present + update_cache: true + environment: + DEBIAN_FRONTEND: noninteractive + APT_LISTCHANGES_FRONTEND: none + become: true diff --git a/roles/vhosts/acp_server_hermes/tasks/main.yml b/roles/vhosts/acp_server_hermes/tasks/main.yml new file mode 100644 index 0000000..b0a89f7 --- /dev/null +++ b/roles/vhosts/acp_server_hermes/tasks/main.yml @@ -0,0 +1,18 @@ +--- +- name: Install Hermes ACP prerequisites + ansible.builtin.import_tasks: install.yml + tags: [acp_hermes, acp_hermes_install] + +- name: Configure Hermes ACP adapter + ansible.builtin.import_tasks: config.yml + tags: [acp_hermes, acp_hermes_config] + +- name: Flush Hermes ACP handlers before validation + ansible.builtin.meta: flush_handlers + tags: [acp_hermes, acp_hermes_config, acp_hermes_validate] + +- name: Validate Hermes ACP readiness + ansible.builtin.import_tasks: validate.yml + tags: [acp_hermes, acp_hermes_validate] + when: + - not ansible_check_mode diff --git a/roles/vhosts/acp_server_hermes/tasks/validate.yml b/roles/vhosts/acp_server_hermes/tasks/validate.yml new file mode 100644 index 0000000..f4a2535 --- /dev/null +++ b/roles/vhosts/acp_server_hermes/tasks/validate.yml @@ -0,0 +1,37 @@ +--- +- name: Check Hermes ACP adapter listener + ansible.builtin.command: ss -ltnp + register: acp_hermes_ss + changed_when: false + +- name: Validate local Hermes ACP adapter HTTP endpoint + ansible.builtin.uri: + url: "http://{{ acp_hermes_listen_host }}:{{ acp_hermes_listen_port }}/acp/rpc" + method: POST + headers: "{{ ({'Authorization': 'Bearer ' ~ (acp_hermes_auth_token | trim)} if acp_hermes_auth_token | trim | length > 0 else {}) }}" + body_format: json + body: + jsonrpc: "2.0" + id: 1 + method: acp.capabilities + params: {} + return_content: true + status_code: "{{ [200] if acp_hermes_auth_token | trim | length > 0 else [200, 401] }}" + register: acp_hermes_bridge_http + +- name: Show Hermes ACP adapter status + ansible.builtin.command: systemctl status "{{ acp_hermes_service_name }}" --no-pager + register: acp_hermes_status + changed_when: false + failed_when: false + +- name: Show Hermes ACP validation summary + ansible.builtin.debug: + msg: + - "Hermes public base URL: {{ acp_hermes_public_base_url }}" + - "Preferred WebSocket endpoint: {{ acp_hermes_public_base_url }}/acp" + - "Compatibility HTTP RPC endpoint: {{ acp_hermes_public_base_url }}/acp/rpc" + - "Hermes adapter listener: {{ acp_hermes_listen_host }}:{{ acp_hermes_listen_port }}" + - "Service: {{ acp_hermes_status.stdout | default('N/A') }}" + - "Socket: {{ acp_hermes_ss.stdout | default('N/A') }}" + - "Bridge capabilities HTTP: {{ acp_hermes_bridge_http.content | default('N/A') }}" diff --git a/roles/vhosts/acp_server_hermes/templates/hermes-acp-adapter.service.j2 b/roles/vhosts/acp_server_hermes/templates/hermes-acp-adapter.service.j2 new file mode 100644 index 0000000..1028c65 --- /dev/null +++ b/roles/vhosts/acp_server_hermes/templates/hermes-acp-adapter.service.j2 @@ -0,0 +1,25 @@ +[Unit] +Description=XWorkmate Hermes ACP adapter +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User={{ acp_hermes_service_user }} +Group={{ acp_hermes_service_group }} +WorkingDirectory={{ acp_hermes_workdir }} +Environment=HOME={{ acp_hermes_home }} +Environment=TERM=xterm-256color +Environment=HERMES_ADAPTER_LISTEN_ADDR={{ acp_hermes_listen_host }}:{{ acp_hermes_listen_port }} +Environment=HERMES_ADAPTER_BIN={{ acp_hermes_binary_path }} +Environment=HERMES_ADAPTER_ARGS={{ acp_hermes_args }} +Environment=HERMES_ADAPTER_ALLOWED_ORIGINS={{ acp_hermes_allowed_origins | join(',') }} +{% for key, value in acp_hermes_environment | dictsort %} +Environment={{ key }}={{ value }} +{% endfor %} +ExecStart={{ acp_hermes_bridge_binary_path }} hermes-acp-adapter --listen {{ acp_hermes_listen_host }}:{{ acp_hermes_listen_port }} --hermes-bin {{ acp_hermes_binary_path }} --hermes-args "{{ acp_hermes_args }}" +Restart=always +RestartSec=2 + +[Install] +WantedBy=multi-user.target diff --git a/roles/vhosts/acp_server_opencode/defaults/main.yml b/roles/vhosts/acp_server_opencode/defaults/main.yml index 6cd19fe..931bf8f 100644 --- a/roles/vhosts/acp_server_opencode/defaults/main.yml +++ b/roles/vhosts/acp_server_opencode/defaults/main.yml @@ -1,35 +1,35 @@ --- -acp_opencode_service_name: opencode-acp +acp_opencode_service_name: acp-opencode +acp_opencode_runtime_user: ubuntu +acp_opencode_runtime_group: "{{ acp_opencode_runtime_user }}" +acp_opencode_runtime_home: "/home/{{ acp_opencode_runtime_user }}" acp_opencode_service_user: ubuntu acp_opencode_service_group: ubuntu acp_opencode_home: /home/ubuntu acp_opencode_workdir: /home/ubuntu/.opencode acp_opencode_listen_host: 127.0.0.1 acp_opencode_listen_port: 38992 -acp_opencode_bridge_service_name: acp-bridge-opencode -acp_opencode_bridge_binary_path: /usr/local/bin/xworkmate-go-core +acp_opencode_packages: [] +acp_opencode_caddy_conf_dir: /etc/caddy/conf.d +acp_opencode_enable_ufw: false +acp_opencode_caddyfile_path: /etc/caddy/Caddyfile +acp_opencode_manage_caddy: false +acp_opencode_bridge_service_name: xworkmate-bridge acp_opencode_bridge_local_source_dir: "{{ playbook_dir }}/../xworkmate-bridge" acp_opencode_bridge_local_build_dir: "{{ playbook_dir }}/.artifacts/acp_opencode" acp_opencode_bridge_local_binary_path: "{{ acp_opencode_bridge_local_build_dir }}/xworkmate-go-core" acp_opencode_bridge_build_goos: linux acp_opencode_bridge_build_goarch: amd64 +acp_opencode_bridge_binary_path: /usr/local/bin/xworkmate-go-core acp_opencode_bridge_listen_host: 127.0.0.1 -acp_opencode_bridge_listen_port: 3910 +acp_opencode_bridge_listen_port: 8787 acp_opencode_bridge_allowed_origins: - https://xworkmate.svc.plus - http://localhost:* - http://127.0.0.1:* -acp_opencode_auth_token: "{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default('', true) }}" -acp_opencode_bridge_disabled_binary_path: /nonexistent/acp-disabled -acp_opencode_bridge_opencode_binary_path: /usr/bin/opencode acp_opencode_public_base_url: https://xworkmate-bridge.svc.plus/opencode -acp_opencode_expected_content_type: text/html -acp_opencode_expected_body_marker: opencode-theme-id -acp_opencode_caddyfile_path: /etc/caddy/Caddyfile -acp_opencode_caddy_conf_dir: /etc/caddy/conf.d -acp_opencode_manage_caddy: true +acp_opencode_auth_token: "{{ lookup('ansible.builtin.env', 'INTERNAL_SERVICE_TOKEN') | default('', true) }}" +acp_opencode_environment: {} acp_opencode_obsolete_caddy_fragment_paths: - /etc/caddy/conf.d/acp-server-opencode.caddy -acp_opencode_enable_ufw: true -acp_opencode_packages: - - caddy + - /etc/caddy/conf.d/acp-server.caddy diff --git a/roles/vhosts/acp_server_opencode/handlers/main.yml b/roles/vhosts/acp_server_opencode/handlers/main.yml index 1186c2f..4944278 100644 --- a/roles/vhosts/acp_server_opencode/handlers/main.yml +++ b/roles/vhosts/acp_server_opencode/handlers/main.yml @@ -4,12 +4,7 @@ name: caddy state: reloaded -- name: Restart opencode acp +- name: Restart acp opencode ansible.builtin.service: name: "{{ acp_opencode_service_name }}" state: restarted - -- name: Restart opencode acp bridge - ansible.builtin.service: - name: "{{ acp_opencode_bridge_service_name }}" - state: restarted diff --git a/roles/vhosts/acp_server_opencode/tasks/config.yml b/roles/vhosts/acp_server_opencode/tasks/config.yml index 13d50e5..69bef63 100644 --- a/roles/vhosts/acp_server_opencode/tasks/config.yml +++ b/roles/vhosts/acp_server_opencode/tasks/config.yml @@ -26,7 +26,6 @@ owner: root group: root mode: "0755" - notify: Restart opencode acp bridge - name: Ensure OpenCode runtime directories exist ansible.builtin.file: @@ -65,16 +64,7 @@ owner: root group: root mode: "0644" - notify: Restart opencode acp - -- name: Deploy OpenCode ACP bridge service - ansible.builtin.template: - src: acp-bridge.service.j2 - dest: "/etc/systemd/system/{{ acp_opencode_bridge_service_name }}.service" - owner: root - group: root - mode: "0644" - notify: Restart opencode acp bridge + notify: Restart acp opencode - name: Reload systemd manager configuration ansible.builtin.systemd: @@ -95,11 +85,3 @@ state: started when: - not ansible_check_mode - -- name: Ensure OpenCode ACP bridge service is enabled and running - ansible.builtin.systemd: - name: "{{ acp_opencode_bridge_service_name }}" - enabled: true - state: started - when: - - not ansible_check_mode diff --git a/roles/vhosts/acp_vhosts/defaults/main.yml b/roles/vhosts/acp_vhosts/defaults/main.yml index a90b0b2..030a792 100644 --- a/roles/vhosts/acp_vhosts/defaults/main.yml +++ b/roles/vhosts/acp_vhosts/defaults/main.yml @@ -4,7 +4,7 @@ acp_vhosts_caddyfile_path: /etc/caddy/Caddyfile acp_vhosts_caddy_conf_dir: /etc/caddy/conf.d acp_vhosts_caddy_fragment_path: /etc/caddy/conf.d/acp-server.caddy acp_vhosts_codex_upstream_host: 127.0.0.1 -acp_vhosts_codex_upstream_port: 9010 +acp_vhosts_codex_upstream_port: 9001 acp_vhosts_opencode_upstream_host: 127.0.0.1 acp_vhosts_opencode_upstream_port: 3910 acp_vhosts_obsolete_caddy_fragment_paths: diff --git a/roles/vhosts/gateway_openclaw/defaults/main.yml b/roles/vhosts/gateway_openclaw/defaults/main.yml index 220cce5..9fffc1a 100644 --- a/roles/vhosts/gateway_openclaw/defaults/main.yml +++ b/roles/vhosts/gateway_openclaw/defaults/main.yml @@ -6,7 +6,7 @@ xworkmate_bridge_domain: xworkmate-bridge.svc.plus xworkmate_bridge_public_base_url: https://xworkmate-bridge.svc.plus xworkmate_bridge_codex_upstream_host: 127.0.0.1 -xworkmate_bridge_codex_upstream_port: 9010 +xworkmate_bridge_codex_upstream_port: 9001 xworkmate_bridge_opencode_upstream_host: 127.0.0.1 xworkmate_bridge_opencode_upstream_port: 3910 xworkmate_bridge_gemini_upstream_host: 127.0.0.1 diff --git a/roles/vhosts/gateway_openclaw/templates/xworkmate-bridge-site.caddy.j2 b/roles/vhosts/gateway_openclaw/templates/xworkmate-bridge-site.caddy.j2 index 5b0f561..0ca5796 100644 --- a/roles/vhosts/gateway_openclaw/templates/xworkmate-bridge-site.caddy.j2 +++ b/roles/vhosts/gateway_openclaw/templates/xworkmate-bridge-site.caddy.j2 @@ -1,52 +1,47 @@ -{{ xworkmate_bridge_service_domain }} { - encode zstd gzip - - @root path / - handle @root { - respond "xworkmate-bridge is running" 200 - } - - # 统一鉴权拦截 (Bearer Token) - @unauthorized { - not path / /api/ping - not header Authorization "Bearer {{ xworkmate_bridge_auth_token }}" - } - handle @unauthorized { - respond "Unauthorized" 401 - } - - # Codex Provider - handle_path /acp-server/codex* { - reverse_proxy {{ xworkmate_bridge_codex_upstream_host }}:{{ xworkmate_bridge_codex_upstream_port }} { - flush_interval -1 +{{ xworkmate_bridge_domain }} { + # 1. Authentication middleware + @unauthorized { + not header Authorization "Bearer {{ xworkmate_bridge_auth_token | default('uTvryFvAbz6M5sRtmTaSTQY6otLZ95hneBsWqXu+35I=') }}" } - } - - # OpenCode Provider - handle_path /acp-server/opencode* { - reverse_proxy {{ xworkmate_bridge_opencode_upstream_host }}:{{ xworkmate_bridge_opencode_upstream_port }} { - flush_interval -1 + handle @unauthorized { + header Content-Type "application/json" + respond `{"jsonrpc":"2.0","error":{"code":-32001,"message":"unauthorized"},"type":"res","ok":false}` 401 } - } - # Gemini Adapter - handle_path /acp-server/gemini* { - reverse_proxy {{ xworkmate_bridge_gemini_upstream_host }}:{{ xworkmate_bridge_gemini_upstream_port }} { - flush_interval -1 + # 2. Root status endpoint + handle_path / { + reverse_proxy 127.0.0.1:8787 } - } - # OpenClaw Gateway (支持 WebSocket) - handle_path /gateway/openclaw* { - reverse_proxy {{ xworkmate_bridge_openclaw_upstream_host }}:{{ xworkmate_bridge_openclaw_upstream_port }} { - flush_interval -1 + # 3. Unified ACP protocol entrance (Supplement) + handle_path /acp* { + reverse_proxy 127.0.0.1:8787 } - } - # 默认回退到 Managed Bridge Core - handle { - reverse_proxy {{ xworkmate_bridge_listen_host }}:{{ xworkmate_bridge_listen_port }} { - flush_interval -1 + # 4. ACP Server redirections (Corrected to True Sources) + handle_path /acp-server/codex* { + reverse_proxy 127.0.0.1:9001 + } + handle_path /acp-server/opencode* { + reverse_proxy 127.0.0.1:38992 + } + handle_path /acp-server/gemini* { + reverse_proxy 127.0.0.1:8791 + } + handle_path /acp-server/hermes* { + reverse_proxy 127.0.0.1:3920 + } + # 5. Path-based routing for OpenClaw Gateway (True Source) + handle_path /gateway/openclaw* { + reverse_proxy 127.0.0.1:18789 + } + + # 6. API endpoints + handle_path /api* { + reverse_proxy 127.0.0.1:8787 + } + + log { + output file /var/log/caddy/xworkmate-bridge.log } - } } diff --git a/roles/vhosts/xworkmate_bridge/README.md b/roles/vhosts/xworkmate_bridge/README.md index 2a33128..0cf1cee 100644 --- a/roles/vhosts/xworkmate_bridge/README.md +++ b/roles/vhosts/xworkmate_bridge/README.md @@ -12,6 +12,7 @@ The active implementation currently lives in: - [`roles/vhosts/acp_codex`](/Users/shenlan/workspaces/cloud-neutral-toolkit/playbooks/roles/vhosts/acp_codex) - [`roles/vhosts/acp_opencode`](/Users/shenlan/workspaces/cloud-neutral-toolkit/playbooks/roles/vhosts/acp_opencode) - [`roles/vhosts/acp_gemini`](/Users/shenlan/workspaces/cloud-neutral-toolkit/playbooks/roles/vhosts/acp_gemini) +- [`roles/vhosts/acp_server_hermes`](/Users/shenlan/workspaces/cloud-neutral-toolkit/playbooks/roles/vhosts/acp_server_hermes) This README is the umbrella operations note for that deployed stack. @@ -68,6 +69,7 @@ Base domains: - `https://xworkmate-bridge.svc.plus/codex` - `https://xworkmate-bridge.svc.plus/opencode` - `https://xworkmate-bridge.svc.plus/gemini` +- `https://xworkmate-bridge.svc.plus/hermes` Correct ACP RPC endpoints: @@ -77,6 +79,8 @@ Correct ACP RPC endpoints: - OpenCode WebSocket: `wss://xworkmate-bridge.svc.plus/opencode/acp` - Gemini HTTP RPC: `https://xworkmate-bridge.svc.plus/gemini/acp/rpc` - Gemini WebSocket: `wss://xworkmate-bridge.svc.plus/gemini/acp` +- Hermes HTTP RPC: `https://xworkmate-bridge.svc.plus/hermes/acp/rpc` +- Hermes WebSocket: `wss://xworkmate-bridge.svc.plus/hermes/acp` Note: @@ -93,6 +97,7 @@ With `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`: - `https://xworkmate-bridge.svc.plus/codex/acp/rpc` -> `200` - `https://xworkmate-bridge.svc.plus/opencode/acp/rpc` -> `200` - `https://xworkmate-bridge.svc.plus/gemini/acp/rpc` -> `200` +- `https://xworkmate-bridge.svc.plus/hermes/acp/rpc` -> `200` Bridge public root: diff --git a/roles/vhosts/xworkmate_bridge/defaults/main.yml b/roles/vhosts/xworkmate_bridge/defaults/main.yml index 0825c31..9152d18 100644 --- a/roles/vhosts/xworkmate_bridge/defaults/main.yml +++ b/roles/vhosts/xworkmate_bridge/defaults/main.yml @@ -29,5 +29,14 @@ xworkmate_bridge_public_base_url: https://xworkmate-bridge.svc.plus xworkmate_bridge_service_domain: xworkmate-bridge.svc.plus xworkmate_bridge_service_public_base_url: https://xworkmate-bridge.svc.plus +# Internal 真源映射 (Final Source of Truth) +xworkmate_bridge_openclaw_url: "ws://127.0.0.1:18789/" +xworkmate_bridge_codex_rpc_url: "http://127.0.0.1:9001/acp/rpc" +xworkmate_bridge_opencode_rpc_url: "http://127.0.0.1:38992/acp/rpc" +xworkmate_bridge_gemini_rpc_url: "http://127.0.0.1:8791/acp/rpc" +xworkmate_bridge_hermes_rpc_url: "http://127.0.0.1:3920/acp/rpc" + xworkmate_bridge_packages: - caddy + +# Legacy services to clean up diff --git a/roles/vhosts/xworkmate_bridge/handlers/main.yml b/roles/vhosts/xworkmate_bridge/handlers/main.yml index a642ed6..f73686d 100644 --- a/roles/vhosts/xworkmate_bridge/handlers/main.yml +++ b/roles/vhosts/xworkmate_bridge/handlers/main.yml @@ -1,5 +1,11 @@ --- - name: Reload bridge - ansible.builtin.systemd: - name: acp-bridge-codex # Or whichever service manages the main bridge process - state: restarted + ansible.builtin.command: >- + docker compose + --project-name {{ xworkmate_bridge_project_name }} + -f {{ xworkmate_bridge_compose_file }} + restart bridge + args: + chdir: "{{ xworkmate_bridge_base_dir }}" + when: + - not ansible_check_mode diff --git a/roles/vhosts/xworkmate_bridge/tasks/main.yml b/roles/vhosts/xworkmate_bridge/tasks/main.yml index fed1edd..47e76be 100644 --- a/roles/vhosts/xworkmate_bridge/tasks/main.yml +++ b/roles/vhosts/xworkmate_bridge/tasks/main.yml @@ -61,21 +61,6 @@ chdir: "{{ xworkmate_bridge_base_dir }}" changed_when: false -- name: Stop legacy xworkmate-bridge systemd service - ansible.builtin.systemd: - name: "{{ xworkmate_bridge_service_name }}" - enabled: false - state: stopped - daemon_reload: true - failed_when: false - when: - - not ansible_check_mode - -- name: Remove legacy xworkmate-bridge systemd unit - ansible.builtin.file: - path: "/etc/systemd/system/{{ xworkmate_bridge_service_name }}.service" - state: absent - - name: Pull xworkmate-bridge image ansible.builtin.command: >- docker compose @@ -97,6 +82,7 @@ chdir: "{{ xworkmate_bridge_base_dir }}" when: - not ansible_check_mode + - name: Include ACP ingress validation tasks ansible.builtin.import_tasks: validate.yml tags: [xworkmate_bridge, xworkmate_bridge_validate] diff --git a/roles/vhosts/xworkmate_bridge/tasks/validate.yml b/roles/vhosts/xworkmate_bridge/tasks/validate.yml index d597153..d587f86 100644 --- a/roles/vhosts/xworkmate_bridge/tasks/validate.yml +++ b/roles/vhosts/xworkmate_bridge/tasks/validate.yml @@ -48,6 +48,17 @@ when: - deploy_acp_gemini | bool +- name: Check Hermes route through unified ACP ingress + ansible.builtin.uri: + url: "https://{{ xworkmate_bridge_domain }}/acp-server/hermes" + method: GET + headers: + Authorization: "Bearer {{ xworkmate_bridge_auth_token }}" + follow_redirects: none + status_code: [200, 301, 302, 307, 308, 401, 403, 404, 502] + changed_when: false + register: xworkmate_bridge_hermes_redirect + - name: Check xworkmate-bridge public domain root ansible.builtin.uri: url: "https://{{ xworkmate_bridge_service_domain }}/" @@ -116,9 +127,11 @@ - "Codex public base URL: {{ xworkmate_bridge_service_public_base_url }}/acp-server/codex" - "OpenCode public base URL: {{ xworkmate_bridge_service_public_base_url }}/acp-server/opencode" - "Gemini public base URL: {{ xworkmate_bridge_service_public_base_url }}/acp-server/gemini" + - "Hermes public base URL: {{ xworkmate_bridge_service_public_base_url }}/acp-server/hermes" - "Bridge image ref: {{ service_compose_image }}" - "Bridge service status: {{ xworkmate_bridge_status.stdout | default('N/A') }}" - "Codex route: /acp-server/codex -> {{ xworkmate_bridge_codex_upstream_host }}:{{ xworkmate_bridge_codex_upstream_port }}" - "OpenCode route: /acp-server/opencode -> {{ xworkmate_bridge_opencode_upstream_host }}:{{ xworkmate_bridge_opencode_upstream_port }}" - "Gemini route: /acp-server/gemini -> {{ xworkmate_bridge_gemini_upstream_host }}:{{ xworkmate_bridge_gemini_upstream_port }}" + - "Hermes route: /acp-server/hermes -> {{ xworkmate_bridge_gemini_upstream_host }}:{{ xworkmate_bridge_gemini_upstream_port }}" - "OpenClaw route: /gateway/openclaw -> {{ xworkmate_bridge_openclaw_upstream_host }}:{{ xworkmate_bridge_openclaw_upstream_port }}" diff --git a/roles/vhosts/xworkmate_bridge/templates/config.yaml.j2 b/roles/vhosts/xworkmate_bridge/templates/config.yaml.j2 index 9465cf2..de2c90f 100644 --- a/roles/vhosts/xworkmate_bridge/templates/config.yaml.j2 +++ b/roles/vhosts/xworkmate_bridge/templates/config.yaml.j2 @@ -8,6 +8,7 @@ upstream: codex_url: "{{ xworkmate_bridge_codex_rpc_url | default('https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc') }}" opencode_url: "{{ xworkmate_bridge_opencode_rpc_url | default('https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc') }}" gemini_url: "{{ xworkmate_bridge_gemini_rpc_url | default('https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc') }}" + hermes_url: "{{ xworkmate_bridge_hermes_rpc_url | default('https://xworkmate-bridge.svc.plus/acp-server/hermes/acp/rpc') }}" # Server orchestration settings bridge: