Merge pull request #13 from Twest2/StepForge pr/09-linux-dnf-rpm
Template tests / tests (push) Failing after 34s
Template tests / tests (push) Failing after 34s
Add production .rpm packaging and dnf setup; share runtime staging (plan PR 9)
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# StepForge on dnf-based Linux (Fedora / RHEL)
|
||||
|
||||
This is the setup and packaging guide for **dnf-based** distributions. Debian,
|
||||
Ubuntu, and other apt-based systems have a separate guide:
|
||||
[apt.md](apt.md).
|
||||
|
||||
## Install from the .rpm
|
||||
|
||||
```bash
|
||||
sudo dnf install ./stepforge-<version>-1.<arch>.rpm
|
||||
```
|
||||
|
||||
dnf pulls the required runtime libraries automatically (they are declared as
|
||||
`Requires`). The package installs:
|
||||
|
||||
- the app and a fixed Electron runtime under `/opt/stepforge`,
|
||||
- the `stepforge` launcher at `/usr/bin/stepforge`,
|
||||
- a desktop entry, icons, and `.sfgz`/`.sfglt` file associations.
|
||||
|
||||
Launch it from your application menu or run `stepforge`.
|
||||
|
||||
### Sandbox
|
||||
|
||||
The launcher runs **sandboxed**. On most modern kernels the Chromium
|
||||
user-namespace sandbox works out of the box; the package's `%post` also makes
|
||||
the setuid `chrome-sandbox` helper usable as a fallback. StepForge will **not**
|
||||
silently launch unsandboxed.
|
||||
|
||||
## Install from the portable tarball
|
||||
|
||||
The portable tarball (same one shipped for apt systems) includes the
|
||||
`/usr/bin/stepforge` launcher. Install the runtime libraries first:
|
||||
|
||||
```bash
|
||||
bash scripts/linux/dnf/install-runtime-deps.sh
|
||||
tar -xzf stepforge_<version>_linux-x64.tar.gz
|
||||
./usr/bin/stepforge
|
||||
```
|
||||
|
||||
## Capture capabilities on dnf systems
|
||||
|
||||
- **X11**: full per-click capture with an accurate marker (needs `xinput`).
|
||||
- **Wayland** (Fedora's default): screen capture via the XDG Desktop Portal +
|
||||
PipeWire; the portal asks permission once per recording. Per-click capture
|
||||
with coordinates is not exposed by Wayland, so recording uses a global
|
||||
hotkey or interval trigger. StepForge reports the active trigger honestly.
|
||||
|
||||
Open Settings → Diagnostics in the app to see the detected session type,
|
||||
portal/PipeWire status, and the active capture profile.
|
||||
|
||||
## Build the .rpm yourself
|
||||
|
||||
```bash
|
||||
bash scripts/linux/dnf/install-build-deps.sh # rpm-build, rpmdevtools, Xvfb, …
|
||||
nvm install && nvm use # pinned Node 22 (see .nvmrc)
|
||||
npm ci
|
||||
npm run package:linux:rpm # -> build/artifacts/*.rpm + sha256
|
||||
```
|
||||
|
||||
The builder stages **only** runtime files (shared with the `.deb` builder via
|
||||
`packaging/linux/common/stage-runtime.sh`): the app code, a fixed Electron
|
||||
runtime, and production npm dependencies. It never copies the development
|
||||
`node_modules` and fails if `node_modules` is missing.
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared staging for the Linux packages. Populates $STAGE_ROOT with the FHS
|
||||
# layout common to the .deb and .rpm: a pruned runtime-only /opt/stepforge, the
|
||||
# launcher, desktop entry, icons, MIME registration, and the license.
|
||||
#
|
||||
# Distro-specific metadata (Depends vs Requires) and the packaging step
|
||||
# (dpkg-deb vs rpmbuild) stay in the per-distro builders. This file only
|
||||
# assembles the payload so the two never drift.
|
||||
#
|
||||
# Usage: ROOT_DIR=<repo> STAGE_ROOT=<dir> bash stage-runtime.sh
|
||||
set -euo pipefail
|
||||
|
||||
: "${ROOT_DIR:?ROOT_DIR must be set}"
|
||||
: "${STAGE_ROOT:?STAGE_ROOT must be set}"
|
||||
|
||||
# A packaged app must contain a fixed runtime; never install at build time and
|
||||
# never ship without node_modules.
|
||||
if [ ! -d "$ROOT_DIR/node_modules/electron/dist" ]; then
|
||||
echo "error: node_modules/electron is missing. Run 'npm ci' before packaging." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP_DIR="$STAGE_ROOT/opt/stepforge"
|
||||
mkdir -p "$APP_DIR/node_modules" \
|
||||
"$STAGE_ROOT/usr/bin" \
|
||||
"$STAGE_ROOT/usr/share/applications" \
|
||||
"$STAGE_ROOT/usr/share/mime/packages"
|
||||
|
||||
# --- application code (runtime only) ----------------------------------------
|
||||
for item in app core exporters package.json package-lock.json; do
|
||||
cp -a "$ROOT_DIR/$item" "$APP_DIR/$item"
|
||||
done
|
||||
|
||||
# --- runtime node_modules ----------------------------------------------------
|
||||
# The fixed Electron runtime (needed at runtime even though it is a dev dep):
|
||||
cp -a "$ROOT_DIR/node_modules/electron" "$APP_DIR/node_modules/electron"
|
||||
# Production npm dependencies (tesseract.js + language data + transitive):
|
||||
while IFS= read -r dep; do
|
||||
[ -n "$dep" ] || continue
|
||||
rel="${dep#"$ROOT_DIR"/}"
|
||||
[ "$rel" != "$dep" ] || continue
|
||||
[ -d "$dep" ] || continue
|
||||
mkdir -p "$APP_DIR/$(dirname "$rel")"
|
||||
cp -a "$dep" "$APP_DIR/$rel"
|
||||
done < <(cd "$ROOT_DIR" && npm ls --omit=dev --all --parseable 2>/dev/null | tail -n +2)
|
||||
|
||||
# Guard: the development-only packaging toolchain must not have leaked in.
|
||||
if [ -d "$APP_DIR/node_modules/electron-builder" ] || [ -d "$APP_DIR/node_modules/app-builder-lib" ]; then
|
||||
echo "error: build-only dependency leaked into the package payload." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- launcher ----------------------------------------------------------------
|
||||
install -m 0755 "$ROOT_DIR/packaging/linux/common/launcher.sh" "$STAGE_ROOT/usr/bin/stepforge"
|
||||
|
||||
# --- desktop entry, icons, MIME ---------------------------------------------
|
||||
install -m 0644 "$ROOT_DIR/packaging/linux/common/stepforge.desktop" "$STAGE_ROOT/usr/share/applications/stepforge.desktop"
|
||||
install -m 0644 "$ROOT_DIR/packaging/linux/common/stepforge-mime.xml" "$STAGE_ROOT/usr/share/mime/packages/stepforge.xml"
|
||||
for size in 16 32 48 64 128 256 512; do
|
||||
icon="$ROOT_DIR/packaging/assets/icons/stepforge-${size}.png"
|
||||
[ -f "$icon" ] || continue
|
||||
dest="$STAGE_ROOT/usr/share/icons/hicolor/${size}x${size}/apps"
|
||||
mkdir -p "$dest"
|
||||
install -m 0644 "$icon" "$dest/stepforge.png"
|
||||
done
|
||||
@@ -25,59 +25,13 @@ case "$DEB_ARCH" in
|
||||
*) NODE_ARCH="$DEB_ARCH" ;;
|
||||
esac
|
||||
|
||||
# A packaged app must contain a fixed runtime; never install at build time from
|
||||
# within the package step, and never ship without node_modules.
|
||||
if [ ! -d "$ROOT_DIR/node_modules/electron/dist" ]; then
|
||||
echo "error: node_modules/electron is missing. Run 'npm ci' before packaging." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK_DIR="$(mktemp -d "${OUT_DIR%/}/.deb.XXXXXX")"
|
||||
trap 'rm -rf "$WORK_DIR"' EXIT
|
||||
APP_DIR="$WORK_DIR/opt/stepforge"
|
||||
mkdir -p "$APP_DIR" "$WORK_DIR/usr/bin" "$WORK_DIR/DEBIAN"
|
||||
mkdir -p "$WORK_DIR/usr/share/applications"
|
||||
mkdir -p "$WORK_DIR/usr/share/mime/packages"
|
||||
mkdir -p "$WORK_DIR/usr/share/doc/stepforge"
|
||||
|
||||
# --- application code (runtime only) ----------------------------------------
|
||||
for item in app core exporters package.json package-lock.json; do
|
||||
cp -a "$ROOT_DIR/$item" "$APP_DIR/$item"
|
||||
done
|
||||
# Stage the shared runtime-only payload (fails if node_modules is missing).
|
||||
ROOT_DIR="$ROOT_DIR" STAGE_ROOT="$WORK_DIR" bash "$ROOT_DIR/packaging/linux/common/stage-runtime.sh"
|
||||
|
||||
# --- runtime node_modules ----------------------------------------------------
|
||||
# The fixed Electron runtime (needed at runtime even though it is a dev dep):
|
||||
mkdir -p "$APP_DIR/node_modules"
|
||||
cp -a "$ROOT_DIR/node_modules/electron" "$APP_DIR/node_modules/electron"
|
||||
# Production npm dependencies (tesseract.js + language data + transitive):
|
||||
while IFS= read -r dep; do
|
||||
[ -n "$dep" ] || continue
|
||||
rel="${dep#"$ROOT_DIR"/}"
|
||||
[ "$rel" != "$dep" ] || continue # only paths under the repo
|
||||
[ -d "$dep" ] || continue
|
||||
mkdir -p "$APP_DIR/$(dirname "$rel")"
|
||||
cp -a "$dep" "$APP_DIR/$rel"
|
||||
done < <(npm ls --omit=dev --all --parseable 2>/dev/null | tail -n +2)
|
||||
|
||||
# Guard: the development-only packaging toolchain must not have leaked in.
|
||||
if [ -d "$APP_DIR/node_modules/electron-builder" ] || [ -d "$APP_DIR/node_modules/app-builder-lib" ]; then
|
||||
echo "error: build-only dependency leaked into the package payload." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- launcher ----------------------------------------------------------------
|
||||
install -m 0755 "$ROOT_DIR/packaging/linux/common/launcher.sh" "$WORK_DIR/usr/bin/stepforge"
|
||||
|
||||
# --- desktop entry, icons, MIME ---------------------------------------------
|
||||
install -m 0644 "$ROOT_DIR/packaging/linux/common/stepforge.desktop" "$WORK_DIR/usr/share/applications/stepforge.desktop"
|
||||
install -m 0644 "$ROOT_DIR/packaging/linux/common/stepforge-mime.xml" "$WORK_DIR/usr/share/mime/packages/stepforge.xml"
|
||||
for size in 16 32 48 64 128 256 512; do
|
||||
icon="$ROOT_DIR/packaging/assets/icons/stepforge-${size}.png"
|
||||
[ -f "$icon" ] || continue
|
||||
dest="$WORK_DIR/usr/share/icons/hicolor/${size}x${size}/apps"
|
||||
mkdir -p "$dest"
|
||||
install -m 0644 "$icon" "$dest/stepforge.png"
|
||||
done
|
||||
mkdir -p "$WORK_DIR/DEBIAN" "$WORK_DIR/usr/share/doc/stepforge"
|
||||
|
||||
# --- license + docs pointer --------------------------------------------------
|
||||
if [ -f "$ROOT_DIR/LICENSE" ]; then
|
||||
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build a production StepForge .rpm from a pruned, runtime-only tree, mirroring
|
||||
# the .deb builder. Stages the shared payload via common/stage-runtime.sh, then
|
||||
# packages it with rpmbuild against a prebuilt BuildRoot (no compilation).
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
VERSION="$(node -p "require('./package.json').version")"
|
||||
MAINTAINER="${STEPFORGE_MAINTAINER:-StepForge <[email protected]>}"
|
||||
OUT_DIR="${STEPFORGE_PACKAGE_DIR:-$ROOT_DIR/build/artifacts}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||
echo "error: rpmbuild is not installed. Run scripts/linux/dnf/install-build-deps.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# RPM arch label from the host.
|
||||
RPM_ARCH="$(rpm --eval '%{_arch}' 2>/dev/null || uname -m)"
|
||||
|
||||
BUILD_ROOT="$(mktemp -d "${OUT_DIR%/}/.rpm.XXXXXX")"
|
||||
trap 'rm -rf "$BUILD_ROOT"' EXIT
|
||||
STAGE="$BUILD_ROOT/buildroot"
|
||||
mkdir -p "$STAGE"
|
||||
|
||||
# Shared runtime-only payload (fails if node_modules is missing).
|
||||
ROOT_DIR="$ROOT_DIR" STAGE_ROOT="$STAGE" bash "$ROOT_DIR/packaging/linux/common/stage-runtime.sh"
|
||||
|
||||
# License into the RPM's conventional location.
|
||||
mkdir -p "$STAGE/usr/share/licenses/stepforge"
|
||||
if [ -f "$ROOT_DIR/LICENSE" ]; then
|
||||
install -m 0644 "$ROOT_DIR/LICENSE" "$STAGE/usr/share/licenses/stepforge/LICENSE"
|
||||
elif [ -f "$ROOT_DIR/docs/LICENSE" ]; then
|
||||
install -m 0644 "$ROOT_DIR/docs/LICENSE" "$STAGE/usr/share/licenses/stepforge/LICENSE"
|
||||
else
|
||||
# rpmbuild %license requires the file to exist; write a pointer if absent.
|
||||
echo "See project LICENSE." > "$STAGE/usr/share/licenses/stepforge/LICENSE"
|
||||
fi
|
||||
|
||||
# Materialize the spec with version/maintainer substituted.
|
||||
SPEC="$BUILD_ROOT/stepforge.spec"
|
||||
sed -e "s/@VERSION@/$VERSION/" -e "s#@MAINTAINER@#$MAINTAINER#" \
|
||||
"$ROOT_DIR/packaging/linux/fedora/stepforge.spec" > "$SPEC"
|
||||
|
||||
rpmbuild -bb \
|
||||
--define "_topdir $BUILD_ROOT/rpmbuild" \
|
||||
--define "_rpmdir $OUT_DIR" \
|
||||
--define "_build_id_links none" \
|
||||
--buildroot "$STAGE" \
|
||||
--target "$RPM_ARCH" \
|
||||
"$SPEC" >/dev/null
|
||||
|
||||
# rpmbuild writes to $OUT_DIR/<arch>/<name>.rpm — surface the final path.
|
||||
RPM_FILE="$(find "$OUT_DIR" -name "stepforge-${VERSION}-1*.${RPM_ARCH}.rpm" -newer "$SPEC" | head -1)"
|
||||
if [ -z "$RPM_FILE" ]; then
|
||||
RPM_FILE="$(find "$OUT_DIR" -name "stepforge-${VERSION}-1*.rpm" | head -1)"
|
||||
fi
|
||||
[ -n "$RPM_FILE" ] || { echo "error: rpmbuild did not produce an .rpm" >&2; exit 1; }
|
||||
|
||||
# Checksum.
|
||||
( cd "$(dirname "$RPM_FILE")" && sha256sum "$(basename "$RPM_FILE")" > "$(basename "$RPM_FILE").sha256" )
|
||||
|
||||
echo "$RPM_FILE"
|
||||
@@ -0,0 +1,69 @@
|
||||
# StepForge RPM spec. The payload is prebuilt into a staging BuildRoot by
|
||||
# packaging/linux/fedora/package.sh (which stages a pruned runtime tree), so
|
||||
# this spec only packages and declares metadata — it does not compile.
|
||||
#
|
||||
# Placeholders @VERSION@ / @MAINTAINER@ are substituted by package.sh.
|
||||
|
||||
%global debug_package %{nil}
|
||||
%global __brp_check_rpaths %{nil}
|
||||
%define _build_id_links none
|
||||
|
||||
Name: stepforge
|
||||
Version: @VERSION@
|
||||
Release: 1%{?dist}
|
||||
Summary: Local-first step-by-step guide capture and export tool
|
||||
|
||||
License: MPL-2.0
|
||||
URL: https://github.com/Twest2/StepForge
|
||||
|
||||
# Runtime shared libraries (Chromium/Electron) + capture integration.
|
||||
Requires: nss
|
||||
Requires: nspr
|
||||
Requires: atk
|
||||
Requires: at-spi2-atk
|
||||
Requires: cups-libs
|
||||
Requires: gtk3
|
||||
Requires: mesa-libgbm
|
||||
Requires: alsa-lib
|
||||
Requires: libxkbcommon
|
||||
Recommends: xinput
|
||||
Recommends: xdg-desktop-portal
|
||||
Recommends: pipewire
|
||||
|
||||
# The payload is architecture-specific (bundles the Electron binary).
|
||||
%description
|
||||
StepForge captures step-by-step workflows as screenshots, lets you annotate
|
||||
and describe each step, and exports to Markdown, PDF, DOCX, PPTX, HTML, and
|
||||
more. Local-first: no telemetry, with an optional user-configured local AI
|
||||
integration. This package bundles a fixed Electron runtime and only
|
||||
production dependencies; it does not install anything at runtime.
|
||||
|
||||
%files
|
||||
/opt/stepforge
|
||||
/usr/bin/stepforge
|
||||
/usr/share/applications/stepforge.desktop
|
||||
/usr/share/mime/packages/stepforge.xml
|
||||
/usr/share/icons/hicolor/*/apps/stepforge.png
|
||||
%license /usr/share/licenses/stepforge/LICENSE
|
||||
|
||||
%post
|
||||
# Make the Chromium setuid sandbox helper usable so the app launches sandboxed.
|
||||
HELPER=/opt/stepforge/node_modules/electron/dist/chrome-sandbox
|
||||
if [ -e "$HELPER" ]; then
|
||||
chown root:root "$HELPER" || true
|
||||
chmod 4755 "$HELPER" || true
|
||||
fi
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true; fi
|
||||
if command -v update-mime-database >/dev/null 2>&1; then update-mime-database /usr/share/mime || true; fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q /usr/share/icons/hicolor || true; fi
|
||||
|
||||
%postun
|
||||
if [ "$1" = 0 ]; then
|
||||
if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true; fi
|
||||
if command -v update-mime-database >/dev/null 2>&1; then update-mime-database /usr/share/mime || true; fi
|
||||
if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q /usr/share/icons/hicolor || true; fi
|
||||
fi
|
||||
|
||||
%changelog
|
||||
* Fri Jul 03 2026 @MAINTAINER@ - @VERSION@-1
|
||||
- Production runtime-only package (pruned tree, fixed Electron runtime).
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install the BUILD toolchain for producing StepForge packages on dnf-based
|
||||
# systems (Fedora/RHEL). For DEVELOPERS/packagers only; never shipped inside
|
||||
# the end-user package.
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v dnf >/dev/null 2>&1; then
|
||||
echo "This script is for dnf-based systems (Fedora/RHEL)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||
|
||||
PACKAGES=(
|
||||
rpm-build rpmdevtools # build the .rpm
|
||||
desktop-file-utils # validate the .desktop entry
|
||||
ca-certificates # npm ci over https
|
||||
xorg-x11-server-Xvfb # headless smoke test under Xvfb
|
||||
)
|
||||
|
||||
echo "Installing StepForge build dependencies via dnf..."
|
||||
$SUDO dnf install -y "${PACKAGES[@]}"
|
||||
|
||||
cat <<'MSG'
|
||||
Done. Also install the pinned Node toolchain (see .nvmrc — Node 22.12+):
|
||||
nvm install && nvm use # or another Node 22 LTS install method
|
||||
Then, from the repo root:
|
||||
npm ci
|
||||
npm run package:linux:rpm
|
||||
MSG
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install the RUNTIME libraries StepForge needs on dnf-based systems (Fedora,
|
||||
# RHEL/derivatives). These are the shared libraries the packaged Electron
|
||||
# runtime links against, plus the X11/portal integration used for capture.
|
||||
# For END USERS installing from the tarball; the .rpm declares the same set as
|
||||
# Requires so dnf pulls them automatically.
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v dnf >/dev/null 2>&1; then
|
||||
echo "This script is for dnf-based systems (Fedora/RHEL). Use the apt script on Debian/Ubuntu." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||
|
||||
PACKAGES=(
|
||||
# Chromium/Electron shared libraries
|
||||
nss nspr atk at-spi2-atk at-spi2-core cups-libs libdrm
|
||||
gtk3 mesa-libgbm alsa-lib libxkbcommon
|
||||
libXcomposite libXdamage libXfixes libXrandr libxshmfence
|
||||
# X11 per-click capture (marker-accurate) — X11 sessions only
|
||||
xorg-x11-server-utils xinput
|
||||
# Wayland screen-share via the XDG portal + PipeWire
|
||||
xdg-desktop-portal pipewire
|
||||
)
|
||||
|
||||
echo "Installing StepForge runtime dependencies via dnf..."
|
||||
# Some package names differ across Fedora releases; install best-effort so one
|
||||
# missing optional name doesn't abort the whole set.
|
||||
$SUDO dnf install -y "${PACKAGES[@]}" || {
|
||||
echo "Some packages were unavailable; retrying individually..." >&2
|
||||
for pkg in "${PACKAGES[@]}"; do $SUDO dnf install -y "$pkg" || echo " (skipped: $pkg)" >&2; done
|
||||
}
|
||||
echo "Done. StepForge runtime dependencies are installed."
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# Integration test: build the production .rpm and assert it is a real,
|
||||
# runtime-only package. Honest skip policy: skip ONLY when the prerequisites
|
||||
# are genuinely absent (rpmbuild missing or node_modules not installed). Once
|
||||
# we build, any structural failure fails the test.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
if ! command -v rpmbuild >/dev/null 2>&1; then
|
||||
echo "package-rpm SKIPPED: rpmbuild not installed (not a dnf-based build host)"
|
||||
exit 0
|
||||
fi
|
||||
if [ ! -d "$ROOT_DIR/node_modules/electron/dist" ]; then
|
||||
echo "package-rpm SKIPPED: node_modules/electron missing (run npm ci first)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
OUT_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$OUT_DIR"' EXIT
|
||||
|
||||
RPM="$(STEPFORGE_PACKAGE_DIR="$OUT_DIR" bash packaging/linux/fedora/package.sh | tail -1)"
|
||||
[ -f "$RPM" ] || { echo "package-rpm FAILED: builder produced no .rpm" >&2; exit 1; }
|
||||
|
||||
fail() { echo "package-rpm FAILED: $1" >&2; exit 1; }
|
||||
|
||||
listing="$(rpm -qlp "$RPM" 2>/dev/null)"
|
||||
|
||||
for needle in \
|
||||
'/usr/bin/stepforge' \
|
||||
'/usr/share/applications/stepforge.desktop' \
|
||||
'/usr/share/mime/packages/stepforge.xml' \
|
||||
'/opt/stepforge/node_modules/electron/dist/electron' \
|
||||
'/opt/stepforge/app/main.js'; do
|
||||
echo "$listing" | grep -qF "$needle" || fail "missing packaged file: $needle"
|
||||
done
|
||||
echo "$listing" | grep -q '/usr/share/icons/hicolor/256x256/apps/stepforge.png' || fail "missing 256px icon"
|
||||
|
||||
# No dev tree / build tooling / app docs.
|
||||
for banned in 'electron-builder' '/opt/stepforge/docs/' '/opt/stepforge/ai_prompts/' '/opt/stepforge/examples/'; do
|
||||
echo "$listing" | grep -qF "$banned" && fail "unexpected payload: $banned" || true
|
||||
done
|
||||
|
||||
# Metadata sanity.
|
||||
rpm -qip "$RPM" 2>/dev/null | grep -q '^Name *: stepforge' || fail "rpm Name is not stepforge"
|
||||
rpm -qp --requires "$RPM" 2>/dev/null | grep -q '^nss' || fail "rpm does not Require nss"
|
||||
|
||||
echo "package-rpm OK ($(basename "$RPM"))"
|
||||
@@ -33,6 +33,55 @@ test('the old non-production package-linux.sh is gone', () => {
|
||||
assert.equal(exists('scripts/package-linux.sh'), false);
|
||||
});
|
||||
|
||||
test('Fedora/dnf packaging files exist in their own separate locations', () => {
|
||||
for (const f of [
|
||||
'packaging/linux/fedora/package.sh',
|
||||
'packaging/linux/fedora/stepforge.spec',
|
||||
'packaging/linux/common/stage-runtime.sh',
|
||||
'scripts/linux/dnf/install-runtime-deps.sh',
|
||||
'scripts/linux/dnf/install-build-deps.sh',
|
||||
'docs/linux/dnf.md',
|
||||
'tests/integration/linux/package-rpm.test.sh',
|
||||
]) {
|
||||
assert.ok(exists(f), `expected ${f} to exist`);
|
||||
}
|
||||
});
|
||||
|
||||
test('the RPM spec declares runtime Requires, license, and MPL-2.0', () => {
|
||||
const spec = read('packaging/linux/fedora/stepforge.spec');
|
||||
assert.match(spec, /^License:\s+MPL-2\.0$/m);
|
||||
assert.match(spec, /^Requires:\s+nss$/m);
|
||||
assert.match(spec, /%license/);
|
||||
assert.match(spec, /chrome-sandbox/); // %post makes the sandbox helper setuid
|
||||
assert.match(spec, /@VERSION@/);
|
||||
assert.doesNotMatch(spec, /fully offline/i);
|
||||
});
|
||||
|
||||
test('the rpm builder shares staging and requires rpmbuild + node_modules', () => {
|
||||
const script = read('packaging/linux/fedora/package.sh');
|
||||
assert.match(script, /stage-runtime\.sh/);
|
||||
assert.match(script, /rpmbuild/);
|
||||
assert.match(script, /rpm --eval '%\{_arch\}'|uname -m/); // arch detected
|
||||
assert.doesNotMatch(script, /cp -a "\$ROOT_DIR\/node_modules" /);
|
||||
});
|
||||
|
||||
test('dnf setup scripts target dnf and keep build vs runtime deps separate', () => {
|
||||
const runtime = read('scripts/linux/dnf/install-runtime-deps.sh');
|
||||
const build = read('scripts/linux/dnf/install-build-deps.sh');
|
||||
assert.match(runtime, /dnf install/);
|
||||
assert.match(runtime, /nss/);
|
||||
assert.doesNotMatch(runtime, /rpm-build|rpmdevtools/, 'runtime script must not install build tools');
|
||||
assert.match(build, /rpm-build/);
|
||||
});
|
||||
|
||||
test('the shared staging never copies the whole dev node_modules', () => {
|
||||
const staging = read('packaging/linux/common/stage-runtime.sh');
|
||||
assert.match(staging, /npm ls --omit=dev/);
|
||||
assert.match(staging, /electron-builder/); // leak guard
|
||||
assert.doesNotMatch(staging, /cp -a "\$ROOT_DIR\/node_modules" "\$APP_DIR/);
|
||||
assert.match(staging, /node_modules\/electron\/dist/); // requires the runtime
|
||||
});
|
||||
|
||||
test('the desktop entry is valid and app-branded', () => {
|
||||
const desktop = read('packaging/linux/common/stepforge.desktop');
|
||||
assert.match(desktop, /^\[Desktop Entry\]/);
|
||||
@@ -62,11 +111,9 @@ test('the launcher refuses an unsandboxed launch unless explicitly opted in', ()
|
||||
assert.doesNotMatch(launcher, /npm (install|ci|rebuild)/);
|
||||
});
|
||||
|
||||
test('the package builder requires node_modules and guards against dev-dep leaks', () => {
|
||||
test('the deb builder detects arch and delegates to shared staging', () => {
|
||||
const script = read('packaging/linux/debian/package.sh');
|
||||
assert.match(script, /node_modules\/electron\/dist/);
|
||||
assert.match(script, /npm ls --omit=dev/);
|
||||
assert.match(script, /electron-builder/); // the leak guard references it
|
||||
assert.match(script, /stage-runtime\.sh/); // runtime-only staging is shared
|
||||
assert.match(script, /dpkg --print-architecture/); // arch detected, not hardcoded
|
||||
assert.doesNotMatch(script, /cp -a "\$ROOT_DIR\/node_modules" /); // never copy the whole dev tree
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user