summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-27 23:17:18 +0900
committerAdam Malczewski <[email protected]>2026-05-27 23:17:18 +0900
commit60f56367dfc1e3c5095d034bed4fb4f572e32b55 (patch)
treeaa4bc7be095e7030ce0b1a67e33b1abe4cc8c36a
parent4f0ed4ed9456e30344228f0106f6bb104417da3d (diff)
downloaddispatch-60f56367dfc1e3c5095d034bed4fb4f572e32b55.tar.gz
dispatch-60f56367dfc1e3c5095d034bed4fb4f572e32b55.zip
refactor(packaging): split into dispatch/dispatch-systemd/dispatch-s6, separate dispatch-electron, add bun-based frontend serve
PKGBUILD is now a split package producing three .pkg.tar.zst files in one makepkg run: - dispatch base application files (/opt/dispatch), CLI wrappers (dispatch-api, dispatch-frontend), env configs (/etc/dispatch/dispatch-api.conf, /etc/dispatch/dispatch-frontend.conf) - dispatch-systemd systemd user units for dispatch-api + dispatch-frontend (conflicts with dispatch-s6) - dispatch-s6 s6-rc service pipelines (-srv + -log) for both services (conflicts with dispatch-systemd) The Electron desktop wrapper moved to its own self-contained PKGBUILD at packaging/electron/, producing dispatch-electron. It bundles its own copy of the frontend dist + electron entry points under /opt/dispatch-electron and does not depend on the base dispatch package, so users can install it standalone and point VITE_API_URL at any backend at build time. Files under packaging/ are now read directly from ${_projectdir}/packaging/ rather than via source=(); the two s6 service dirs share basenames (run, type) which would collide inside ${srcdir}. New frontend static server: packages/frontend/serve.ts uses Bun's built-in HTTP server (no extra runtime deps) with SPA fallback to index.html and path-traversal protection. PORT/HOST/DIST_DIR overridable via env. Exposed as 'bun run --cwd packages/frontend serve' and via /usr/bin/dispatch-frontend. Build scripts: - bin/build-pkg now prints the freshest built packages - bin/install-pkg installs dispatch + dispatch-systemd by default; accepts package names or --all; searches both packaging/ and packaging/electron/ - bin/build-pkg-electron new, builds the electron split - bin/build-pkg-windows replaces bin/windows-pkg; drops the hard-coded WSL copy step, just prints the win-unpacked path .gitignore extended to cover packaging/*.tar.zst and packaging/electron/{src,pkg,*.pkg.tar.zst,*.tar.zst}.
-rw-r--r--.gitignore5
-rwxr-xr-xbin/build-pkg17
-rwxr-xr-xbin/build-pkg-electron20
-rwxr-xr-xbin/build-pkg-windows38
-rwxr-xr-xbin/install-pkg65
-rwxr-xr-xbin/windows-pkg31
-rw-r--r--packages/frontend/package.json1
-rw-r--r--packages/frontend/serve.ts66
-rw-r--r--packaging/PKGBUILD174
-rwxr-xr-xpackaging/dispatch-electron-wrapper.sh2
-rwxr-xr-xpackaging/dispatch-frontend-wrapper.sh30
-rw-r--r--packaging/dispatch-frontend.conf21
-rw-r--r--packaging/dispatch-frontend.service16
-rw-r--r--packaging/dispatch-s6.install46
-rw-r--r--packaging/dispatch-systemd.install24
-rw-r--r--packaging/dispatch.install29
-rw-r--r--packaging/electron/PKGBUILD100
-rwxr-xr-xpackaging/electron/dispatch-electron-wrapper.sh2
-rw-r--r--packaging/electron/dispatch-electron.install32
-rw-r--r--packaging/electron/dispatch.desktop (renamed from packaging/dispatch.desktop)0
-rw-r--r--packaging/electron/dispatch.png (renamed from packaging/dispatch.png)bin10192 -> 10192 bytes
-rw-r--r--packaging/electron/dispatch.svg (renamed from packaging/dispatch.svg)0
-rw-r--r--packaging/s6/dispatch-api-log/consumer-for1
-rw-r--r--packaging/s6/dispatch-api-log/pipeline-name1
-rw-r--r--packaging/s6/dispatch-api-log/run6
-rw-r--r--packaging/s6/dispatch-api-log/type1
-rw-r--r--packaging/s6/dispatch-api-srv/producer-for1
-rw-r--r--packaging/s6/dispatch-api-srv/run29
-rw-r--r--packaging/s6/dispatch-api-srv/type1
-rw-r--r--packaging/s6/dispatch-frontend-log/consumer-for1
-rw-r--r--packaging/s6/dispatch-frontend-log/pipeline-name1
-rw-r--r--packaging/s6/dispatch-frontend-log/run6
-rw-r--r--packaging/s6/dispatch-frontend-log/type1
-rw-r--r--packaging/s6/dispatch-frontend-srv/producer-for1
-rw-r--r--packaging/s6/dispatch-frontend-srv/run25
-rw-r--r--packaging/s6/dispatch-frontend-srv/type1
36 files changed, 695 insertions, 100 deletions
diff --git a/.gitignore b/.gitignore
index a5273b1..26d88ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,4 +13,9 @@ references/
packaging/src/
packaging/pkg/
packaging/*.pkg.tar.zst
+packaging/*.tar.zst
+packaging/electron/src/
+packaging/electron/pkg/
+packaging/electron/*.pkg.tar.zst
+packaging/electron/*.tar.zst
packages/frontend/release/
diff --git a/bin/build-pkg b/bin/build-pkg
index 904a0bb..d257efc 100755
--- a/bin/build-pkg
+++ b/bin/build-pkg
@@ -1,8 +1,23 @@
#!/usr/bin/env bash
+# Build the dispatch Arch split package: dispatch + dispatch-systemd + dispatch-s6.
+# One makepkg run produces three .pkg.tar.zst files in packaging/.
+#
+# Usage:
+# bin/build-pkg # build
+# bin/build-pkg --noconfirm # forward extra args to makepkg
+#
+# Override frontend build target with VITE_API_URL, e.g.:
+# VITE_API_URL="https://api.example.com" bin/build-pkg
+
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-PACKAGING_DIR="$(dirname "$SCRIPT_DIR")/packaging"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+PACKAGING_DIR="$PROJECT_DIR/packaging"
cd "$PACKAGING_DIR"
makepkg -fd "$@"
+
+echo ""
+echo "Built packages (newest first):"
+ls -1t "$PACKAGING_DIR"/*.pkg.tar.zst 2>/dev/null | head -5
diff --git a/bin/build-pkg-electron b/bin/build-pkg-electron
new file mode 100755
index 0000000..ceec321
--- /dev/null
+++ b/bin/build-pkg-electron
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# Build the dispatch-electron Arch package (Linux desktop wrapper).
+# Depends on the `dispatch` package being built/installed at the matching version.
+#
+# Usage:
+# bin/build-pkg-electron # build
+# bin/build-pkg-electron --noconfirm
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+PACKAGING_DIR="$PROJECT_DIR/packaging/electron"
+
+cd "$PACKAGING_DIR"
+makepkg -fd "$@"
+
+echo ""
+echo "Built package:"
+ls -1t "$PACKAGING_DIR"/*.pkg.tar.zst 2>/dev/null | head -1
diff --git a/bin/build-pkg-windows b/bin/build-pkg-windows
new file mode 100755
index 0000000..c5760e0
--- /dev/null
+++ b/bin/build-pkg-windows
@@ -0,0 +1,38 @@
+#!/usr/bin/env bash
+# Build the Windows Electron output via electron-builder.
+# Produces an unpacked directory at packages/frontend/release/win-unpacked.
+#
+# Usage:
+# bin/build-pkg-windows
+#
+# Override the API URL the frontend bundle points to:
+# VITE_API_URL="http://your-host:18390" bin/build-pkg-windows
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+FRONTEND_DIR="$PROJECT_DIR/packages/frontend"
+VITE_API_URL="${VITE_API_URL:-http://localhost:18390}"
+
+cd "$PROJECT_DIR"
+
+# Install deps (electron + electron-builder live as frontend devDependencies)
+bun install
+
+# Build the SPA bundle, then package it for Windows (unpacked dir)
+cd "$FRONTEND_DIR"
+VITE_API_URL="$VITE_API_URL" bun run dist:win
+
+WIN_BUILD="$FRONTEND_DIR/release/win-unpacked"
+
+if [ ! -d "$WIN_BUILD" ]; then
+ echo "Build failed: no win-unpacked directory found at $WIN_BUILD" >&2
+ exit 1
+fi
+
+echo ""
+echo "Windows Electron build ready at:"
+echo " $WIN_BUILD"
+echo ""
+echo "Copy that folder to a Windows machine and run Dispatch.exe."
diff --git a/bin/install-pkg b/bin/install-pkg
index 89e55b2..72e218b 100755
--- a/bin/install-pkg
+++ b/bin/install-pkg
@@ -1,15 +1,68 @@
#!/usr/bin/env bash
+# Install one or more built dispatch packages with yay -U.
+#
+# Default: installs the freshest dispatch + dispatch-systemd from packaging/.
+# Pass package names (without version) to install a custom set.
+#
+# Usage:
+# bin/install-pkg # dispatch + dispatch-systemd
+# bin/install-pkg dispatch dispatch-s6 # dispatch + dispatch-s6
+# bin/install-pkg dispatch dispatch-electron # dispatch + electron wrapper
+# bin/install-pkg --all # every freshest pkg found
+
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-PACKAGING_DIR="$(dirname "$SCRIPT_DIR")/packaging"
+PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
+PACKAGING_DIR="$PROJECT_DIR/packaging"
+ELECTRON_DIR="$PROJECT_DIR/packaging/electron"
+
+# Find the most recent .pkg.tar.zst matching a package name in a given dir.
+find_pkg() {
+ local name="$1" dir="$2"
+ ls -t "$dir"/"$name"-[0-9]*-x86_64.pkg.tar.zst 2>/dev/null | head -1
+}
+
+# Resolve a package name to its freshest tarball, searching both dirs.
+resolve_pkg() {
+ local name="$1" path
+ path=$(find_pkg "$name" "$PACKAGING_DIR")
+ [ -n "$path" ] || path=$(find_pkg "$name" "$ELECTRON_DIR")
+ echo "$path"
+}
+
+declare -a names
+declare -a paths
+
+if [ $# -eq 0 ]; then
+ names=(dispatch dispatch-systemd)
+elif [ "${1:-}" = "--all" ]; then
+ names=(dispatch dispatch-systemd dispatch-s6 dispatch-electron)
+else
+ names=("$@")
+fi
-PKG=$(ls -t "$PACKAGING_DIR"/dispatch-[0-9]*-x86_64.pkg.tar.zst 2>/dev/null | head -1)
+for name in "${names[@]}"; do
+ path=$(resolve_pkg "$name")
+ if [ -z "$path" ]; then
+ # --all is lenient: skip missing packages
+ if [ "${1:-}" = "--all" ]; then
+ echo "warn: no built package found for '$name', skipping" >&2
+ continue
+ fi
+ echo "error: no built package found for '$name'" >&2
+ echo " run bin/build-pkg (or bin/build-pkg-electron) first." >&2
+ exit 1
+ fi
+ paths+=("$path")
+done
-if [ -z "$PKG" ]; then
- echo "No package found. Run bin/build-pkg first." >&2
+if [ ${#paths[@]} -eq 0 ]; then
+ echo "error: nothing to install." >&2
exit 1
fi
-echo "Installing $PKG"
-yay -U "$PKG" "$@"
+echo "Installing:"
+printf ' %s\n' "${paths[@]}"
+echo ""
+yay -U "${paths[@]}"
diff --git a/bin/windows-pkg b/bin/windows-pkg
deleted file mode 100755
index 693168c..0000000
--- a/bin/windows-pkg
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
-PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
-FRONTEND_DIR="$PROJECT_DIR/packages/frontend"
-DEST="/mnt/c/Users/micro/OneDrive/Documents/Dispatch"
-
-# Install deps (includes electron + electron-builder as devDependencies)
-cd "$PROJECT_DIR"
-bun install
-
-# Build frontend and package for Windows (use production port)
-cd "$FRONTEND_DIR"
-VITE_API_URL="http://localhost:18390" bun run dist:win
-
-# Find the output directory
-WIN_BUILD=$(ls -d "$FRONTEND_DIR/release/win-unpacked" 2>/dev/null || true)
-
-if [ -z "$WIN_BUILD" ]; then
- echo "Build failed: no win-unpacked directory found in release/" >&2
- exit 1
-fi
-
-# Copy to Windows Documents
-rm -rf "$DEST"
-mkdir -p "$DEST"
-cp -r "$WIN_BUILD"/. "$DEST"/
-
-echo "Windows build copied to: C:\\Users\\micro\\OneDrive\\Documents\\Dispatch"
-echo "Run Dispatch.exe from that folder."
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 453df07..1d11524 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -8,6 +8,7 @@
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
+ "serve": "bun serve.ts",
"test": "vitest run",
"test:watch": "vitest",
"typecheck": "svelte-check --tsconfig ./tsconfig.json",
diff --git a/packages/frontend/serve.ts b/packages/frontend/serve.ts
new file mode 100644
index 0000000..75b0a9f
--- /dev/null
+++ b/packages/frontend/serve.ts
@@ -0,0 +1,66 @@
+// Static file server for the built frontend (packages/frontend/dist).
+// Uses Bun's built-in HTTP server — no extra runtime dependencies.
+//
+// Environment variables:
+// PORT — port to listen on (default: 18391)
+// HOST — interface to bind to (default: 0.0.0.0)
+// DIST_DIR — absolute path to the static assets directory
+// (default: <this-file's-dir>/dist)
+//
+// SPA fallback: requests that don't match a file on disk fall back to
+// index.html so client-side routing works.
+
+import { existsSync, statSync } from "node:fs";
+import { join, normalize, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const moduleDir = (() => {
+ try {
+ // @ts-expect-error — Bun provides import.meta.dir
+ return import.meta.dir as string;
+ } catch {
+ return resolve(fileURLToPath(import.meta.url), "..");
+ }
+})();
+
+const port = Number(process.env.PORT) || 18391;
+const host = process.env.HOST || "0.0.0.0";
+const distDir = resolve(process.env.DIST_DIR || join(moduleDir, "dist"));
+
+if (!existsSync(distDir) || !statSync(distDir).isDirectory()) {
+ console.error(`[dispatch-frontend] dist directory not found: ${distDir}`);
+ console.error("[dispatch-frontend] Build the frontend first (bun run --cwd packages/frontend build)");
+ process.exit(1);
+}
+
+const indexPath = join(distDir, "index.html");
+
+function safeResolve(urlPath: string): string | null {
+ // Normalize and prevent path-traversal outside distDir.
+ const decoded = decodeURIComponent(urlPath);
+ const candidate = resolve(distDir, "." + normalize(decoded));
+ if (!candidate.startsWith(distDir)) return null;
+ return candidate;
+}
+
+Bun.serve({
+ port,
+ hostname: host,
+ async fetch(req) {
+ const url = new URL(req.url);
+ let pathname = url.pathname;
+ if (pathname === "/" || pathname === "") pathname = "/index.html";
+
+ const resolved = safeResolve(pathname);
+ if (resolved && existsSync(resolved) && statSync(resolved).isFile()) {
+ return new Response(Bun.file(resolved));
+ }
+
+ // SPA fallback — serve index.html for unknown routes
+ return new Response(Bun.file(indexPath), {
+ headers: { "content-type": "text/html; charset=utf-8" },
+ });
+ },
+});
+
+console.log(`[dispatch-frontend] serving ${distDir} on http://${host}:${port}`);
diff --git a/packaging/PKGBUILD b/packaging/PKGBUILD
index de49d4e..4ac87a8 100644
--- a/packaging/PKGBUILD
+++ b/packaging/PKGBUILD
@@ -1,30 +1,36 @@
# Maintainer: dispatch
-pkgname=dispatch
+#
+# Split package: builds the base application once and produces three packages:
+# dispatch — application files + CLI wrappers + env configs
+# dispatch-systemd — systemd user units for dispatch-api / dispatch-frontend
+# dispatch-s6 — s6 service definitions for dispatch-api / dispatch-frontend
+#
+# The Electron desktop wrapper lives in a separate PKGBUILD at packaging/electron/.
+#
+# Build with:
+# bin/build-pkg # makepkg -fd in packaging/
+
+pkgbase=dispatch
+pkgname=('dispatch' 'dispatch-systemd' 'dispatch-s6')
pkgver=0.0.1
pkgrel=1
-pkgdesc='AI Agent Dispatch Interface'
arch=('x86_64')
url='https://github.com/anomalyco/dispatch'
license=('MIT')
-depends=('electron' 'bun')
-makedepends=()
-install=dispatch.install
-backup=('etc/dispatch/dispatch-api.conf')
-source=(
- 'dispatch-api.service'
- 'dispatch-api.conf'
- 'dispatch-electron-wrapper.sh'
- 'dispatch-api-wrapper.sh'
- 'dispatch.desktop'
- 'dispatch.svg'
-)
-sha256sums=('SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP' 'SKIP')
+makedepends=('bun')
+# All static files are read directly from ${_projectdir}/packaging/. We don't
+# use source=() because two of the s6 files share basenames (run, type), which
+# would collide inside ${srcdir}.
+source=()
+sha256sums=()
_projectdir="${startdir}/.."
+_packagingdir="${_projectdir}/packaging"
prepare() {
mkdir -p "${srcdir}/dispatch-${pkgver}"
- # Copy project files into the src build directory (use -a to preserve symlinks)
+
+ # Copy project files into the src build directory (preserve symlinks)
cp -a \
"${_projectdir}/packages" \
"${_projectdir}/package.json" \
@@ -32,7 +38,6 @@ prepare() {
"${_projectdir}/tsconfig.base.json" \
"${srcdir}/dispatch-${pkgver}/"
- # Copy dispatch.toml if it exists
if [ -f "${_projectdir}/dispatch.toml" ]; then
cp "${_projectdir}/dispatch.toml" "${srcdir}/dispatch-${pkgver}/"
fi
@@ -41,18 +46,30 @@ prepare() {
build() {
cd "${srcdir}/dispatch-${pkgver}"
- # Install all deps (including devDependencies needed for vite build)
+ # Install all deps (including devDependencies for vite build)
bun install --frozen-lockfile
# Build the frontend with production API port
- VITE_API_URL="http://localhost:18390" bun run --cwd packages/frontend build
+ VITE_API_URL="${VITE_API_URL:-http://localhost:18390}" \
+ bun run --cwd packages/frontend build
- # Reinstall with production only to slim down node_modules for packaging
+ # Slim node_modules for runtime
rm -rf node_modules
bun install --frozen-lockfile --production
}
-package() {
+# ----------------------------------------------------------------------------
+# dispatch — application files, CLI wrappers, env configs
+# ----------------------------------------------------------------------------
+package_dispatch() {
+ pkgdesc='AI Agent Dispatch — backend + frontend (application files)'
+ depends=('bun')
+ install=dispatch.install
+ backup=(
+ 'etc/dispatch/dispatch-api.conf'
+ 'etc/dispatch/dispatch-frontend.conf'
+ )
+
cd "${srcdir}/dispatch-${pkgver}"
local optdir="${pkgdir}/opt/dispatch"
@@ -67,61 +84,112 @@ package() {
install -dm755 "${optdir}/packages/core"
cp -a packages/core/. "${optdir}/packages/core/"
- # Frontend: built dist only
+ # Frontend: built dist + serve script + package.json (for workspace resolution)
install -dm755 "${optdir}/packages/frontend/dist"
cp -a packages/frontend/dist/. "${optdir}/packages/frontend/dist/"
-
- # Frontend: electron launcher files
- install -dm755 "${optdir}/packages/frontend/electron"
- cp -a packages/frontend/electron/. "${optdir}/packages/frontend/electron/"
-
- # Frontend: package.json (needed for workspace resolution)
+ install -Dm644 packages/frontend/serve.ts "${optdir}/packages/frontend/serve.ts"
install -Dm644 packages/frontend/package.json "${optdir}/packages/frontend/package.json"
# Runtime node_modules (preserve symlinks for bun workspaces)
install -dm755 "${optdir}/node_modules"
cp -a node_modules/. "${optdir}/node_modules/"
- # Root manifest and lockfile
+ # Root manifest + lockfile
install -Dm644 package.json "${optdir}/package.json"
install -Dm644 bun.lock "${optdir}/bun.lock"
- # Default config (if present)
+ # Optional default config
if [ -f dispatch.toml ]; then
install -Dm644 dispatch.toml "${optdir}/dispatch.toml"
fi
- # --- systemd user service ---
- install -Dm644 \
- "${srcdir}/dispatch-api.service" \
- "${pkgdir}/usr/lib/systemd/user/dispatch-api.service"
-
- # --- Environment config (preserved across upgrades via backup=()) ---
+ # --- Environment configs (preserved across upgrades via backup=()) ---
install -Dm644 \
- "${srcdir}/dispatch-api.conf" \
+ "${_packagingdir}/dispatch-api.conf" \
"${pkgdir}/etc/dispatch/dispatch-api.conf"
+ install -Dm644 \
+ "${_packagingdir}/dispatch-frontend.conf" \
+ "${pkgdir}/etc/dispatch/dispatch-frontend.conf"
- # --- Wrapper scripts ---
- install -Dm755 \
- "${srcdir}/dispatch-electron-wrapper.sh" \
- "${pkgdir}/usr/bin/dispatch"
-
+ # --- CLI wrappers ---
install -Dm755 \
- "${srcdir}/dispatch-api-wrapper.sh" \
+ "${_packagingdir}/dispatch-api-wrapper.sh" \
"${pkgdir}/usr/bin/dispatch-api"
-
- # --- Desktop integration ---
- install -Dm644 \
- "${srcdir}/dispatch.desktop" \
- "${pkgdir}/usr/share/applications/dispatch.desktop"
-
- install -Dm644 \
- "${srcdir}/dispatch.svg" \
- "${pkgdir}/usr/share/icons/hicolor/scalable/apps/dispatch.svg"
+ install -Dm755 \
+ "${_packagingdir}/dispatch-frontend-wrapper.sh" \
+ "${pkgdir}/usr/bin/dispatch-frontend"
# --- License ---
if [ -f "${_projectdir}/LICENSE" ]; then
install -Dm644 "${_projectdir}/LICENSE" \
- "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
+ "${pkgdir}/usr/share/licenses/dispatch/LICENSE"
fi
}
+
+# ----------------------------------------------------------------------------
+# dispatch-systemd — systemd user unit files
+# ----------------------------------------------------------------------------
+package_dispatch-systemd() {
+ pkgdesc='Systemd user units for the Dispatch API and Frontend services'
+ depends=("dispatch=${pkgver}" 'systemd')
+ conflicts=('dispatch-s6')
+ install=dispatch-systemd.install
+
+ install -Dm644 \
+ "${_packagingdir}/dispatch-api.service" \
+ "${pkgdir}/usr/lib/systemd/user/dispatch-api.service"
+
+ install -Dm644 \
+ "${_packagingdir}/dispatch-frontend.service" \
+ "${pkgdir}/usr/lib/systemd/user/dispatch-frontend.service"
+}
+
+# ----------------------------------------------------------------------------
+# dispatch-s6 — s6 service definitions (for Artix / standalone s6)
+# ----------------------------------------------------------------------------
+package_dispatch-s6() {
+ pkgdesc='s6-rc service definitions for the Dispatch API and Frontend services'
+ depends=("dispatch=${pkgver}" 's6' 's6-rc')
+ conflicts=('dispatch-systemd')
+ install=dispatch-s6.install
+
+ # s6-rc layout: each service is split into a `-srv` (the daemon) and a
+ # `-log` (s6-log consumer), linked by producer-for/consumer-for/pipeline-name.
+ # The pipeline-name lets `s6-rc -u change dispatch-api` bring both up together.
+
+ # --- dispatch-api-srv ---
+ install -Dm755 "${_packagingdir}/s6/dispatch-api-srv/run" \
+ "${pkgdir}/etc/s6/sv/dispatch-api-srv/run"
+ install -Dm644 "${_packagingdir}/s6/dispatch-api-srv/type" \
+ "${pkgdir}/etc/s6/sv/dispatch-api-srv/type"
+ install -Dm644 "${_packagingdir}/s6/dispatch-api-srv/producer-for" \
+ "${pkgdir}/etc/s6/sv/dispatch-api-srv/producer-for"
+
+ # --- dispatch-api-log ---
+ install -Dm755 "${_packagingdir}/s6/dispatch-api-log/run" \
+ "${pkgdir}/etc/s6/sv/dispatch-api-log/run"
+ install -Dm644 "${_packagingdir}/s6/dispatch-api-log/type" \
+ "${pkgdir}/etc/s6/sv/dispatch-api-log/type"
+ install -Dm644 "${_packagingdir}/s6/dispatch-api-log/consumer-for" \
+ "${pkgdir}/etc/s6/sv/dispatch-api-log/consumer-for"
+ install -Dm644 "${_packagingdir}/s6/dispatch-api-log/pipeline-name" \
+ "${pkgdir}/etc/s6/sv/dispatch-api-log/pipeline-name"
+
+ # --- dispatch-frontend-srv ---
+ install -Dm755 "${_packagingdir}/s6/dispatch-frontend-srv/run" \
+ "${pkgdir}/etc/s6/sv/dispatch-frontend-srv/run"
+ install -Dm644 "${_packagingdir}/s6/dispatch-frontend-srv/type" \
+ "${pkgdir}/etc/s6/sv/dispatch-frontend-srv/type"
+ install -Dm644 "${_packagingdir}/s6/dispatch-frontend-srv/producer-for" \
+ "${pkgdir}/etc/s6/sv/dispatch-frontend-srv/producer-for"
+
+ # --- dispatch-frontend-log ---
+ install -Dm755 "${_packagingdir}/s6/dispatch-frontend-log/run" \
+ "${pkgdir}/etc/s6/sv/dispatch-frontend-log/run"
+ install -Dm644 "${_packagingdir}/s6/dispatch-frontend-log/type" \
+ "${pkgdir}/etc/s6/sv/dispatch-frontend-log/type"
+ install -Dm644 "${_packagingdir}/s6/dispatch-frontend-log/consumer-for" \
+ "${pkgdir}/etc/s6/sv/dispatch-frontend-log/consumer-for"
+ install -Dm644 "${_packagingdir}/s6/dispatch-frontend-log/pipeline-name" \
+ "${pkgdir}/etc/s6/sv/dispatch-frontend-log/pipeline-name"
+}
diff --git a/packaging/dispatch-electron-wrapper.sh b/packaging/dispatch-electron-wrapper.sh
deleted file mode 100755
index df24713..0000000
--- a/packaging/dispatch-electron-wrapper.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-exec /usr/bin/electron --name="Dispatch" /opt/dispatch/packages/frontend/electron/main.cjs "$@"
diff --git a/packaging/dispatch-frontend-wrapper.sh b/packaging/dispatch-frontend-wrapper.sh
new file mode 100755
index 0000000..06aa9b0
--- /dev/null
+++ b/packaging/dispatch-frontend-wrapper.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# dispatch-frontend-wrapper.sh
+# Wrapper script for running the Dispatch Frontend static-file server
+# outside of systemd / s6.
+# Install to /usr/bin/dispatch-frontend (chmod 755).
+#
+# Usage:
+# dispatch-frontend # runs the server, inheriting the current environment
+# dispatch-frontend --help # passes flags through to bun
+
+set -euo pipefail
+
+DISPATCH_CONF="/etc/dispatch/dispatch-frontend.conf"
+DISPATCH_DIR="/opt/dispatch"
+BUN="/usr/bin/bun"
+ENTRY_POINT="packages/frontend/serve.ts"
+
+# Source the environment file if it exists
+if [[ -f "$DISPATCH_CONF" ]]; then
+ set -o allexport
+ # shellcheck source=/etc/dispatch/dispatch-frontend.conf
+ source "$DISPATCH_CONF"
+ set +o allexport
+fi
+
+# Change to the application directory
+cd "$DISPATCH_DIR"
+
+# Hand off to bun — exec replaces this process so signals are forwarded correctly
+exec "$BUN" "$ENTRY_POINT" "$@"
diff --git a/packaging/dispatch-frontend.conf b/packaging/dispatch-frontend.conf
new file mode 100644
index 0000000..37d2984
--- /dev/null
+++ b/packaging/dispatch-frontend.conf
@@ -0,0 +1,21 @@
+# /etc/dispatch/dispatch-frontend.conf
+# Environment configuration for the Dispatch Frontend static-file server.
+# Sourced by systemd (EnvironmentFile=), the s6 run script, and the
+# dispatch-frontend-wrapper.sh script for manual invocation.
+#
+# Lines beginning with '#' are comments and are ignored.
+# Syntax: KEY=value (no spaces around '=', no 'export' keyword needed for systemd)
+
+# ---------------------------------------------------------------------------
+# Server
+# ---------------------------------------------------------------------------
+
+# Port the static file server listens on (default: 18391).
+PORT=18391
+
+# Interface to bind to (default: 0.0.0.0).
+# HOST=0.0.0.0
+
+# Directory containing the built frontend assets.
+# Defaults to /opt/dispatch/packages/frontend/dist
+# DIST_DIR=/opt/dispatch/packages/frontend/dist
diff --git a/packaging/dispatch-frontend.service b/packaging/dispatch-frontend.service
new file mode 100644
index 0000000..4aca1a3
--- /dev/null
+++ b/packaging/dispatch-frontend.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Dispatch Frontend (static file server)
+After=dispatch-api.service
+
+[Service]
+Type=simple
+WorkingDirectory=/opt/dispatch
+ExecStart=/usr/bin/bun packages/frontend/serve.ts
+EnvironmentFile=-/etc/dispatch/dispatch-frontend.conf
+Restart=on-failure
+RestartSec=5
+StandardOutput=journal
+StandardError=journal
+
+[Install]
+WantedBy=default.target
diff --git a/packaging/dispatch-s6.install b/packaging/dispatch-s6.install
new file mode 100644
index 0000000..efa4c9b
--- /dev/null
+++ b/packaging/dispatch-s6.install
@@ -0,0 +1,46 @@
+post_install() {
+ install -dm755 -o tradam -g tradam /var/log/dispatch-api
+ install -dm755 -o tradam -g tradam /var/log/dispatch-frontend
+
+ echo ""
+ echo "==> Dispatch s6-rc services installed at /etc/s6/sv/"
+ echo " Service pipelines: dispatch-api (= dispatch-api-srv | dispatch-api-log)"
+ echo " dispatch-frontend (= dispatch-frontend-srv | dispatch-frontend-log)"
+ echo " To enable and start:"
+ echo " s6 repository sync"
+ echo " s6 set enable -I pull dispatch-api-srv dispatch-api-log"
+ echo " s6 set enable -I pull dispatch-frontend-srv dispatch-frontend-log"
+ echo " s6 set commit -f"
+ echo " s6 live install"
+ echo " s6-rc -u change dispatch-api"
+ echo " s6-rc -u change dispatch-frontend"
+ echo ""
+}
+
+post_upgrade() {
+ install -dm755 -o tradam -g tradam /var/log/dispatch-api 2>/dev/null || true
+ install -dm755 -o tradam -g tradam /var/log/dispatch-frontend 2>/dev/null || true
+
+ # Remove any stale legacy service dirs from the old (pre-srv/-log split)
+ # layout, which would otherwise confuse s6 repository sync.
+ for legacy in /etc/s6/sv/dispatch-api /etc/s6/sv/dispatch-frontend; do
+ if [ -d "$legacy" ] && [ -d "$legacy/log" ] && [ ! -e "$legacy/producer-for" ]; then
+ rm -rf "$legacy"
+ fi
+ done
+
+ echo ""
+ echo "==> Dispatch s6 services upgraded."
+ echo " Restart:"
+ echo " s6-rc -d change dispatch-api && s6-rc -u change dispatch-api"
+ echo " s6-rc -d change dispatch-frontend && s6-rc -u change dispatch-frontend"
+ echo ""
+}
+
+pre_remove() {
+ echo ""
+ echo "==> Stopping dispatch services..."
+ s6-rc -d change dispatch-api 2>/dev/null || true
+ s6-rc -d change dispatch-frontend 2>/dev/null || true
+ echo ""
+}
diff --git a/packaging/dispatch-systemd.install b/packaging/dispatch-systemd.install
new file mode 100644
index 0000000..3bd29be
--- /dev/null
+++ b/packaging/dispatch-systemd.install
@@ -0,0 +1,24 @@
+post_install() {
+ echo ""
+ echo "==> Dispatch systemd user units installed."
+ echo " Enable and start:"
+ echo " systemctl --user daemon-reload"
+ echo " systemctl --user enable --now dispatch-api dispatch-frontend"
+ echo ""
+}
+
+post_upgrade() {
+ echo ""
+ echo "==> Dispatch systemd units upgraded."
+ echo " Reload and restart:"
+ echo " systemctl --user daemon-reload"
+ echo " systemctl --user restart dispatch-api dispatch-frontend"
+ echo ""
+}
+
+pre_remove() {
+ echo ""
+ echo "==> Stop dispatch services before removal:"
+ echo " systemctl --user disable --now dispatch-api dispatch-frontend"
+ echo ""
+}
diff --git a/packaging/dispatch.install b/packaging/dispatch.install
index 7d1439f..76837f5 100644
--- a/packaging/dispatch.install
+++ b/packaging/dispatch.install
@@ -1,16 +1,31 @@
post_install() {
echo ""
- echo "==> Dispatch has been installed."
- echo " Edit /etc/dispatch/dispatch-api.conf to configure the API."
- echo " Then enable and start the service:"
- echo " systemctl --user enable --now dispatch-api"
+ echo "==> Dispatch (base) has been installed."
+ echo " Application files: /opt/dispatch"
+ echo " Config:"
+ echo " /etc/dispatch/dispatch-api.conf"
+ echo " /etc/dispatch/dispatch-frontend.conf"
+ echo ""
+ echo " Run manually:"
+ echo " dispatch-api # backend (port 18390 by default)"
+ echo " dispatch-frontend # static frontend (port 18391 by default)"
+ echo ""
+ echo " To run as a service, install one of:"
+ echo " pacman -S dispatch-systemd # systemd user services"
+ echo " pacman -S dispatch-s6 # s6 services (Artix)"
+ echo ""
+ echo " For the Electron desktop wrapper:"
+ echo " pacman -S dispatch-electron"
echo ""
}
post_upgrade() {
echo ""
- echo "==> Dispatch has been upgraded."
- echo " You may need to restart the service:"
- echo " systemctl --user restart dispatch-api"
+ echo "==> Dispatch (base) upgraded."
+ echo " If you use dispatch-systemd, restart the units:"
+ echo " systemctl --user restart dispatch-api dispatch-frontend"
+ echo " If you use dispatch-s6, restart the services:"
+ echo " s6-svc -r /run/service/dispatch-api"
+ echo " s6-svc -r /run/service/dispatch-frontend"
echo ""
}
diff --git a/packaging/electron/PKGBUILD b/packaging/electron/PKGBUILD
new file mode 100644
index 0000000..a259418
--- /dev/null
+++ b/packaging/electron/PKGBUILD
@@ -0,0 +1,100 @@
+# Maintainer: dispatch
+#
+# Electron desktop wrapper for Dispatch on Linux.
+#
+# Self-contained: bundles its own copy of the built frontend dist + electron
+# entry points under /opt/dispatch-electron/. Does NOT depend on the `dispatch`
+# package — install that separately (with dispatch-systemd or dispatch-s6) if
+# you want to run the backend locally, or point VITE_API_URL at a remote
+# instance at build time.
+#
+# Build with:
+# bin/build-pkg-electron # makepkg -fd in packaging/electron/
+#
+# Override the backend URL the bundled frontend talks to:
+# VITE_API_URL="http://your-host:18390" bin/build-pkg-electron
+
+pkgname=dispatch-electron
+pkgver=0.0.1
+pkgrel=1
+pkgdesc='Electron desktop wrapper for Dispatch (self-contained)'
+arch=('x86_64')
+url='https://github.com/anomalyco/dispatch'
+license=('MIT')
+depends=('electron')
+makedepends=('bun')
+install=dispatch-electron.install
+source=()
+sha256sums=()
+
+_projectdir="${startdir}/../.."
+_packagingdir="${_projectdir}/packaging"
+_electrondir="${_packagingdir}/electron"
+
+prepare() {
+ mkdir -p "${srcdir}/dispatch-electron-${pkgver}"
+
+ # Copy project files into the src build directory (preserve symlinks)
+ cp -a \
+ "${_projectdir}/packages" \
+ "${_projectdir}/package.json" \
+ "${_projectdir}/bun.lock" \
+ "${_projectdir}/tsconfig.base.json" \
+ "${srcdir}/dispatch-electron-${pkgver}/"
+}
+
+build() {
+ cd "${srcdir}/dispatch-electron-${pkgver}"
+
+ # Install all deps (including devDependencies for vite build)
+ bun install --frozen-lockfile
+
+ # Build the SPA. VITE_API_URL is baked in at build time.
+ VITE_API_URL="${VITE_API_URL:-http://localhost:18390}" \
+ bun run --cwd packages/frontend build
+}
+
+package() {
+ cd "${srcdir}/dispatch-electron-${pkgver}"
+
+ local optdir="${pkgdir}/opt/dispatch-electron"
+
+ # --- Bundled SPA dist ---
+ install -dm755 "${optdir}/dist"
+ cp -a packages/frontend/dist/. "${optdir}/dist/"
+
+ # --- Electron entry points ---
+ # main.cjs loads ../dist/index.html — so the dist must sit alongside electron/.
+ install -Dm644 \
+ packages/frontend/electron/main.cjs \
+ "${optdir}/electron/main.cjs"
+ install -Dm644 \
+ packages/frontend/electron/preload.cjs \
+ "${optdir}/electron/preload.cjs"
+
+ # --- /usr/bin/dispatch (electron wrapper) ---
+ install -Dm755 \
+ "${_electrondir}/dispatch-electron-wrapper.sh" \
+ "${pkgdir}/usr/bin/dispatch"
+
+ # --- Desktop integration ---
+ install -Dm644 \
+ "${_electrondir}/dispatch.desktop" \
+ "${pkgdir}/usr/share/applications/dispatch.desktop"
+
+ install -Dm644 \
+ "${_electrondir}/dispatch.svg" \
+ "${pkgdir}/usr/share/icons/hicolor/scalable/apps/dispatch.svg"
+
+ if [ -f "${_electrondir}/dispatch.png" ]; then
+ install -Dm644 \
+ "${_electrondir}/dispatch.png" \
+ "${pkgdir}/usr/share/icons/hicolor/512x512/apps/dispatch.png"
+ fi
+
+ # --- License ---
+ if [ -f "${_projectdir}/LICENSE" ]; then
+ install -Dm644 "${_projectdir}/LICENSE" \
+ "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
+ fi
+}
diff --git a/packaging/electron/dispatch-electron-wrapper.sh b/packaging/electron/dispatch-electron-wrapper.sh
new file mode 100755
index 0000000..948db3b
--- /dev/null
+++ b/packaging/electron/dispatch-electron-wrapper.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+exec /usr/bin/electron --name="Dispatch" /opt/dispatch-electron/electron/main.cjs "$@"
diff --git a/packaging/electron/dispatch-electron.install b/packaging/electron/dispatch-electron.install
new file mode 100644
index 0000000..9eea6b1
--- /dev/null
+++ b/packaging/electron/dispatch-electron.install
@@ -0,0 +1,32 @@
+post_install() {
+ echo ""
+ echo "==> Dispatch Electron desktop wrapper installed (self-contained)."
+ echo " Launch from your menu (Dispatch) or run:"
+ echo " dispatch"
+ echo ""
+ echo " The bundled frontend talks to the API URL baked in at build"
+ echo " time (default: http://localhost:18390). To run a local backend:"
+ echo " pacman -S dispatch dispatch-systemd # systemd"
+ echo " pacman -S dispatch dispatch-s6 # s6 / Artix"
+ echo ""
+ echo " To point the wrapper at a different backend, rebuild with:"
+ echo " VITE_API_URL=\"http://your-host:18390\" bin/build-pkg-electron"
+ echo ""
+
+ if command -v update-desktop-database >/dev/null 2>&1; then
+ update-desktop-database -q /usr/share/applications 2>/dev/null || true
+ fi
+ if command -v gtk-update-icon-cache >/dev/null 2>&1; then
+ gtk-update-icon-cache -q /usr/share/icons/hicolor 2>/dev/null || true
+ fi
+}
+
+post_upgrade() {
+ post_install
+}
+
+post_remove() {
+ if command -v update-desktop-database >/dev/null 2>&1; then
+ update-desktop-database -q /usr/share/applications 2>/dev/null || true
+ fi
+}
diff --git a/packaging/dispatch.desktop b/packaging/electron/dispatch.desktop
index 0ed44af..0ed44af 100644
--- a/packaging/dispatch.desktop
+++ b/packaging/electron/dispatch.desktop
diff --git a/packaging/dispatch.png b/packaging/electron/dispatch.png
index 84d743a..84d743a 100644
--- a/packaging/dispatch.png
+++ b/packaging/electron/dispatch.png
Binary files differ
diff --git a/packaging/dispatch.svg b/packaging/electron/dispatch.svg
index 2aae97f..2aae97f 100644
--- a/packaging/dispatch.svg
+++ b/packaging/electron/dispatch.svg
diff --git a/packaging/s6/dispatch-api-log/consumer-for b/packaging/s6/dispatch-api-log/consumer-for
new file mode 100644
index 0000000..8a696f4
--- /dev/null
+++ b/packaging/s6/dispatch-api-log/consumer-for
@@ -0,0 +1 @@
+dispatch-api-srv
diff --git a/packaging/s6/dispatch-api-log/pipeline-name b/packaging/s6/dispatch-api-log/pipeline-name
new file mode 100644
index 0000000..9dab7e6
--- /dev/null
+++ b/packaging/s6/dispatch-api-log/pipeline-name
@@ -0,0 +1 @@
+dispatch-api
diff --git a/packaging/s6/dispatch-api-log/run b/packaging/s6/dispatch-api-log/run
new file mode 100644
index 0000000..5d1b821
--- /dev/null
+++ b/packaging/s6/dispatch-api-log/run
@@ -0,0 +1,6 @@
+#!/bin/sh
+# Logger for dispatch-api. Reads stdout from dispatch-api-srv and writes to
+# /var/log/dispatch-api/ using s6-log (auto-rotation, timestamping).
+
+exec /usr/bin/s6-setuidgid tradam \
+ /usr/bin/s6-log -b -d3 -- n10 s10000000 T /var/log/dispatch-api
diff --git a/packaging/s6/dispatch-api-log/type b/packaging/s6/dispatch-api-log/type
new file mode 100644
index 0000000..5883cff
--- /dev/null
+++ b/packaging/s6/dispatch-api-log/type
@@ -0,0 +1 @@
+longrun
diff --git a/packaging/s6/dispatch-api-srv/producer-for b/packaging/s6/dispatch-api-srv/producer-for
new file mode 100644
index 0000000..88f7df2
--- /dev/null
+++ b/packaging/s6/dispatch-api-srv/producer-for
@@ -0,0 +1 @@
+dispatch-api-log
diff --git a/packaging/s6/dispatch-api-srv/run b/packaging/s6/dispatch-api-srv/run
new file mode 100644
index 0000000..5754e3d
--- /dev/null
+++ b/packaging/s6/dispatch-api-srv/run
@@ -0,0 +1,29 @@
+#!/bin/sh
+# dispatch-api service — runs the Dispatch backend API as the `tradam` user.
+# stdout/stderr are piped to dispatch-api-log via the s6-rc pipeline.
+
+DISPATCH_CONF="/etc/dispatch/dispatch-api.conf"
+DISPATCH_DIR="/opt/dispatch"
+
+if [ -f "$DISPATCH_CONF" ]; then
+ set -a
+ . "$DISPATCH_CONF"
+ set +a
+fi
+
+cd "$DISPATCH_DIR" || exit 1
+
+# Merge stderr into stdout so both get logged by the consumer.
+exec 2>&1
+
+# Drop privileges to tradam and run bun.
+exec /usr/bin/s6-setuidgid tradam \
+ /usr/bin/env \
+ HOME=/home/tradam \
+ USER=tradam \
+ LOGNAME=tradam \
+ PATH=/usr/local/bin:/usr/bin:/bin \
+ NODE_ENV="${NODE_ENV:-production}" \
+ PORT="${PORT:-18390}" \
+ OPENCODE_API_KEY="${OPENCODE_API_KEY:-}" \
+ /usr/bin/bun packages/api/src/index.ts
diff --git a/packaging/s6/dispatch-api-srv/type b/packaging/s6/dispatch-api-srv/type
new file mode 100644
index 0000000..5883cff
--- /dev/null
+++ b/packaging/s6/dispatch-api-srv/type
@@ -0,0 +1 @@
+longrun
diff --git a/packaging/s6/dispatch-frontend-log/consumer-for b/packaging/s6/dispatch-frontend-log/consumer-for
new file mode 100644
index 0000000..f941d04
--- /dev/null
+++ b/packaging/s6/dispatch-frontend-log/consumer-for
@@ -0,0 +1 @@
+dispatch-frontend-srv
diff --git a/packaging/s6/dispatch-frontend-log/pipeline-name b/packaging/s6/dispatch-frontend-log/pipeline-name
new file mode 100644
index 0000000..0ee914b
--- /dev/null
+++ b/packaging/s6/dispatch-frontend-log/pipeline-name
@@ -0,0 +1 @@
+dispatch-frontend
diff --git a/packaging/s6/dispatch-frontend-log/run b/packaging/s6/dispatch-frontend-log/run
new file mode 100644
index 0000000..4dda559
--- /dev/null
+++ b/packaging/s6/dispatch-frontend-log/run
@@ -0,0 +1,6 @@
+#!/bin/sh
+# Logger for dispatch-frontend. Reads stdout from dispatch-frontend-srv and
+# writes to /var/log/dispatch-frontend/ using s6-log.
+
+exec /usr/bin/s6-setuidgid tradam \
+ /usr/bin/s6-log -b -d3 -- n10 s10000000 T /var/log/dispatch-frontend
diff --git a/packaging/s6/dispatch-frontend-log/type b/packaging/s6/dispatch-frontend-log/type
new file mode 100644
index 0000000..5883cff
--- /dev/null
+++ b/packaging/s6/dispatch-frontend-log/type
@@ -0,0 +1 @@
+longrun
diff --git a/packaging/s6/dispatch-frontend-srv/producer-for b/packaging/s6/dispatch-frontend-srv/producer-for
new file mode 100644
index 0000000..c29eb09
--- /dev/null
+++ b/packaging/s6/dispatch-frontend-srv/producer-for
@@ -0,0 +1 @@
+dispatch-frontend-log
diff --git a/packaging/s6/dispatch-frontend-srv/run b/packaging/s6/dispatch-frontend-srv/run
new file mode 100644
index 0000000..3411476
--- /dev/null
+++ b/packaging/s6/dispatch-frontend-srv/run
@@ -0,0 +1,25 @@
+#!/bin/sh
+# dispatch-frontend service — serves the built frontend assets as the `tradam` user.
+# stdout/stderr are piped to dispatch-frontend-log via the s6-rc pipeline.
+
+DISPATCH_CONF="/etc/dispatch/dispatch-frontend.conf"
+DISPATCH_DIR="/opt/dispatch"
+
+if [ -f "$DISPATCH_CONF" ]; then
+ set -a
+ . "$DISPATCH_CONF"
+ set +a
+fi
+
+cd "$DISPATCH_DIR" || exit 1
+
+exec 2>&1
+
+exec /usr/bin/s6-setuidgid tradam \
+ /usr/bin/env \
+ HOME=/home/tradam \
+ USER=tradam \
+ LOGNAME=tradam \
+ PATH=/usr/local/bin:/usr/bin:/bin \
+ PORT="${PORT:-18391}" \
+ /usr/bin/bun packages/frontend/serve.ts
diff --git a/packaging/s6/dispatch-frontend-srv/type b/packaging/s6/dispatch-frontend-srv/type
new file mode 100644
index 0000000..5883cff
--- /dev/null
+++ b/packaging/s6/dispatch-frontend-srv/type
@@ -0,0 +1 @@
+longrun