Browse Source

docs(plan): Debian packaging implementation plan (plan 3 of 3)

Fszontagh 1 month ago
parent
commit
467eb9c52f
1 changed files with 448 additions and 0 deletions
  1. 448 0
      docs/superpowers/plans/2026-05-31-smartbotic-vectorapi-packaging.md

+ 448 - 0
docs/superpowers/plans/2026-05-31-smartbotic-vectorapi-packaging.md

@@ -0,0 +1,448 @@
+# smartbotic-vectorapi Packaging Implementation Plan (Plan 3 of 3)
+
+> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:subagent-driven-development. Implement task-by-task. Steps use `- [ ]`. Backend + web UI are on `main` (pushed). The DB (`smartbotic-database` 2.3.x) is installed locally and published to `repository.smartbotics.ai`.
+
+**Goal:** Produce two Debian packages — `smartbotic-vectorapi` (the server binary + systemd unit + config conffile + `openapi.json`/`llms.txt`) and `smartbotic-vectorapi-webui` (the built React assets) — for **Debian 13 (trixie)** via Docker, plus a `--local` native build for dev/testing on this box, and publish via the existing `/data/smartbotics-deb-repo` flow.
+
+**Architecture:** Mirror `smartbotic-database/packaging` (the sibling project, `/usr` layout, dedicated system user, `Type=notify` unit) and `callerai/packaging` (separate `-webui` package, Docker base+build images, `dependencies.conf`). `packaging/build.sh` orchestrates: `--local` (native CMake build → `create-debs.sh` → `dist/local/`) and the default Docker Debian-13 build (`Dockerfile.base` + `Dockerfile.build` → `dist/debian13/`). The Debian-13 base image adds the smartbotics apt repo (auth from `/data/smartbotics-deb-repo/.env`) to install `libsmartbotic-db-client-dev` (2.3.x) and Node for the web UI build.
+
+**Layout produced by the packages:**
+- `/usr/bin/smartbotic-vectorapi`
+- `/etc/smartbotic-vectorapi/config.json` (dpkg conffile)
+- `/lib/systemd/system/smartbotic-vectorapi.service`
+- `/usr/share/smartbotic-vectorapi/{openapi.json,llms.txt}`
+- `/usr/share/smartbotic-vectorapi/webui/*` (the `-webui` package)
+- runtime: system user `smartbotic-vectorapi`, data dir `/var/lib/smartbotic-vectorapi`
+
+**Verification:** `--local` build → `dpkg -i` install → the service starts, logs a generated admin key once, `/healthz` + `/openapi.json` answer, and (with the `-webui` package) `/` serves the SPA. The Docker Debian-13 build produces the prod artifacts (best-effort here: needs Docker + repo creds; the local path is the hard gate).
+
+**Reference files (read them):** `/data/smartbotic-database/packaging/{build.sh,deb/create-debs.sh,deb/templates/control.server,deb/systemd/smartbotic-database.service,deb/scripts/server.postinst,Dockerfile.base,Dockerfile.build}` and `/data/callerai/packaging/{build.sh,deb/create-debs.sh,Dockerfile.base}`. `/data/smartbotics-deb-repo/scripts/{add-packages.sh,create-repo.sh,sync-repo.sh}` + `README.md`.
+
+---
+
+## File structure
+
+```
+packaging/
+├── build.sh                       # orchestrator: --local + Docker debian13
+├── Dockerfile.base                # debian:13 + build deps + smartbotics repo + node
+├── Dockerfile.build               # builds the project + runs create-debs.sh
+├── deb/
+│   ├── create-debs.sh             # builds the 2 .debs from a CMake build dir
+│   ├── templates/
+│   │   ├── control.server         # smartbotic-vectorapi (amd64)
+│   │   └── control.webui          # smartbotic-vectorapi-webui (all)
+│   ├── scripts/
+│   │   ├── server.postinst        # create user, dirs, enable+start
+│   │   ├── server.prerm           # stop on remove
+│   │   └── server.postrm          # purge: remove data dir + user
+│   └── systemd/
+│       └── smartbotic-vectorapi.service
+config/config.json                 # already exists (minimal bootstrap conffile)
+VERSION                            # already 0.1.0
+```
+
+---
+
+## Task P1: systemd unit, control templates, maintainer scripts
+
+**Files:** create `packaging/deb/systemd/smartbotic-vectorapi.service`, `packaging/deb/templates/control.server`, `packaging/deb/templates/control.webui`, `packaging/deb/scripts/server.postinst`, `server.prerm`, `server.postrm`.
+
+- [ ] **Step 1: `packaging/deb/systemd/smartbotic-vectorapi.service`**
+
+```ini
+[Unit]
+Description=Smartbotic VectorAPI Service
+Documentation=https://git.smartbotics.ai/fszontagh/smartbotic-vectorapi
+After=network-online.target smartbotic-database.service
+Wants=network-online.target
+
+[Service]
+# The binary calls sd_notify(READY=1) after the HTTP listener is up (when built
+# with libsystemd). Type=notify makes dependents block until we're actually
+# serving. If built without libsystemd, sd_notify is a no-op — in that case
+# change Type to "exec".
+Type=notify
+NotifyAccess=main
+User=smartbotic-vectorapi
+Group=smartbotic-vectorapi
+ExecStart=/usr/bin/smartbotic-vectorapi --config /etc/smartbotic-vectorapi/config.json
+Restart=on-failure
+RestartSec=3
+WorkingDirectory=/var/lib/smartbotic-vectorapi
+LimitNOFILE=65536
+
+# Secrets (optional): seed the admin key / OpenAI key on first boot.
+EnvironmentFile=-/etc/smartbotic-vectorapi/vectorapi.env
+
+# Security hardening (mirrors callerai-webapi). Outbound network is allowed
+# (needed for OpenAI embeddings); only writes to the data dir are permitted.
+NoNewPrivileges=true
+ProtectSystem=strict
+ProtectHome=true
+PrivateTmp=true
+PrivateDevices=true
+ProtectKernelTunables=true
+ProtectKernelModules=true
+ProtectControlGroups=true
+RestrictRealtime=true
+RestrictSUIDSGID=true
+RestrictNamespaces=true
+LockPersonality=true
+ReadWritePaths=/var/lib/smartbotic-vectorapi
+SystemCallFilter=@system-service
+SystemCallErrorNumber=EPERM
+
+[Install]
+WantedBy=multi-user.target
+```
+
+- [ ] **Step 2: `packaging/deb/templates/control.server`**
+
+```
+Package: smartbotic-vectorapi
+Version: {{VERSION}}
+Architecture: amd64
+Maintainer: Ferenc Szontagh <ferenc.szontagh@smartbotics.ai>
+Description: Smartbotic VectorAPI — REST API for RAG vectors and JSON
+ General-purpose, client-agnostic REST API in front of smartbotic-database:
+ multi-project namespaces, multi-key per-project-grant authorization, OpenAI
+ embedding generation for RAG, similarity search, JSON document CRUD, and
+ DB-backed hot-reloaded settings. Primary client: n8n workflows.
+Homepage: https://smartbotics.ai
+Section: contrib/utils
+Priority: optional
+Depends: libsmartbotic-db-client (>= 2.3.0), libssl3t64, libsystemd0, libspdlog1.15
+Recommends: smartbotic-vectorapi-webui (= {{VERSION}})
+Installed-Size: {{INSTALLED_SIZE}}
+```
+> Note: gRPC/protobuf/abseil come transitively via `libsmartbotic-db-client`'s own Depends. The implementer MUST verify the binary's actual shared-lib needs with `ldd build/src/smartbotic-vectorapi | grep -iE 'not found'` (should be empty on a machine with the deps) and `ldd ... | grep -oE 'lib[a-z0-9_+-]+\.so[0-9.]*'` to confirm `libspdlog`'s exact soname on the build distro — adjust the `libspdlog1.15` token if the trixie soname differs. If spdlog turns out to be statically linked (header-only build), drop it from Depends.
+
+- [ ] **Step 3: `packaging/deb/templates/control.webui`**
+
+```
+Package: smartbotic-vectorapi-webui
+Version: {{VERSION}}
+Architecture: all
+Maintainer: Ferenc Szontagh <ferenc.szontagh@smartbotics.ai>
+Description: Smartbotic VectorAPI — web administration console
+ React-based admin console for smartbotic-vectorapi: login, project switching,
+ dashboard, collections, JSON document editor (Monaco, bundled offline), RAG
+ search tester, and project/key administration. Served by smartbotic-vectorapi.
+Homepage: https://smartbotics.ai
+Section: contrib/utils
+Priority: optional
+Depends: smartbotic-vectorapi (>= {{VERSION}})
+Installed-Size: {{INSTALLED_SIZE}}
+```
+
+- [ ] **Step 4: `packaging/deb/scripts/server.postinst`**
+
+```bash
+#!/bin/bash
+# postinst for smartbotic-vectorapi
+set -e
+
+CONFIG_FILE="/etc/smartbotic-vectorapi/config.json"
+DATA_DIR="/var/lib/smartbotic-vectorapi"
+
+case "$1" in
+    configure)
+        if ! getent group smartbotic-vectorapi >/dev/null 2>&1; then
+            addgroup --system smartbotic-vectorapi
+        fi
+        if ! getent passwd smartbotic-vectorapi >/dev/null 2>&1; then
+            adduser --system --ingroup smartbotic-vectorapi --home "$DATA_DIR" \
+                --shell /usr/sbin/nologin --gecos "Smartbotic VectorAPI Service" smartbotic-vectorapi
+        fi
+
+        install -d -o smartbotic-vectorapi -g smartbotic-vectorapi -m 0750 "$DATA_DIR"
+        install -d -o root -g smartbotic-vectorapi -m 0750 /etc/smartbotic-vectorapi
+        if [ -f "$CONFIG_FILE" ]; then
+            chown root:smartbotic-vectorapi "$CONFIG_FILE"
+            chmod 640 "$CONFIG_FILE"
+        fi
+
+        systemctl daemon-reload || true
+        if [ -z "$2" ]; then
+            systemctl enable smartbotic-vectorapi.service || true
+            systemctl start smartbotic-vectorapi.service || true
+        else
+            systemctl reset-failed smartbotic-vectorapi.service 2>/dev/null || true
+            systemctl restart smartbotic-vectorapi.service || true
+        fi
+        ;;
+    abort-upgrade|abort-remove|abort-deconfigure) ;;
+    *) echo "postinst called with unknown argument '$1'" >&2; exit 1 ;;
+esac
+exit 0
+```
+
+- [ ] **Step 5: `packaging/deb/scripts/server.prerm`**
+
+```bash
+#!/bin/bash
+set -e
+case "$1" in
+    remove|deconfigure)
+        systemctl stop smartbotic-vectorapi.service 2>/dev/null || true
+        systemctl disable smartbotic-vectorapi.service 2>/dev/null || true
+        ;;
+    upgrade|failed-upgrade) ;;
+esac
+exit 0
+```
+
+- [ ] **Step 6: `packaging/deb/scripts/server.postrm`**
+
+```bash
+#!/bin/bash
+set -e
+case "$1" in
+    purge)
+        rm -rf /var/lib/smartbotic-vectorapi
+        if getent passwd smartbotic-vectorapi >/dev/null 2>&1; then
+            deluser --system smartbotic-vectorapi 2>/dev/null || true
+        fi
+        if getent group smartbotic-vectorapi >/dev/null 2>&1; then
+            delgroup --system smartbotic-vectorapi 2>/dev/null || true
+        fi
+        ;;
+    remove|upgrade|disappear|failed-upgrade|abort-install|abort-upgrade) ;;
+esac
+systemctl daemon-reload 2>/dev/null || true
+exit 0
+```
+
+- [ ] **Step 7: Commit** `build(pkg): systemd unit, control templates, maintainer scripts`
+
+---
+
+## Task P2: `create-debs.sh`
+
+**Files:** create `packaging/deb/create-debs.sh` (executable). Mirror `smartbotic-database/packaging/deb/create-debs.sh` (read it) — same helpers (`calc_installed_size`, `build_deb` with `sed` placeholder substitution + perms + `dpkg-deb --build --root-owner-group`), but produce TWO packages.
+
+- [ ] **Step 1: write `packaging/deb/create-debs.sh`**
+
+```bash
+#!/usr/bin/env bash
+# create-debs.sh — build the smartbotic-vectorapi + -webui .deb packages
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SOURCE_DIR="${SOURCE_DIR:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
+BUILD_DIR="${BUILD_DIR:-$SOURCE_DIR/build}"
+OUTPUT_DIR="${OUTPUT_DIR:-$SOURCE_DIR/dist}"
+BUILD_DEB_REVISION="${BUILD_DEB_REVISION:-1}"
+
+TEMPLATE_DIR="$SCRIPT_DIR/templates"
+SCRIPTS_DIR="$SCRIPT_DIR/scripts"
+SYSTEMD_DIR="$SCRIPT_DIR/systemd"
+CONFIG_DIR="$SOURCE_DIR/config"
+API_DIR="$SOURCE_DIR/api"
+WEBUI_DIST="${WEBUI_DIST:-$BUILD_DIR/webui/dist}"
+
+VERSION="$(cat "$SOURCE_DIR/VERSION")"
+DEB_VERSION="${VERSION}-${BUILD_DEB_REVISION}"
+
+echo "=== smartbotic-vectorapi deb builder ==="
+echo "VERSION=$VERSION DEB_VERSION=$DEB_VERSION"
+echo "BUILD_DIR=$BUILD_DIR OUTPUT_DIR=$OUTPUT_DIR WEBUI_DIST=$WEBUI_DIST"
+mkdir -p "$OUTPUT_DIR"
+
+calc_installed_size() {
+    local staging_dir="$1" total=0
+    for d in "$staging_dir"/*/; do
+        [ "$(basename "$d")" = "DEBIAN" ] && continue
+        total=$(( total + $(du -sk "$d" | awk '{print $1}') ))
+    done
+    echo "$total"
+}
+
+build_deb() {
+    local pkg_name="$1" staging_dir="$2" arch="$3"
+    local installed_size; installed_size="$(calc_installed_size "$staging_dir")"
+    sed -i -e "s/{{VERSION}}/${DEB_VERSION}/g" -e "s/{{INSTALLED_SIZE}}/${installed_size}/g" "$staging_dir/DEBIAN/control"
+    find "$staging_dir" -type d -exec chmod 0755 {} +
+    find "$staging_dir/DEBIAN" -type f -exec chmod 0644 {} +
+    for f in postinst prerm postrm preinst; do
+        [ -f "$staging_dir/DEBIAN/$f" ] && chmod 0755 "$staging_dir/DEBIAN/$f"
+    done
+    local deb_file="${OUTPUT_DIR}/${pkg_name}_${DEB_VERSION}_${arch}.deb"
+    dpkg-deb --build --root-owner-group "$staging_dir" "$deb_file"
+    echo "  -> $deb_file"
+}
+
+# ---- Package 1: smartbotic-vectorapi (server) ----
+echo "--- smartbotic-vectorapi ---"
+PKG1="$(mktemp -d)"; trap 'rm -rf "$PKG1"' EXIT
+mkdir -p "$PKG1/DEBIAN" "$PKG1/usr/bin" "$PKG1/etc/smartbotic-vectorapi" \
+         "$PKG1/lib/systemd/system" "$PKG1/usr/share/smartbotic-vectorapi"
+
+[ -f "$BUILD_DIR/src/smartbotic-vectorapi" ] || { echo "ERROR: binary not found at $BUILD_DIR/src/smartbotic-vectorapi" >&2; exit 1; }
+cp "$BUILD_DIR/src/smartbotic-vectorapi" "$PKG1/usr/bin/smartbotic-vectorapi"
+chmod 0755 "$PKG1/usr/bin/smartbotic-vectorapi"
+cp "$CONFIG_DIR/config.json" "$PKG1/etc/smartbotic-vectorapi/config.json"
+cp "$SYSTEMD_DIR/smartbotic-vectorapi.service" "$PKG1/lib/systemd/system/"
+cp "$API_DIR/openapi.json" "$API_DIR/llms.txt" "$PKG1/usr/share/smartbotic-vectorapi/"
+cp "$TEMPLATE_DIR/control.server" "$PKG1/DEBIAN/control"
+cp "$SCRIPTS_DIR/server.postinst" "$PKG1/DEBIAN/postinst"
+cp "$SCRIPTS_DIR/server.prerm"    "$PKG1/DEBIAN/prerm"
+cp "$SCRIPTS_DIR/server.postrm"   "$PKG1/DEBIAN/postrm"
+printf '/etc/smartbotic-vectorapi/config.json\n' > "$PKG1/DEBIAN/conffiles"
+build_deb "smartbotic-vectorapi" "$PKG1" "amd64"
+
+# ---- Package 2: smartbotic-vectorapi-webui (static assets) ----
+echo "--- smartbotic-vectorapi-webui ---"
+PKG2="$(mktemp -d)"; trap 'rm -rf "$PKG1" "$PKG2"' EXIT
+mkdir -p "$PKG2/DEBIAN" "$PKG2/usr/share/smartbotic-vectorapi/webui"
+if [ -d "$WEBUI_DIST" ] && [ -n "$(ls -A "$WEBUI_DIST" 2>/dev/null)" ]; then
+    cp -a "$WEBUI_DIST"/. "$PKG2/usr/share/smartbotic-vectorapi/webui/"
+else
+    echo "ERROR: webui dist not found at $WEBUI_DIST (build with -DBUILD_WEBUI=ON)" >&2; exit 1
+fi
+cp "$TEMPLATE_DIR/control.webui" "$PKG2/DEBIAN/control"
+build_deb "smartbotic-vectorapi-webui" "$PKG2" "all"
+
+echo "=== Done. Packages in $OUTPUT_DIR ==="
+ls -lh "$OUTPUT_DIR"/*.deb
+```
+
+- [ ] **Step 2: Commit** `build(pkg): create-debs.sh (server + webui packages)`
+
+---
+
+## Task P3: `build.sh` + Dockerfiles
+
+**Files:** create `packaging/build.sh` (exec), `packaging/Dockerfile.base`, `packaging/Dockerfile.build`. Mirror `smartbotic-database/packaging/{build.sh,Dockerfile.base,Dockerfile.build}` (read them) and `callerai/packaging/Dockerfile.base` (for the apt-repo-add pattern).
+
+- [ ] **Step 1: `packaging/build.sh`** — same flag set/structure as `smartbotic-database/packaging/build.sh`: `--local` (native), `--install` (local only), `--deb-revision N`, `--rebuild-base`, `--no-cache`, `--repo`, `--suite NAME`, `--sync`. 
+  - `build_local()`: `cmake -B "$PROJECT_DIR/build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_WEBUI=ON -DBUILD_TESTS=OFF -S "$PROJECT_DIR"` → `cmake --build` → `OUTPUT_DIR=dist/local BUILD_DEB_REVISION=$REV packaging/deb/create-debs.sh` → optional `--install` (`sudo dpkg -i dist/local/smartbotic-vectorapi_*.deb dist/local/smartbotic-vectorapi-webui_*.deb || sudo apt-get install -f -y`).
+  - `build_docker()`: build `Dockerfile.base` (tag `smartbotic-vectorapi-build-base:debian13`, passing `--build-arg REPO_USER=.. --build-arg REPO_PASS=..` read from `${DEB_REPO_DIR:-/data/smartbotics-deb-repo}/.env`), then `docker buildx build -f Dockerfile.build --build-arg BASE_IMAGE=... --build-arg BUILD_VERSION=$VERSION --build-arg BUILD_DEB_REVISION=$REV --target packages --output type=local,dest=dist/debian13 .`.
+  - repo ops (when `--repo`): call `$DEB_REPO_DIR/scripts/add-packages.sh "$OUTPUT_DIR"`, then `create-repo.sh --suite "$SUITE"`, then (if `--sync`) `sync-repo.sh`.
+  Base the script almost verbatim on `smartbotic-database/packaging/build.sh`, changing names and the cmake invocation (add `-DBUILD_WEBUI=ON -DBUILD_TESTS=OFF`) and the install package list (2 debs).
+
+- [ ] **Step 2: `packaging/Dockerfile.base`** (Debian 13 build base)
+
+```dockerfile
+FROM debian:13-slim
+
+ARG REPO_USER
+ARG REPO_PASS
+ENV DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+        ca-certificates curl gnupg cmake ninja-build g++ pkg-config \
+        nlohmann-json3-dev libspdlog-dev libssl-dev libsystemd-dev git \
+    && rm -rf /var/lib/apt/lists/*
+
+# Node 22 for the web UI build (vite 7 needs node >=20.19/22).
+RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
+    && apt-get install -y --no-install-recommends nodejs \
+    && rm -rf /var/lib/apt/lists/*
+
+# Smartbotics apt repo — provides libsmartbotic-db-client-dev (2.3.x).
+RUN curl -fsSL https://repository.smartbotics.ai/smartbotics-repo.gpg \
+        | gpg --dearmor -o /usr/share/keyrings/smartbotics-repo.gpg \
+    && printf 'machine repository.smartbotics.ai\nlogin %s\npassword %s\n' "$REPO_USER" "$REPO_PASS" \
+        > /etc/apt/auth.conf.d/smartbotics.conf \
+    && chmod 600 /etc/apt/auth.conf.d/smartbotics.conf \
+    && echo "deb [signed-by=/usr/share/keyrings/smartbotics-repo.gpg] https://repository.smartbotics.ai trixie main" \
+        > /etc/apt/sources.list.d/smartbotics.list \
+    && apt-get update \
+    && apt-get install -y --no-install-recommends libsmartbotic-db-client-dev \
+    && rm -rf /var/lib/apt/lists/*
+```
+
+- [ ] **Step 3: `packaging/Dockerfile.build`**
+
+```dockerfile
+ARG BASE_IMAGE=smartbotic-vectorapi-build-base:debian13
+FROM ${BASE_IMAGE} AS builder
+ARG BUILD_VERSION=0.1.0
+ARG BUILD_DEB_REVISION=1
+WORKDIR /build
+COPY . /build/
+RUN cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_WEBUI=ON -DBUILD_TESTS=OFF -S . \
+    && cmake --build build -j"$(nproc)"
+RUN OUTPUT_DIR=/packages BUILD_DEB_REVISION=${BUILD_DEB_REVISION} packaging/deb/create-debs.sh
+
+FROM scratch AS packages
+COPY --from=builder /packages/*.deb /
+```
+
+- [ ] **Step 4: Commit** `build(pkg): build.sh orchestrator + Debian-13 Docker base/build images`
+
+---
+
+## Task P4: Local build + install + service smoke (the hard gate)
+
+This runs on THIS machine (Ubuntu 26.04 → produces a dev/test deb; the prod artifact is the Docker/trixie build in P5). Requires sudo for install.
+
+- [ ] **Step 1: Local build of both debs**
+
+Run: `cd /data/smartbotic-vectorapi && ./packaging/build.sh --local`
+Expected: configures with `-DBUILD_WEBUI=ON`, builds the binary + webui dist, runs `create-debs.sh`, and emits `dist/local/smartbotic-vectorapi_0.1.0-1_amd64.deb` + `dist/local/smartbotic-vectorapi-webui_0.1.0-1_all.deb`.
+
+- [ ] **Step 2: Inspect the packages**
+
+Run: `dpkg-deb -I dist/local/smartbotic-vectorapi_*.deb && dpkg-deb -c dist/local/smartbotic-vectorapi_*.deb | grep -E 'bin/|etc/|systemd|share'` and the same `-c` for the `-webui` deb (should list `usr/share/smartbotic-vectorapi/webui/index.html` + assets incl. the monaco worker chunks). Confirm control Depends + conffiles look right.
+
+- [ ] **Step 3: Install + verify the service** (needs sudo — if sudo prompts in this env, the operator runs these via `! sudo ...`; otherwise run directly)
+
+```bash
+sudo dpkg -i dist/local/smartbotic-vectorapi_*.deb dist/local/smartbotic-vectorapi-webui_*.deb || sudo apt-get install -f -y
+systemctl status smartbotic-vectorapi --no-pager | head -8
+journalctl -u smartbotic-vectorapi --no-pager -n 20 | grep -i "Generated initial admin API key" || true   # the one-time key
+curl -s localhost:8080/healthz; echo
+curl -s localhost:8080/openapi.json | head -c 60; echo
+curl -s -o /dev/null -w "SPA: %{http_code}\n" localhost:8080/      # webui package serves the SPA
+```
+Expected: service `active (running)`; the journal shows the generated admin key once; `/healthz` → `{"status":"ok"}`; `/openapi.json` starts with the spec; `/` → 200 (SPA from the `-webui` package). **Note:** the service's `--webui-dir` default is `/usr/share/smartbotic-vectorapi/webui` (compiled default), which the `-webui` package populates — confirm `config.json` / the binary default points there (it does: `Config::webuiDir` default). If the unit serves the SPA only when the webui package is installed, that's correct.
+
+> If `Type=notify` causes a startup timeout (binary built without libsystemd → never sends READY), edit the unit to `Type=exec` and document it. Verify the local binary has systemd: `ldd build/src/smartbotic-vectorapi | grep -i systemd` (should show libsystemd if HAVE_SYSTEMD).
+
+- [ ] **Step 4: Clean up the smoke** — `sudo apt-get remove -y smartbotic-vectorapi smartbotic-vectorapi-webui` (or leave installed for ongoing dev use; ask). Document outcome.
+
+- [ ] **Step 5: Commit** any fixes made during the smoke (e.g. unit `Type`, Depends soname). Message: `build(pkg): local deb build verified (install + service smoke)`.
+
+---
+
+## Task P5: Docker Debian-13 build (prod artifact)
+
+- [ ] **Step 1:** Ensure `.dockerignore` excludes `build/`, `webui/node_modules`, `dist/`, `.git` (create `/data/smartbotic-vectorapi/.dockerignore`).
+- [ ] **Step 2:** Run `cd /data/smartbotic-vectorapi && ./packaging/build.sh` (default = Docker Debian-13). It builds the base image (installing `libsmartbotic-db-client-dev` from `repository.smartbotics.ai` using creds from `/data/smartbotics-deb-repo/.env`) then the build image, emitting `dist/debian13/*.deb`.
+  - Expected: `dist/debian13/smartbotic-vectorapi_0.1.0-1_amd64.deb` + `..._webui_0.1.0-1_all.deb`, built against trixie's `libsmartbotic-db-client` 2.3.x and trixie shared-lib sonames.
+  - **If the smartbotics repo creds/connectivity aren't available in this environment**, this step is BLOCKED on infra (not code): report DONE_WITH_CONCERNS, having confirmed the Dockerfiles are correct by other means (e.g. `docker build` of the base up to the apt step). The `--local` debs from P4 remain the verified deliverable.
+- [ ] **Step 3:** Inspect: `dpkg-deb -I dist/debian13/smartbotic-vectorapi_*.deb` — confirm Depends resolved for trixie (esp. the `libspdlog` soname on trixie).
+- [ ] **Step 4: Commit** `build(pkg): Debian-13 Docker build produces prod debs`.
+
+---
+
+## Task P6: Publish to the smartbotics apt repository (operator step)
+
+- [ ] **Step 1:** Stage + sign + (optionally) upload:
+```bash
+cd /data/smartbotic-vectorapi
+./packaging/build.sh --repo --suite trixie            # build (Docker) + add to repo + create-repo
+# or, to also upload:
+./packaging/build.sh --repo --suite trixie --sync
+```
+This calls `/data/smartbotics-deb-repo/scripts/{add-packages.sh,create-repo.sh --suite trixie,sync-repo.sh}`. Publishing uploads to `repository.smartbotics.ai` — an outward-facing action; only run with explicit operator confirmation.
+- [ ] **Step 2:** Verify on a client: `apt update && apt-cache policy smartbotic-vectorapi` shows the published version.
+
+---
+
+## Packaging definition of done
+
+- `./packaging/build.sh --local` produces both `.deb`s in `dist/local/`.
+- Installing them yields a running `smartbotic-vectorapi` service that logs a one-time admin key, answers `/healthz`+`/openapi.json`, and serves the SPA at `/`.
+- `./packaging/build.sh` (Docker) produces Debian-13 `.deb`s in `dist/debian13/` (or DONE_WITH_CONCERNS if repo creds are unavailable in-env).
+- The repo-publish path (`--repo --suite trixie [--sync]`) is wired to `/data/smartbotics-deb-repo` (run on operator confirmation).
+
+## Self-review
+
+**Coverage:** systemd unit + user + data dir (P1) · two packages server+webui (P1 control, P2 create-debs) · conffile config (P2) · openapi/llms shipped (P2) · orchestrator + Docker trixie (P3) · local verify (P4) · prod artifact (P5) · publish (P6). Mirrors `smartbotic-database` (/usr layout, Type=notify, system user) and `callerai` (separate -webui, Docker base+build, repo-add).
+
+**Risks flagged inline:** `libspdlog` soname may differ on trixie (verify via `ldd`/`dpkg-deb -I`); `Type=notify` requires the binary built with libsystemd (fallback `Type=exec`); Docker P5 depends on smartbotics-repo creds/connectivity; sudo install in P4 may need the operator.
+