#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT_DIR" PKG_NAME="pubscript" PKG_VERSION="0.1.0" ARCH="amd64" DEB_NAME="${PKG_NAME}_${PKG_VERSION}_${ARCH}.deb" STAGE_ROOT="/tmp/opencode/pubscript-deb-stage" STAGE_DIR="${STAGE_ROOT}/${PKG_NAME}_${PKG_VERSION}_${ARCH}" echo "==> [1/6] Building production web bundle (apps/web/dist)..." pnpm --filter @pubscript/web run build echo "==> [2/6] Preparing Debian package staging tree at ${STAGE_DIR}..." rm -rf "$STAGE_DIR" mkdir -p \ "$STAGE_DIR/DEBIAN" \ "$STAGE_DIR/usr/bin" \ "$STAGE_DIR/usr/share/applications" \ "$STAGE_DIR/usr/share/icons/hicolor/scalable/apps" \ "$STAGE_DIR/usr/share/pixmaps" \ "$STAGE_DIR/opt/pubscript/apps/web" \ "$STAGE_DIR/opt/pubscript/server/dist" echo "==> [3/6] Bundling server entry and native SQLite/Argon2 runtime modules..." cat > "$STAGE_DIR/opt/pubscript/package.json" <<'EOF' { "name": "pubscript-desktop-runtime", "version": "0.1.0", "private": true, "type": "module" } EOF # Bundle server TypeScript + Fastify + @pubscript/core into a single ESM file, # keeping only native addons (better-sqlite3, argon2) external. ESBUILD_BIN="$(find "$ROOT_DIR/node_modules/.pnpm" -path '*/esbuild/bin/esbuild' -type f | head -n 1)" "$ESBUILD_BIN" server/src/index.ts \ --bundle \ --platform=node \ --target=node22 \ --format=esm \ --banner:js="import { createRequire as __psCreateRequire } from 'node:module'; const require = __psCreateRequire(import.meta.url);" \ --outfile="$STAGE_DIR/opt/pubscript/server/dist/server.mjs" \ --external:better-sqlite3 \ --external:argon2 # Copy better-sqlite3 and argon2 plus their transitive runtime dependencies node - "$ROOT_DIR/server" "$STAGE_DIR/opt/pubscript/node_modules" <<'NODE_EOF' import { cpSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs"; import { dirname, join } from "node:path"; import { createRequire } from "node:module"; const [serverDir, outModules] = process.argv.slice(2); mkdirSync(outModules, { recursive: true }); const seen = new Set(); function copyPkg(pkgName, fromDir) { if (seen.has(pkgName)) return; seen.add(pkgName); const req = createRequire(join(fromDir, "package.json")); let pkgJsonPath; try { pkgJsonPath = req.resolve(`${pkgName}/package.json`); } catch { const entry = req.resolve(pkgName); let cur = dirname(realpathSync(entry)); while (cur !== "/" && !existsSync(join(cur, "package.json"))) { cur = dirname(cur); } pkgJsonPath = join(cur, "package.json"); } const realPkgDir = dirname(realpathSync(pkgJsonPath)); const destDir = join(outModules, pkgName); mkdirSync(dirname(destDir), { recursive: true }); cpSync(realPkgDir, destDir, { recursive: true, dereference: true }); const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf8")); const deps = Object.keys(pkgJson.dependencies ?? {}); for (const dep of deps) { copyPkg(dep, realPkgDir); } } copyPkg("better-sqlite3", serverDir); copyPkg("argon2", serverDir); NODE_EOF cp -a server/src/migrations "$STAGE_DIR/opt/pubscript/server/dist/migrations" cp -a apps/web/dist "$STAGE_DIR/opt/pubscript/apps/web/dist" echo "==> [4/6] Compiling native GTK3 + WebKit2GTK desktop binary (/usr/bin/pubscript)..." gcc $(pkg-config --cflags gtk+-3.0) \ -O2 -Wall -Wextra \ desktop/src/pubscript-desktop.c \ $(pkg-config --libs gtk+-3.0) -ldl \ -o "$STAGE_DIR/usr/bin/pubscript" chmod 0755 "$STAGE_DIR/usr/bin/pubscript" echo "==> [5/6] Installing icons, .desktop launcher, and DEBIAN metadata..." cp apps/web/public/logo.svg "$STAGE_DIR/usr/share/icons/hicolor/scalable/apps/pubscript.svg" cp apps/web/public/logo.svg "$STAGE_DIR/usr/share/pixmaps/pubscript.svg" cat > "$STAGE_DIR/usr/share/applications/pubscript.desktop" <<'EOF' [Desktop Entry] Version=1.0 Type=Application Name=PubScript GenericName=Novel & Manuscript Writing Studio Comment=Local-first writing studio for novelists with AI co-writing, plot boards, and manuscript export Exec=/usr/bin/pubscript %U Icon=pubscript Terminal=false Categories=Office;WordProcessor;TextEditor;Publishing; Keywords=writing;novel;manuscript;markdown;epub;author;editor; StartupNotify=true StartupWMClass=pubscript EOF INSTALLED_KB="$(du -sk "$STAGE_DIR/usr" "$STAGE_DIR/opt" | awk '{s+=$1} END {print s}')" cat > "$STAGE_DIR/DEBIAN/control" <= 2.34), libgtk-3-0 (>= 3.24), libwebkit2gtk-4.1-0 | libwebkit2gtk-4.0-37, nodejs (>= 18) Maintainer: Schwitzz Homepage: https://github.com/Schwitzz/PubScript Description: PubScript — Local-First Novel & Manuscript Writing Studio A multi-surface writing studio for novelists featuring an OnlyOffice/Word-grade manuscript editor, granular text-anchored AI co-writing suggestions, autosave revision history, character & world-building sheets, plot beat templates, interactive whiteboard, and deterministic HTML/EPUB/ODT/DOCX/PDF export. EOF cat > "$STAGE_DIR/DEBIAN/postinst" <<'EOF' #!/bin/sh set -e if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true fi if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q -t -f /usr/share/icons/hicolor || true fi exit 0 EOF chmod 0755 "$STAGE_DIR/DEBIAN/postinst" echo "==> Verifying compiled desktop binary & embedded server via --headless-check..." PUBSCRIPT_APP_ROOT="$STAGE_DIR/opt/pubscript" \ PUBSCRIPT_DESKTOP_PORT=18099 \ "$STAGE_DIR/usr/bin/pubscript" --headless-check echo "==> [6/6] Building .deb archive with dpkg-deb..." mkdir -p "$ROOT_DIR/dist" dpkg-deb --root-owner-group --build "$STAGE_DIR" "$ROOT_DIR/dist/${DEB_NAME}" cp -f "$ROOT_DIR/dist/${DEB_NAME}" "$ROOT_DIR/${DEB_NAME}" echo "==> Built Debian package: $ROOT_DIR/dist/${DEB_NAME} (and $ROOT_DIR/${DEB_NAME})" dpkg-deb --info "$ROOT_DIR/dist/${DEB_NAME}"