Files
geo/.gitea/workflows/desktop-client-build.yml
T
root 21fee01688 chore: scope desktop client build to apps/desktop-client and document regenerate route
Drop the broad path filters that triggered the desktop client build on
unrelated workspace changes, and add a swagger description for the
existing article regenerate endpoint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:08:15 +08:00

729 lines
26 KiB
YAML

name: Desktop Client Build
on:
push:
branches:
- main
paths:
- "apps/desktop-client/**"
workflow_dispatch:
inputs:
platforms:
description: "Build platforms: mac, win, or mac,win"
required: true
default: "mac,win"
mac_arches:
description: "macOS architectures: arm64, x64, or comma-separated. universal requires a macOS runner."
required: true
default: "arm64"
win_arches:
description: "Windows architectures: x64, arm64, ia32, or comma-separated"
required: true
default: "x64"
version_prefix:
description: "Client version prefix. Final version is <prefix>.<patch>."
required: true
default: "0.1"
version_patch:
description: "Optional fixed patch number. Empty means auto-increment."
required: false
default: ""
output_dir:
description: "NAS output directory"
required: true
default: "/vol1/1000/share/gl-test"
host:
description: "NAS SSH host"
required: true
default: "192.168.100.19"
port:
description: "NAS SSH port"
required: true
default: "22"
user:
description: "NAS SSH user"
required: true
default: "admin"
password_source:
description: "NAS SSH password source"
required: true
default: "repository_secret"
type: choice
options:
- repository_secret
- workflow_input
ssh_password:
description: "NAS SSH password. Used only when password_source is workflow_input."
required: false
default: ""
use_sudo:
description: "Use sudo for NAS file operations"
required: true
default: "true"
type: choice
options:
- "true"
- "false"
permissions:
contents: read
concurrency:
group: desktop-client-build
cancel-in-progress: false
env:
DEFAULT_DESKTOP_PLATFORMS: "mac,win"
DEFAULT_MAC_ARCHES: "arm64"
DEFAULT_WIN_ARCHES: "x64"
DEFAULT_VERSION_PREFIX: "0.1"
DEFAULT_NAS_OUTPUT_DIR: "/vol1/1000/share/gl-test"
DEFAULT_NAS_HOST: "192.168.100.19"
DEFAULT_NAS_PORT: "22"
DEFAULT_NAS_USER: "admin"
DEFAULT_NAS_USE_SUDO: "true"
PNPM_VERSION: "10.33.0"
jobs:
resolve:
name: Resolve Build Metadata
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
build_mac: ${{ steps.meta.outputs.build_mac }}
build_win: ${{ steps.meta.outputs.build_win }}
client_version: ${{ steps.meta.outputs.client_version }}
mac_arch_flags: ${{ steps.meta.outputs.mac_arch_flags }}
nas_host: ${{ steps.meta.outputs.nas_host }}
nas_port: ${{ steps.meta.outputs.nas_port }}
nas_user: ${{ steps.meta.outputs.nas_user }}
nas_use_sudo: ${{ steps.meta.outputs.nas_use_sudo }}
output_dir: ${{ steps.meta.outputs.output_dir }}
release_name: ${{ steps.meta.outputs.release_name }}
win_arch_flags: ${{ steps.meta.outputs.win_arch_flags }}
env:
REQUESTED_PLATFORMS: ${{ inputs.platforms }}
REQUESTED_MAC_ARCHES: ${{ inputs.mac_arches }}
REQUESTED_WIN_ARCHES: ${{ inputs.win_arches }}
REQUESTED_VERSION_PREFIX: ${{ inputs.version_prefix }}
REQUESTED_VERSION_PATCH: ${{ inputs.version_patch }}
REQUESTED_OUTPUT_DIR: ${{ inputs.output_dir }}
REQUESTED_NAS_HOST: ${{ inputs.host }}
REQUESTED_NAS_PORT: ${{ inputs.port }}
REQUESTED_NAS_USER: ${{ inputs.user }}
REQUESTED_NAS_USE_SUDO: ${{ inputs.use_sudo }}
NAS_SSH_PASSWORD_SOURCE: ${{ inputs.password_source }}
NAS_SSH_PASSWORD_INPUT: ${{ inputs.ssh_password }}
NAS_SSH_PASSWORD_SECRET: ${{ secrets.NAS_SSH_PASSWORD }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install SSH tools
run: |
set -eu
apt-get update
apt-get install -y openssh-client sshpass
- name: Resolve version and targets
id: meta
run: |
set -eu
normalize_csv() {
printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]'
}
arch_flags() {
value="$(normalize_csv "$1")"
allowed="$2"
result=""
if [ -z "${value}" ]; then
echo "::error::Architecture list cannot be empty"
exit 1
fi
for arch in $(printf '%s' "${value}" | tr ',' ' '); do
found=false
for candidate in ${allowed}; do
if [ "${arch}" = "${candidate}" ]; then
found=true
break
fi
done
if [ "${found}" != "true" ]; then
echo "::error::Unsupported architecture '${arch}'. Allowed: ${allowed}"
exit 1
fi
result="${result} --${arch}"
done
printf '%s' "${result# }"
}
platforms="$(normalize_csv "${REQUESTED_PLATFORMS:-${DEFAULT_DESKTOP_PLATFORMS}}")"
build_mac=false
build_win=false
for platform in $(printf '%s' "${platforms}" | tr ',' ' '); do
case "${platform}" in
mac|macos)
build_mac=true
;;
win|windows)
build_win=true
;;
*)
echo "::error::Unsupported platform '${platform}'. Use mac, win, or mac,win."
exit 1
;;
esac
done
if [ "${build_mac}" != "true" ] && [ "${build_win}" != "true" ]; then
echo "::error::At least one platform must be selected."
exit 1
fi
mac_arches="$(normalize_csv "${REQUESTED_MAC_ARCHES:-${DEFAULT_MAC_ARCHES}}")"
case ",${mac_arches}," in
*,universal,*)
echo "::error::mac universal cannot be built on the current ubuntu-latest runner. Use mac_arches=arm64 or x64, or run this job on a macOS runner."
exit 1
;;
esac
mac_arch_flags="$(arch_flags "${mac_arches}" "x64 arm64")"
win_arch_flags="$(arch_flags "${REQUESTED_WIN_ARCHES:-${DEFAULT_WIN_ARCHES}}" "x64 arm64 ia32")"
version_prefix="${REQUESTED_VERSION_PREFIX:-${DEFAULT_VERSION_PREFIX}}"
if ! printf '%s' "${version_prefix}" | grep -Eq '^[0-9]+\.[0-9]+$'; then
echo "::error::version_prefix must look like 0.1"
exit 1
fi
output_dir="${REQUESTED_OUTPUT_DIR:-${DEFAULT_NAS_OUTPUT_DIR}}"
output_dir="${output_dir%/}"
case "${output_dir}" in
/*) ;;
*)
echo "::error::output_dir must be an absolute NAS path"
exit 1
;;
esac
case "${output_dir}" in
*[!A-Za-z0-9_./-]*)
echo "::error::output_dir contains unsupported characters"
exit 1
;;
esac
nas_host="${REQUESTED_NAS_HOST:-${DEFAULT_NAS_HOST}}"
nas_port="${REQUESTED_NAS_PORT:-${DEFAULT_NAS_PORT}}"
nas_user="${REQUESTED_NAS_USER:-${DEFAULT_NAS_USER}}"
nas_use_sudo="${REQUESTED_NAS_USE_SUDO:-${DEFAULT_NAS_USE_SUDO}}"
case "${nas_port}" in
*[!0-9]*|"")
echo "::error::NAS SSH port must be numeric"
exit 1
;;
esac
case "${nas_host}" in
*[!A-Za-z0-9_.:-]*|"")
echo "::error::NAS host contains unsupported characters"
exit 1
;;
esac
case "${nas_user}" in
*[!A-Za-z0-9_.@-]*|"")
echo "::error::NAS user contains unsupported characters"
exit 1
;;
esac
case "${nas_use_sudo}" in
true|false) ;;
*)
echo "::error::use_sudo must be true or false"
exit 1
;;
esac
if [ -n "${REQUESTED_VERSION_PATCH:-}" ]; then
if ! printf '%s' "${REQUESTED_VERSION_PATCH}" | grep -Eq '^[0-9]+$'; then
echo "::error::version_patch must be a non-negative integer"
exit 1
fi
version_patch="${REQUESTED_VERSION_PATCH}"
else
password_source="${NAS_SSH_PASSWORD_SOURCE:-repository_secret}"
case "${password_source}" in
repository_secret)
nas_ssh_password="${NAS_SSH_PASSWORD_SECRET:-}"
;;
workflow_input)
nas_ssh_password="${NAS_SSH_PASSWORD_INPUT:-}"
;;
*)
echo "::error::password_source must be repository_secret or workflow_input"
exit 1
;;
esac
if [ -n "${nas_ssh_password}" ]; then
echo "::add-mask::${nas_ssh_password}"
fi
if [ -z "${nas_ssh_password}" ]; then
if [ "${password_source}" = "workflow_input" ]; then
echo "::error::Missing workflow input ssh_password"
else
echo "::error::Missing repository secret NAS_SSH_PASSWORD"
fi
exit 1
fi
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -p "${nas_port}" "${nas_host}" >> ~/.ssh/known_hosts
sudo_password_b64="$(printf '%s' "${nas_ssh_password}" | base64 | tr -d '\n')"
remote_names="$(
{
printf 'OUTPUT_DIR=%s\n' "${output_dir}"
printf 'USE_SUDO=%s\n' "${nas_use_sudo}"
printf 'SUDO_PASSWORD_B64=%s\n' "${sudo_password_b64}"
cat <<'REMOTE_SCRIPT'
set -eu
SUDO_PASSWORD="$(printf '%s' "${SUDO_PASSWORD_B64}" | base64 -d 2>/dev/null || true)"
unset SUDO_PASSWORD_B64
run_cmd() {
if [ "${USE_SUDO}" = "true" ]; then
printf '%s\n' "${SUDO_PASSWORD}" | sudo -S -p "" "$@"
else
"$@"
fi
}
run_cmd mkdir -p "${OUTPUT_DIR}"
run_cmd find "${OUTPUT_DIR}" -maxdepth 3 \( -type f -o -type d -o -type l \) -print 2>/dev/null | sed 's#.*/##'
REMOTE_SCRIPT
} | SSHPASS="${nas_ssh_password}" sshpass -e ssh \
-p "${nas_port}" \
-o BatchMode=no \
-o StrictHostKeyChecking=yes \
"${nas_user}@${nas_host}" \
"sh -s"
)"
prefix_regex="$(printf '%s' "${version_prefix}" | sed 's/\./\\./g')"
remote_patch="$(
printf '%s\n' "${remote_names}" \
| sed -n "s/.*${prefix_regex}\.\([0-9][0-9]*\).*/\1/p" \
| sort -n \
| tail -n1
)"
if [ -n "${remote_patch}" ]; then
version_patch=$((remote_patch + 1))
else
version_patch=0
fi
fi
client_version="${version_prefix}.${version_patch}"
release_name="desktop-client-${client_version}"
{
echo "build_mac=${build_mac}"
echo "build_win=${build_win}"
echo "client_version=${client_version}"
echo "mac_arch_flags=${mac_arch_flags}"
echo "nas_host=${nas_host}"
echo "nas_port=${nas_port}"
echo "nas_user=${nas_user}"
echo "nas_use_sudo=${nas_use_sudo}"
echo "output_dir=${output_dir}"
echo "release_name=${release_name}"
echo "win_arch_flags=${win_arch_flags}"
} >> "${GITHUB_OUTPUT}"
echo "Version: ${client_version}"
echo "Platforms: mac=${build_mac}, win=${build_win}"
echo "macOS arch flags: ${mac_arch_flags}"
echo "Windows arch flags: ${win_arch_flags}"
echo "Use sudo on NAS: ${nas_use_sudo}"
echo "NAS output: ${nas_user}@${nas_host}:${output_dir}/${release_name}"
build-client:
name: Build Desktop Client
needs: resolve
runs-on: ubuntu-latest
timeout-minutes: 120
env:
BUILD_MAC: ${{ needs.resolve.outputs.build_mac }}
BUILD_WIN: ${{ needs.resolve.outputs.build_win }}
CLIENT_VERSION: ${{ needs.resolve.outputs.client_version }}
MAC_ARCH_FLAGS: ${{ needs.resolve.outputs.mac_arch_flags }}
WIN_ARCH_FLAGS: ${{ needs.resolve.outputs.win_arch_flags }}
CSC_IDENTITY_AUTO_DISCOVERY: "false"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Setup pnpm
run: |
set -eu
corepack enable
corepack prepare pnpm@${PNPM_VERSION} --activate
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Wine for Windows packaging
if: needs.resolve.outputs.build_win == 'true'
run: |
set -eu
if command -v wine >/dev/null 2>&1 && command -v xvfb-run >/dev/null 2>&1; then
wine --version
if dpkg --print-foreign-architectures | grep -qx i386 && dpkg -s wine32:i386 >/dev/null 2>&1; then
exit 0
fi
fi
dpkg --add-architecture i386
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
wine \
wine64 \
wine32:i386 \
xvfb
wine --version
- name: Build unsigned desktop packages
working-directory: apps/desktop-client
run: |
set -eu
unset CSC_LINK CSC_NAME CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID WIN_CSC_LINK WIN_CSC_KEY_PASSWORD
export CSC_IDENTITY_AUTO_DISCOVERY=false
node -e 'const fs = require("fs"); const p = "package.json"; const pkg = JSON.parse(fs.readFileSync(p, "utf8")); pkg.version = process.env.CLIENT_VERSION; fs.writeFileSync(p, `${JSON.stringify(pkg, null, 2)}\n`); console.log(`Desktop client version: ${pkg.version}`);'
rm -rf release
pnpm run build:obf
if [ "${BUILD_MAC}" = "true" ]; then
pnpm exec electron-builder --mac zip ${MAC_ARCH_FLAGS} --publish never -c.mac.target=zip -c.extraMetadata.version="${CLIENT_VERSION}"
fi
if [ "${BUILD_WIN}" = "true" ]; then
xvfb-run -a --server-args="-screen 0 1024x768x24" \
pnpm exec electron-builder --win zip ${WIN_ARCH_FLAGS} --publish never -c.win.target=zip -c.extraMetadata.version="${CLIENT_VERSION}"
fi
- name: Collect desktop artifacts
working-directory: apps/desktop-client
run: |
set -eu
artifact_root="${RUNNER_TEMP}/desktop-client-artifacts"
mkdir -p "${artifact_root}"
if [ "${BUILD_MAC}" = "true" ]; then
mkdir -p "${artifact_root}/mac"
mac_zip_count="$(find release -maxdepth 1 -type f -name "*.zip" ! -iname "*win*" ! -iname "*windows*" | wc -l | tr -d ' ')"
if [ "${mac_zip_count}" = "0" ]; then
echo "::error::No macOS zip artifact found"
find release -maxdepth 1 -type f -name "*.zip" -print
exit 1
fi
if [ "${mac_zip_count}" != "1" ]; then
echo "::error::Expected exactly one macOS zip artifact, found ${mac_zip_count}."
find release -maxdepth 1 -type f -name "*.zip" -print
exit 1
fi
mac_zip="$(find release -maxdepth 1 -type f -name "*.zip" ! -iname "*win*" ! -iname "*windows*" | head -n1)"
cp -p "${mac_zip}" "${artifact_root}/mac/省心推-mac-${CLIENT_VERSION}.zip"
fi
if [ "${BUILD_WIN}" = "true" ]; then
mkdir -p "${artifact_root}/win"
win_zip_count="$(find release -maxdepth 1 -type f -name "*.zip" \( -iname "*win*" -o -iname "*windows*" \) | wc -l | tr -d ' ')"
if [ "${win_zip_count}" = "0" ]; then
echo "::error::No Windows zip artifact found"
find release -maxdepth 1 -type f -name "*.zip" -print
exit 1
fi
if [ "${win_zip_count}" != "1" ]; then
echo "::error::Expected exactly one Windows zip artifact, found ${win_zip_count}."
find release -maxdepth 1 -type f -name "*.zip" -print
exit 1
fi
win_zip="$(find release -maxdepth 1 -type f -name "*.zip" \( -iname "*win*" -o -iname "*windows*" \) | head -n1)"
cp -p "${win_zip}" "${artifact_root}/win/省心推-win-${CLIENT_VERSION}.zip"
fi
find "${artifact_root}" -maxdepth 3 -type f -print
- name: Upload desktop artifacts
uses: actions/upload-artifact@v3
with:
name: desktop-client
path: ${{ runner.temp }}/desktop-client-artifacts
if-no-files-found: error
retention-days: 14
publish-to-nas:
name: Publish Client Artifacts to NAS
needs:
- resolve
- build-client
if: >-
always() &&
needs.resolve.result == 'success' &&
needs.build-client.result == 'success'
runs-on: ubuntu-latest
timeout-minutes: 30
env:
CLIENT_VERSION: ${{ needs.resolve.outputs.client_version }}
NAS_HOST: ${{ needs.resolve.outputs.nas_host }}
NAS_PORT: ${{ needs.resolve.outputs.nas_port }}
NAS_USER: ${{ needs.resolve.outputs.nas_user }}
NAS_USE_SUDO: ${{ needs.resolve.outputs.nas_use_sudo }}
OUTPUT_DIR: ${{ needs.resolve.outputs.output_dir }}
RELEASE_NAME: ${{ needs.resolve.outputs.release_name }}
NAS_SSH_PASSWORD_SOURCE: ${{ inputs.password_source }}
NAS_SSH_PASSWORD_INPUT: ${{ inputs.ssh_password }}
NAS_SSH_PASSWORD_SECRET: ${{ secrets.NAS_SSH_PASSWORD }}
steps:
- name: Install SSH tools
run: |
set -eu
apt-get update
apt-get install -y openssh-client sshpass tar
- name: Download client artifacts
uses: actions/download-artifact@v3
with:
name: desktop-client
path: desktop-artifacts
- name: Write manifest
run: |
set -eu
artifact_count="$(find desktop-artifacts -type f | wc -l | tr -d ' ')"
if [ "${artifact_count}" = "0" ]; then
echo "::error::No downloaded artifacts found"
exit 1
fi
printf '%s\n' "${CLIENT_VERSION}" > desktop-artifacts/VERSION
cat > desktop-artifacts/manifest.json <<EOF
{
"app": "desktop-client",
"version": "${CLIENT_VERSION}",
"releaseName": "${RELEASE_NAME}",
"commit": "${GITHUB_SHA}",
"runNumber": "${GITHUB_RUN_NUMBER}",
"runId": "${GITHUB_RUN_ID}",
"repository": "${GITHUB_REPOSITORY}",
"createdAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
}
EOF
find desktop-artifacts -maxdepth 3 -type f -print
- name: Upload artifacts to NAS
run: |
set -eu
password_source="${NAS_SSH_PASSWORD_SOURCE:-repository_secret}"
case "${password_source}" in
repository_secret)
nas_ssh_password="${NAS_SSH_PASSWORD_SECRET:-}"
;;
workflow_input)
nas_ssh_password="${NAS_SSH_PASSWORD_INPUT:-}"
;;
*)
echo "::error::password_source must be repository_secret or workflow_input"
exit 1
;;
esac
if [ -n "${nas_ssh_password}" ]; then
echo "::add-mask::${nas_ssh_password}"
fi
if [ -z "${nas_ssh_password}" ]; then
if [ "${password_source}" = "workflow_input" ]; then
echo "::error::Missing workflow input ssh_password"
else
echo "::error::Missing repository secret NAS_SSH_PASSWORD"
fi
exit 1
fi
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -p "${NAS_PORT}" "${NAS_HOST}" >> ~/.ssh/known_hosts
remote="${NAS_USER}@${NAS_HOST}"
ssh_opts="-p ${NAS_PORT} -o BatchMode=no -o StrictHostKeyChecking=yes"
scp_opts="-P ${NAS_PORT} -o BatchMode=no -o StrictHostKeyChecking=yes"
remote_release_dir="${OUTPUT_DIR}/${RELEASE_NAME}"
sudo_password_b64="$(printf '%s' "${nas_ssh_password}" | base64 | tr -d '\n')"
archive_path="${RUNNER_TEMP:-/tmp}/${RELEASE_NAME}.tar.gz"
remote_archive="/tmp/${RELEASE_NAME}-${GITHUB_RUN_ID:-manual}.tar.gz"
{
printf 'OUTPUT_DIR=%s\n' "${OUTPUT_DIR}"
printf 'RELEASE_NAME=%s\n' "${RELEASE_NAME}"
printf 'USE_SUDO=%s\n' "${NAS_USE_SUDO}"
printf 'SUDO_PASSWORD_B64=%s\n' "${sudo_password_b64}"
cat <<'REMOTE_SCRIPT'
set -eu
SUDO_PASSWORD="$(printf '%s' "${SUDO_PASSWORD_B64}" | base64 -d 2>/dev/null || true)"
unset SUDO_PASSWORD_B64
run_cmd() {
if [ "${USE_SUDO}" = "true" ]; then
printf '%s\n' "${SUDO_PASSWORD}" | sudo -S -p "" "$@"
else
"$@"
fi
}
run_cmd mkdir -p "${OUTPUT_DIR}/${RELEASE_NAME}"
REMOTE_SCRIPT
} | SSHPASS="${nas_ssh_password}" sshpass -e ssh ${ssh_opts} "${remote}" "sh -s"
tar -C desktop-artifacts -czf "${archive_path}" .
SSHPASS="${nas_ssh_password}" sshpass -e scp ${scp_opts} "${archive_path}" "${remote}:${remote_archive}"
{
printf 'REMOTE_ARCHIVE=%s\n' "${remote_archive}"
printf 'REMOTE_RELEASE_DIR=%s\n' "${remote_release_dir}"
printf 'USE_SUDO=%s\n' "${NAS_USE_SUDO}"
printf 'SUDO_PASSWORD_B64=%s\n' "${sudo_password_b64}"
cat <<'REMOTE_SCRIPT'
set -eu
SUDO_PASSWORD="$(printf '%s' "${SUDO_PASSWORD_B64}" | base64 -d 2>/dev/null || true)"
unset SUDO_PASSWORD_B64
if [ "${USE_SUDO}" = "true" ]; then
printf '%s\n' "${SUDO_PASSWORD}" | sudo -S -p "" tar -xzf "${REMOTE_ARCHIVE}" -C "${REMOTE_RELEASE_DIR}"
rm -f "${REMOTE_ARCHIVE}"
else
tar -xzf "${REMOTE_ARCHIVE}" -C "${REMOTE_RELEASE_DIR}"
rm -f "${REMOTE_ARCHIVE}"
fi
REMOTE_SCRIPT
} | SSHPASS="${nas_ssh_password}" sshpass -e ssh ${ssh_opts} "${remote}" "sh -s"
{
printf 'OUTPUT_DIR=%s\n' "${OUTPUT_DIR}"
printf 'RELEASE_NAME=%s\n' "${RELEASE_NAME}"
printf 'USE_SUDO=%s\n' "${NAS_USE_SUDO}"
printf 'SUDO_PASSWORD_B64=%s\n' "${sudo_password_b64}"
cat <<'REMOTE_SCRIPT'
set -eu
SUDO_PASSWORD="$(printf '%s' "${SUDO_PASSWORD_B64}" | base64 -d 2>/dev/null || true)"
unset SUDO_PASSWORD_B64
if [ "${USE_SUDO}" = "true" ]; then
tmp_file="$(mktemp)"
printf '%s\n' "${RELEASE_NAME}" > "${tmp_file}"
printf '%s\n' "${SUDO_PASSWORD}" | sudo -S -p "" cp "${tmp_file}" "${OUTPUT_DIR}/latest-desktop-client.txt"
rm -f "${tmp_file}"
else
printf '%s\n' "${RELEASE_NAME}" > "${OUTPUT_DIR}/latest-desktop-client.txt"
fi
REMOTE_SCRIPT
} | SSHPASS="${nas_ssh_password}" sshpass -e ssh ${ssh_opts} "${remote}" "sh -s"
{
printf 'OUTPUT_DIR=%s\n' "${OUTPUT_DIR}"
printf 'CLIENT_VERSION=%s\n' "${CLIENT_VERSION}"
printf 'USE_SUDO=%s\n' "${NAS_USE_SUDO}"
printf 'SUDO_PASSWORD_B64=%s\n' "${sudo_password_b64}"
cat <<'REMOTE_SCRIPT'
set -eu
SUDO_PASSWORD="$(printf '%s' "${SUDO_PASSWORD_B64}" | base64 -d 2>/dev/null || true)"
unset SUDO_PASSWORD_B64
run_cmd() {
if [ "${USE_SUDO}" = "true" ]; then
printf '%s\n' "${SUDO_PASSWORD}" | sudo -S -p "" "$@"
else
"$@"
fi
}
versioned_paths_desc() {
root="$1"
pattern="$2"
prefix="$3"
suffix="$4"
for candidate in "${root}"/${pattern}; do
[ -e "${candidate}" ] || continue
name="${candidate##*/}"
version="${name#${prefix}}"
if [ -n "${suffix}" ]; then
version="${version%${suffix}}"
fi
case "${version}" in
*[!0-9.]*|""|*.*.*.*)
continue
;;
esac
major="$(printf '%s' "${version}" | cut -d. -f1)"
minor="$(printf '%s' "${version}" | cut -d. -f2)"
patch="$(printf '%s' "${version}" | cut -d. -f3)"
case "${major}${minor}${patch}" in
*[!0-9]*|"")
continue
;;
esac
printf '%s.%s.%s %s\n' "${major}" "${minor}" "${patch}" "${candidate}"
done | sort -t. -k1,1nr -k2,2nr -k3,3nr | awk '{print $2}'
}
keep_latest_two() {
root="$1"
pattern="$2"
prefix="$3"
suffix="$4"
mode="$5"
keep=0
for path in $(versioned_paths_desc "${root}" "${pattern}" "${prefix}" "${suffix}"); do
keep=$((keep + 1))
if [ "${keep}" -gt 2 ]; then
if [ "${mode}" = "dir" ]; then
run_cmd rm -rf "${path}"
else
run_cmd rm -f "${path}"
fi
fi
done
}
run_cmd rm -rf "${OUTPUT_DIR}/mac" "${OUTPUT_DIR}/win"
keep_latest_two "${OUTPUT_DIR}" "desktop-client-*" "desktop-client-" "" "dir"
REMOTE_SCRIPT
} | SSHPASS="${nas_ssh_password}" sshpass -e ssh ${ssh_opts} "${remote}" "sh -s"
echo "Uploaded desktop client ${CLIENT_VERSION} to ${remote}:${remote_release_dir}"