Skip to content

fix(relay): restore commandline bundle CI #16

fix(relay): restore commandline bundle CI

fix(relay): restore commandline bundle CI #16

Workflow file for this run

name: Relay CI
on:
push:
branches:
- "**"
paths:
- "relay/**"
pull_request:
paths:
- "relay/**"
permissions:
contents: write
env:
BUNDLED_NODE_VERSION: "24.14.1"
CHROME_DEVTOOLS_MCP_VERSION: "0.20.0"
BUNDLED_PYTHON_VERSION: "3.13.10"
BUNDLED_PYTHON_STANDALONE_RELEASE: "20251202"
BUNDLED_WINDOWS_GIT_VERSION: "2.49.0.windows.1"
BUNDLED_FFMPEG_RELEASE_TAG: "n7.1-2"
COMMANDLINE_PACKAGE_PROFILE: "default-data-v5"
PIP_INDEX_URL: "https://pypi.org/simple"
ENABLE_COS_UPLOAD: ${{ vars.ENABLE_COS_UPLOAD || '' }}
jobs:
prepare-cli-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.select.outputs.matrix }}
selected_platforms: ${{ steps.select.outputs.selected_platforms }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Select CLI target platforms
id: select
env:
MATRIX_JSON: >-
[
{"goos":"linux","goarch":"amd64","suffix":"linux-amd64"},
{"goos":"linux","goarch":"arm64","suffix":"linux-arm64"},
{"goos":"darwin","goarch":"amd64","suffix":"darwin-amd64"},
{"goos":"darwin","goarch":"arm64","suffix":"darwin-arm64"},
{"goos":"windows","goarch":"amd64","suffix":"windows-amd64","ext":".exe"}
]
REQUESTED_PLATFORMS: ${{ vars.RELAY_CLI_BUILD_PLATFORMS || 'all' }}
run: python3 .github/scripts/filter-platform-matrix.py
prepare-gui-matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.select.outputs.matrix }}
selected_platforms: ${{ steps.select.outputs.selected_platforms }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Select GUI target platforms
id: select
env:
MATRIX_JSON: >-
[
{"runner":"ubuntu-latest","goos":"linux","goarch":"amd64","suffix":"linux-amd64","wails_tags":"desktop_cua,webkit2_41","cgo_ldflags_allow":"","macos_deployment_target":"","wails_extra_args":"","setup":"sudo apt-get update -qq\nsudo apt-get install -y -qq libgtk-3-dev libwebkit2gtk-4.1-dev desktop-file-utils patchelf squashfs-tools"},
{"runner":"macos-15","goos":"darwin","goarch":"arm64","suffix":"darwin-arm64","wails_tags":"desktop_cua","cgo_ldflags_allow":"^(-weak_framework|ScreenCaptureKit)$","macos_deployment_target":"14.4","wails_extra_args":"","setup":""},
{"runner":"macos-15-intel","goos":"darwin","goarch":"amd64","suffix":"darwin-amd64","wails_tags":"desktop_cua","cgo_ldflags_allow":"^(-weak_framework|ScreenCaptureKit)$","macos_deployment_target":"14.4","wails_extra_args":"","setup":""},
{"runner":"windows-latest","goos":"windows","goarch":"amd64","suffix":"windows-amd64","ext":".exe","wails_tags":"desktop_cua","cgo_ldflags_allow":"","macos_deployment_target":"","wails_extra_args":"-nsis","setup":"pwsh -File .github/scripts/setup-windows-gui-build.ps1"}
]
REQUESTED_PLATFORMS: ${{ vars.RELAY_GUI_BUILD_PLATFORMS || 'all' }}
run: python3 .github/scripts/filter-platform-matrix.py
prepare-bundle-matrix:
needs:
- prepare-cli-matrix
- prepare-gui-matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.union.outputs.matrix }}
selected_platforms: ${{ steps.union.outputs.selected_platforms }}
steps:
- name: Combine bundle target platforms
id: union
env:
CLI_MATRIX: ${{ needs.prepare-cli-matrix.outputs.matrix }}
GUI_MATRIX: ${{ needs.prepare-gui-matrix.outputs.matrix }}
run: |
python3 - <<'PY'
import json
import os
cli = json.loads(os.environ["CLI_MATRIX"]).get("include", [])
gui = json.loads(os.environ["GUI_MATRIX"]).get("include", [])
selected = []
seen = set()
for item in cli + gui:
suffix = item["suffix"]
if suffix in seen:
continue
seen.add(suffix)
selected.append(
{
"goos": item["goos"],
"goarch": item["goarch"],
"suffix": suffix,
}
)
outputs = {
"matrix": json.dumps({"include": selected}, separators=(",", ":")),
"selected_platforms": ",".join(item["suffix"] for item in selected),
}
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as handle:
for key, value in outputs.items():
handle.write(f"{key}={value}\n")
PY
prepare-bundles:
needs:
- prepare-bundle-matrix
runs-on: ubuntu-latest
if: needs.prepare-bundle-matrix.outputs.selected_platforms != ''
strategy:
matrix: ${{ fromJSON(needs.prepare-bundle-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Restore shared runtime bundle cache
id: runtime-cache
uses: actions/cache@v4
with:
path: |
relay/internal/nodebundle/assets
relay/internal/chromemcpbundle/assets
relay/internal/commandlinebundle/assets
key: relay-runtime-bundle-${{ runner.os }}-${{ matrix.suffix }}-${{ env.BUNDLED_NODE_VERSION }}-${{ env.CHROME_DEVTOOLS_MCP_VERSION }}-${{ env.BUNDLED_PYTHON_VERSION }}-${{ env.BUNDLED_PYTHON_STANDALONE_RELEASE }}-${{ env.BUNDLED_WINDOWS_GIT_VERSION }}-${{ env.BUNDLED_FFMPEG_RELEASE_TAG }}-${{ env.COMMANDLINE_PACKAGE_PROFILE }}-${{ hashFiles('.gitmodules', 'relay/managed-command-providers.json', 'relay/scripts/prepare-node-bundle.mjs', 'relay/scripts/prepare-chrome-devtools-bundle.mjs', 'relay/scripts/prepare-commandline-bundle.mjs', 'relay/scripts/lib/subprojects.mjs', 'subprojects/cli-anything/**/agent-harness/setup.py', 'subprojects/cli-anything/**/agent-harness/cli_anything/**') }}
- uses: actions/setup-node@v4
with:
node-version: ${{ env.BUNDLED_NODE_VERSION }}
- uses: actions/setup-python@v5
with:
python-version: ${{ env.BUNDLED_PYTHON_VERSION }}
- name: Install bundle prerequisites
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq p7zip-full
- name: Show shared bundle platforms
run: |
echo "Preparing shared bundles for: ${{ needs.prepare-bundle-matrix.outputs.selected_platforms }}"
- name: Prepare shared Node runtime
if: steps.runtime-cache.outputs.cache-hit != 'true'
run: node relay/scripts/prepare-node-bundle.mjs --target-platform=${{ matrix.goos }}-${{ matrix.goarch }} --node-version=${{ env.BUNDLED_NODE_VERSION }}
- name: Prepare bundled Chrome DevTools runtime
if: steps.runtime-cache.outputs.cache-hit != 'true'
run: node relay/scripts/prepare-chrome-devtools-bundle.mjs --target-platform=${{ matrix.goos }}-${{ matrix.goarch }} --node-version=${{ env.BUNDLED_NODE_VERSION }} --package-version=${{ env.CHROME_DEVTOOLS_MCP_VERSION }}
- name: Prepare bundled command line runtime
if: steps.runtime-cache.outputs.cache-hit != 'true'
run: node relay/scripts/prepare-commandline-bundle.mjs --target-platform=${{ matrix.goos }}-${{ matrix.goarch }} --node-version=${{ env.BUNDLED_NODE_VERSION }} --python-version=${{ env.BUNDLED_PYTHON_VERSION }} --python-standalone-release=${{ env.BUNDLED_PYTHON_STANDALONE_RELEASE }} --windows-git-version=${{ env.BUNDLED_WINDOWS_GIT_VERSION }} --ffmpeg-release-tag=${{ env.BUNDLED_FFMPEG_RELEASE_TAG }} --package-profile=${{ env.COMMANDLINE_PACKAGE_PROFILE }}
- name: Archive shared runtime bundle
run: |
tar -czf relay-runtime-bundle-${{ matrix.suffix }}.tar.gz \
relay/internal/nodebundle/assets \
relay/internal/chromemcpbundle/assets \
relay/internal/commandlinebundle/assets
- name: Upload shared runtime bundle artifact
uses: actions/upload-artifact@v4
with:
name: relay-runtime-bundle-${{ matrix.suffix }}
path: relay-runtime-bundle-${{ matrix.suffix }}.tar.gz
build-cli:
needs:
- prepare-cli-matrix
- prepare-bundles
runs-on: ubuntu-latest
strategy:
matrix: ${{ fromJSON(needs.prepare-cli-matrix.outputs.matrix) }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Determine version
id: version
shell: bash
run: |
echo "version=dev-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
- name: Show selected platforms
run: |
echo "Selected CLI platforms: ${{ needs.prepare-cli-matrix.outputs.selected_platforms }}"
- name: Download shared runtime bundle artifact
uses: actions/download-artifact@v4
with:
name: relay-runtime-bundle-${{ matrix.suffix }}
path: .
- name: Extract shared runtime bundle
shell: bash
run: |
tar -xzf relay-runtime-bundle-${{ matrix.suffix }}.tar.gz
- name: Install Linux FUSE build dependencies
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
run: |
sudo apt-get update -qq
sudo apt-get install -y -qq libfuse-dev libfuse2t64 libx11-dev libxtst-dev libxinerama-dev libxrandr-dev
- name: Build
working-directory: relay
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
run: |
go build -ldflags="-s -w -X main.Version=${{ steps.version.outputs.version }}" \
-o ../synapse-relay-${{ matrix.suffix }}${{ matrix.ext }} ./cmd/synapse-relay
- name: Run Relay VFS unit/build validation
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
working-directory: relay
run: |
bash scripts/validate-vfs-unit.sh
- name: Build Linux mount helper
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
working-directory: relay
env:
CGO_ENABLED: "1"
GOOS: linux
GOARCH: amd64
run: |
go build -tags 'relay_fuse,desktop_cua' -ldflags="-s -w -X main.Version=${{ steps.version.outputs.version }}" \
-o ../synapse-relay-mount-linux-amd64 ./cmd/synapse-relay-mount
- name: Upload artifacts to Tencent COS
if: startsWith(github.ref, 'refs/tags/') || env.ENABLE_COS_UPLOAD == 'true'
continue-on-error: true
env:
ARTIFACT_PATH: synapse-relay-${{ matrix.suffix }}${{ matrix.ext }}
COS_BUCKET: ${{ secrets.COS_BUCKET }}
COS_PREFIX: ${{ github.sha }}
COS_REGION: ${{ secrets.COS_REGION }}
COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }}
COS_SECRET_KEY: ${{ secrets.COS_SECRET_KEY }}
shell: bash
run: |
if [ -z "$COS_SECRET_ID" ] || [ -z "$COS_SECRET_KEY" ] || [ -z "$COS_BUCKET" ] || [ -z "$COS_REGION" ]; then
echo "COS secrets are not configured; skipping backup upload."
exit 0
fi
python3 - <<'PY'
from datetime import UTC, datetime
from email.utils import format_datetime
from pathlib import Path
import hashlib
import hmac
import mimetypes
import os
import sys
import urllib.parse
import urllib.request
artifact_path = Path(os.environ["ARTIFACT_PATH"].strip())
if not artifact_path.is_file():
print(f"Artifact not found: {artifact_path}", file=sys.stderr)
sys.exit(1)
bucket = os.environ["COS_BUCKET"].strip()
prefix = os.environ["COS_PREFIX"].strip().strip("/")
region = os.environ["COS_REGION"].strip()
secret_id = os.environ["COS_SECRET_ID"].strip()
secret_key = os.environ["COS_SECRET_KEY"].strip()
host = f"{bucket}.cos.{region}.myqcloud.com"
def hmac_sha1(key: bytes, msg: str) -> str:
return hmac.new(key, msg.encode("utf-8"), hashlib.sha1).hexdigest()
def urlencode(value: str) -> str:
return urllib.parse.quote(value, safe="-_.~")
def make_auth(method: str, uri_path: str, date_value: str) -> str:
start = int(datetime.now(UTC).timestamp()) - 60
end = start + 3600
key_time = f"{start};{end}"
sign_key = hmac_sha1(secret_key.encode("utf-8"), key_time)
headers = {"Host": host, "Date": date_value}
header_items = sorted((urlencode(k.lower()), urlencode(v.strip())) for k, v in headers.items())
header_list = ";".join(k for k, _ in header_items)
http_headers = "&".join(f"{k}={v}" for k, v in header_items)
http_string = "\n".join([method.lower(), uri_path, "", http_headers, ""])
string_to_sign = "\n".join([
"sha1",
key_time,
hashlib.sha1(http_string.encode()).hexdigest(),
"",
])
signature = hmac_sha1(sign_key.encode("utf-8"), string_to_sign)
return "&".join([
"q-sign-algorithm=sha1",
f"q-ak={secret_id}",
f"q-sign-time={key_time}",
f"q-key-time={key_time}",
f"q-header-list={header_list}",
"q-url-param-list=",
f"q-signature={signature}",
])
object_key = f"{prefix}/{artifact_path.name}" if prefix else artifact_path.name
uri_path = "/" + "/".join(urlencode(part) for part in object_key.split("/"))
date_value = format_datetime(datetime.now(UTC), usegmt=True)
content_type = mimetypes.guess_type(artifact_path.name)[0] or "application/octet-stream"
request = urllib.request.Request(
f"https://{host}{uri_path}",
data=artifact_path.read_bytes(),
method="PUT",
headers={
"Authorization": make_auth("PUT", uri_path, date_value),
"Content-Type": content_type,
"Date": date_value,
"Host": host,
},
)
with urllib.request.urlopen(request, timeout=300) as response:
print(f"Uploaded {object_key} -> {response.status}")
PY
- name: Upload artifact
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: synapse-relay-${{ matrix.suffix }}
path: synapse-relay-${{ matrix.suffix }}${{ matrix.ext }}
- name: Upload Linux mount helper artifact
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: synapse-relay-mount-linux-amd64
path: synapse-relay-mount-linux-amd64
build-gui:
needs:
- prepare-gui-matrix
- prepare-bundles
strategy:
matrix: ${{ fromJSON(needs.prepare-gui-matrix.outputs.matrix) }}
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- uses: actions/setup-node@v4
with:
node-version: ${{ env.BUNDLED_NODE_VERSION }}
- uses: actions/setup-python@v5
with:
python-version: ${{ env.BUNDLED_PYTHON_VERSION }}
- name: Platform setup
if: matrix.setup != ''
run: ${{ matrix.setup }}
- name: Install Wails
run: go install github.com/wailsapp/wails/v2/cmd/wails@v2.11.0
- name: Determine version
id: version
shell: bash
run: |
echo "version=dev-${GITHUB_SHA::8}" >> $GITHUB_OUTPUT
- name: Resolve Windows package variants
id: windows_package_variants
if: matrix.goos == 'windows'
shell: bash
env:
REQUESTED_WINDOWS_VARIANTS: ${{ vars.RELAY_GUI_WINDOWS_PACKAGE_VARIANTS || 'all' }}
run: |
raw="$(printf '%s' "${REQUESTED_WINDOWS_VARIANTS:-all}" | tr '[:upper:]' '[:lower:]' | xargs)"
if [ -z "$raw" ]; then
raw="all"
fi
case "$raw" in
all)
want_portable=true
want_setup=true
;;
portable)
want_portable=true
want_setup=false
;;
setup)
want_portable=false
want_setup=true
;;
*)
echo "::error::Unsupported Windows package variant '$raw'. Use all, portable, or setup."
exit 1
;;
esac
if [ "$want_setup" = true ]; then
wails_extra_args="-nsis"
else
wails_extra_args=""
fi
echo "value=$raw" >> "$GITHUB_OUTPUT"
echo "want_portable=$want_portable" >> "$GITHUB_OUTPUT"
echo "want_setup=$want_setup" >> "$GITHUB_OUTPUT"
echo "wails_extra_args=$wails_extra_args" >> "$GITHUB_OUTPUT"
echo "Resolved Windows package variants: $raw"
- name: Show selected platforms
run: |
echo "Selected GUI platforms: ${{ needs.prepare-gui-matrix.outputs.selected_platforms }}"
- name: Download shared runtime bundle artifact
uses: actions/download-artifact@v4
with:
name: relay-runtime-bundle-${{ matrix.suffix }}
path: .
- name: Extract shared runtime bundle
shell: bash
run: |
tar -xzf relay-runtime-bundle-${{ matrix.suffix }}.tar.gz
- name: Prepare GUI build assets
shell: bash
run: |
mode="portable"
if [ "${{ matrix.goos }}" = "windows" ]; then
mode="packaged"
fi
node relay/scripts/prepare-gui-build-assets.mjs --goos=${{ matrix.goos }} --runtime-mode="$mode"
- name: Build GUI binary
shell: bash
working-directory: relay/cmd/synapse-relay-gui
env:
CGO_LDFLAGS_ALLOW: ${{ matrix.cgo_ldflags_allow }}
run: |
if [ -n "${{ matrix.macos_deployment_target }}" ]; then
export MACOSX_DEPLOYMENT_TARGET="${{ matrix.macos_deployment_target }}"
fi
extra_args="${{ matrix.wails_extra_args }}"
if [ "${{ matrix.goos }}" = "windows" ]; then
extra_args="${{ steps.windows_package_variants.outputs.wails_extra_args }}"
fi
if [ -n "$extra_args" ]; then
wails build "$extra_args" -tags "${{ matrix.wails_tags }},relay_packaged_runtime" -ldflags="-s -w -X main.Version=${{ steps.version.outputs.version }}"
else
wails build -tags "${{ matrix.wails_tags }},relay_packaged_runtime" -ldflags="-s -w -X main.Version=${{ steps.version.outputs.version }}"
fi
- name: Rename artifact
shell: bash
run: |
if [ "${{ matrix.goos }}" = "windows" ]; then
find relay/cmd/synapse-relay-gui/build/bin -maxdepth 1 -type f -print
main_exe="$(find relay/cmd/synapse-relay-gui/build/bin -maxdepth 1 -type f \( -name 'synapse-relay-gui.exe' -o -name 'synapse-relay-gui-windows-*.exe' \) ! -name '*-setup.exe' | head -n 1)"
test -n "$main_exe"
if [ "${{ steps.windows_package_variants.outputs.want_portable }}" = "true" ]; then
portable_root="synapse-relay-gui-${{ matrix.suffix }}-portable"
mkdir -p "$portable_root"
cp "$main_exe" "$portable_root/synapse-relay-gui.exe"
runtime_stage="$(tr -d '\r\n' < relay/cmd/synapse-relay-gui/build/windows/runtime-path.txt)"
runtime_stage_unix="$(cygpath -u "$runtime_stage")"
mkdir -p "$portable_root/runtime"
cp -R "$runtime_stage_unix"/. "$portable_root/runtime"/
portable_zip="synapse-relay-gui-${{ matrix.suffix }}-portable.zip"
portable_root_win="$(cygpath -w "$portable_root")"
portable_zip_win="$(cygpath -w "$portable_zip")"
powershell.exe -NoLogo -NoProfile -Command "\$ErrorActionPreference='Stop'; Add-Type -AssemblyName 'System.IO.Compression.FileSystem'; if (Test-Path -LiteralPath '$portable_zip_win') { Remove-Item -LiteralPath '$portable_zip_win' -Force }; [System.IO.Compression.ZipFile]::CreateFromDirectory('$portable_root_win', '$portable_zip_win', [System.IO.Compression.CompressionLevel]::Optimal, \$false)"
fi
if [ "${{ steps.windows_package_variants.outputs.want_setup }}" = "true" ]; then
setup_exe="$(find relay/cmd/synapse-relay-gui/build/bin -maxdepth 1 -type f \( -name 'synapse-relay-gui*-setup.exe' -o -name 'synapse-relay-gui*-installer.exe' -o -name 'Synapse Relay*-installer.exe' \) | head -n 1)"
test -n "$setup_exe"
mv "$setup_exe" synapse-relay-gui-${{ matrix.suffix }}-setup.exe
fi
elif [ "${{ matrix.goos }}" = "darwin" ]; then
app_path="relay/cmd/synapse-relay-gui/build/bin/Synapse Relay.app"
test -d "$app_path"
bash relay/scripts/package-gui-macos.sh --app-path="$app_path" --suffix="${{ matrix.suffix }}"
else
bash relay/scripts/package-gui-linux.sh \
--binary-path="relay/cmd/synapse-relay-gui/build/bin/synapse-relay-gui" \
--suffix="${{ matrix.suffix }}" \
--version="${{ steps.version.outputs.version }}"
fi
- name: Upload artifacts to Tencent COS
if: startsWith(github.ref, 'refs/tags/') || env.ENABLE_COS_UPLOAD == 'true'
continue-on-error: true
env:
ARTIFACT_GLOB: synapse-relay-gui-${{ matrix.suffix }}*
COS_BUCKET: ${{ secrets.COS_BUCKET }}
COS_PREFIX: ${{ github.sha }}
COS_REGION: ${{ secrets.COS_REGION }}
COS_SECRET_ID: ${{ secrets.COS_SECRET_ID }}
COS_SECRET_KEY: ${{ secrets.COS_SECRET_KEY }}
shell: bash
run: |
if [ -z "$COS_SECRET_ID" ] || [ -z "$COS_SECRET_KEY" ] || [ -z "$COS_BUCKET" ] || [ -z "$COS_REGION" ]; then
echo "COS secrets are not configured; skipping backup upload."
exit 0
fi
python3 - <<'PY'
from datetime import UTC, datetime
from email.utils import format_datetime
from pathlib import Path
import glob
import hashlib
import hmac
import mimetypes
import os
import sys
import urllib.parse
import urllib.request
bucket = os.environ["COS_BUCKET"].strip()
prefix = os.environ["COS_PREFIX"].strip().strip("/")
region = os.environ["COS_REGION"].strip()
secret_id = os.environ["COS_SECRET_ID"].strip()
secret_key = os.environ["COS_SECRET_KEY"].strip()
artifact_glob = os.environ["ARTIFACT_GLOB"].strip()
host = f"{bucket}.cos.{region}.myqcloud.com"
def hmac_sha1(key: bytes, msg: str) -> str:
return hmac.new(key, msg.encode("utf-8"), hashlib.sha1).hexdigest()
def urlencode(value: str) -> str:
return urllib.parse.quote(value, safe="-_.~")
def make_auth(method: str, uri_path: str, date_value: str) -> str:
start = int(datetime.now(UTC).timestamp()) - 60
end = start + 3600
key_time = f"{start};{end}"
sign_key = hmac_sha1(secret_key.encode("utf-8"), key_time)
headers = {"Host": host, "Date": date_value}
header_items = sorted((urlencode(k.lower()), urlencode(v.strip())) for k, v in headers.items())
header_list = ";".join(k for k, _ in header_items)
http_headers = "&".join(f"{k}={v}" for k, v in header_items)
http_string = "\n".join([method.lower(), uri_path, "", http_headers, ""])
string_to_sign = "\n".join([
"sha1",
key_time,
hashlib.sha1(http_string.encode()).hexdigest(),
"",
])
signature = hmac_sha1(sign_key.encode("utf-8"), string_to_sign)
return "&".join([
"q-sign-algorithm=sha1",
f"q-ak={secret_id}",
f"q-sign-time={key_time}",
f"q-key-time={key_time}",
f"q-header-list={header_list}",
"q-url-param-list=",
f"q-signature={signature}",
])
files = sorted(Path(path) for path in glob.glob(artifact_glob) if Path(path).is_file())
if not files:
print(f"No artifact files matched {artifact_glob}; skipping COS upload.")
sys.exit(0)
failures = []
for path in files:
object_key = f"{prefix}/{path.name}" if prefix else path.name
uri_path = "/" + "/".join(urlencode(part) for part in object_key.split("/"))
date_value = format_datetime(datetime.now(UTC), usegmt=True)
content_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream"
request = urllib.request.Request(
f"https://{host}{uri_path}",
data=path.read_bytes(),
method="PUT",
headers={
"Authorization": make_auth("PUT", uri_path, date_value),
"Content-Type": content_type,
"Date": date_value,
"Host": host,
},
)
try:
with urllib.request.urlopen(request, timeout=300) as response:
print(f"Uploaded {object_key} -> {response.status}")
except Exception as exc:
failures.append((object_key, str(exc)))
if failures:
for object_key, error in failures:
print(f"Failed {object_key}: {error}", file=sys.stderr)
sys.exit(1)
PY
- name: Upload Windows Portable Artifact
if: matrix.goos == 'windows' && steps.windows_package_variants.outputs.want_portable == 'true'
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: synapse-relay-gui-${{ matrix.suffix }}-portable
path: synapse-relay-gui-${{ matrix.suffix }}-portable.zip
- name: Upload Windows Setup Artifact
if: matrix.goos == 'windows' && steps.windows_package_variants.outputs.want_setup == 'true'
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: synapse-relay-gui-${{ matrix.suffix }}-setup
path: synapse-relay-gui-${{ matrix.suffix }}-setup.exe
- name: Upload artifact
if: matrix.goos != 'windows'
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: synapse-relay-gui-${{ matrix.suffix }}
path: synapse-relay-gui-${{ matrix.suffix }}*