sap-drawio: add 'Sketch to SAP BTP' custom-store app (FastAPI+React, single-port, seed-on-first-run SQLite, exposed via auto-SSO)

This commit is contained in:
2026-07-22 09:01:10 -07:00
parent e35d5589b6
commit 5f6499125a
6 changed files with 244 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
# sap-drawio — Build Files
Image source repo (Dockerfile lives there, NOT in this store folder):
`~/projects/sap-architecture-diagrams` (git remote `git.alexzaw.dev/alexz/sap-architecture-diagrams`).
## What the image is
A single multi-stage image, `git.alexzaw.dev/alexz/sap-drawio:latest`:
1. **Builder stage** (`node:20-bookworm-slim`) — builds the React (CRA + CRACO,
Yarn Classic 1.22.22) frontend with `REACT_APP_BACKEND_URL=""` so all API
calls are same-origin (`/api`), working from any host/domain.
2. **Runtime stage** (`python:3.12-slim`) — installs `backend/requirements.txt`,
copies `backend/` to `/app/backend` and the built UI to `/app/frontend/build`
(FastAPI/uvicorn serves both from one process on port 8011), bakes the
12 MB SQLite catalog seed to `/seed/app.db`, and runs as root (homelab —
avoids the non-root volume-permission trap).
## How to build
```bash
cd /etc/runtipi/repos/runtipi/apps/sap-drawio/build
./build.sh
```
Builds and pushes `git.alexzaw.dev/alexz/sap-drawio:latest`. Override
`IMAGE_NAME` / `IMAGE_TAG` / `SRC` env vars if needed. To build without
pushing, run the `docker build` line directly from `$SRC`.
## Seed-on-first-run behavior
`docker/entrypoint.sh` (in the source repo) copies `/seed/app.db`
`/data/app.db` only if `/data/app.db` is absent, then execs uvicorn. This
means:
- First install: the persistent volume (`${APP_DATA_DIR}/data` on the host)
gets the baked catalog (icons/services) as its starting point.
- Every subsequent `app update`/restart: the existing `/data/app.db` (with any
saved conversions, added logos, etc.) is left untouched — the image rebuild
never overwrites live data.
## LLM dependency
Calls an Ollama-compatible host at `OLLAMA_URL` (form field, defaults to
`http://192.168.0.32:11434`, this homeserver's local `ollama-nvidia` app).
Models (`VISION_MODEL`, `VALIDATOR_MODEL`) are also form fields so they can be
changed without a rebuild. `qwen3-vl:8b` / `gemma4:31b` must exist on the
Ollama host for the defaults to work.
## Architecture note
**amd64 only.** No multi-arch build — matches the host architecture; not
needed for this homelab deployment.
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
set -euo pipefail
# Builds (and, unless SKIP_PUSH=1, pushes) the sap-drawio image from the app
# source repo. Both the floating "latest" tag and the version-pinned tag
# (kept in sync with config.json "version") are built and pushed.
#
# Before building, this script (re)generates a deterministic, catalog-only
# seed DB (backend/data/seed.db) from the developer's live app.db. This is
# REQUIRED — the live app.db is WAL-mode with uncommitted -wal data and a
# full conversion/history table, neither of which may ever ship in the image
# (see sap-drawio Phase-1 code review FIX B1 / M2). The live app.db is never
# opened directly by this script; it is only ever `cp`-copied at the OS
# level, so it cannot be mutated by an incidental SQLite WAL checkpoint.
#
# Env overrides:
# IMAGE_NAME default git.alexzaw.dev/alexz/sap-drawio
# IMAGE_TAG default latest
# VERSION_TAG default 1.0.0 (must match config.json "version")
# SRC default ~/projects/sap-architecture-diagrams
# SKIP_PUSH set to 1 to build both tags locally only (no push) — used
# for pre-push smoke-testing.
IMAGE_NAME="${IMAGE_NAME:-git.alexzaw.dev/alexz/sap-drawio}"
IMAGE_TAG="${IMAGE_TAG:-latest}"
VERSION_TAG="${VERSION_TAG:-1.0.0}"
SRC="${SRC:-$HOME/projects/sap-architecture-diagrams}"
SKIP_PUSH="${SKIP_PUSH:-0}"
cd "$SRC"
echo "== [1/3] Generating deterministic catalog-only seed DB =="
SEED_WORK="$(mktemp -d)"
trap 'rm -rf "$SEED_WORK"' EXIT
# Copy the live dev DB (main + WAL + SHM, if present) at the OS level ONLY.
# backend/data/app.db is NEVER opened directly by this script.
cp backend/data/app.db "$SEED_WORK/app.db"
[ -f backend/data/app.db-wal ] && cp backend/data/app.db-wal "$SEED_WORK/app.db-wal"
[ -f backend/data/app.db-shm ] && cp backend/data/app.db-shm "$SEED_WORK/app.db-shm"
# .backup against the COPY (never the original) merges any committed WAL
# frames into a single consistent snapshot file.
sqlite3 "$SEED_WORK/app.db" ".backup '$SEED_WORK/seed.db'"
# Clear ONLY the conversion/history table (db.py: insert_conversion() writes
# to `conversions`). Every catalog/icon/service/brand/override row is kept.
sqlite3 "$SEED_WORK/seed.db" "DELETE FROM conversions;"
sqlite3 "$SEED_WORK/seed.db" "VACUUM;"
echo "-- Catalog row counts (source vs seed) --"
for t in icon_assets sap_services label_overrides schema_meta; do
src_count=$(sqlite3 "$SEED_WORK/app.db" "SELECT COUNT(*) FROM $t;")
seed_count=$(sqlite3 "$SEED_WORK/seed.db" "SELECT COUNT(*) FROM $t;")
printf ' %-16s source=%-8s seed=%-8s\n' "$t" "$src_count" "$seed_count"
if [ "$src_count" != "$seed_count" ]; then
echo "ERROR: catalog table $t row count mismatch (source=$src_count seed=$seed_count)" >&2
exit 1
fi
done
conv_count=$(sqlite3 "$SEED_WORK/seed.db" "SELECT COUNT(*) FROM conversions;")
echo " conversions seed=$conv_count (must be 0)"
if [ "$conv_count" != "0" ]; then
echo "ERROR: seed.db still has $conv_count conversion/history rows" >&2
exit 1
fi
cp "$SEED_WORK/seed.db" backend/data/seed.db
echo "Seed written to backend/data/seed.db ($(du -h backend/data/seed.db | cut -f1))"
echo "== [2/3] Building image (tags: ${IMAGE_TAG}, ${VERSION_TAG}) =="
sudo docker build "$@" -t "${IMAGE_NAME}:${IMAGE_TAG}" -t "${IMAGE_NAME}:${VERSION_TAG}" .
if [ "$SKIP_PUSH" = "1" ]; then
echo "== [3/3] SKIP_PUSH=1 — not pushing. Built locally: ${IMAGE_NAME}:${IMAGE_TAG}, ${IMAGE_NAME}:${VERSION_TAG} =="
exit 0
fi
echo "== [3/3] Pushing image =="
sudo docker push "${IMAGE_NAME}:${IMAGE_TAG}"
sudo docker push "${IMAGE_NAME}:${VERSION_TAG}"