ci: ship bridge via image ref artifact
This commit is contained in:
parent
6314df67d5
commit
0fcaa845e1
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
||||
.git
|
||||
.github
|
||||
dist
|
||||
build
|
||||
.worktrees
|
||||
84
.github/workflows/pipeline.yml
vendored
84
.github/workflows/pipeline.yml
vendored
@ -25,6 +25,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
concurrency:
|
||||
group: pipeline-${{ github.ref }}
|
||||
@ -64,33 +65,67 @@ jobs:
|
||||
name: Build
|
||||
needs: prep
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SERVICE_REGISTRY: ghcr.io
|
||||
SERVICE_IMAGE_REPO_OWNER: ${{ vars.IMAGE_REPO_OWNER || github.repository_owner }}
|
||||
SERVICE_IMAGE_NAME: xworkmate-bridge
|
||||
outputs:
|
||||
artifact_name: ${{ steps.artifact_meta.outputs.artifact_name }}
|
||||
service_image_ref: ${{ steps.service_ref.outputs.image_ref }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: ${{ github.event_name != 'pull_request' }}
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
registry: ghcr.io
|
||||
username: ${{ vars.GHCR_USERNAME || github.repository_owner }}
|
||||
password: ${{ secrets.GHCR_TOKEN || github.token }}
|
||||
|
||||
- name: Build x86 artifact
|
||||
run: bash ./scripts/github-actions/build-artifact.sh dist
|
||||
- name: Resolve service image ref
|
||||
id: service_ref
|
||||
run: |
|
||||
set -euo pipefail
|
||||
image_repo="${SERVICE_REGISTRY}/${SERVICE_IMAGE_REPO_OWNER}/${SERVICE_IMAGE_NAME}"
|
||||
image_tag="${GITHUB_SHA}"
|
||||
image_ref="${image_repo}:${image_tag}"
|
||||
|
||||
- name: Smoke test built artifact
|
||||
run: ./dist/xworkmate-bridge --help >/dev/null
|
||||
echo "image_repo=${image_repo}" >> "$GITHUB_OUTPUT"
|
||||
echo "image_tag=${image_tag}" >> "$GITHUB_OUTPUT"
|
||||
echo "image_ref=${image_ref}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build and optionally push service image
|
||||
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.service_ref.outputs.image_ref }}
|
||||
|
||||
- name: Write image ref artifact
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p dist
|
||||
printf '%s\n' "${{ steps.service_ref.outputs.image_ref }}" > dist/service-image-ref.txt
|
||||
|
||||
- name: Record artifact metadata
|
||||
id: artifact_meta
|
||||
run: echo "artifact_name=xworkmate-bridge-linux-amd64" >> "$GITHUB_OUTPUT"
|
||||
run: echo "artifact_name=xworkmate-bridge-service-image-${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Upload artifact
|
||||
- name: Upload image ref artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
|
||||
with:
|
||||
name: ${{ steps.artifact_meta.outputs.artifact_name }}
|
||||
path: dist/xworkmate-bridge
|
||||
path: dist/service-image-ref.txt
|
||||
|
||||
deploy:
|
||||
name: Deploy
|
||||
@ -101,6 +136,8 @@ jobs:
|
||||
run_apply: ${{ steps.deploy_meta.outputs.run_apply }}
|
||||
env:
|
||||
INTERNAL_SERVICE_TOKEN: ${{ github.event_name == 'workflow_dispatch' && inputs.internal_service_token || secrets.INTERNAL_SERVICE_TOKEN }}
|
||||
GHCR_USERNAME: ${{ vars.GHCR_USERNAME || github.repository_owner }}
|
||||
GHCR_PASSWORD: ${{ secrets.GHCR_TOKEN || github.token }}
|
||||
steps:
|
||||
- name: Checkout service repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@ -114,18 +151,11 @@ jobs:
|
||||
token: ${{ secrets.WORKSPACE_REPO_TOKEN || github.token }}
|
||||
path: playbooks
|
||||
|
||||
- name: Download build artifact
|
||||
- name: Download build image ref artifact
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
|
||||
with:
|
||||
name: ${{ needs.build.outputs.artifact_name }}
|
||||
path: xworkmate-bridge/dist
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version-file: xworkmate-bridge/go.mod
|
||||
cache: true
|
||||
cache-dependency-path: xworkmate-bridge/go.sum
|
||||
path: xworkmate-bridge/dist/image-artifact
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
@ -166,7 +196,9 @@ jobs:
|
||||
working-directory: xworkmate-bridge
|
||||
env:
|
||||
INTERNAL_SERVICE_TOKEN: ${{ env.INTERNAL_SERVICE_TOKEN }}
|
||||
XWORKMATE_BRIDGE_ARTIFACT_PATH: ${{ github.workspace }}/xworkmate-bridge/dist/xworkmate-bridge
|
||||
GHCR_USERNAME: ${{ env.GHCR_USERNAME }}
|
||||
GHCR_PASSWORD: ${{ env.GHCR_PASSWORD }}
|
||||
XWORKMATE_BRIDGE_IMAGE_ARTIFACT_PATH: ${{ github.workspace }}/xworkmate-bridge/dist/image-artifact/service-image-ref.txt
|
||||
run: bash ./scripts/github-actions/deploy.sh "${{ steps.deploy_meta.outputs.target_host }}" "${{ steps.deploy_meta.outputs.run_apply }}" ../playbooks
|
||||
|
||||
publish_release:
|
||||
@ -177,7 +209,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Download build artifact
|
||||
- name: Download image ref artifact
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
|
||||
with:
|
||||
name: ${{ needs.build.outputs.artifact_name }}
|
||||
@ -198,7 +230,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh release create "${{ steps.release_meta.outputs.tag }}" \
|
||||
dist/xworkmate-bridge#xworkmate-bridge-linux-amd64 \
|
||||
dist/service-image-ref.txt#xworkmate-bridge-service-image-ref.txt \
|
||||
--repo "${{ github.repository }}" \
|
||||
--target "${{ github.sha }}" \
|
||||
--title "${{ steps.release_meta.outputs.title }}" \
|
||||
@ -206,7 +238,9 @@ jobs:
|
||||
|
||||
validate:
|
||||
name: Validate
|
||||
needs: deploy
|
||||
needs:
|
||||
- build
|
||||
- deploy
|
||||
if: ${{ needs.deploy.result == 'success' && needs.deploy.outputs.run_apply == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
@ -221,4 +255,4 @@ jobs:
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Validate deployed endpoints
|
||||
run: bash ./scripts/github-actions/validate-deploy.sh
|
||||
run: bash ./scripts/github-actions/validate-deploy.sh "${{ needs.build.outputs.service_image_ref }}" "${BRIDGE_SERVER_URL}" "${OPENCLAW_URL}" "${CODEX_RPC_URL}" "${OPENCODE_RPC_URL}" "${GEMINI_RPC_URL}" "${INTERNAL_SERVICE_TOKEN}"
|
||||
|
||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
# Stage 1 - build the bridge binary
|
||||
FROM golang:1.25.1 AS builder
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w" -o /out/xworkmate-bridge .
|
||||
|
||||
# Stage 2 - minimal runtime image
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /out/xworkmate-bridge /usr/local/bin/xworkmate-bridge
|
||||
|
||||
EXPOSE 8787
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/xworkmate-bridge", "serve", "--listen", "0.0.0.0:8787"]
|
||||
54
internal/acp/runtime_version.go
Normal file
54
internal/acp/runtime_version.go
Normal file
@ -0,0 +1,54 @@
|
||||
package acp
|
||||
|
||||
import "strings"
|
||||
|
||||
type imageVersionInfo struct {
|
||||
ImageRef string `json:"image_ref"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
func parseImageVersionInfo(imageRef string) imageVersionInfo {
|
||||
ref := strings.TrimSpace(imageRef)
|
||||
info := imageVersionInfo{ImageRef: ref}
|
||||
if ref == "" {
|
||||
return info
|
||||
}
|
||||
|
||||
if idx := strings.LastIndex(ref, "@"); idx >= 0 {
|
||||
ref = ref[:idx]
|
||||
}
|
||||
|
||||
tag := ref
|
||||
if idx := strings.LastIndex(tag, ":"); idx >= 0 && idx > strings.LastIndex(tag, "/") {
|
||||
tag = tag[idx+1:]
|
||||
}
|
||||
tag = strings.TrimSpace(tag)
|
||||
info.Tag = tag
|
||||
|
||||
switch {
|
||||
case isHexCommit(tag):
|
||||
info.Commit = tag
|
||||
info.Version = tag
|
||||
default:
|
||||
info.Version = tag
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func isHexCommit(value string) bool {
|
||||
if len(value) != 40 {
|
||||
return false
|
||||
}
|
||||
for _, r := range value {
|
||||
switch {
|
||||
case r >= '0' && r <= '9':
|
||||
case r >= 'a' && r <= 'f':
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@ -74,23 +75,7 @@ func Serve(args []string) error {
|
||||
server := NewServer()
|
||||
httpServer := &http.Server{
|
||||
Addr: strings.TrimSpace(*listen),
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/":
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
_, _ = w.Write([]byte("xworkmate-bridge is running"))
|
||||
case "/bridge/bootstrap/health":
|
||||
server.HandleBridgeBootstrapHealth(w, r)
|
||||
case "/bridge/bootstrap/consume":
|
||||
server.HandleBridgeBootstrapConsume(w, r)
|
||||
case "/acp/rpc":
|
||||
server.HandleRPC(w, r)
|
||||
case "/acp":
|
||||
server.HandleWebSocket(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}),
|
||||
Handler: server.Handler(),
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 5 * time.Minute,
|
||||
IdleTimeout: 2 * time.Minute,
|
||||
@ -115,6 +100,37 @@ func NewServer() *Server {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Handler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/":
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
_, _ = w.Write([]byte("xworkmate-bridge is running"))
|
||||
case "/api/ping":
|
||||
info := parseImageVersionInfo(os.Getenv("IMAGE"))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"status": "ok",
|
||||
"image": info.ImageRef,
|
||||
"tag": info.Tag,
|
||||
"commit": info.Commit,
|
||||
"version": info.Version,
|
||||
})
|
||||
case "/bridge/bootstrap/health":
|
||||
s.HandleBridgeBootstrapHealth(w, r)
|
||||
case "/bridge/bootstrap/consume":
|
||||
s.HandleBridgeBootstrapConsume(w, r)
|
||||
case "/acp/rpc":
|
||||
s.HandleRPC(w, r)
|
||||
case "/acp":
|
||||
s.HandleWebSocket(w, r)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
origin := strings.TrimSpace(r.Header.Get("Origin"))
|
||||
if !s.originAllowed(origin) {
|
||||
|
||||
@ -8,6 +8,70 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPHandlerRootAndPingExposeRuntimeVersionInfo(t *testing.T) {
|
||||
t.Setenv("IMAGE", "ghcr.io/x-evor/xworkmate-bridge:0123456789abcdef0123456789abcdef01234567")
|
||||
|
||||
server := NewServer()
|
||||
handler := server.Handler()
|
||||
|
||||
rootRecorder := httptest.NewRecorder()
|
||||
rootRequest := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/", nil)
|
||||
handler.ServeHTTP(rootRecorder, rootRequest)
|
||||
|
||||
if rootRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("expected root 200, got %d", rootRecorder.Code)
|
||||
}
|
||||
if !strings.Contains(rootRecorder.Body.String(), "xworkmate-bridge is running") {
|
||||
t.Fatalf("expected root body to contain service banner, got %q", rootRecorder.Body.String())
|
||||
}
|
||||
|
||||
pingRecorder := httptest.NewRecorder()
|
||||
pingRequest := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/api/ping", nil)
|
||||
handler.ServeHTTP(pingRecorder, pingRequest)
|
||||
|
||||
if pingRecorder.Code != http.StatusOK {
|
||||
t.Fatalf("expected ping 200, got %d", pingRecorder.Code)
|
||||
}
|
||||
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(pingRecorder.Body.Bytes(), &payload); err != nil {
|
||||
t.Fatalf("decode ping payload: %v", err)
|
||||
}
|
||||
|
||||
if got := payload["status"]; got != "ok" {
|
||||
t.Fatalf("expected status ok, got %#v", got)
|
||||
}
|
||||
if got := payload["image"]; got != "ghcr.io/x-evor/xworkmate-bridge:0123456789abcdef0123456789abcdef01234567" {
|
||||
t.Fatalf("expected full image ref, got %#v", got)
|
||||
}
|
||||
if got := payload["tag"]; got != "0123456789abcdef0123456789abcdef01234567" {
|
||||
t.Fatalf("expected full image tag, got %#v", got)
|
||||
}
|
||||
if got := payload["commit"]; got != "0123456789abcdef0123456789abcdef01234567" {
|
||||
t.Fatalf("expected full image commit, got %#v", got)
|
||||
}
|
||||
if got := payload["version"]; got != "0123456789abcdef0123456789abcdef01234567" {
|
||||
t.Fatalf("expected full image version, got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseImageVersionInfoHandlesTaggedImageRef(t *testing.T) {
|
||||
info := parseImageVersionInfo("ghcr.io/x-evor/xworkmate-bridge:main-2026-04-12")
|
||||
|
||||
if info.ImageRef != "ghcr.io/x-evor/xworkmate-bridge:main-2026-04-12" {
|
||||
t.Fatalf("expected full image ref, got %q", info.ImageRef)
|
||||
}
|
||||
if info.Tag != "main-2026-04-12" {
|
||||
t.Fatalf("expected tag main-2026-04-12, got %q", info.Tag)
|
||||
}
|
||||
if info.Commit != "" {
|
||||
t.Fatalf("expected empty commit for non-hex tag, got %q", info.Commit)
|
||||
}
|
||||
if info.Version != "main-2026-04-12" {
|
||||
t.Fatalf("expected version main-2026-04-12, got %q", info.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWebSocketRejectsUnknownOrigin(t *testing.T) {
|
||||
t.Setenv("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus")
|
||||
|
||||
|
||||
@ -4,7 +4,25 @@ set -euo pipefail
|
||||
TARGET_HOST="${1:?target host is required}"
|
||||
RUN_APPLY="${2:?run_apply flag is required}"
|
||||
PLAYBOOK_DIR="${3:-playbooks}"
|
||||
XWORKMATE_BRIDGE_ARTIFACT_PATH="${XWORKMATE_BRIDGE_ARTIFACT_PATH:?artifact path is required}"
|
||||
XWORKMATE_BRIDGE_IMAGE_ARTIFACT_PATH="${XWORKMATE_BRIDGE_IMAGE_ARTIFACT_PATH:?image artifact path is required}"
|
||||
|
||||
if [[ ! -f "${XWORKMATE_BRIDGE_IMAGE_ARTIFACT_PATH}" ]]; then
|
||||
echo "image artifact not found at ${XWORKMATE_BRIDGE_IMAGE_ARTIFACT_PATH}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SERVICE_COMPOSE_IMAGE="$(tr -d '\n' < "${XWORKMATE_BRIDGE_IMAGE_ARTIFACT_PATH}" | xargs)"
|
||||
if [[ -z "${SERVICE_COMPOSE_IMAGE}" ]]; then
|
||||
echo "service compose image is empty" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
image_no_digest="${SERVICE_COMPOSE_IMAGE%@*}"
|
||||
image_tag="${image_no_digest##*:}"
|
||||
if [[ -z "${image_tag}" || "${image_no_digest}" == "${image_tag}" ]]; then
|
||||
echo "invalid service image ref: ${SERVICE_COMPOSE_IMAGE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "${PLAYBOOK_DIR}"
|
||||
|
||||
@ -13,7 +31,6 @@ args=(
|
||||
-i inventory.ini
|
||||
deploy_xworkmate_bridge_vhosts.yml
|
||||
-l "${TARGET_HOST}"
|
||||
-e "xworkmate_bridge_artifact_path=${XWORKMATE_BRIDGE_ARTIFACT_PATH}"
|
||||
)
|
||||
|
||||
if [[ "${RUN_APPLY}" != "true" ]]; then
|
||||
@ -21,4 +38,7 @@ if [[ "${RUN_APPLY}" != "true" ]]; then
|
||||
fi
|
||||
|
||||
ANSIBLE_CONFIG="${PWD}/ansible.cfg" \
|
||||
SERVICE_COMPOSE_IMAGE="${SERVICE_COMPOSE_IMAGE}" \
|
||||
GHCR_USERNAME="${GHCR_USERNAME:-}" \
|
||||
GHCR_PASSWORD="${GHCR_PASSWORD:-}" \
|
||||
"${args[@]}"
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE_REF="${1:?image_ref is required}"
|
||||
|
||||
normalize_url() {
|
||||
local value="$1"
|
||||
if [[ "${value}" =~ ^https:([^/].*)$ ]]; then
|
||||
@ -27,12 +29,31 @@ websocket_probe_url() {
|
||||
printf '%s\n' "${value}"
|
||||
}
|
||||
|
||||
BASE_URL="$(normalize_url "${BRIDGE_SERVER_URL:-${1:-https://xworkmate-bridge.svc.plus}}")"
|
||||
OPENCLAW_HTTP_PROBE_URL="$(websocket_probe_url "${OPENCLAW_URL:-${2:-wss://openclaw.svc.plus}}")"
|
||||
CODEX_RPC_URL="$(normalize_url "${CODEX_RPC_URL:-${3:-https://acp-server.svc.plus/codex/acp/rpc}}")"
|
||||
OPENCODE_RPC_URL="$(normalize_url "${OPENCODE_RPC_URL:-${4:-https://acp-server.svc.plus/opencode/acp/rpc}}")"
|
||||
GEMINI_RPC_URL="$(normalize_url "${GEMINI_RPC_URL:-${5:-https://acp-server.svc.plus/gemini/acp/rpc}}")"
|
||||
AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-${INTERNAL_SERVICE_TOKEN:-${6:-}}}"
|
||||
image_ref="$(printf '%s' "${IMAGE_REF}" | tr -d '\n' | xargs)"
|
||||
if [[ -z "${image_ref}" ]]; then
|
||||
echo "image_ref is required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
image_no_digest="${image_ref%@*}"
|
||||
tag="${image_no_digest##*:}"
|
||||
if [[ "${image_no_digest}" == "${tag}" ]]; then
|
||||
tag=""
|
||||
fi
|
||||
|
||||
commit=""
|
||||
version="${tag}"
|
||||
|
||||
if [[ "${tag}" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
commit="${tag}"
|
||||
fi
|
||||
|
||||
BASE_URL="$(normalize_url "${BRIDGE_SERVER_URL:-${2:-https://xworkmate-bridge.svc.plus}}")"
|
||||
OPENCLAW_HTTP_PROBE_URL="$(websocket_probe_url "${OPENCLAW_URL:-${3:-wss://openclaw.svc.plus}}")"
|
||||
CODEX_RPC_URL="$(normalize_url "${CODEX_RPC_URL:-${4:-https://acp-server.svc.plus/codex/acp/rpc}}")"
|
||||
OPENCODE_RPC_URL="$(normalize_url "${OPENCODE_RPC_URL:-${5:-https://acp-server.svc.plus/opencode/acp/rpc}}")"
|
||||
GEMINI_RPC_URL="$(normalize_url "${GEMINI_RPC_URL:-${6:-https://acp-server.svc.plus/gemini/acp/rpc}}")"
|
||||
AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-${INTERNAL_SERVICE_TOKEN:-${7:-}}}"
|
||||
|
||||
curl_common=(
|
||||
--silent
|
||||
@ -93,6 +114,36 @@ probe_safe_http_endpoint() {
|
||||
esac
|
||||
}
|
||||
|
||||
ping_json="$(
|
||||
curl \
|
||||
"${curl_common[@]}" \
|
||||
"${BASE_URL}/api/ping"
|
||||
)"
|
||||
|
||||
PING_JSON="${ping_json}" python3 - "${image_ref}" "${tag}" "${commit}" "${version}" <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
image_ref, tag, commit, version = sys.argv[1:5]
|
||||
payload = json.loads(os.environ["PING_JSON"])
|
||||
|
||||
if payload.get("status") != "ok":
|
||||
raise SystemExit("ping status not ok")
|
||||
|
||||
if payload.get("image") != image_ref:
|
||||
raise SystemExit(f"expected image {image_ref!r}, got {payload.get('image')!r}")
|
||||
|
||||
if tag and payload.get("tag") != tag:
|
||||
raise SystemExit(f"expected tag {tag!r}, got {payload.get('tag')!r}")
|
||||
|
||||
if commit and payload.get("commit") != commit:
|
||||
raise SystemExit(f"expected commit {commit!r}, got {payload.get('commit')!r}")
|
||||
|
||||
if version and payload.get("version") != version:
|
||||
raise SystemExit(f"expected version {version!r}, got {payload.get('version')!r}")
|
||||
PY
|
||||
|
||||
bridge_root="$(curl "${curl_common[@]}" "${auth_headers[@]}" "${BASE_URL}/")"
|
||||
grep -qi 'xworkmate-bridge' <<<"${bridge_root}"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user