diff --git a/client-gui2 b/client-gui2 index 6b9004ef..e80f9f12 160000 --- a/client-gui2 +++ b/client-gui2 @@ -1 +1 @@ -Subproject commit 6b9004ef02fde44dd95019a34edb3d750e0502f6 +Subproject commit e80f9f12edb5926141bc3157234f7715020cc484 diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index d41bbc54..86309ec7 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/packaging/appimage/AppRun b/packaging/appimage/AppRun new file mode 100755 index 00000000..5ae332cb --- /dev/null +++ b/packaging/appimage/AppRun @@ -0,0 +1,120 @@ +#!/bin/bash + +set -u + +umask 077 + +if [[ -z "${APPDIR:-}" ]]; then + echo "Komp AC: APPDIR is not set by the AppImage runtime" >&2 + exit 1 +fi + +data_home="${XDG_DATA_HOME:-${HOME:?HOME is not set}/.local/share}" +data_root="${KOMP_AC_DATA_DIR:-$data_home/komp_ac}" +postgres_root="$data_root/postgresql" +dump_root="$data_root/dumps" +tantivy_root="$data_root/tantivy_indexes" +secret_file="$data_root/jwt.secret" +log_file="$data_root/server.log" +lock_dir="$data_root/appimage.lock" +server="$APPDIR/usr/bin/komp-ac-server" +gui="$APPDIR/AppRun.gui" +server_db_mode_file="$APPDIR/usr/share/komp-ac/server-db-mode" + +if [[ ! -r "$server_db_mode_file" ]]; then + echo "Komp AC: bundled server database mode is missing" >&2 + exit 1 +fi +server_db_mode="$(<"$server_db_mode_file")" +if [[ "$server_db_mode" != embedded && "$server_db_mode" != external ]]; then + echo "Komp AC: invalid bundled server database mode: $server_db_mode" >&2 + exit 1 +fi + +mkdir -p "$data_root" "$postgres_root" "$dump_root" "$tantivy_root" + +if [[ ! -s "$secret_file" ]]; then + secret_tmp="$secret_file.$$" + if ! od -An -N 48 -tx1 /dev/urandom | tr -d ' \n' > "$secret_tmp"; then + rm -f -- "$secret_tmp" + echo "Komp AC: could not generate the local server secret" >&2 + exit 1 + fi + chmod 600 "$secret_tmp" + if [[ -e "$secret_file" ]]; then + rm -f -- "$secret_tmp" + else + mv -- "$secret_tmp" "$secret_file" + fi +fi + +if ! mkdir "$lock_dir" 2>/dev/null; then + lock_pid="" + if [[ -r "$lock_dir/pid" ]]; then + read -r lock_pid < "$lock_dir/pid" || true + fi + if [[ "$lock_pid" =~ ^[0-9]+$ ]] && kill -0 "$lock_pid" 2>/dev/null; then + echo "Komp AC is already running (launcher PID $lock_pid)." >&2 + exit 1 + fi + rm -f -- "$lock_dir/pid" + if ! rmdir "$lock_dir" 2>/dev/null || ! mkdir "$lock_dir" 2>/dev/null; then + echo "Komp AC: could not acquire the application lock at $lock_dir" >&2 + exit 1 + fi +fi +printf '%s\n' "$$" > "$lock_dir/pid" + +server_pid="" +cleanup() { + status=$? + trap - EXIT INT TERM HUP + if [[ -n "$server_pid" ]] && kill -0 "$server_pid" 2>/dev/null; then + kill -TERM "$server_pid" 2>/dev/null || true + wait "$server_pid" 2>/dev/null || true + fi + rm -f -- "$lock_dir/pid" + rmdir "$lock_dir" 2>/dev/null || true + exit "$status" +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM +trap 'exit 129' HUP + +export GRPC_ENDPOINT="http://127.0.0.1:50051" + +if (exec 3<>/dev/tcp/127.0.0.1/50051) 2>/dev/null; then + echo "Komp AC: local port 50051 is already in use" >&2 + exit 1 +fi + +RUST_DB_MODE="$server_db_mode" \ +KOMP_AC_POSTGRES_DIR="$postgres_root" \ +PG_DUMP_DIR="$dump_root" \ +TANTIVY_INDEX_DIR="$tantivy_root" \ +JWT_SECRET="$(<"$secret_file")" \ +GRPC_LISTEN_ADDRESS="127.0.0.1:50051" \ +"$server" server >> "$log_file" 2>&1 & +server_pid=$! + +ready=false +for ((attempt = 0; attempt < 600; attempt++)); do + if ! kill -0 "$server_pid" 2>/dev/null; then + wait "$server_pid" || true + echo "Komp AC: the bundled server failed to start; see $log_file" >&2 + exit 1 + fi + if (exec 3<>/dev/tcp/127.0.0.1/50051) 2>/dev/null; then + ready=true + break + fi + sleep 0.1 +done + +if [[ "$ready" != true ]]; then + echo "Komp AC: the bundled server did not become ready; see $log_file" >&2 + exit 1 +fi + +"$gui" diff --git a/packaging/appimage/Containerfile.debian b/packaging/appimage/Containerfile.debian new file mode 100644 index 00000000..933f0c42 --- /dev/null +++ b/packaging/appimage/Containerfile.debian @@ -0,0 +1,55 @@ +FROM docker.io/library/node:22-bookworm + +ARG RUST_TOOLCHAIN=1.85.0 + +ENV DEBIAN_FRONTEND=noninteractive \ + RUSTUP_HOME=/opt/rust/rustup \ + CARGO_HOME=/opt/rust/cargo \ + PATH=/opt/rust/cargo/bin:${PATH} \ + YARN_CACHE_FOLDER=/var/cache/yarn \ + APPIMAGE_EXTRACT_AND_RUN=1 + +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + binutils \ + build-essential \ + ca-certificates \ + clang \ + cmake \ + curl \ + desktop-file-utils \ + file \ + git \ + libayatana-appindicator3-dev \ + libclang-dev \ + libfuse2 \ + libgtk-3-dev \ + librsvg2-dev \ + libssl-dev \ + libwebkit2gtk-4.1-dev \ + libxdo-dev \ + patchelf \ + pkg-config \ + protobuf-compiler \ + python3 \ + squashfs-tools \ + wget \ + xz-utils \ + zsync \ + && rm -rf /var/lib/apt/lists/* + +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \ + | sh -s -- -y --profile minimal --default-toolchain "${RUST_TOOLCHAIN}" \ + && rustc --version \ + && cargo --version + +RUN node --version \ + && yarn_version="$(yarn --version)" \ + && test "$yarn_version" = 1.22.22 \ + && echo "yarn $yarn_version" + +COPY container-build-debian.sh /usr/local/bin/build-komp-ac-debian +RUN chmod 755 /usr/local/bin/build-komp-ac-debian + +WORKDIR /work +ENTRYPOINT ["/usr/local/bin/build-komp-ac-debian"] diff --git a/packaging/appimage/README.md b/packaging/appimage/README.md new file mode 100644 index 00000000..27dd676e --- /dev/null +++ b/packaging/appimage/README.md @@ -0,0 +1,121 @@ +# Combined Linux AppImage + +This packaging layer keeps `server` and `client-gui2` independently buildable +and combines their release artifacts into one `x86_64` Linux AppImage. The +AppImage entry point starts the selected server build, waits for its local gRPC +port, runs the GUI, and terminates the server when the GUI exits. + +With the `full-embed` server feature, the PostgreSQL installation, database, +dumps, JWT secret and server log are persistent and are not stored in the +read-only AppImage. By default they live under +`${XDG_DATA_HOME:-$HOME/.local/share}/komp_ac`. Set `KOMP_AC_DATA_DIR` to +override that root. A `full` build instead uses the external PostgreSQL settings +supplied through `RUST_DB_*` environment variables. Both modes create fresh +runtime Tantivy indexes under the application-data root; existing repository +indexes are never packaged. + +## Build both and combine + +The frontend dependencies must already be present in `client-gui2/node_modules`. +The build also requires Nix and Tauri's Linux build dependencies. It uses +`appimagetool` from `APPIMAGETOOL` or `PATH` when available, otherwise it reuses +Tauri's cached `linuxdeploy-plugin-appimage.AppImage`. It does not fetch or +install frontend dependencies. + +```sh +packaging/appimage/build.sh ./KompAC.AppImage +``` + +This performs the existing builds independently: + +1. `nix build '.?submodules=1#portable-server'` +2. `yarn tauri build --bundles appimage` in `client-gui2` +3. `assemble.sh` to add the portable server and supervisor to the GUI AppImage + +The standalone server and GUI build processes remain available unchanged. + +## Combine existing artifacts + +Already-built artifacts can be combined without rebuilding either application. +The first argument may be either a completed GUI AppImage or Tauri's completed +`.AppDir` directory: + +```sh +packaging/appimage/assemble.sh \ + client-gui2/src-tauri/target/release/bundle/appimage/komp_ac.AppDir \ + result/bin/server \ + embedded \ + ./KompAC.AppImage +``` + +The generated AppImage uses local embedded mode and binds gRPC only to +`127.0.0.1:50051`. Only one combined AppImage instance may run against a data +directory at a time. + +## Debian production build from NixOS + +`build-debian.sh` uses rootless Podman to build inside a Debian 12 userspace. +The host source is mounted read-only and copied into the temporary container. +Cargo and Yarn caches use named Podman volumes, so neither Cargo target files +nor frontend dependencies are written into the repository. The source snapshot +excludes build outputs, local environment files and generated runtime data such +as Tantivy indexes, dumps and embedded PostgreSQL data. + +The server feature is a required argument. Nothing implicitly chooses the +database implementation: + +```sh +# One-click application with PostgreSQL embedded in the server binary +packaging/appimage/build-debian.sh full-embed + +# GUI and server using a separately managed PostgreSQL instance +packaging/appimage/build-debian.sh full +``` + +The default embedded outputs are: + +```text +debian-dist/KompAC-embedded-debian12-amd64.AppImage +debian-dist/bin/client-gui2 +debian-dist/bin/server +``` + +`full-embed` runs `cargo build --release --package server --features +full-embed`; PostgreSQL 17.10.0 is embedded by `postgresql_embedded`. `full` +runs the same command with `--features full` and configures the AppImage launcher +for external database mode. OpenSSL is linked statically in both builds. The GUI +is built by Tauri inside Debian with its normal AppImage dependency collection. + +An alternative output directory and filename may be supplied: + +```sh +packaging/appimage/build-debian.sh \ + full-embed \ + /path/to/releases \ + KompAC-0.8.48-debian12-amd64.AppImage +``` + +An external build needs its database connection when launched: + +```sh +RUST_DB_USER=komp_ac \ +RUST_DB_PASSWORD=secret \ +RUST_DB_HOST=127.0.0.1 \ +RUST_DB_PORT=5432 \ +RUST_DB_NAME=komp_ac \ +./debian-dist/KompAC-external-debian12-amd64.AppImage +``` + +The build requires network access for the Debian builder image, APT packages, +Rust crates, frontend packages, PostgreSQL's embedded archive and Tauri's +AppImage tooling. The named cache volumes are retained for later production +builds. They can be inspected with `podman volume ls`. + +On NixOS, the host script uses `/etc/containers/containers.conf` when Podman's +vendor configuration below `/usr/share` is not readable. An explicitly supplied +`CONTAINERS_CONF` is always preserved. If `/usr/share/containers/seccomp.json` +is also unreadable, the script visibly passes `--security-opt +seccomp=unconfined` to the rootless build containers. Restore normal host +permissions with `sudo chmod 755 /usr/share` to retain seccomp isolation. The +same fallback disables automatic OCI hook discovery when its vendor directory +is unreadable; explicitly configured hook overrides are never replaced. diff --git a/packaging/appimage/assemble.sh b/packaging/appimage/assemble.sh new file mode 100755 index 00000000..005d29fa --- /dev/null +++ b/packaging/appimage/assemble.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +set -euo pipefail + +usage() { + echo "Usage: $0 GUI.AppImage-or-AppDir SERVER_BINARY SERVER_DB_MODE OUTPUT.AppImage" >&2 +} + +if [[ $# -ne 4 ]]; then + usage + exit 2 +fi + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +gui_bundle="$(realpath -- "$1")" +server_binary="$(realpath -- "$2")" +server_db_mode="$3" +output_parent="$(dirname -- "$4")" +mkdir -p "$output_parent" +output="$(cd -- "$output_parent" && pwd)/$(basename -- "$4")" + +if [[ "$server_db_mode" != embedded && "$server_db_mode" != external ]]; then + echo "SERVER_DB_MODE must be embedded or external, got: $server_db_mode" >&2 + exit 2 +fi + +if [[ ! -d "$gui_bundle" && ! -x "$gui_bundle" ]]; then + echo "GUI bundle is neither an AppDir nor an executable AppImage: $gui_bundle" >&2 + exit 1 +fi +if [[ ! -x "$server_binary" ]]; then + echo "Server binary is not executable: $server_binary" >&2 + exit 1 +fi + +work_dir="$(mktemp -d /tmp/komp-ac-appimage.XXXXXX)" +cleanup() { + case "$work_dir" in + /tmp/komp-ac-appimage.*) + rm -rf -- "$work_dir" + ;; + *) + echo "Refusing to remove unexpected temporary directory: $work_dir" >&2 + ;; + esac +} +trap cleanup EXIT INT TERM HUP + +app_dir="$work_dir/squashfs-root" +if [[ -d "$gui_bundle" ]]; then + mkdir "$app_dir" + cp -a -- "$gui_bundle/." "$app_dir/" +else + ( + cd -- "$work_dir" + "$gui_bundle" --appimage-extract >/dev/null + ) +fi + +if [[ ! -e "$app_dir/AppRun" ]]; then + echo "The GUI AppImage did not contain an AppRun entry point" >&2 + exit 1 +fi + +mv -- "$app_dir/AppRun" "$app_dir/AppRun.gui" +install -Dm755 "$script_dir/AppRun" "$app_dir/AppRun" +install -Dm755 "$server_binary" "$app_dir/usr/bin/komp-ac-server" +install -d "$app_dir/usr/share/komp-ac" +printf '%s\n' "$server_db_mode" > "$app_dir/usr/share/komp-ac/server-db-mode" + +# Tauri names the root icon after productName (`komp_ac.png`) while its desktop +# entry names the Rust binary (`Icon=client-gui2`). AppImage packers require the +# desktop icon name to resolve at the AppDir root. +if [[ -f "$app_dir/komp_ac.png" && ! -e "$app_dir/client-gui2.png" ]]; then + ln -s komp_ac.png "$app_dir/client-gui2.png" +fi + +if [[ -n "${APPIMAGETOOL:-}" ]]; then + ARCH=x86_64 "$APPIMAGETOOL" "$app_dir" "$output" +elif command -v appimagetool >/dev/null 2>&1; then + ARCH=x86_64 appimagetool "$app_dir" "$output" +else + cache_home="${XDG_CACHE_HOME:-${HOME:?HOME is not set}/.cache}" + appimage_plugin="$cache_home/tauri/linuxdeploy-plugin-appimage.AppImage" + if [[ ! -x "$appimage_plugin" ]]; then + echo "No AppImage packer found; set APPIMAGETOOL to appimagetool's executable path" >&2 + exit 1 + fi + ARCH=x86_64 \ + LDAI_NO_APPSTREAM=1 \ + LDAI_OUTPUT="$output" \ + "$appimage_plugin" --appimage-extract-and-run --appdir="$app_dir" +fi +echo "Created $output" diff --git a/packaging/appimage/build-debian.sh b/packaging/appimage/build-debian.sh new file mode 100755 index 00000000..a5fe02a3 --- /dev/null +++ b/packaging/appimage/build-debian.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +set -euo pipefail + +if [[ $# -lt 1 || $# -gt 3 ]]; then + echo "Usage: $0 SERVER_FEATURE [OUTPUT_DIRECTORY] [OUTPUT_FILENAME]" >&2 + echo "SERVER_FEATURE must be full-embed or full" >&2 + exit 2 +fi + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "$script_dir/../.." && pwd)" +server_feature="$1" +output_directory="${2:-$repo_root/debian-dist}" + +case "$server_feature" in + full-embed) + server_db_mode=embedded + default_output_name=KompAC-embedded-debian12-amd64.AppImage + ;; + full) + server_db_mode=external + default_output_name=KompAC-external-debian12-amd64.AppImage + ;; + *) + echo "SERVER_FEATURE must be full-embed or full, got: $server_feature" >&2 + exit 2 + ;; +esac + +output_name="${3:-$default_output_name}" + +if [[ "$output_name" == */* || "$output_name" != *.AppImage ]]; then + echo "OUTPUT_FILENAME must be a portable filename ending in .AppImage" >&2 + exit 2 +fi +if ! command -v podman >/dev/null 2>&1; then + echo "podman is required to build the Debian production AppImage" >&2 + exit 1 +fi + +# Podman normally reads a vendor configuration from /usr/share before the +# NixOS-generated configuration in /etc. Some NixOS installations have no +# traversable /usr/share, so point Podman directly at the generated file. +if [[ -z "${CONTAINERS_CONF:-}" ]] \ + && [[ ! -r /usr/share/containers/containers.conf ]] \ + && [[ -r /etc/containers/containers.conf ]]; then + export CONTAINERS_CONF=/etc/containers/containers.conf + echo "Using NixOS Podman configuration: $CONTAINERS_CONF" +fi + +podman_security_options=() +if [[ ! -r /usr/share/containers/seccomp.json ]]; then + podman_security_options=(--security-opt seccomp=unconfined) + echo "Warning: Podman's seccomp profile is unreadable; build-container seccomp is disabled" + echo "Host fix: sudo chmod 755 /usr/share" +fi + +if [[ ! -r /usr/share/containers/oci/hooks.d ]]; then + if [[ -n "${CONTAINERS_CONF_OVERRIDE:-}" ]]; then + echo "Podman's OCI hooks directory is unreadable and CONTAINERS_CONF_OVERRIDE is already set" >&2 + echo "Fix the host with: sudo chmod 755 /usr/share" >&2 + exit 1 + fi + export CONTAINERS_CONF_OVERRIDE="$script_dir/containers-nix-unreadable-usr-share.conf" + echo "Disabling unreadable vendor OCI hooks with: $CONTAINERS_CONF_OVERRIDE" +fi + +mkdir -p "$output_directory" +output_directory="$(cd -- "$output_directory" && pwd)" +image=localhost/komp-ac-debian-builder:bookworm + +echo "Building Debian 12 production artifacts" +echo " server feature: $server_feature" +echo " database mode: $server_db_mode" +echo " output: $output_directory/$output_name" +echo +echo "+ podman build ${podman_security_options[*]} --platform linux/amd64 --file $script_dir/Containerfile.debian --tag $image $script_dir" +podman build \ + "${podman_security_options[@]}" \ + --platform linux/amd64 \ + --file "$script_dir/Containerfile.debian" \ + --tag "$image" \ + "$script_dir" + +echo +echo "+ podman run ${podman_security_options[*]} ... SERVER_FEATURE=$server_feature SERVER_DB_MODE=$server_db_mode ... $image" +podman run --rm \ + "${podman_security_options[@]}" \ + --platform linux/amd64 \ + --env "OUTPUT_NAME=$output_name" \ + --env "SERVER_FEATURE=$server_feature" \ + --env "SERVER_DB_MODE=$server_db_mode" \ + --volume "$repo_root:/source:ro" \ + --volume "$output_directory:/out" \ + --volume komp-ac-debian-cargo-target:/cargo-target \ + --volume komp-ac-debian-cargo-registry:/opt/rust/cargo/registry \ + --volume komp-ac-debian-cargo-git:/opt/rust/cargo/git \ + --volume komp-ac-debian-yarn-cache:/var/cache/yarn \ + "$image" + +echo "Created $output_directory/$output_name" diff --git a/packaging/appimage/build.sh b/packaging/appimage/build.sh new file mode 100755 index 00000000..c66b2c97 --- /dev/null +++ b/packaging/appimage/build.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +set -euo pipefail + +if [[ $# -gt 1 ]]; then + echo "Usage: $0 [OUTPUT.AppImage]" >&2 + exit 2 +fi + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd -- "$script_dir/../.." && pwd)" +output="${1:-$repo_root/KompAC.AppImage}" +work_dir="$(mktemp -d /tmp/komp-ac-appimage-build.XXXXXX)" +cleanup() { + case "$work_dir" in + /tmp/komp-ac-appimage-build.*) + rm -rf -- "$work_dir" + ;; + *) + echo "Refusing to remove unexpected temporary directory: $work_dir" >&2 + ;; + esac +} +trap cleanup EXIT INT TERM HUP + +( + cd -- "$repo_root" + nix build '.?submodules=1#portable-server' --out-link "$work_dir/portable-server" +) + +set +e +( + cd -- "$repo_root/client-gui2" + yarn tauri build --bundles appimage +) +tauri_status=$? +set -e + +shopt -s nullglob +gui_appimages=("$repo_root"/client-gui2/src-tauri/target/release/bundle/appimage/*.AppImage) +gui_bundle="" +if [[ $tauri_status -eq 0 && ${#gui_appimages[@]} -gt 0 ]]; then + gui_bundle="${gui_appimages[0]}" + for candidate in "${gui_appimages[@]:1}"; do + if [[ "$candidate" -nt "$gui_bundle" ]]; then + gui_bundle="$candidate" + fi + done +fi + +if [[ -z "$gui_bundle" ]]; then + gui_appdir="$repo_root/client-gui2/src-tauri/target/release/bundle/appimage/komp_ac.AppDir" + if [[ -d "$gui_appdir" ]] && \ + cmp -s \ + "$repo_root/client-gui2/src-tauri/target/release/client-gui2" \ + "$gui_appdir/usr/bin/client-gui2"; then + gui_bundle="$gui_appdir" + echo "Tauri did not emit a current GUI AppImage; using its completed AppDir" + else + echo "The Tauri build did not produce a current GUI AppImage or AppDir" >&2 + exit "${tauri_status:-1}" + fi +fi + +if [[ $tauri_status -ne 0 ]]; then + echo "Continuing because the release GUI and its AppDir were completed before linuxdeploy failed" +fi + +"$script_dir/assemble.sh" \ + "$gui_bundle" \ + "$work_dir/portable-server/bin/server" \ + embedded \ + "$output" diff --git a/packaging/appimage/container-build-debian.sh b/packaging/appimage/container-build-debian.sh new file mode 100755 index 00000000..d0dc61cf --- /dev/null +++ b/packaging/appimage/container-build-debian.sh @@ -0,0 +1,136 @@ +#!/bin/bash + +set -euo pipefail + +if [[ "$(uname -m)" != "x86_64" ]]; then + echo "The Debian production build requires an x86_64 container" >&2 + exit 1 +fi + +output_name="${OUTPUT_NAME:-KompAC-debian12-amd64.AppImage}" +if [[ "$output_name" == */* || "$output_name" != *.AppImage ]]; then + echo "OUTPUT_NAME must be a portable filename ending in .AppImage" >&2 + exit 1 +fi + +server_feature="${SERVER_FEATURE:-}" +server_db_mode="${SERVER_DB_MODE:-}" +case "$server_feature:$server_db_mode" in + full-embed:embedded|full:external) + ;; + *) + echo "Expected SERVER_FEATURE/SERVER_DB_MODE to be full-embed/embedded or full/external" >&2 + exit 2 + ;; +esac + +source_root=/work/source +rm -rf -- "$source_root" +mkdir -p "$source_root" /out/bin /cargo-target /var/cache/yarn + +echo "+ copying source snapshot into the build container" +tar \ + --exclude-vcs \ + --exclude='./target' \ + --exclude='*/target' \ + --exclude='./.direnv' \ + --exclude='*/.direnv' \ + --exclude='./.env' \ + --exclude='*/.env' \ + --exclude='*/tantivy_indexes' \ + --exclude='*/dumps' \ + --exclude='*/.postgres-data' \ + --exclude='*/benchmark-results' \ + --exclude='*/.mutants-run' \ + --exclude='*/__pycache__' \ + --exclude='./client-gui2/node_modules' \ + --exclude='./client-gui2/dist' \ + --exclude='./debian-dist' \ + --exclude='./result' \ + -C /source -cf - . \ + | tar -C "$source_root" -xf - + +cd "$source_root/client-gui2" +echo "+ yarn install --frozen-lockfile --non-interactive" +yarn install --frozen-lockfile --non-interactive + +cd "$source_root" +echo "+ cargo build --release --package server --features $server_feature" +OPENSSL_STATIC=1 \ +POSTGRESQL_VERSION='=17.10.0' \ +SQLX_OFFLINE=true \ +CARGO_TARGET_DIR=/cargo-target \ +cargo build --release --package server --features "$server_feature" + +server_binary=/cargo-target/release/server +if [[ ! -x "$server_binary" ]]; then + echo "The release server binary was not produced" >&2 + exit 1 +fi +if readelf -l "$server_binary" | grep -q '/nix/store'; then + echo "The Debian server unexpectedly contains a Nix interpreter" >&2 + exit 1 +fi +if readelf -d "$server_binary" | grep -Eq 'libssl\.so|libcrypto\.so'; then + echo "The release server did not link OpenSSL statically" >&2 + exit 1 +fi +install -Dm755 "$server_binary" /out/bin/server + +cd "$source_root/client-gui2" +echo "+ yarn tauri build --bundles appimage" +set +e +APPIMAGE_EXTRACT_AND_RUN=1 \ +CARGO_TARGET_DIR=/cargo-target \ +yarn tauri build --bundles appimage +tauri_status=$? +set -e + +shopt -s nullglob +gui_appimages=(/cargo-target/release/bundle/appimage/*.AppImage) +gui_bundle="" +if [[ $tauri_status -eq 0 && ${#gui_appimages[@]} -gt 0 ]]; then + gui_bundle="${gui_appimages[0]}" + for candidate in "${gui_appimages[@]:1}"; do + if [[ "$candidate" -nt "$gui_bundle" ]]; then + gui_bundle="$candidate" + fi + done +fi + +if [[ -z "$gui_bundle" ]]; then + gui_appdir=/cargo-target/release/bundle/appimage/komp_ac.AppDir + if [[ -d "$gui_appdir" ]] \ + && cmp -s /cargo-target/release/client-gui2 "$gui_appdir/usr/bin/client-gui2"; then + gui_bundle="$gui_appdir" + echo "Tauri completed its AppDir; bypassing its failed linuxdeploy output step" + else + echo "Tauri did not produce a current GUI AppImage or AppDir" >&2 + exit "${tauri_status:-1}" + fi +fi + +if [[ $tauri_status -ne 0 ]]; then + echo "Continuing because the current release GUI and AppDir were completed before linuxdeploy failed" +fi + +install -Dm755 /cargo-target/release/client-gui2 /out/bin/client-gui2 + +cd "$source_root" +echo "+ packaging/appimage/assemble.sh $gui_bundle $server_binary $server_db_mode /out/$output_name" +packaging/appimage/assemble.sh \ + "$gui_bundle" \ + "$server_binary" \ + "$server_db_mode" \ + "/out/$output_name" + +if [[ ! -x "/out/$output_name" ]]; then + echo "The combined AppImage was not produced" >&2 + exit 1 +fi + +echo +echo "Debian production artifacts:" +echo " /out/$output_name" +echo " /out/bin/client-gui2" +echo " /out/bin/server" diff --git a/packaging/appimage/containers-nix-unreadable-usr-share.conf b/packaging/appimage/containers-nix-unreadable-usr-share.conf new file mode 100644 index 00000000..18c6a8d8 --- /dev/null +++ b/packaging/appimage/containers-nix-unreadable-usr-share.conf @@ -0,0 +1,2 @@ +[engine] +hooks_dir = [] diff --git a/server b/server index 8837ddbc..590dea31 160000 --- a/server +++ b/server @@ -1 +1 @@ -Subproject commit 8837ddbcbd59aeaf41bcf261cac05dc1fcfd9928 +Subproject commit 590dea3135c0a477dd737e040dbda84454bbd6a9