Merge branch 'main' into release/v0.2

This commit is contained in:
Haitao Pan 2026-03-17 20:02:33 +08:00
commit a60788c50c
6 changed files with 122 additions and 35 deletions

View File

@ -26,6 +26,9 @@ runs:
# main → latest
type=raw,enable=${{ github.ref == 'refs/heads/main' }},value=latest
# commit → short sha
type=sha,format=short,prefix=
# release tagv1.2.3
type=ref,event=tag
type=semver,pattern={{version}}

View File

@ -145,10 +145,17 @@ jobs:
# -------------------------------------------------------------
- uses: actions/checkout@v4
- name: Resolve short sha tag
id: vars
shell: bash
run: |
set -euo pipefail
echo "sha_short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}@${{ steps.build.outputs.digest }}
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}:${{ steps.vars.outputs.sha_short }}
output-file: sbom.spdx.json
- uses: actions/upload-artifact@v4
@ -161,7 +168,7 @@ jobs:
# -------------------------------------------------------------
- uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}@${{ steps.build.outputs.digest }}
image-ref: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}:${{ steps.vars.outputs.sha_short }}
severity: HIGH,CRITICAL
exit-code: '1'
@ -173,5 +180,5 @@ jobs:
env:
COSIGN_EXPERIMENTAL: "true"
run: |
COSIGN_IMAGE=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}@${{ steps.build.outputs.digest }}
COSIGN_IMAGE=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.image.name }}:${{ steps.vars.outputs.sha_short }}
cosign sign --yes "$COSIGN_IMAGE"

View File

@ -83,10 +83,10 @@ jobs:
strategy:
matrix:
service:
- { name: account, workdir: account, dockerfile: account/Dockerfile }
- { name: dashboard, workdir: dashboard, dockerfile: dashboard/Dockerfile }
- { name: rag-server, workdir: rag-server, dockerfile: rag-server/Dockerfile }
- { name: xcontrol-init, workdir: ., dockerfile: xcontrol-init/Dockerfile }
- { name: account, image: accounts, workdir: account, dockerfile: account/Dockerfile }
- { name: dashboard, image: dashboard, workdir: dashboard, dockerfile: dashboard/Dockerfile }
- { name: rag-server, image: rag-server, workdir: rag-server, dockerfile: rag-server/Dockerfile }
- { name: xcontrol-init, image: xcontrol-init, workdir: ., dockerfile: xcontrol-init/Dockerfile }
steps:
# -------------------------------------------------------------
@ -110,7 +110,7 @@ jobs:
id: meta
uses: ./.github/actions/auto-tag
with:
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.image }}
# -------------------------------------------------------------
# Docker Buildx setup
@ -157,9 +157,9 @@ jobs:
run: |
set -euo pipefail
SERVICE="${{ matrix.service.name }}"
ORIGIN_IMG="${{ env.REGISTRY }}/${{ env.ORG }}/${SERVICE}@${{ steps.build.outputs.digest }}"
TARGET_REPO="docker.io/${TARGET_NS}/${SERVICE}"
IMAGE_NAME="${{ matrix.service.image }}"
ORIGIN_IMG="${{ env.REGISTRY }}/${{ env.ORG }}/${IMAGE_NAME}@${{ steps.build.outputs.digest }}"
TARGET_REPO="docker.io/${TARGET_NS}/${IMAGE_NAME}"
TAG="latest"
docker pull "$ORIGIN_IMG"
@ -173,10 +173,10 @@ jobs:
strategy:
matrix:
service:
- { name: dashboard, workdir: dashboard, dockerfile: dashboard/Dockerfile }
- { name: account, workdir: account, dockerfile: account/Dockerfile }
- { name: rag-server, workdir: rag-server, dockerfile: rag-server/Dockerfile }
- { name: xcontrol-init, workdir: ., dockerfile: xcontrol-init/Dockerfile }
- { name: dashboard, image: dashboard, workdir: dashboard, dockerfile: dashboard/Dockerfile }
- { name: account, image: accounts, workdir: account, dockerfile: account/Dockerfile }
- { name: rag-server, image: rag-server, workdir: rag-server, dockerfile: rag-server/Dockerfile }
- { name: xcontrol-init, image: xcontrol-init, workdir: ., dockerfile: xcontrol-init/Dockerfile }
steps:
# -------------------------------------------------------------
@ -184,17 +184,24 @@ jobs:
# -------------------------------------------------------------
- uses: actions/checkout@v4
- name: Resolve short sha tag
id: vars
shell: bash
run: |
set -euo pipefail
echo "sha_short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
# -------------------------------------------------------------
# SBOM Generation
# -------------------------------------------------------------
- uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}@${{ steps.build.outputs.digest }}
image: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.image }}:${{ steps.vars.outputs.sha_short }}
output-file: sbom.spdx.json
- uses: actions/upload-artifact@v4
with:
name: sbom-${{ matrix.service.name }}
name: sbom-${{ matrix.service.image }}
path: sbom.spdx.json
# -------------------------------------------------------------
@ -202,7 +209,7 @@ jobs:
# -------------------------------------------------------------
- uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}@${{ steps.build.outputs.digest }}
image-ref: ${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.image }}:${{ steps.vars.outputs.sha_short }}
severity: HIGH,CRITICAL
exit-code: '1'
@ -217,5 +224,5 @@ jobs:
env:
COSIGN_EXPERIMENTAL: "true"
run: |
IMG=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.name }}@${{ steps.build.outputs.digest }}
IMG=${{ env.REGISTRY }}/${{ env.ORG }}/${{ matrix.service.image }}:${{ steps.vars.outputs.sha_short }}
cosign sign --yes "$IMG"

