Files
live_recorder/scripts/build-fnos-package.sh
T
2026-08-02 14:43:37 +08:00

157 lines
5.8 KiB
Bash
Executable File

#!/bin/bash
set -euo pipefail
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
VERSION=1.0.0
OUTPUT="${1:-$ROOT_DIR/artifacts/fnos/liverecorder-${VERSION}-x86_64.fpk}"
WORKSPACE_CACHE=$(CDPATH= cd -- "$ROOT_DIR/.." && pwd)
DOTNET_BIN="${DOTNET:-$WORKSPACE_CACHE/.dotnet8/dotnet}"
NUGET_FEED="${LIVERECORDER_NUGET_FEED:-$WORKSPACE_CACHE/.nuget-feed}"
NUGET_PACKAGES="${NUGET_PACKAGES:-$WORKSPACE_CACHE/.nuget-packages}"
DOTNET_CLI_HOME="${DOTNET_CLI_HOME:-$WORKSPACE_CACHE/.dotnet-cli-home}"
BUILD_TMP_ROOT="${LIVERECORDER_BUILD_TMPDIR:-$WORKSPACE_CACHE/.fnos-build-tmp}"
SOURCE_DATE_EPOCH="${SOURCE_DATE_EPOCH:-$(git -C "$ROOT_DIR" show -s --format=%ct HEAD)}"
mkdir -p "$BUILD_TMP_ROOT" "$(dirname -- "$OUTPUT")"
WORK_DIR=$(mktemp -d "${BUILD_TMP_ROOT%/}/liverecorder-fnos-build.XXXXXX")
trap 'rm -rf -- "$WORK_DIR"' EXIT
for command_name in npm apt-get dpkg-deb tar md5sum sha256sum node; do
command -v "$command_name" >/dev/null 2>&1 || {
printf 'required build command is missing: %s\n' "$command_name" >&2
exit 1
}
done
test -x "$DOTNET_BIN" || { printf 'missing .NET SDK: %s\n' "$DOTNET_BIN" >&2; exit 1; }
test -d "$NUGET_FEED" || { printf 'missing offline NuGet feed: %s\n' "$NUGET_FEED" >&2; exit 1; }
printf 'Building frontend...\n'
npm run build --prefix "$ROOT_DIR/frontend"
printf 'Publishing self-contained .NET application...\n'
export NUGET_PACKAGES DOTNET_CLI_HOME
"$DOTNET_BIN" restore "$ROOT_DIR/src/LiveRecorder.WebApi/LiveRecorder.WebApi.csproj" \
-r linux-x64 \
--source "$NUGET_FEED" \
--source "https://api.nuget.org/v3/index.json" \
--disable-parallel
"$DOTNET_BIN" publish "$ROOT_DIR/src/LiveRecorder.WebApi/LiveRecorder.WebApi.csproj" \
-c Release \
-r linux-x64 \
--self-contained true \
--no-restore \
-p:DebugType=None \
-p:DebugSymbols=false \
-p:PublishSingleFile=false \
-p:PublishReadyToRun=false \
-o "$WORK_DIR/payload/server" \
/maxcpucount:1
rm -f "$WORK_DIR/payload/server/"*.pdb
mkdir -p "$WORK_DIR/payload/server/wwwroot"
cp -a "$ROOT_DIR/frontend/dist/." "$WORK_DIR/payload/server/wwwroot/"
printf 'Downloading pinned Debian Bookworm native runtime packages...\n'
APT_ROOT="$WORK_DIR/apt"
mkdir -p \
"$APT_ROOT/etc/apt" \
"$APT_ROOT/var/lib/apt/lists/partial" \
"$APT_ROOT/var/lib/dpkg" \
"$APT_ROOT/var/cache/apt/archives/partial"
cp "$ROOT_DIR/scripts/fnos-bookworm.sources.list" "$APT_ROOT/etc/apt/sources.list"
touch "$APT_ROOT/var/lib/dpkg/status"
APT_OPTIONS=(
-o "Dir::Etc::sourcelist=$APT_ROOT/etc/apt/sources.list"
-o "Dir::Etc::sourceparts=-"
-o "Dir::State::status=$APT_ROOT/var/lib/dpkg/status"
-o "Dir::State::lists=$APT_ROOT/var/lib/apt/lists"
-o "Dir::Cache::archives=$APT_ROOT/var/cache/apt/archives"
-o "Debug::NoLocking=1"
-o "APT::Architecture=amd64"
-o "Acquire::Languages=none"
)
apt-get "${APT_OPTIONS[@]}" update
apt-get "${APT_OPTIONS[@]}" \
--download-only \
--no-install-recommends \
--yes \
install \
postgresql-15 \
postgresql-client-15 \
nodejs \
ffmpeg \
curl \
ca-certificates
RUNTIME_ROOT="$WORK_DIR/payload/runtime"
mkdir -p "$RUNTIME_ROOT"
shopt -s nullglob
packages=("$APT_ROOT"/var/cache/apt/archives/*.deb)
test "${#packages[@]}" -gt 0 || { printf 'APT did not download runtime packages\n' >&2; exit 1; }
for package_file in "${packages[@]}"; do
case "$(basename -- "$package_file")" in
libc6_*|libc-bin_*)
# Native programs must use the fnOS glibc/loader as one matched
# pair. Bundling Debian's libc while an executable still starts
# through the host loader can crash before main() on newer fnOS
# releases. All other runtime libraries remain private to the app.
continue
;;
esac
dpkg-deb -x "$package_file" "$RUNTIME_ROOT"
done
shopt -u nullglob
for forbidden_glibc_file in \
"$RUNTIME_ROOT/lib/x86_64-linux-gnu/libc.so.6" \
"$RUNTIME_ROOT/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2"; do
test ! -e "$forbidden_glibc_file" || {
printf 'host glibc must not be shadowed: %s\n' "$forbidden_glibc_file" >&2
exit 1
}
done
rm -rf \
"$RUNTIME_ROOT/usr/share/doc" \
"$RUNTIME_ROOT/usr/share/man" \
"$RUNTIME_ROOT/usr/share/lintian" \
"$RUNTIME_ROOT/usr/share/locale"
if [ ! -e "$RUNTIME_ROOT/usr/bin/node" ] && [ -x "$RUNTIME_ROOT/usr/bin/nodejs" ]; then
ln -s nodejs "$RUNTIME_ROOT/usr/bin/node"
fi
for required_file in \
"$RUNTIME_ROOT/usr/lib/postgresql/15/bin/postgres" \
"$RUNTIME_ROOT/usr/lib/postgresql/15/bin/initdb" \
"$RUNTIME_ROOT/usr/bin/node" \
"$RUNTIME_ROOT/usr/bin/ffmpeg" \
"$RUNTIME_ROOT/usr/bin/curl"; do
test -x "$required_file" || { printf 'native runtime file is missing: %s\n' "$required_file" >&2; exit 1; }
done
mkdir -p "$WORK_DIR/payload/ui" "$WORK_DIR/package"
cp "$ROOT_DIR/fnos/ui/config" "$WORK_DIR/payload/ui/config"
node "$ROOT_DIR/scripts/generate-fnos-icons.mjs" "$WORK_DIR/package" "$WORK_DIR/payload/ui/images"
printf 'Packing fnOS payload...\n'
tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owner \
-czf "$WORK_DIR/package/app.tgz" \
-C "$WORK_DIR/payload" \
server runtime ui
cp -a "$ROOT_DIR/fnos/cmd" "$ROOT_DIR/fnos/config" "$ROOT_DIR/fnos/wizard" "$WORK_DIR/package/"
chmod 0755 "$WORK_DIR/package/cmd/"*
checksum=$(md5sum "$WORK_DIR/package/app.tgz" | cut -d' ' -f1)
sed "s/@CHECKSUM@/$checksum/" "$ROOT_DIR/fnos/manifest" >"$WORK_DIR/package/manifest"
tar --sort=name --mtime="@$SOURCE_DATE_EPOCH" --owner=0 --group=0 --numeric-owner \
-czf "$OUTPUT" \
-C "$WORK_DIR/package" \
app.tgz cmd config wizard ICON.PNG ICON_256.PNG manifest
(
cd "$(dirname -- "$OUTPUT")"
sha256sum "$(basename -- "$OUTPUT")" >"$(basename -- "$OUTPUT").sha256"
)
"$ROOT_DIR/scripts/verify-fnos-package.sh" "$OUTPUT"
printf 'Built %s\n' "$OUTPUT"