feat(release): add static browser builds
This commit is contained in:
parent
d9a6655fd9
commit
2bfbe67f47
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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 = [] }
|
||||
|
|
|
|||
Loading…
Reference in New Issue