Add production .deb packaging, apt setup, desktop integration, and icons
Template tests / tests (pull_request) Failing after 31s
Template tests / tests (pull_request) Failing after 31s
Phase 3 of the improvement plan (PR 8 of the sequence): the apt/X11 packaging half of Linux support, in separate Linux-specific files. Replaces the old scripts/package-linux.sh, which the audit flagged as "not production packaging" (it copied the whole dev node_modules — including vulnerable build deps — plus docs/prompts/examples/audit files, hardcoded amd64, declared only xinput, lacked desktop/icon/MIME integration, and could build without node_modules). Production builder (packaging/linux/debian/package.sh): - Stages ONLY runtime files: app code, a fixed Electron runtime, and the production npm deps (enumerated via npm ls --omit=dev). Never copies the development node_modules; guards against electron-builder/app-builder-lib leaking in. Fails if node_modules is absent instead of shipping an unusable artifact. - Detects architecture (dpkg --print-architecture, x64/arm64) rather than hardcoding amd64. Generates DEBIAN/control from control.in with proper runtime Depends, real maintainer, and homepage. - Installs a desktop entry, hicolor icons (16–512), .sfgz/.sfglt MIME registration, the launcher, and the license. postinst makes chrome-sandbox setuid and refreshes desktop/MIME/icon caches; postrm cleans them. - Emits a .deb, a portable tarball that now INCLUDES /usr/bin/stepforge (the old tarball omitted it), and a sha256 sums file. Launcher (packaging/linux/common/launcher.sh): - Runs sandboxed; prefers the user-namespace sandbox, accepts a root-owned setuid helper, and otherwise refuses to launch with an actionable message. --no-sandbox requires an explicit STEPFORGE_ALLOW_NO_SANDBOX opt-in. Never installs anything at runtime. Setup (separate build vs runtime, apt only): - scripts/linux/apt/install-runtime-deps.sh (Chromium/Electron libs, X11 tools, portal/PipeWire) and install-build-deps.sh (dpkg-dev, fakeroot, xvfb). Runtime script installs no build tools. Assets: original StepForge icon — packaging/assets/stepforge.svg plus a generator (scripts/make-icons.js) that renders the PNG set with the repo's own rasterizer/PNG writer (no third-party art). npm run icons regenerates them. Wiring: package.json gains package:linux:deb / package:linux:rpm / icons; build-release.sh uses the production builder and requires node_modules; README points at the apt/dnf guides. Tests: tests/unit/packaging-linux.test.js (structural: files present in their separate locations, old script gone, valid desktop entry, templated arch + runtime Depends, launcher gates --no-sandbox, builder requires node_modules and guards dev-dep leaks, apt build/runtime dep separation, original icon set generates a valid PNG) runs in the normal suite; tests/integration/linux/package-deb.test.sh builds a real .deb and asserts the right files present and the dev tree / build tooling / app docs absent (honest skip only when dpkg-deb/node_modules are genuinely missing). Verified locally: 276 unit tests pass; the integration test builds and validates stepforge_0.3.2_amd64.deb; build-release E2E passes with the new production package. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -14,7 +14,14 @@ mkdir -p "$BUILD_ROOT"
|
||||
|
||||
bash "$ROOT_DIR/scripts/bootstrap-offline.sh"
|
||||
node "$ROOT_DIR/scripts/make-sample-guide.js" --root "$EXAMPLES_ROOT"
|
||||
STEPFORGE_PACKAGE_DIR="$ARTIFACT_DIR" bash "$ROOT_DIR/scripts/package-linux.sh" >/dev/null
|
||||
# Production Linux package: a pruned runtime tree with real desktop
|
||||
# integration. Requires node_modules (fails otherwise); never installs at
|
||||
# build time. Skipped only when the Electron runtime is genuinely absent.
|
||||
if [ -d "$ROOT_DIR/node_modules/electron/dist" ]; then
|
||||
STEPFORGE_PACKAGE_DIR="$ARTIFACT_DIR" bash "$ROOT_DIR/packaging/linux/debian/package.sh" >/dev/null
|
||||
else
|
||||
echo "[build-release] skipping Linux .deb: node_modules/electron missing (run npm ci)" >&2
|
||||
fi
|
||||
|
||||
BUILD_ROOT="$BUILD_ROOT" \
|
||||
ARTIFACT_DIR="$ARTIFACT_DIR" \
|
||||
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install the BUILD toolchain for producing StepForge packages on apt-based
|
||||
# systems. These are for DEVELOPERS/packagers only and are never shipped inside
|
||||
# the end-user package.
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v apt-get >/dev/null 2>&1; then
|
||||
echo "This script is for apt-based systems (Debian/Ubuntu)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||
|
||||
PACKAGES=(
|
||||
dpkg-dev fakeroot # build the .deb
|
||||
desktop-file-utils # validate the .desktop entry
|
||||
ca-certificates # npm ci over https
|
||||
xvfb # headless smoke test under Xvfb
|
||||
)
|
||||
|
||||
echo "Installing StepForge build dependencies via apt..."
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends "${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:deb
|
||||
MSG
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install the RUNTIME libraries StepForge needs on apt-based systems
|
||||
# (Debian/Ubuntu). These are the shared libraries the packaged Electron runtime
|
||||
# links against, plus the X11/portal integration used for capture. This is for
|
||||
# END USERS installing from the tarball; the .deb declares the same set as
|
||||
# Depends so apt pulls them automatically.
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v apt-get >/dev/null 2>&1; then
|
||||
echo "This script is for apt-based systems (Debian/Ubuntu). Use the dnf script on Fedora." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then SUDO="sudo"; fi
|
||||
|
||||
PACKAGES=(
|
||||
# Chromium/Electron shared libraries
|
||||
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2
|
||||
libgtk-3-0 libgbm1 libasound2 libxkbcommon0 libatspi2.0-0
|
||||
libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libxshmfence1
|
||||
# X11 per-click capture (marker-accurate) — X11 sessions only
|
||||
xinput x11-utils
|
||||
# Wayland screen-share via the XDG portal + PipeWire
|
||||
xdg-desktop-portal pipewire
|
||||
)
|
||||
|
||||
echo "Installing StepForge runtime dependencies via apt..."
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y --no-install-recommends "${PACKAGES[@]}"
|
||||
echo "Done. StepForge runtime dependencies are installed."
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
// Generate the StepForge PNG icon set from original geometry using the repo's
|
||||
// own rasterizer + PNG writer (no external image tooling or third-party art).
|
||||
// Mirrors packaging/assets/stepforge.svg. Output: packaging/assets/icons/.
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { createImage, fillRect, fillOval } = require('../core/raster');
|
||||
const { encodePng } = require('../core/png');
|
||||
|
||||
const OUT_DIR = path.join(__dirname, '..', 'packaging', 'assets', 'icons');
|
||||
const SIZES = [16, 32, 48, 64, 128, 256, 512];
|
||||
|
||||
const BG_TOP = [37, 99, 235, 255];
|
||||
const BG_BOTTOM = [30, 58, 138, 255];
|
||||
const WHITE = [255, 255, 255, 255];
|
||||
const SPARK = [250, 204, 21, 255];
|
||||
|
||||
function lerp(a, b, t) {
|
||||
return [
|
||||
Math.round(a[0] + (b[0] - a[0]) * t),
|
||||
Math.round(a[1] + (b[1] - a[1]) * t),
|
||||
Math.round(a[2] + (b[2] - a[2]) * t),
|
||||
255,
|
||||
];
|
||||
}
|
||||
|
||||
function renderIcon(size) {
|
||||
const img = createImage(size, size, [0, 0, 0, 0]);
|
||||
const s = size / 256; // scale from the 256px reference design
|
||||
|
||||
// Rounded-square background approximated by a vertical gradient fill.
|
||||
for (let y = 0; y < size; y += 1) {
|
||||
fillRect(img, 0, y, size, 1, lerp(BG_TOP, BG_BOTTOM, y / size));
|
||||
}
|
||||
|
||||
// Three ascending steps (x, y, w, h in reference px).
|
||||
const steps = [
|
||||
[52, 150, 52, 54],
|
||||
[102, 116, 52, 88],
|
||||
[152, 82, 52, 122],
|
||||
];
|
||||
for (const [x, y, w, h] of steps) {
|
||||
fillRect(img, Math.round(x * s), Math.round(y * s), Math.round(w * s), Math.round(h * s), WHITE);
|
||||
}
|
||||
|
||||
// Capture spark on the top step.
|
||||
const r = Math.max(2, Math.round(16 * s));
|
||||
fillOval(img, Math.round(178 * s - r), Math.round(60 * s - r), r * 2, r * 2, SPARK);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
function main() {
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||
for (const size of SIZES) {
|
||||
const png = encodePng(renderIcon(size));
|
||||
fs.writeFileSync(path.join(OUT_DIR, `stepforge-${size}.png`), png);
|
||||
}
|
||||
// A conventional default name for the desktop entry / hicolor 256px slot.
|
||||
fs.copyFileSync(path.join(OUT_DIR, 'stepforge-256.png'), path.join(OUT_DIR, 'stepforge.png'));
|
||||
console.log(`wrote ${SIZES.length + 1} icons to ${OUT_DIR}`);
|
||||
}
|
||||
|
||||
if (require.main === module) main();
|
||||
|
||||
module.exports = { renderIcon, SIZES };
|
||||
@@ -1,92 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VERSION="$(node -p "const pkg=require('${ROOT_DIR}/package.json'); pkg.buildVersion || pkg.version" 2>/dev/null || echo 0.0.0)"
|
||||
OUT_DIR="${STEPFORGE_PACKAGE_DIR:-$ROOT_DIR/build/artifacts}"
|
||||
mkdir -p "$OUT_DIR"
|
||||
WORK_DIR="$(mktemp -d "${OUT_DIR%/}/.pkg.XXXXXX")"
|
||||
APP_DIR="$WORK_DIR/opt/stepforge"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$APP_DIR" "$WORK_DIR/usr/bin" "$WORK_DIR/DEBIAN"
|
||||
|
||||
copy_item() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
if [[ -e "$ROOT_DIR/$src" ]]; then
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
cp -a "$ROOT_DIR/$src" "$dest"
|
||||
fi
|
||||
}
|
||||
|
||||
# Application payload: only the files needed to run the app.
|
||||
copy_item app "$APP_DIR/app"
|
||||
copy_item core "$APP_DIR/core"
|
||||
copy_item exporters "$APP_DIR/exporters"
|
||||
copy_item scripts "$APP_DIR/scripts"
|
||||
copy_item README.md "$APP_DIR/README.md"
|
||||
copy_item LICENSE "$APP_DIR/LICENSE"
|
||||
copy_item docs "$APP_DIR/docs"
|
||||
copy_item ai_prompts "$APP_DIR/ai_prompts"
|
||||
copy_item package.json "$APP_DIR/package.json"
|
||||
copy_item package-lock.json "$APP_DIR/package-lock.json"
|
||||
copy_item examples "$APP_DIR/examples"
|
||||
copy_item build/agent_audit.md "$APP_DIR/build/agent_audit.md"
|
||||
|
||||
if [[ -d "$ROOT_DIR/node_modules" ]]; then
|
||||
cp -a "$ROOT_DIR/node_modules" "$APP_DIR/node_modules"
|
||||
fi
|
||||
|
||||
cat > "$WORK_DIR/usr/bin/stepforge" <<'EOF'
|
||||
#!/usr/bin/env sh
|
||||
APP_DIR=/opt/stepforge
|
||||
ELECTRON="$APP_DIR/node_modules/.bin/electron"
|
||||
SANDBOX_HELPER="$APP_DIR/node_modules/electron/dist/chrome-sandbox"
|
||||
cd "$APP_DIR" || exit 1
|
||||
if command -v stat >/dev/null 2>&1 && [ -e "$SANDBOX_HELPER" ]; then
|
||||
helper_uid="$(stat -c '%u' "$SANDBOX_HELPER" 2>/dev/null || echo '')"
|
||||
helper_mode="$(stat -c '%a' "$SANDBOX_HELPER" 2>/dev/null || echo '')"
|
||||
if [ "$helper_uid" = "0" ] && [ -n "$helper_mode" ]; then
|
||||
helper_mode_num=$((8#$helper_mode))
|
||||
if [ $((helper_mode_num & 04000)) -ne 0 ]; then
|
||||
exec "$ELECTRON" "$APP_DIR" "$@"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
printf '%s\n' '[stepforge] Electron sandbox helper is not configured for this install; starting with --no-sandbox' >&2
|
||||
exec "$ELECTRON" --no-sandbox "$APP_DIR" "$@"
|
||||
EOF
|
||||
chmod 0755 "$WORK_DIR/usr/bin/stepforge"
|
||||
|
||||
cat > "$WORK_DIR/DEBIAN/control" <<EOF
|
||||
Package: stepforge
|
||||
Version: $VERSION
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Depends: xinput
|
||||
Maintainer: StepForge <[email protected]>
|
||||
Description: Offline desktop guide capture and export tool
|
||||
A fully offline desktop app for step-by-step documentation, built for local
|
||||
capture, annotation, and export workflows.
|
||||
EOF
|
||||
|
||||
DEB_FILE="$OUT_DIR/stepforge_${VERSION}_amd64.deb"
|
||||
TAR_FILE="$OUT_DIR/stepforge_${VERSION}_linux-x64.tar.gz"
|
||||
|
||||
if command -v dpkg-deb >/dev/null 2>&1; then
|
||||
dpkg-deb --build "$WORK_DIR" "$DEB_FILE" >/dev/null
|
||||
else
|
||||
echo "dpkg-deb is not installed; skipping .deb build" >&2
|
||||
fi
|
||||
|
||||
tar -C "$WORK_DIR/opt" -czf "$TAR_FILE" stepforge
|
||||
|
||||
printf '%s\n' "$DEB_FILE"
|
||||
printf '%s\n' "$TAR_FILE"
|
||||
Reference in New Issue
Block a user