feat(release): add static browser builds

This commit is contained in:
Hand Sonic 2026-07-27 00:37:41 +08:00 committed by GitHub
parent d9a6655fd9
commit 2bfbe67f47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 323 additions and 5 deletions

View File

@ -335,6 +335,108 @@ jobs:
Copy-Item $installer.FullName $offlineName -Force
gh release upload "${env:GITHUB_REF_NAME}" $offlineName --repo "${env:GITHUB_REPOSITORY}" --clobber
static-browser:
# Fully static musl builds of the browser (dbx-web) variant. No glibc
# dependency, so the tarball runs on any Linux distribution (verified in
# the oldest pullable Ubuntu container for each arch).
needs: build
strategy:
fail-fast: false
matrix:
include:
- platform: ubuntu-22.04
target: x86_64-unknown-linux-musl
arch: x64
rustflags: "-C target-feature=+crt-static"
- platform: ubuntu-22.04-arm
target: aarch64-unknown-linux-musl
arch: arm64
# 64K max-page-size keeps the binary compatible with 4K/16K/64K
# page-size ARM64 kernels.
rustflags: "-C target-feature=+crt-static -C link-arg=-z -C link-arg=max-page-size=65536"
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v6
- name: Install frontend dependencies
run: pnpm install
- name: Setup Rust
uses: dtolnay/rust-toolchain@1.97.1
with:
targets: ${{ matrix.target }}
- name: Install cargo-zigbuild
run: pip3 install cargo-zigbuild
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: "./ -> target"
shared-key: static-browser-${{ matrix.target }}
cache-on-failure: true
- name: Build frontend
run: pnpm build
- name: Build static web binary
env:
RUSTFLAGS: ${{ matrix.rustflags }}
run: |
cargo zigbuild --release -p dbx-web --target ${{ matrix.target }} \
--no-default-features --features "duckdb-bundled,mq-admin"
- name: Package static browser runtime
env:
DBX_STATIC_TARGET: ${{ matrix.target }}
run: |
chmod +x scripts/package-web-static.sh scripts/verify-web-static.sh
./scripts/package-web-static.sh
- name: Verify inside oldest available Ubuntu container
run: |
VERIFY_IMAGE=""
for image in ubuntu:14.04 ubuntu:16.04; do
if docker pull "$image" >/dev/null 2>&1; then
VERIFY_IMAGE="$image"
break
fi
done
if [ -z "$VERIFY_IMAGE" ]; then
echo "no legacy Ubuntu image available for $(uname -m)" >&2
exit 1
fi
echo "Verifying in $VERIFY_IMAGE"
docker run --rm -v "$PWD:/workspace" -w /workspace "$VERIFY_IMAGE" \
bash scripts/verify-web-static.sh \
"dist-web-static/dbx-linux-${{ matrix.arch }}-browser-static.tar.gz"
- name: Upload release asset
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${GITHUB_REF_NAME#v}"
PACKAGE_VERSION="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "dbx-web") | .version')"
if [ "$PACKAGE_VERSION" != "$VERSION" ]; then
echo "Release tag version $VERSION does not match dbx-web package version $PACKAGE_VERSION" >&2
exit 1
fi
SOURCE="dist-web-static/dbx-linux-${{ matrix.arch }}-browser-static.tar.gz"
ASSET="DBX_${VERSION}_${{ matrix.arch }}-browser-static.tar.gz"
CHECKSUM="${ASSET}.sha256"
cp "$SOURCE" "$ASSET"
sha256sum "$ASSET" | tee "$CHECKSUM"
gh release upload "${GITHUB_REF_NAME}" "$ASSET" "$CHECKSUM" \
--repo "${GITHUB_REPOSITORY}" --clobber
cleanup-release-signatures:
needs: build
runs-on: ubuntu-latest
@ -424,7 +526,7 @@ jobs:
publish:
# Keep the release as a draft until every platform and the Nix package pass.
needs: [cleanup-release-signatures, docker-manifest, jdbc-plugin, nix-packaging]
needs: [cleanup-release-signatures, docker-manifest, jdbc-plugin, nix-packaging, static-browser]
runs-on: ubuntu-latest
steps:
- name: Publish draft release

View File

@ -32,10 +32,11 @@ test = false
bench = false
[features]
default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher"]
default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher", "system-fonts"]
duckdb-bundled = ["duckdb/bundled", "duckdb/parquet"]
mq-admin = []
sqlite-sqlcipher = ["rusqlite/bundled-sqlcipher-vendored-openssl"]
system-fonts = ["font-kit"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
@ -88,7 +89,7 @@ argon2 = "0.5"
sha2 = "0.10"
async-trait = "0.1"
bytes = "1"
font-kit = "0.14.3"
font-kit = { version = "0.14.3", optional = true }
zip = { version = "4", default-features = false, features = ["deflate"] }
sysinfo = { version = "0.32", features = ["system"] }
tempfile = "3"

View File

@ -318,6 +318,7 @@ pub fn uninstall_jdbc_plugin(plugins_root: &Path) -> Result<JdbcPluginStatus, St
// ---- System Fonts ----
#[cfg(feature = "system-fonts")]
pub fn list_system_fonts() -> Vec<String> {
let source = font_kit::source::SystemSource::new();
match source.all_families() {
@ -335,6 +336,11 @@ pub fn list_system_fonts() -> Vec<String> {
}
}
#[cfg(not(feature = "system-fonts"))]
pub fn list_system_fonts() -> Vec<String> {
vec![]
}
// ---- Internal helpers ----
fn jdbc_drivers_dir(plugins_root: &Path) -> PathBuf {

View File

@ -9,10 +9,11 @@ name = "dbx-web"
path = "src/main.rs"
[features]
default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher"]
default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher", "system-fonts"]
duckdb-bundled = ["dbx-core/duckdb-bundled"]
mq-admin = ["dbx-core/mq-admin"]
sqlite-sqlcipher = ["dbx-core/sqlite-sqlcipher"]
system-fonts = ["dbx-core/system-fonts"]
[dependencies]
dbx-core = { path = "../dbx-core", default-features = false }

View File

@ -0,0 +1,112 @@
#!/usr/bin/env bash
set -euo pipefail
# Package a fully static musl-linked dbx-web binary together with the built
# frontend into a portable tarball that runs on any Linux (no glibc needed).
target="${DBX_STATIC_TARGET:-aarch64-unknown-linux-musl}"
dist_dir="${DBX_FRONTEND_DIST:-dist}"
output_dir="${DBX_STATIC_OUTPUT_DIR:-dist-web-static}"
case "$target" in
x86_64-unknown-linux-musl*)
arch_label="x64"
;;
aarch64-unknown-linux-musl*)
arch_label="arm64"
;;
*)
echo "unsupported static web target: $target" >&2
exit 2
;;
esac
binary="${DBX_WEB_BINARY:-target/${target}/release/dbx-web}"
package_name="${DBX_STATIC_PACKAGE_NAME:-dbx-linux-${arch_label}-browser-static}"
package_dir="${output_dir}/${package_name}"
tarball="${output_dir}/${package_name}.tar.gz"
if [ ! -x "$binary" ]; then
echo "missing static dbx-web binary: $binary" >&2
exit 1
fi
if [ ! -f "${dist_dir}/index.html" ]; then
echo "missing frontend build output: ${dist_dir}/index.html" >&2
exit 1
fi
if readelf -l "$binary" | grep -q 'Requesting program interpreter'; then
readelf -l "$binary" | grep 'Requesting program interpreter' >&2 || true
echo "dbx-web is not fully static: ELF has a program interpreter" >&2
exit 1
fi
if readelf -d "$binary" 2>/dev/null | grep -q 'Shared library:'; then
readelf -d "$binary" | grep 'Shared library:' >&2 || true
echo "dbx-web is not fully static: ELF has dynamic shared library dependencies" >&2
exit 1
fi
rm -rf "$output_dir"
mkdir -p \
"$package_dir/bin" \
"$package_dir/dist" \
"$package_dir/data"
cp "$binary" "$package_dir/bin/dbx-web-bin"
chmod +x "$package_dir/bin/dbx-web-bin"
cp -a "${dist_dir}/." "$package_dir/dist/"
cat > "$package_dir/dbx" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
SOURCE="${BASH_SOURCE[0]}"
while [ -h "$SOURCE" ]; do
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
SOURCE="$(readlink "$SOURCE")"
[[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE"
done
ROOT="$(cd -P "$(dirname "$SOURCE")" && pwd)"
export DBX_PACKAGE_ROOT="$ROOT"
export DBX_STATIC_DIR="${DBX_STATIC_DIR:-$ROOT/dist}"
export DBX_DATA_DIR="${DBX_DATA_DIR:-$ROOT/data}"
port="${DBX_PORT:-4224}"
base_path="${DBX_PUBLIC_BASE_PATH:-/}"
case "$base_path" in
"") base_path="/" ;;
/*) ;;
*) base_path="/$base_path" ;;
esac
printf 'DBX browser UI: http://127.0.0.1:%s%s\n' "$port" "$base_path"
cd "$ROOT"
exec "$ROOT/bin/dbx-web-bin" "$@"
EOF
chmod +x "$package_dir/dbx"
ln -sfn dbx "$package_dir/dbx-web"
cat > "$package_dir/README.txt" <<EOF
DBX ${arch_label} static browser package
Run:
./dbx
Then open:
http://127.0.0.1:4224
This package runs a musl-linked static dbx-web binary and serves the bundled
frontend from ./dist. The backend binary has no ELF interpreter and no DT_NEEDED
shared library entries, so it runs on any Linux distribution regardless of the
system glibc version (verified down to Ubuntu 14.04).
Useful environment variables:
DBX_PORT=4224
DBX_DATA_DIR=./data
DBX_PASSWORD=your-password
DBX_DISABLE_PASSWORD=1
EOF
tar -C "$output_dir" -czf "$tarball" "$package_name"
sha256sum "$tarball" | tee "${tarball}.sha256"
file "$package_dir/bin/dbx-web-bin"
du -sh "$package_dir" "$tarball"

View File

@ -0,0 +1,95 @@
#!/usr/bin/env bash
set -euo pipefail
# Verify the static web package inside an old-glibc container (e.g.
# ubuntu:14.04). The container arch matches the package arch, so the binary
# runs natively. Confirms the ELF is fully static and the HTTP server serves
# the bundled frontend.
package_tarball="${1:-${DBX_STATIC_TARBALL:-}}"
if [ -z "$package_tarball" ]; then
echo "usage: $0 <package-tarball>" >&2
exit 2
fi
verify_seconds="${DBX_STATIC_VERIFY_SECONDS:-30}"
runtime_dir="${DBX_STATIC_VERIFY_DIR:-/tmp/dbx-web-static-verify}"
if [ ! -f "$package_tarball" ]; then
echo "missing package tarball: $package_tarball" >&2
exit 1
fi
if ! command -v curl >/dev/null 2>&1 || ! command -v readelf >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
# Archived releases (14.04, etc.) have moved to old-releases.ubuntu.com
if [ -f /etc/apt/sources.list ] && ! apt-get update 2>/dev/null; then
sed -i 's|archive\.ubuntu\.com|old-releases.ubuntu.com|g' /etc/apt/sources.list
sed -i 's|security\.ubuntu\.com|old-releases.ubuntu.com|g' /etc/apt/sources.list
apt-get -o Acquire::Check-Valid-Until=false update
fi
apt-get install -y --no-install-recommends ca-certificates bash binutils curl procps
rm -rf /var/lib/apt/lists/*
fi
rm -rf "$runtime_dir"
mkdir -p "$runtime_dir"
tar -xzf "$package_tarball" -C "$runtime_dir" --strip-components=1
for required_runtime_path in \
dbx \
bin/dbx-web-bin \
dist/index.html; do
if [ ! -e "$runtime_dir/$required_runtime_path" ]; then
echo "missing packaged static web runtime path: $required_runtime_path" >&2
exit 1
fi
done
if readelf -l "$runtime_dir/bin/dbx-web-bin" | grep -q 'Requesting program interpreter'; then
readelf -l "$runtime_dir/bin/dbx-web-bin" | grep 'Requesting program interpreter' >&2 || true
echo "packaged dbx-web is not static: ELF has a program interpreter" >&2
exit 1
fi
if readelf -d "$runtime_dir/bin/dbx-web-bin" 2>/dev/null | grep -q 'Shared library:'; then
readelf -d "$runtime_dir/bin/dbx-web-bin" | grep 'Shared library:' >&2 || true
echo "packaged dbx-web is not static: ELF has dynamic shared library dependencies" >&2
exit 1
fi
cd "$runtime_dir"
export RUST_BACKTRACE="${RUST_BACKTRACE:-full}"
export DBX_DISABLE_PASSWORD="${DBX_DISABLE_PASSWORD:-1}"
export DBX_PORT="${DBX_PORT:-4224}"
echo "static web verification page size: $(getconf PAGE_SIZE 2>/dev/null || echo unknown)"
./dbx >/tmp/dbx-web-static.log 2>&1 &
pid=$!
cleanup() {
kill "$pid" >/dev/null 2>&1 || true
wait "$pid" >/dev/null 2>&1 || true
}
trap cleanup EXIT
for _ in $(seq 1 "$verify_seconds"); do
if ! kill -0 "$pid" 2>/dev/null; then
cat /tmp/dbx-web-static.log || true
wait "$pid"
exit 1
fi
if curl -fsS "http://127.0.0.1:${DBX_PORT}/" >/tmp/dbx-web-static-index.html 2>/dev/null; then
curl -fsS "http://127.0.0.1:${DBX_PORT}/api/auth/check" >/tmp/dbx-web-static-auth.json
grep -q '"authenticated":true' /tmp/dbx-web-static-auth.json
echo "static web verification HTTP check passed"
tail -40 /tmp/dbx-web-static.log || true
exit 0
fi
sleep 1
done
cat /tmp/dbx-web-static.log || true
echo "static dbx-web did not become ready within ${verify_seconds}s" >&2
exit 1

View File

@ -13,10 +13,11 @@ name = "dbx_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[features]
default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher"]
default = ["duckdb-bundled", "mq-admin", "sqlite-sqlcipher", "system-fonts"]
duckdb-bundled = ["duckdb", "duckdb/parquet", "dbx-core/duckdb-bundled"]
mq-admin = ["dbx-core/mq-admin"]
sqlite-sqlcipher = ["dbx-core/sqlite-sqlcipher"]
system-fonts = ["dbx-core/system-fonts"]
[build-dependencies]
tauri-build = { version = "2.5.6", features = [] }