View File

@ -30,13 +30,13 @@ jobs:
IMAGES=(
"ghcr.io/cloud-neutral-toolkit/openresty-geoip"
"ghcr.io/cloud-neutral-toolkit/postgres-runtime"
"ghcr.io/cloud-neutral-toolkit/account"
"ghcr.io/cloud-neutral-toolkit/accounts"
"ghcr.io/cloud-neutral-toolkit/dashboard"
"ghcr.io/cloud-neutral-toolkit/rag-server"
"ghcr.io/cloud-neutral-toolkit/xcontrol-init"
"docker.io/cloudneutral/openresty-geoip"
"docker.io/cloudneutral/postgres-runtime"
"docker.io/cloudneutral/account"
"docker.io/cloudneutral/accounts"
"docker.io/cloudneutral/dashboard"
"docker.io/cloudneutral/rag-server"
"docker.io/cloudneutral/xcontrol-init"

View File

@ -112,6 +112,14 @@ type testEmailSender struct {
messages []capturedEmail
}
type stubAgentStatusReader struct {
statuses []agentserver.StatusSnapshot
}
func (s stubAgentStatusReader) Statuses() []agentserver.StatusSnapshot {
return append([]agentserver.StatusSnapshot(nil), s.statuses...)
}
func (s *testEmailSender) Send(ctx context.Context, msg EmailMessage) error {
_ = ctx
s.mu.Lock()
@ -643,6 +651,60 @@ func TestSyncConfigSnapshotFallsBackWhenRenderFails(t *testing.T) {
}
}
func TestSyncConfigSnapshotIncludesNodeDisplayMetadata(t *testing.T) {
gin.SetMode(gin.TestMode)
router, _, token := newAuthenticatedSyncHarness(t, WithAgentStatusReader(stubAgentStatusReader{
statuses: []agentserver.StatusSnapshot{
{Agent: agentserver.Identity{ID: "jp-xhttp.svc.plus", Name: "Japan Node"}},
{Agent: agentserver.Identity{ID: "us-xhttp.svc.plus", Name: "US Node"}},
},
}))
req := httptest.NewRequest(http.MethodGet, "/api/auth/sync/config?since_version=0", nil)
req.Header.Set("Authorization", "Bearer "+token)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected sync config success, got %d: %s", rr.Code, rr.Body.String())
}
resp := decodeSyncConfigResponse(t, rr)
if len(resp.Nodes) != 2 {
t.Fatalf("expected 2 nodes, got %d", len(resp.Nodes))
}
byHost := make(map[string]map[string]interface{}, len(resp.Nodes))
for _, node := range resp.Nodes {
host, _ := node["host"].(string)
if strings.TrimSpace(host) == "" {
t.Fatalf("expected node host to be populated: %#v", node)
}
byHost[host] = node
}
jp := byHost["jp-xhttp.svc.plus"]
if jp == nil {
t.Fatalf("expected jp-xhttp.svc.plus node in response: %#v", byHost)
}
if got, _ := jp["id"].(string); got != "jp-xhttp.svc.plus" {
t.Fatalf("expected node id to match host, got %q", got)
}
if got, _ := jp["name"].(string); got != "Japan Node" {
t.Fatalf("expected node name to preserve display name, got %q", got)
}
if got, _ := jp["display_name"].(string); got != "Japan Node" {
t.Fatalf("expected display_name to preserve display name, got %q", got)
}
if got, _ := jp["server_name"].(string); got != "jp-xhttp.svc.plus" {
t.Fatalf("expected server_name to match host, got %q", got)
}
if got, _ := jp["uri_scheme_xhttp"].(string); strings.TrimSpace(got) == "" {
t.Fatalf("expected uri_scheme_xhttp to be populated")
}
}
func TestResendVerificationEndpoint(t *testing.T) {
gin.SetMode(gin.TestMode)

View File

@ -92,6 +92,7 @@ func (h *handler) respondSyncConfigSnapshot(c *gin.Context) {
for _, host := range hosts {
nodeName := resolveNodeName(host, registeredNames)
countryCode := countryCodeForHost(host)
nodeID := host
vlessURI := renderVLESSURIScheme(xhttpScheme, map[string]string{
"UUID": proxyUUID,
"DOMAIN": host,
@ -104,8 +105,10 @@ func (h *handler) respondSyncConfigSnapshot(c *gin.Context) {
})
profiles = append(profiles, gin.H{
"id": strings.TrimSpace(user.ID),
"id": nodeID,
"remark": nodeName,
"display_name": nodeName,
"host": host,
"address": host,
"port": 443,
"uuid": proxyUUID,
@ -117,19 +120,24 @@ func (h *handler) respondSyncConfigSnapshot(c *gin.Context) {
"vless_uri": vlessURI,
})
nodes = append(nodes, gin.H{
"id": strings.TrimSpace(user.ID),
"name": nodeName,
"protocol": "vless",
"transport": "xhttp",
"security": "tls",
"address": host,
"port": 443,
"uuid": proxyUUID,
"flow": "",
"source": "server",
"country_code": countryCode,
"updated_at": updatedAt,
"vless_uri": vlessURI,
"id": nodeID,
"name": nodeName,
"display_name": nodeName,
"remark": nodeName,
"host": host,
"protocol": "vless",
"transport": "xhttp",
"security": "tls",
"address": host,
"port": 443,
"server_name": host,
"uuid": proxyUUID,
"flow": "",
"source": "server",
"country_code": countryCode,
"updated_at": updatedAt,
"vless_uri": vlessURI,
"uri_scheme_xhttp": vlessURI,
})
}
}