Compare commits

..

1 Commits

Author SHA1 Message Date
0007981f6d
ci: backport release/* source validation workflow to release/v1.1.5 (#20)
让现有 release/v1.1.5 分支自身包含门禁 workflow(pull_request_target 用 base 分支版本)。
详见 iac_modules/docs/tldr-github-branch-model.md

Co-authored-by: Haitao Pan <manbuzhe2009@qq.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:41:29 +08:00
10 changed files with 50 additions and 135 deletions

View File

@ -11,10 +11,6 @@ acp_opencode_workdir: "{{ ansible_env.HOME | default('/home/' + acp_opencode_ser
# user-level npm global bin lives under ~/.local/bin; include Homebrew + system.
acp_opencode_npm_global_bin: "{{ acp_opencode_home + '/.local/bin' if ansible_os_family == 'Darwin' else '/usr/bin' }}"
acp_opencode_path: "{{ acp_opencode_npm_global_bin }}:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin"
# OpenCode CLI binary the adapter spawns lazily (mirrors gemini/codex which use
# {{ acp_X_npm_global_bin }}/<cli>). Was hardcoded to /usr/bin/opencode in the
# unit template; use the resolved npm global bin so macOS (~/.local/bin) works too.
acp_opencode_binary_path: "{{ acp_opencode_npm_global_bin }}/opencode"
acp_opencode_listen_host: 127.0.0.1
acp_opencode_listen_port: 38992
acp_opencode_packages: []

View File

@ -79,29 +79,6 @@
- "{{ acp_opencode_home }}/.local"
- "{{ acp_opencode_workdir }}"
# Resolve the OpenCode CLI at deploy time instead of assuming a fixed path.
# npm installs it wherever the active node prefix points (NodeSource -> /usr/bin
# on Debian/Ubuntu, ~/.local/bin or Homebrew on macOS), so probe the real path
# with the role PATH and fall back to the OS-aware default when not yet present.
- name: Resolve OpenCode CLI binary path
ansible.builtin.shell: |
set -eu
export PATH="{{ acp_opencode_path }}:${PATH}"
command -v opencode || true
args:
executable: /bin/bash
register: acp_opencode_resolved_bin
changed_when: false
- name: Use resolved OpenCode CLI binary path when present
ansible.builtin.set_fact:
acp_opencode_binary_path: "{{ acp_opencode_resolved_bin.stdout_lines[0] | trim }}"
when: acp_opencode_resolved_bin.stdout | default('') | trim | length > 0
- name: Report effective OpenCode CLI binary path
ansible.builtin.debug:
msg: "OpenCode CLI binary resolved to: {{ acp_opencode_binary_path }}"
- name: Deploy Caddy main file
ansible.builtin.template:
src: Caddyfile.j2

View File

@ -20,68 +20,33 @@
# 用 curl 重试循环替代 uri服务刚 (重)启时 adapter 会先 accept TCP 但短时间内不
# 应答(读挂起),而 uri 默认 30s 超时 + retries/until 在连接超时上不可靠循环(实测
# 仅试一次即失败)。每次 5s 上限、真重试给冷启动足够时间adapter 就绪后 ~4ms 回 200
# 包在 block/rescue探针失败时把 systemctl status + journalctl 打到 CI 日志,
# 否则 play 在此中止,看不到 adapter 进程真正的崩溃原因(如 last code 000
- name: Validate OpenCode local ACP endpoint
block:
- name: Validate OpenCode local ACP endpoint (readiness retry)
ansible.builtin.shell: |
set -eu
url="http://{{ acp_opencode_listen_host }}:{{ acp_opencode_listen_port }}/acp/rpc"
body='{"jsonrpc":"2.0","id":1,"method":"acp.capabilities","params":{}}'
code=""
for i in $(seq 1 30); do
code="$(curl -s -m 5 -o /dev/null -w '%{http_code}' -X POST "$url" \
-H 'Content-Type: application/json' -d "$body" 2>/dev/null || true)"
if [ "$code" = "200" ]; then
echo "OpenCode ACP endpoint ready after ${i} attempt(s)"
exit 0
fi
sleep 2
done
echo "OpenCode ACP endpoint ${url} not ready after retries (last code: ${code:-none})" >&2
exit 1
args:
executable: /bin/bash
changed_when: false
register: acp_opencode_adapter_probe
rescue:
- name: Capture OpenCode ACP service status on failure
ansible.builtin.command: systemctl status "{{ acp_opencode_service_name }}" --no-pager --full
register: acp_opencode_status_fail
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Capture recent OpenCode ACP service logs on failure
ansible.builtin.command: journalctl -u "{{ acp_opencode_service_name }}" -n 80 --no-pager
register: acp_opencode_journal_fail
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Show OpenCode ACP failure diagnostics
ansible.builtin.debug:
msg:
- "Probe stderr: {{ acp_opencode_adapter_probe.stderr | default('N/A') }}"
- "Listeners: {{ acp_opencode_ss.stdout | default('N/A') }}"
- "Service status: {{ acp_opencode_status_fail.stdout | default('N/A') }}"
- "Recent logs: {{ acp_opencode_journal_fail.stdout | default('N/A') }}"
- name: Fail after emitting OpenCode ACP diagnostics
ansible.builtin.fail:
msg: >-
OpenCode ACP endpoint
{{ acp_opencode_listen_host }}:{{ acp_opencode_listen_port }} did not
become ready. See the diagnostics above (service status + journal) for
the adapter crash cause.
- name: Validate OpenCode local ACP endpoint (readiness retry)
ansible.builtin.shell: |
set -eu
url="http://{{ acp_opencode_listen_host }}:{{ acp_opencode_listen_port }}/acp/rpc"
body='{"jsonrpc":"2.0","id":1,"method":"acp.capabilities","params":{}}'
code=""
for i in $(seq 1 30); do
code="$(curl -s -m 5 -o /dev/null -w '%{http_code}' -X POST "$url" \
-H 'Content-Type: application/json' -d "$body" 2>/dev/null || true)"
if [ "$code" = "200" ]; then
echo "OpenCode ACP endpoint ready after ${i} attempt(s)"
exit 0
fi
sleep 2
done
echo "OpenCode ACP endpoint ${url} not ready after retries (last code: ${code:-none})" >&2
exit 1
args:
executable: /bin/bash
changed_when: false
register: acp_opencode_adapter_probe
- name: Show OpenCode ACP status
ansible.builtin.command: systemctl status "{{ acp_opencode_service_name }}" --no-pager
register: acp_opencode_status
changed_when: false
failed_when: false
when: ansible_os_family != 'Darwin'
- name: Show OpenCode ACP validation summary
ansible.builtin.debug:
@ -90,6 +55,6 @@
- "Preferred WebSocket endpoint: {{ acp_opencode_public_base_url }}/acp"
- "Compatibility HTTP RPC endpoint: {{ acp_opencode_public_base_url }}/acp/rpc"
- "OpenCode ACP adapter listener: {{ acp_opencode_listen_host }}:{{ acp_opencode_listen_port }}"
- "Readiness probe: {{ acp_opencode_adapter_probe.stdout | default('N/A') }}"
- "Service: {{ acp_opencode_status.stdout | default('N/A') }}"
- "Socket: {{ acp_opencode_ss.stdout | default('N/A') }}"
- "Adapter capabilities HTTP: {{ acp_opencode_adapter_http.content | default('N/A') }}"

View File

@ -0,0 +1,27 @@
[Unit]
Description=XWorkmate OpenCode ACP bridge server
After=network-online.target {{ acp_opencode_service_name }}.service
Wants=network-online.target
[Service]
Type=simple
User={{ acp_opencode_service_user }}
Group={{ acp_opencode_service_group }}
WorkingDirectory={{ acp_opencode_workdir }}
Environment=HOME={{ acp_opencode_home }}
Environment=TERM=xterm-256color
Environment=ACP_LISTEN_ADDR={{ acp_opencode_bridge_listen_host }}:{{ acp_opencode_bridge_listen_port }}
Environment=ACP_ALLOWED_ORIGINS={{ acp_opencode_bridge_allowed_origins | join(',') }}
{% if acp_opencode_auth_token | trim | length > 0 %}
Environment=ACP_AUTH_TOKEN={{ acp_opencode_auth_token }}
{% endif %}
Environment=ACP_CODEX_BIN={{ acp_opencode_bridge_disabled_binary_path }}
Environment=ACP_CLAUDE_BIN={{ acp_opencode_bridge_disabled_binary_path }}
Environment=ACP_GEMINI_BIN={{ acp_opencode_bridge_disabled_binary_path }}
Environment=ACP_OPENCODE_BIN={{ acp_opencode_bridge_opencode_binary_path }}
ExecStart={{ acp_opencode_bridge_binary_path }} serve --listen {{ acp_opencode_bridge_listen_host }}:{{ acp_opencode_bridge_listen_port }}
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target

View File

@ -9,10 +9,9 @@ User={{ acp_opencode_service_user }}
Group={{ acp_opencode_service_group }}
WorkingDirectory={{ acp_opencode_workdir }}
Environment=HOME={{ acp_opencode_home }}
Environment=PATH={{ acp_opencode_path }}
Environment=TERM=xterm-256color
Environment=OPENCODE_ADAPTER_ALLOWED_ORIGINS={{ acp_opencode_bridge_allowed_origins | join(',') }}
ExecStart={{ acp_opencode_bridge_binary_path }} adapter opencode --listen {{ acp_opencode_listen_host }}:{{ acp_opencode_listen_port }} --opencode-bin {{ acp_opencode_binary_path }} --cwd {{ acp_opencode_workdir }}
ExecStart={{ acp_opencode_bridge_binary_path }} adapter opencode --listen {{ acp_opencode_listen_host }}:{{ acp_opencode_listen_port }} --opencode-bin /usr/bin/opencode --cwd {{ acp_opencode_workdir }}
Restart=always
RestartSec=2

View File

@ -17,7 +17,6 @@
exec "{{ acp_opencode_bridge_binary_path }}" adapter opencode \
-listen {{ acp_opencode_listen_host }}:{{ acp_opencode_listen_port }} \
-opencode-bin "{{ acp_opencode_binary_path }}" \
-cwd "{{ acp_opencode_workdir }}"
</string>
</array>

View File

@ -6,16 +6,6 @@ migrate_litellm_db: "litellm"
migrate_litellm_db_user: "litellm"
migrate_litellm_db_host: "127.0.0.1"
# Public bootstrap redirects
ai_workspace_caddy_base_dir: "{{ caddy_config_dir | default('/etc/caddy') }}"
ai_workspace_caddy_conf_dir: "{{ ai_workspace_caddy_base_dir }}/conf.d"
ai_workspace_caddyfile_path: "{{ ai_workspace_caddy_base_dir }}/Caddyfile"
ai_workspace_caddy_fragment_path: "{{ ai_workspace_caddy_conf_dir }}/install.svc.plus.caddy"
ai_workspace_public_domain: "install.svc.plus"
ai_workspace_install_script_url: "https://raw.githubusercontent.com/ai-workspace-lab/xworkspace-console/main/scripts/setup-ai-workspace-all-in-one.sh"
ai_workspace_xworkmate_install_script_url: "https://raw.githubusercontent.com/ai-workspace-lab/xworkmate-app/main/scripts/install-xworkmate-app.sh"
ai_workspace_manage_caddy: true
# Migration paths
openclaw_data_dir: "~/.openclaw"
xworkspace_state_dir: "~/.local/state/xworkspace"

View File

@ -1,6 +1,5 @@
---
dependencies:
- role: roles/vhosts/caddy
- role: roles/agent_skills
- role: roles/vhosts/gateway_openclaw
- role: roles/vhosts/xworkmate_bridge

View File

@ -1,35 +1,4 @@
---
- name: Ensure AI Workspace Caddy fragment directory exists
ansible.builtin.file:
path: "{{ ai_workspace_caddy_conf_dir }}"
state: directory
mode: "0755"
when: ai_workspace_manage_caddy | bool
- name: Render install.svc.plus redirect fragment
ansible.builtin.template:
src: Caddyfile.j2
dest: "{{ ai_workspace_caddy_fragment_path }}"
mode: "0644"
register: ai_workspace_caddy_fragment
when: ai_workspace_manage_caddy | bool
- name: Validate Caddy configuration
ansible.builtin.command: >-
caddy validate --config {{ ai_workspace_caddyfile_path }}
changed_when: false
when:
- ai_workspace_manage_caddy | bool
- ai_workspace_caddy_fragment.changed
- name: Reload Caddy after updating install redirects
ansible.builtin.service:
name: caddy
state: reloaded
when:
- ai_workspace_manage_caddy | bool
- ai_workspace_caddy_fragment.changed
# =============================================================================
# Final deployment of the prebuilt XWorkspace Console runtime.
#

View File

@ -1,6 +0,0 @@
{{ ai_workspace_public_domain }} {
redir /ai-workspace {{ ai_workspace_install_script_url }} 302
redir /ai-workspace/latest {{ ai_workspace_install_script_url }} 302
redir /xworkmate-app {{ ai_workspace_xworkmate_install_script_url }} 302
redir /xworkmate-app/latest {{ ai_workspace_xworkmate_install_script_url }} 302
}