#!/bin/bash set -euo pipefail ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) VERSION=15.1.0 PGVECTOR_VERSION=0.8.6 PGVECTOR_PACKAGE_VERSION=0.8.6-1.pgdg12%2B1 PGVECTOR_SHA256=b27ff894d1e2d23ebd7528fcb986923391977cbd5c5379ed74527875246854ca OUTPUT="${1:-$ROOT_DIR/artifacts/fnos/nxsir-postgresql-${VERSION}-x86_64.fpk}" DOTNET_BIN="${DOTNET:-dotnet}" NUGET_FEED="${POSTGRES_SERVICE_NUGET_FEED:-}" NUGET_PACKAGES="${NUGET_PACKAGES:-$ROOT_DIR/.cache/nuget-packages}" DOTNET_CLI_HOME="${DOTNET_CLI_HOME:-$ROOT_DIR/.cache/dotnet-cli-home}" BUILD_TMP_ROOT="${POSTGRES_SERVICE_BUILD_TMPDIR:-$ROOT_DIR/.cache/fnos-build-tmp}" if [ -n "${FNPACK:-}" ]; then FNPACK_BIN="$FNPACK" elif [ -x "$ROOT_DIR/.tools/fnpack" ]; then FNPACK_BIN="$ROOT_DIR/.tools/fnpack" else FNPACK_BIN=fnpack fi FNPACK_BIN=$(command -v "$FNPACK_BIN") || { printf 'fnpack is required; install it from the fnOS developer portal or set FNPACK.\n' >&2 exit 1 } for command_name in npm apt-get curl dpkg-deb node sha256sum tar find realpath; do command -v "$command_name" >/dev/null 2>&1 || { printf 'required build command is missing: %s\n' "$command_name" >&2 exit 1 } done DOTNET_BIN=$(command -v "$DOTNET_BIN") || { printf 'missing .NET SDK: %s\n' "$DOTNET_BIN" >&2; exit 1; } if [ -n "$NUGET_FEED" ] && [ ! -d "$NUGET_FEED" ]; then printf 'offline NuGet feed does not exist: %s\n' "$NUGET_FEED" >&2 exit 1 fi mkdir -p "$BUILD_TMP_ROOT" "$(dirname -- "$OUTPUT")" WORK_DIR=$(mktemp -d "${BUILD_TMP_ROOT%/}/postgres-service-fnos-build.XXXXXX") trap 'rm -rf -- "$WORK_DIR"' EXIT STAGE="$WORK_DIR/stage" PACKED_ROOT="$WORK_DIR/packed" FNPACK_TMP_ROOT="$WORK_DIR/fnpack-tmp" RUNTIME_ROOT="$STAGE/app/runtime" EXTRACT_ROOT="$WORK_DIR/runtime-extract" mkdir -p "$STAGE/app/server/wwwroot" "$RUNTIME_ROOT" "$PACKED_ROOT" "$FNPACK_TMP_ROOT" "$EXTRACT_ROOT" cp -a "$ROOT_DIR/fnos-postgresql/." "$STAGE/" printf 'Building PostgreSQL management frontend...\n' npm run build:postgres-admin --prefix "$ROOT_DIR/frontend" cp -a "$ROOT_DIR/frontend/dist-postgres/." "$STAGE/app/server/wwwroot/" printf 'Publishing self-contained PostgreSQL management API...\n' export NUGET_PACKAGES DOTNET_CLI_HOME restore_args=(-r linux-x64 --source "https://api.nuget.org/v3/index.json" --disable-parallel) if [ -n "$NUGET_FEED" ]; then restore_args+=(--source "$NUGET_FEED") fi "$DOTNET_BIN" restore "$ROOT_DIR/src/PostgresService.WebApi/PostgresService.WebApi.csproj" "${restore_args[@]}" "$DOTNET_BIN" publish "$ROOT_DIR/src/PostgresService.WebApi/PostgresService.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 "$STAGE/app/server" \ /maxcpucount:1 rm -f "$STAGE/app/server/"*.pdb printf 'Downloading pinned Debian Bookworm PostgreSQL 15 runtime...\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=$APT_ROOT/var/cache/apt" -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 shopt -s nullglob runtime_packages=("$APT_ROOT"/var/cache/apt/archives/*.deb) test "${#runtime_packages[@]}" -gt 0 || { printf 'APT did not download PostgreSQL runtime packages\n' >&2; exit 1; } for package_file in "${runtime_packages[@]}"; do dpkg-deb -x "$package_file" "$EXTRACT_ROOT" done shopt -u nullglob printf 'Downloading pinned pgvector %s extension...\n' "$PGVECTOR_VERSION" PGVECTOR_DEB="$WORK_DIR/postgresql-15-pgvector.deb" curl --fail --location --retry 3 \ "https://apt.postgresql.org/pub/repos/apt/pool/main/p/pgvector/postgresql-15-pgvector_${PGVECTOR_PACKAGE_VERSION}_amd64.deb" \ --output "$PGVECTOR_DEB" printf '%s %s\n' "$PGVECTOR_SHA256" "$PGVECTOR_DEB" | sha256sum --check --status test "$(dpkg-deb -f "$PGVECTOR_DEB" Package)" = "postgresql-15-pgvector" test "$(dpkg-deb -f "$PGVECTOR_DEB" Version)" = "${PGVECTOR_PACKAGE_VERSION//%2B/+}" dpkg-deb -x "$PGVECTOR_DEB" "$EXTRACT_ROOT" printf 'Assembling relocatable PostgreSQL runtime...\n' cp -a "$EXTRACT_ROOT/." "$RUNTIME_ROOT/" rm -rf \ "$RUNTIME_ROOT/usr/share/doc" \ "$RUNTIME_ROOT/usr/share/man" \ "$RUNTIME_ROOT/usr/share/locale" \ "$RUNTIME_ROOT/var" \ "$RUNTIME_ROOT/etc/init.d" \ "$RUNTIME_ROOT/usr/sbin" # fnOS provides the matching glibc and loader. Keep other Debian libraries but # never package a second libc implementation into the application runtime. find "$RUNTIME_ROOT" -type f \( \ -name 'ld-linux-*.so.*' -o -name 'libc.so.*' -o -name 'libm.so.*' -o \ -name 'libmvec.so.*' -o -name 'libpthread.so.*' -o -name 'libdl.so.*' -o \ -name 'librt.so.*' -o -name 'libresolv.so.*' -o -name 'libutil.so.*' \ \) -delete # Official fnOS packages reject absolute links. Rewrite Debian links to remain # inside the staged runtime and fail if a link target was not packaged. while IFS= read -r -d '' link_path; do target=$(readlink -- "$link_path") case "$target" in /*) staged_target="$RUNTIME_ROOT$target" if [ ! -e "$staged_target" ]; then rm "$link_path" continue fi relative_target=$(realpath --relative-to="$(dirname -- "$link_path")" "$staged_target") rm "$link_path" ln -s "$relative_target" "$link_path" ;; esac done < <(find "$RUNTIME_ROOT" -type l -print0) for required_file in \ "$RUNTIME_ROOT/usr/lib/postgresql/15/bin/postgres" \ "$RUNTIME_ROOT/usr/lib/postgresql/15/bin/initdb" \ "$RUNTIME_ROOT/usr/lib/postgresql/15/bin/pg_ctl" \ "$RUNTIME_ROOT/usr/lib/postgresql/15/bin/pg_dump" \ "$RUNTIME_ROOT/usr/lib/postgresql/15/lib/vector.so" \ "$RUNTIME_ROOT/usr/share/postgresql/15/extension/vector.control"; do test -e "$required_file" || { printf 'runtime file is missing: %s\n' "$required_file" >&2; exit 1; } done node "$ROOT_DIR/scripts/generate-fnos-icons.mjs" "$STAGE" "$STAGE/app/ui/images" chmod 0755 "$STAGE/cmd/"* printf 'Packing PostgreSQL service with official fnOS fnpack...\n' ( cd "$PACKED_ROOT" TMPDIR="$FNPACK_TMP_ROOT" "$FNPACK_BIN" build --directory "$STAGE" ) built_package=$(find "$PACKED_ROOT" -maxdepth 1 -type f -name '*.fpk' -print -quit) test -n "$built_package" || { printf 'fnpack did not create an FPK\n' >&2; exit 1; } mv "$built_package" "$OUTPUT" ( cd "$(dirname -- "$OUTPUT")" sha256sum "$(basename -- "$OUTPUT")" >"$(basename -- "$OUTPUT").sha256" ) "$ROOT_DIR/scripts/verify-fnos-package.sh" "$OUTPUT" printf 'Built %s\n' "$OUTPUT"