build: finish cross-platform release tooling

This commit is contained in:
WHC
2026-10-10 16:21:00 +08:00
parent 9bf7707912
commit 50abcf1a91
29 changed files with 973 additions and 230 deletions
+11 -7
View File
@@ -1,8 +1,9 @@
#!/usr/bin/env bash
# Linux host: cross-build the Windows x86_64 release (mingw-w64, UCRT, static).
# scripts/bootstrap_windows.sh [repo-dir]
# With Nix the toolchain comes from shell-win.nix; otherwise x86_64-w64-mingw32-gcc
# (UCRT flavour, with winpthreads), cmake, ninja, git and perl must be installed.
# With Nix the toolchain comes from shell-win.nix; otherwise $MINGW_HOST-gcc
# (default x86_64-w64-mingw32; UCRT flavour, with winpthreads), cmake, ninja,
# git and perl must be installed. CST_WIN_CFLAGS adds C flags (CI: -Werror).
# Output: dist/windows-x86_64/ and dist/cst-agent-VERSION-windows-x86_64.zip
set -euo pipefail
@@ -21,15 +22,17 @@ if [[ ! -f "$DIR/CMakeLists.txt" ]]; then
fi
cd "$DIR"
if ! command -v x86_64-w64-mingw32-gcc >/dev/null && [[ -z "${IN_NIX_SHELL:-}" ]] &&
H="${MINGW_HOST:-x86_64-w64-mingw32}"
if ! command -v "$H-gcc" >/dev/null && [[ -z "${IN_NIX_SHELL:-}" ]] &&
command -v nix-shell >/dev/null; then
exec nix-shell shell-win.nix --run "$(printf '%q ' "$0" "$PWD")"
fi
need() { command -v "$1" >/dev/null || { echo "missing $1 (mingw-w64 UCRT gcc, cmake, ninja, git, perl)" >&2; exit 1; }; }
need git; need cmake; need ninja; need perl; need x86_64-w64-mingw32-gcc
if ! printf '#include <stdio.h>\n' | x86_64-w64-mingw32-gcc -dM -E - | grep -qw _UCRT; then
echo "x86_64-w64-mingw32-gcc targets msvcrt; the UTF-8 manifest needs a UCRT toolchain" >&2
export MINGW_HOST="$H"
need git; need cmake; need ninja; need perl; need "$H-gcc"
if ! printf '#include <stdio.h>\n' | "$H-gcc" -dM -E - | grep -qw _UCRT; then
echo "$H-gcc targets msvcrt; the UTF-8 manifest needs a UCRT toolchain" >&2
exit 1
fi
@@ -40,7 +43,8 @@ fi
echo "==> cmake (Windows, TUI)"
cmake -S . -B build-win-tui -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=cmake/mingw-w64-x86_64.cmake \
-DCMAKE_BUILD_TYPE=Release -DCST_BUILD_TUI=ON
-DCMAKE_BUILD_TYPE=Release -DCST_BUILD_TUI=ON \
-DCMAKE_C_FLAGS="${CST_WIN_CFLAGS:-}"
# Package only needs the agent binary; skip host-unrunnable Win32 tests.
cmake --build build-win-tui --target cst-agent
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env bash
# Build pinned static deps for a Linux release into third_party/prefix-linux-ARCH:
# mbedTLS, libcurl (HTTP(S) only, mbedTLS) and wide ncurses.
# scripts/build_third_party_linux.sh x86_64|x86|aarch64 [all|mbedtls|curl|ncurses]
# Uses LINUX_HOST-gcc from PATH, else fetches it via ci_linux_toolchain.sh.
# Host tools: cmake ninja make perl, and tic/infocmp with the fallback
# terminal entries (Debian: ncurses-bin ncurses-term).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
ARCH="${1:?usage: $0 x86_64|x86|aarch64 [all|mbedtls|curl|ncurses]}"
TP="$ROOT/third_party"
PREFIX="$TP/prefix-linux-$ARCH"
SRC="$TP/src"
WORK="$TP/build-linux-$ARCH"
JOBS="${JOBS:-$(nproc 2>/dev/null || echo 4)}"
MBEDTLS_VER=3.6.7
MBEDTLS_SHA=a7e8bcbec0e6f761b4af24f25677626b35f762f68eef79c08677a363212d11f6
CURL_VER=8.22.0
CURL_SHA=f7ef3ae8a22e521f289803fe93543eb64c329b58aa73a9e224dfd915a2a5f4f7
NCURSES_VER=6.6
NCURSES_SHA=355b4cbbed880b0381a04c46617b7656e362585d52e9cf84a67e2009b749ff11
# Compiled in, so the TUI starts even where no terminfo database is found.
NCURSES_FALLBACKS=xterm-256color,xterm,screen-256color,tmux-256color,linux,vt100
if [[ -z "${LINUX_HOST:-}" ]] || ! command -v "$LINUX_HOST-gcc" >/dev/null; then
read -r LINUX_HOST bindir < <(bash "$ROOT/scripts/ci_linux_toolchain.sh" "$ARCH")
export PATH="$bindir:$PATH"
fi
T="$LINUX_HOST-"
need() { command -v "$1" >/dev/null || { echo "missing: $1" >&2; exit 1; }; }
need "${T}gcc"
need cmake
need ninja
need make
need tic
need infocmp
mkdir -p "$PREFIX" "$SRC" "$WORK"
fetch() { # file sha url...
local file="$1" sha="$2" u
shift 2
if [[ -f "$SRC/$file" ]] && echo "$sha $SRC/$file" | sha256sum -c --quiet - 2>/dev/null; then
return 0
fi
for u in "$@"; do
echo " try $u"
if curl -fsSL --retry 2 --connect-timeout 15 --max-time 600 -o "$SRC/$file.part" "$u" &&
echo "$sha $SRC/$file.part" | sha256sum -c --quiet -; then
mv "$SRC/$file.part" "$SRC/$file"
return 0
fi
done
rm -f "$SRC/$file.part"
echo "download failed: $file" >&2
exit 1
}
unpack() { # file dir
rm -rf "${WORK:?}/$2"
tar -C "$WORK" -xf "$SRC/$1"
}
gh() { # path -> GitHub URL candidates (CN proxies first)
printf '%s\n' "https://ghfast.top/https://github.com/$1" \
"https://ghproxy.net/https://github.com/$1" "https://github.com/$1"
}
cmake_cross() { # src build args...
local s="$1" b="$2"
shift 2
cmake -S "$s" -B "$b" -G Ninja \
-DCMAKE_SYSTEM_NAME=Linux \
-DCMAKE_C_COMPILER="${T}gcc" -DCMAKE_AR="$(command -v "${T}ar")" \
-DCMAKE_RANLIB="$(command -v "${T}ranlib")" \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$PREFIX" \
-DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_PREFIX_PATH="$PREFIX" \
-DCMAKE_FIND_ROOT_PATH="$PREFIX" \
-DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER \
-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY \
-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY \
-DCMAKE_POSITION_INDEPENDENT_CODE=OFF \
"$@"
cmake --build "$b" -j"$JOBS"
cmake --install "$b"
}
build_mbedtls() {
local f="mbedtls-$MBEDTLS_VER.tar.bz2"
mapfile -t urls < <(gh "Mbed-TLS/mbedtls/releases/download/mbedtls-$MBEDTLS_VER/$f")
fetch "$f" "$MBEDTLS_SHA" "${urls[@]}"
unpack "$f" "mbedtls-$MBEDTLS_VER"
cmake_cross "$WORK/mbedtls-$MBEDTLS_VER" "$WORK/mbedtls-$MBEDTLS_VER/build" \
-DENABLE_TESTING=OFF -DENABLE_PROGRAMS=OFF -DGEN_FILES=OFF \
-DUSE_STATIC_MBEDTLS_LIBRARY=ON -DUSE_SHARED_MBEDTLS_LIBRARY=OFF \
-DMBEDTLS_FATAL_WARNINGS=OFF
echo "mbedTLS $MBEDTLS_VER -> $PREFIX"
}
build_curl() {
local f="curl-$CURL_VER.tar.xz"
mapfile -t urls < <(gh "curl/curl/releases/download/curl-${CURL_VER//./_}/$f")
fetch "$f" "$CURL_SHA" "https://curl.se/download/$f" "${urls[@]}"
unpack "$f" "curl-$CURL_VER"
# Cross builds skip CA detection; this is only libcurl's default, the
# program probes the usual distro paths when it is missing.
cmake_cross "$WORK/curl-$CURL_VER" "$WORK/curl-$CURL_VER/build" \
-DBUILD_SHARED_LIBS=OFF -DBUILD_STATIC_LIBS=ON \
-DCURL_USE_MBEDTLS=ON -DCURL_USE_OPENSSL=OFF \
-DMBEDTLS_INCLUDE_DIR="$PREFIX/include" \
-DMBEDTLS_LIBRARY="$PREFIX/lib/libmbedtls.a" \
-DMBEDX509_LIBRARY="$PREFIX/lib/libmbedx509.a" \
-DMBEDCRYPTO_LIBRARY="$PREFIX/lib/libmbedcrypto.a" \
-DCURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt -DCURL_CA_PATH=none \
-DHTTP_ONLY=ON -DCURL_DISABLE_LDAP=ON -DCURL_USE_LIBPSL=OFF \
-DCURL_USE_LIBSSH2=OFF -DCURL_USE_LIBSSH=OFF -DUSE_LIBIDN2=OFF \
-DUSE_NGHTTP2=OFF -DCURL_ZLIB=OFF -DCURL_BROTLI=OFF -DCURL_ZSTD=OFF \
-DCURL_USE_GSSAPI=OFF -DENABLE_THREADED_RESOLVER=ON \
-DBUILD_CURL_EXE=OFF -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF \
-DBUILD_LIBCURL_DOCS=OFF -DBUILD_MISC_DOCS=OFF -DENABLE_CURL_MANUAL=OFF
echo "curl $CURL_VER -> $PREFIX"
}
build_ncurses() {
local f="ncurses-$NCURSES_VER.tar.gz"
fetch "$f" "$NCURSES_SHA" \
"https://invisible-island.net/archives/ncurses/$f" \
"https://mirrors.tuna.tsinghua.edu.cn/gnu/ncurses/$f" \
"https://ftp.gnu.org/gnu/ncurses/$f"
unpack "$f" "ncurses-$NCURSES_VER"
local b="$WORK/ncurses-$NCURSES_VER/build"
mkdir -p "$b"
cd "$b"
CC="${T}gcc" AR="${T}ar" RANLIB="${T}ranlib" STRIP="${T}strip" \
../configure --host="$LINUX_HOST" --prefix="$PREFIX" \
--without-ada --without-cxx --without-cxx-binding --without-manpages \
--without-progs --without-tests --without-debug --without-shared \
--with-normal --enable-widec --disable-db-install \
--with-default-terminfo-dir=/usr/share/terminfo \
--with-terminfo-dirs=/etc/terminfo:/lib/terminfo:/usr/share/terminfo:/usr/lib/terminfo \
--with-fallbacks="$NCURSES_FALLBACKS" \
--with-tic-path="$(command -v tic)" --with-infocmp-path="$(command -v infocmp)" \
--enable-pc-files --with-pkg-config-libdir="$PREFIX/lib/pkgconfig" \
--with-build-cc="${BUILD_CC:-cc}" >/dev/null
make -j"$JOBS" libs >/dev/null
make install.libs >/dev/null
echo "ncurses $NCURSES_VER -> $PREFIX"
}
case "${2:-all}" in
mbedtls) build_mbedtls ;;
curl) build_curl ;;
ncurses) build_ncurses ;;
all)
build_mbedtls
build_curl
build_ncurses
;;
*)
echo "usage: $0 ARCH [all|mbedtls|curl|ncurses]" >&2
exit 1
;;
esac
echo "DONE $ARCH"
+4 -1
View File
@@ -106,7 +106,10 @@ build_ncurses() {
rm -rf "$src/build-mingw"
mkdir -p "$src/build-mingw"
cd "$src/build-mingw"
../configure --host="${HOST:-x86_64-w64-mingw32}" --prefix="$PREFIX" \
# --host stays canonical (config.sub may not know Debian's "mingw32ucrt");
# the tools themselves come from $HOST.
CC="${T}gcc" AR="${T}ar" RANLIB="${T}ranlib" STRIP="${T}strip" WINDRES="${T}windres" \
../configure --host=x86_64-w64-mingw32 --prefix="$PREFIX" \
--without-ada --without-cxx --without-cxx-binding --without-manpages \
--without-progs --without-tests --without-debug --without-shared \
--enable-widec --enable-sp-funcs --enable-term-driver \
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# Fetch a musl cross gcc for a static Linux release build.
# scripts/ci_linux_toolchain.sh x86_64|x86|aarch64
# Prints "HOST BINDIR" on stdout (e.g. "x86_64-buildroot-linux-musl
# /opt/x86-64--musl--stable-2025.08-1/bin"); progress goes to stderr.
# Bootlin's pinned stable release first, musl.cc as a fallback.
set -euo pipefail
ARCH="${1:?usage: $0 x86_64|x86|aarch64}"
BOOTLIN_VER="${CST_BOOTLIN_VER:-2025.08-1}"
case "$ARCH" in
x86_64)
bl=x86-64 bl_host=x86_64-buildroot-linux-musl mc=x86_64-linux-musl
sha=09fca3aa89540f1b01b5f4210d488cbeb00f522044c53e9989b1dd8a38076912
;;
x86)
bl=x86-i686 bl_host=i686-buildroot-linux-musl mc=i686-linux-musl
sha=3a340ebc386a057f5ddb24d8f4bbde07d2c013c99cebc02ac38565e2849bfda0
;;
aarch64)
bl=aarch64 bl_host=aarch64-buildroot-linux-musl mc=aarch64-linux-musl
sha=defba831ffa1175236f137069333e21ed46d4d19feb5080a90cf248b6fc2cb08
;;
*)
echo "unknown arch: $ARCH" >&2
exit 1
;;
esac
[[ "$BOOTLIN_VER" == 2025.08-1 ]] || sha="" # other releases: no pinned hash
if [[ -n "${CST_TOOLCHAIN_ROOT:-}" ]]; then
ROOT_DIR="$CST_TOOLCHAIN_ROOT"
elif [[ -w /opt || "$(id -u)" -eq 0 ]]; then
ROOT_DIR=/opt
else
ROOT_DIR="$HOME/.local/toolchains"
fi
mkdir -p "$ROOT_DIR"
fetch() { # out url...
local out="$1" u
shift
for u in "$@"; do
echo " try $u" >&2
if curl -fsSL --retry 2 --connect-timeout 15 --max-time 900 -o "$out" "$u"; then
return 0
fi
done
return 1
}
emit() { # host bindir
"$2/$1-gcc" -dumpmachine >&2
[[ -n "${GITHUB_PATH:-}" ]] && echo "$2" >>"$GITHUB_PATH"
echo "$1 $2"
}
name="$bl--musl--stable-$BOOTLIN_VER"
dir="$ROOT_DIR/$name"
if [[ -x "$dir/bin/$bl_host-gcc" ]]; then
emit "$bl_host" "$dir/bin"
exit 0
fi
echo "==> toolchain $name" >&2
tmp="$ROOT_DIR/$name.tar.xz"
if fetch "$tmp" "https://toolchains.bootlin.com/downloads/releases/toolchains/$bl/tarballs/$name.tar.xz" &&
{ [[ -z "$sha" ]] || echo "$sha $tmp" | sha256sum -c --quiet - >&2; }; then
tar -C "$ROOT_DIR" -xJf "$tmp"
rm -f "$tmp"
# Bootlin toolchains are relocatable but must be told once where they are.
[[ -x "$dir/relocate-sdk.sh" ]] && "$dir/relocate-sdk.sh" >&2
emit "$bl_host" "$dir/bin"
exit 0
fi
rm -f "$tmp"
echo "==> bootlin unavailable, falling back to musl.cc" >&2
mdir="$ROOT_DIR/$mc-cross"
if [[ ! -x "$mdir/bin/$mc-gcc" ]]; then
tgz="$ROOT_DIR/$mc-cross.tgz"
fetch "$tgz" "https://musl.cc/$mc-cross.tgz" "https://more.musl.cc/x86_64-linux-musl/$mc-cross.tgz" ||
{ echo "no musl toolchain for $ARCH" >&2; exit 1; }
tar -C "$ROOT_DIR" -xzf "$tgz"
rm -f "$tgz"
fi
emit "$mc" "$mdir/bin"
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
# CI helper: build the pinned TinyCC used for the verification job.
# Distro tcc packages are too old (no <stdatomic.h>), so this builds the same
# mob snapshot nixpkgs ships. Installs to $CST_TCC_PREFIX (default ~/.local/tcc)
# and, under Actions, puts its bin/ on $GITHUB_PATH.
set -euo pipefail
COMMIT="${CST_TCC_COMMIT:-f6385c05308f715bdd2c06336801193a21d69b50}"
PREFIX="${CST_TCC_PREFIX:-$HOME/.local/tcc}"
WORK="${TMPDIR:-/tmp}/tcc-build"
if [[ -x "$PREFIX/bin/tcc" ]]; then
echo "==> tcc already at $PREFIX"
else
rm -rf "$WORK"
mkdir -p "$WORK"
TGZ="$WORK/tinycc.tar.gz"
# repo.or.cz is upstream but often unreachable from CN runners; the GitHub
# mirror carries the same commits.
urls=(
"https://ghfast.top/https://github.com/TinyCC/tinycc/archive/${COMMIT}.tar.gz"
"https://ghproxy.net/https://github.com/TinyCC/tinycc/archive/${COMMIT}.tar.gz"
"https://github.com/TinyCC/tinycc/archive/${COMMIT}.tar.gz"
"https://repo.or.cz/tinycc.git/snapshot/${COMMIT}.tar.gz"
)
ok=0
for u in "${urls[@]}"; do
echo " try $u"
if curl -fsSL --retry 2 --connect-timeout 15 --max-time 300 -o "$TGZ" "$u" &&
tar -tzf "$TGZ" 2>/dev/null | sed -n 1p | grep -q "${COMMIT:0:7}"; then
ok=1
break
fi
done
[[ $ok -eq 1 ]] || { echo "failed to download tinycc $COMMIT" >&2; exit 1; }
tar -C "$WORK" -xzf "$TGZ"
src=$(find "$WORK" -mindepth 1 -maxdepth 1 -type d -name 'tinycc*' | head -1)
cd "$src"
./configure --prefix="$PREFIX" --cc="${BUILD_CC:-cc}"
make -j"$(nproc)"
make install
fi
"$PREFIX/bin/tcc" -v
if [[ -n "${GITHUB_PATH:-}" ]]; then
echo "$PREFIX/bin" >>"$GITHUB_PATH"
fi
+12 -50
View File
@@ -1,64 +1,26 @@
#!/usr/bin/env bash
# CI helper: MinGW-w64 UCRT toolchain via llvm-mingw (no Nix / no /nix).
# Downloads a pinned release, puts it on PATH, then runs bootstrap_windows.sh.
# CI helper: Windows x86_64 release with Debian's mingw-w64 UCRT gcc.
# apt-get install gcc-mingw-w64-ucrt64 binutils-mingw-w64-ucrt64 nsis (trixie+)
# Checks the toolchain, then runs bootstrap_windows.sh, which builds the
# third-party libs, cst-agent.exe, the zip and the NSIS installer.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"
VER="${CST_LLVM_MINGW_VER:-20241030}"
NAME="llvm-mingw-${VER}-ucrt-ubuntu-20.04-x86_64"
TGZ="/tmp/${NAME}.tar.xz"
# Prefer /opt when writable (root CI); else unpack under $HOME.
if [[ -w /opt || "$(id -u)" -eq 0 ]]; then
DIR="/opt/${NAME}"
PARENT=/opt
else
DIR="${HOME}/${NAME}"
PARENT="$HOME"
fi
if [[ ! -x "$DIR/bin/x86_64-w64-mingw32-gcc" ]]; then
echo "==> download $NAME"
# Prefer CN-reachable GitHub proxies; raw github.com is often unreachable.
urls=(
"https://ghfast.top/https://github.com/mstorsjo/llvm-mingw/releases/download/${VER}/${NAME}.tar.xz"
"https://ghproxy.net/https://github.com/mstorsjo/llvm-mingw/releases/download/${VER}/${NAME}.tar.xz"
"https://gitclone.com/github.com/mstorsjo/llvm-mingw/releases/download/${VER}/${NAME}.tar.xz"
"https://github.com/mstorsjo/llvm-mingw/releases/download/${VER}/${NAME}.tar.xz"
)
ok=0
for u in "${urls[@]}"; do
echo " try $u"
if curl -fL --retry 2 --connect-timeout 15 --max-time 600 \
--retry-delay 2 -o "$TGZ" "$u"; then
# Reject tiny error pages that some proxies return with HTTP 200.
sz=$(wc -c <"$TGZ")
if [[ "$sz" -gt 10000000 ]]; then
ok=1
break
fi
echo " too small ($sz bytes), next mirror"
fi
done
[[ $ok -eq 1 ]] || { echo "failed to download llvm-mingw" >&2; exit 1; }
rm -rf "$DIR"
mkdir -p "$PARENT"
tar -C "$PARENT" -xf "$TGZ"
fi
export PATH="$DIR/bin:$PATH"
export MINGW_HOST=x86_64-w64-mingw32
export MINGW_HOST="${MINGW_HOST:-x86_64-w64-mingw32ucrt}"
export BUILD_CC="${BUILD_CC:-cc}"
export CST_REQUIRE_NSIS=1
need() { command -v "$1" >/dev/null || { echo "missing $1" >&2; exit 1; }; }
need x86_64-w64-mingw32-gcc
need x86_64-w64-mingw32-windres
need cmake; need ninja; need perl; need zip; need git; need make
need "$MINGW_HOST-gcc"
need "$MINGW_HOST-windres"
need cmake; need ninja; need perl; need zip; need git; need make; need makensis
if ! printf '#include <stdio.h>\n' | x86_64-w64-mingw32-gcc -dM -E - | grep -qw _UCRT; then
echo "toolchain is not UCRT" >&2
if ! printf '#include <stdio.h>\n' | "$MINGW_HOST-gcc" -dM -E - | grep -qw _UCRT; then
echo "$MINGW_HOST-gcc is not UCRT" >&2
exit 1
fi
"$MINGW_HOST-gcc" --version | head -1
# Do not set a global insteadOf: a single broken proxy (e.g. gitclone 502)
# would take down every third_party clone. build_third_party_mingw.sh tries
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Static musl build of cst-agent for one architecture, packed as
# dist/cst-agent-VERSION-linux-ARCH.tar.gz (+ .sha256).
# scripts/package_linux.sh x86_64|x86|aarch64
# CST_LINUX_CHECK=1 also runs `make check` (only where the binaries can run).
# Builds in the source tree, so the tree is cleaned first.
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
ARCH="${1:?usage: $0 x86_64|x86|aarch64}"
PREFIX="$ROOT/third_party/prefix-linux-$ARCH"
cd "$ROOT"
read -r LINUX_HOST bindir < <(bash scripts/ci_linux_toolchain.sh "$ARCH")
export PATH="$bindir:$PATH" LINUX_HOST
T="$LINUX_HOST-"
if [[ ! -f "$PREFIX/lib/libcurl.a" || ! -f "$PREFIX/lib/libncursesw.a" ]]; then
bash scripts/build_third_party_linux.sh "$ARCH"
fi
# Only the static prefix: no host .pc file may leak into the link.
export PKG_CONFIG_LIBDIR="$PREFIX/lib/pkgconfig"
unset PKG_CONFIG_PATH CC CFLAGS LDFLAGS
mk=(make CC="${T}gcc" AR="${T}ar" STATIC=1
CFLAGS="-std=c11 -Wall -Wextra -O2 -g -Werror")
make clean >/dev/null
make -C vendor/sashidown clean >/dev/null 2>&1 || true
"${mk[@]}" -j"$(nproc)" cst-agent
if "${T}readelf" -l cst-agent | grep -q 'Requesting program interpreter'; then
echo "cst-agent is dynamically linked" >&2
exit 1
fi
if [[ "${CST_LINUX_CHECK:-0}" == 1 ]]; then
"${mk[@]}" check
fi
ver="$(sed -n 's/^#define CST_VERSION_\(MAJOR\|MINOR\|PATCH\) \([0-9]*\)$/\2/p' include/cst_version.h | paste -sd.)"
name="cst-agent-$ver-linux-$ARCH"
rel="dist/$name"
rm -rf "$rel"
mkdir -p "$rel"
cp -f cst-agent "$rel/cst-agent"
"${T}strip" "$rel/cst-agent"
cp -f README.md "$rel/README.md"
echo "$ver" >"$rel/VERSION"
tgz="dist/$name.tar.gz"
tar -C dist -czf "$tgz" "$name"
(cd dist && sha256sum "$name.tar.gz" >"$name.tar.gz.sha256")
ls -la "$rel"
echo "$ROOT/$tgz"
+15
View File
@@ -36,3 +36,18 @@ rm -f "$zip"
(cd "$ROOT/dist" && sha256sum "$(basename "$zip")" >"$(basename "$zip").sha256")
ls -la "$REL"
echo "$zip"
# NSIS installer from the same folder (CST_REQUIRE_NSIS=1: fail without makensis).
setup="$ROOT/dist/cst-agent-$ver-windows-x86_64-setup.exe"
if command -v makensis >/dev/null; then
rm -f "$setup"
makensis -V2 -WX -INPUTCHARSET UTF8 -DVERSION="$ver" -DSRCDIR="$REL" \
-DOUTFILE="$setup" "$ROOT/scripts/windows/cst-agent.nsi"
(cd "$ROOT/dist" && sha256sum "$(basename "$setup")" >"$(basename "$setup").sha256")
echo "$setup"
elif [[ "${CST_REQUIRE_NSIS:-0}" == 1 ]]; then
echo "makensis not found" >&2
exit 1
else
echo "makensis not found; skipping the installer" >&2
fi
@@ -1,10 +1,12 @@
#!/usr/bin/env bash
# Publish dist/cst-agent-VERSION-windows-x86_64.zip (+ .sha256) to the Gitea
# release vVERSION, creating the release (and its tag at HEAD) when missing.
# GITEA_TOKEN=... scripts/release_windows.sh [--build]
# --build runs scripts/bootstrap_windows.sh first. The token is only read from
# the environment and handed to curl on stdin: it never appears in argv, on
# disk or in the output. Assets of the same name are replaced.
# Upload release files to the Gitea release vVERSION, creating the release
# (and its tag at HEAD) when missing.
# GITEA_TOKEN=... scripts/release.sh FILE...
# Each FILE's FILE.sha256 is uploaded too when it exists; assets of the same
# name are replaced. Several CI jobs may call this at once: the first creates
# the release, the others find it. The token is only read from the
# environment and handed to curl on stdin: it never appears in argv, on disk
# or in the output.
#
# CI: set GITEA_TOKEN (or GITHUB_TOKEN), CST_GITEA, CST_REPO. The remote-branch
# check is skipped under GITEA_ACTIONS / GITHUB_ACTIONS / CI.
@@ -19,16 +21,20 @@ TOKEN="${GITEA_TOKEN:-${GITHUB_TOKEN:-}}"
echo "set GITEA_TOKEN (a Gitea token with write:repository)" >&2
exit 1
}
[[ $# -gt 0 ]] || { echo "usage: $0 FILE..." >&2; exit 1; }
command -v python3 >/dev/null || { echo "missing python3" >&2; exit 1; }
files=()
for f in "$@"; do
[[ -f "$f" ]] || { echo "missing $f" >&2; exit 1; }
f="$(cd "$(dirname "$f")" && pwd)/$(basename "$f")"
files+=("$f")
[[ "$f" != *.sha256 && -f "$f.sha256" ]] && files+=("$f.sha256")
done
cd "$ROOT"
[[ "${1:-}" == --build ]] && scripts/bootstrap_windows.sh "$ROOT"
ver="$(sed -n 's/^#define CST_VERSION_\(MAJOR\|MINOR\|PATCH\) \([0-9]*\)$/\2/p' include/cst_version.h | paste -sd.)"
tag="v$ver"
zip="dist/cst-agent-$ver-windows-x86_64.zip"
for f in "$zip" "$zip.sha256"; do
[[ -f "$f" ]] || { echo "missing $f (scripts/package_windows.sh, or --build)" >&2; exit 1; }
done
# If this job was triggered by a version tag, it must match cst_version.h.
if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then
@@ -51,6 +57,12 @@ json() { # CODE [ARGS...]: d = the JSON on stdin, ARGS in sys.argv[1:]
python3 -c "import json,sys; d=json.load(sys.stdin); $code" "$@"
}
BODY="Linux (static, musl, any distro): cst-agent-$ver-linux-{x86_64,x86,aarch64}.tar.gz -- extract and run ./cst-agent, or copy it onto PATH.
Windows x86_64: cst-agent-$ver-windows-x86_64-setup.exe installs per user (no admin rights) and adds PATH; cst-agent-$ver-windows-x86_64.zip is the portable build (install_windows.ps1 inside).
Every file has a .sha256 next to it."
if rel="$(api GET "/releases/tags/$tag" 2>/dev/null)"; then
echo "==> release $tag exists"
else
@@ -62,12 +74,22 @@ else
fi
fi
echo "==> creating release $tag at $head"
body="$(python3 -c 'import json,sys; print(json.dumps({"tag_name": sys.argv[1], "target_commitish": sys.argv[2], "name": "CST-Agent " + sys.argv[1], "body": "Windows x86_64: unzip, or install with install_windows.ps1 from the zip.\\n\\nArtifacts: `cst-agent-VERSION-windows-x86_64.zip` + `.sha256`."}))' "$tag" "$head")"
rel="$(api POST /releases -H 'Content-Type: application/json' -d "$body")"
body="$(python3 -c 'import json,sys; print(json.dumps({"tag_name": sys.argv[1], "target_commitish": sys.argv[2], "name": "CST-Agent " + sys.argv[1], "body": sys.argv[3]}))' "$tag" "$head" "$BODY")"
if ! rel="$(api POST /releases -H 'Content-Type: application/json' -d "$body" 2>/dev/null)"; then
# Another job created it first (409).
rel=""
for _ in 1 2 3 4 5 6; do
sleep 5
rel="$(api GET "/releases/tags/$tag" 2>/dev/null)" && break
rel=""
done
[[ -n "$rel" ]] || { echo "could not create or find release $tag" >&2; exit 1; }
echo "==> release $tag created by another job"
fi
fi
id="$(json 'print(d["id"])' <<<"$rel")"
for f in "$zip" "$zip.sha256"; do
for f in "${files[@]}"; do
name="$(basename "$f")"
old="$(json "print(' '.join(str(a['id']) for a in d.get('assets') or [] if a['name'] == sys.argv[1]))" "$name" <<<"$rel")"
for aid in $old; do
+151
View File
@@ -0,0 +1,151 @@
; CST-Agent per-user installer (no admin rights).
; makensis -INPUTCHARSET UTF8 -DVERSION=0.8.5 -DSRCDIR=dist/windows-x86_64 -DOUTFILE=setup.exe cst-agent.nsi
; Installs the same files as the zip into %LOCALAPPDATA%\Programs\cst-agent,
; adds that folder to the user PATH and registers an uninstaller under HKCU.
Unicode true
SetCompressor /SOLID lzma
RequestExecutionLevel user
!ifndef VERSION
!error "pass -DVERSION=x.y.z"
!endif
!ifndef SRCDIR
!error "pass -DSRCDIR=<dist/windows-x86_64>"
!endif
!ifndef OUTFILE
!define OUTFILE "cst-agent-${VERSION}-windows-x86_64-setup.exe"
!endif
!define APP "CST-Agent"
!define UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\cst-agent"
!include "MUI2.nsh"
!include "LogicLib.nsh"
!include "x64.nsh"
Name "${APP} ${VERSION}"
OutFile "${OUTFILE}"
InstallDir "$LOCALAPPDATA\Programs\cst-agent"
InstallDirRegKey HKCU "${UNINST_KEY}" "InstallLocation"
BrandingText "${APP} ${VERSION}"
VIProductVersion "${VERSION}.0"
VIAddVersionKey /LANG=1033 "ProductName" "${APP}"
VIAddVersionKey /LANG=1033 "ProductVersion" "${VERSION}"
VIAddVersionKey /LANG=1033 "FileVersion" "${VERSION}"
VIAddVersionKey /LANG=1033 "FileDescription" "${APP} installer"
VIAddVersionKey /LANG=1033 "LegalCopyright" "WHC"
!define MUI_ABORTWARNING
!define MUI_FINISHPAGE_RUN
!define MUI_FINISHPAGE_RUN_TEXT "$(RunWebUI)"
!define MUI_FINISHPAGE_RUN_FUNCTION LaunchWebUI
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"
!insertmacro MUI_LANGUAGE "SimpChinese"
LangString NeedX64 ${LANG_ENGLISH} "${APP} needs 64-bit Windows."
LangString NeedX64 ${LANG_SIMPCHINESE} "${APP} 需要 64 位 Windows。"
LangString NeedBuild ${LANG_ENGLISH} "${APP} needs Windows 10 1903 (build 18362) or newer; this is build $0."
LangString NeedBuild ${LANG_SIMPCHINESE} "${APP} 需要 Windows 10 1903(内部版本 18362)或更新版本,当前为 $0。"
LangString RunWebUI ${LANG_ENGLISH} "Open the ${APP} WebUI"
LangString RunWebUI ${LANG_SIMPCHINESE} "打开 ${APP} WebUI"
LangString PathFail ${LANG_ENGLISH} "Could not add $INSTDIR to the user PATH; add it by hand."
LangString PathFail ${LANG_SIMPCHINESE} "未能把 $INSTDIR 加入用户 PATH,请手动添加。"
; PowerShell edits the user PATH: NSIS strings are capped at 1024 chars, and
; .NET broadcasts the change so new terminals see it. The folder is passed in
; the environment, so quotes in it cannot break the command.
!macro EditUserPath ACTION
System::Call 'Kernel32::SetEnvironmentVariable(t "CST_INSTDIR", t "$INSTDIR")'
nsExec::ExecToLog `powershell -NoProfile -ExecutionPolicy Bypass -Command "$$d = $$env:CST_INSTDIR.TrimEnd('\'); $$p = [Environment]::GetEnvironmentVariable('Path', 'User'); $$parts = @([string]$$p -split ';' | Where-Object { $$_ -and $$_.TrimEnd('\') -ine $$d }); if ('${ACTION}' -eq 'add') { $$parts += $$d }; [Environment]::SetEnvironmentVariable('Path', ($$parts -join ';'), 'User')"`
Pop $0
!macroend
; A running exe cannot be overwritten but can be renamed out of the way.
!macro MoveAside NAME
Delete "$INSTDIR\${NAME}.old"
ClearErrors
Delete "$INSTDIR\${NAME}"
${If} ${Errors}
Rename "$INSTDIR\${NAME}" "$INSTDIR\${NAME}.old"
${EndIf}
!macroend
Function .onInit
${IfNot} ${RunningX64}
MessageBox MB_ICONSTOP "$(NeedX64)"
Abort
${EndIf}
SetRegView 64
ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" "CurrentBuildNumber"
${If} $0 < 18362
MessageBox MB_ICONSTOP "$(NeedBuild)"
Abort
${EndIf}
FunctionEnd
Function LaunchWebUI
SetOutPath "$PROFILE"
Exec '"$INSTDIR\cst-agent.exe" --web --web-open'
FunctionEnd
Section "Install"
SetShellVarContext current
SetOutPath "$INSTDIR"
!insertmacro MoveAside "cst-agent.exe"
!insertmacro MoveAside "busybox.exe"
File "${SRCDIR}/cst-agent.exe"
File "${SRCDIR}/busybox.exe"
File "${SRCDIR}/LICENSE.busybox"
File "${SRCDIR}/README.md"
File "${SRCDIR}/cst-agent-update.cmd"
File "${SRCDIR}/install_windows.ps1"
File "${SRCDIR}/VERSION"
WriteUninstaller "$INSTDIR\uninstall.exe"
!insertmacro EditUserPath "add"
${If} $0 != 0
DetailPrint "$(PathFail)"
${EndIf}
; The WebUI's workspace is the shortcut's start folder.
SetOutPath "$PROFILE"
CreateShortCut "$SMPROGRAMS\${APP} WebUI.lnk" "$INSTDIR\cst-agent.exe" "--web --web-open" "$INSTDIR\cst-agent.exe" 0
SetOutPath "$INSTDIR"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayName" "${APP}"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "${VERSION}"
WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "WHC"
WriteRegStr HKCU "${UNINST_KEY}" "DisplayIcon" "$INSTDIR\cst-agent.exe"
WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKCU "${UNINST_KEY}" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S'
WriteRegStr HKCU "${UNINST_KEY}" "URLInfoAbout" "https://git.whcself.xyz/WHC/CST-agent"
WriteRegDWORD HKCU "${UNINST_KEY}" "NoModify" 1
WriteRegDWORD HKCU "${UNINST_KEY}" "NoRepair" 1
SectionEnd
Section "Uninstall"
SetShellVarContext current
!insertmacro EditUserPath "remove"
Delete "$SMPROGRAMS\${APP} WebUI.lnk"
Delete "$INSTDIR\cst-agent.exe"
Delete "$INSTDIR\cst-agent.exe.old"
Delete "$INSTDIR\busybox.exe"
Delete "$INSTDIR\busybox.exe.old"
Delete "$INSTDIR\LICENSE.busybox"
Delete "$INSTDIR\README.md"
Delete "$INSTDIR\cst-agent-update.cmd"
Delete "$INSTDIR\install_windows.ps1"
Delete "$INSTDIR\VERSION"
Delete "$INSTDIR\uninstall.exe"
RMDir "$INSTDIR"
DeleteRegKey HKCU "${UNINST_KEY}"
SectionEnd