diff --git a/apps/sap-drawio/build/README.md b/apps/sap-drawio/build/README.md new file mode 100644 index 0000000..f70235b --- /dev/null +++ b/apps/sap-drawio/build/README.md @@ -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. diff --git a/apps/sap-drawio/build/build.sh b/apps/sap-drawio/build/build.sh new file mode 100644 index 0000000..e759a1b --- /dev/null +++ b/apps/sap-drawio/build/build.sh @@ -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}" diff --git a/apps/sap-drawio/config.json b/apps/sap-drawio/config.json new file mode 100644 index 0000000..4db2ed4 --- /dev/null +++ b/apps/sap-drawio/config.json @@ -0,0 +1,49 @@ +{ + "name": "Sketch to SAP BTP", + "id": "sap-drawio", + "available": true, + "short_desc": "Turn a hand-drawn or Mermaid sketch into a polished SAP BTP draw.io diagram.", + "author": "alexz", + "port": 8011, + "categories": ["development", "utilities"], + "tipi_version": 1, + "version": "1.0.0", + "source": "https://git.alexzaw.dev/alexz/sap-architecture-diagrams", + "website": "https://git.alexzaw.dev/alexz/sap-architecture-diagrams", + "exposable": true, + "dynamic_config": true, + "supported_architectures": ["amd64"], + "min_tipi_version": "4.0.0", + "form_fields": [ + { + "type": "text", + "label": "Ollama URL", + "hint": "Ollama-compatible LLM host", + "required": true, + "env_variable": "OLLAMA_URL", + "default": "http://192.168.0.32:11434" + }, + { + "type": "text", + "label": "Vision model", + "required": true, + "env_variable": "VISION_MODEL", + "default": "qwen3-vl:8b" + }, + { + "type": "text", + "label": "Validator model", + "required": true, + "env_variable": "VALIDATOR_MODEL", + "default": "gemma4:31b" + }, + { + "type": "password", + "label": "Brandfetch API key (optional)", + "required": false, + "env_variable": "BRANDFETCH_API_KEY" + } + ], + "created_at": 1784733792011, + "updated_at": 1784733792011 +} diff --git a/apps/sap-drawio/docker-compose.json b/apps/sap-drawio/docker-compose.json new file mode 100644 index 0000000..75cdecb --- /dev/null +++ b/apps/sap-drawio/docker-compose.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://schemas.runtipi.io/dynamic-compose.json", + "schemaVersion": 2, + "services": [ + { + "name": "sap-drawio", + "image": "git.alexzaw.dev/alexz/sap-drawio:latest", + "isMain": true, + "internalPort": 8011, + "environment": [ + { "key": "TZ", "value": "${TZ}" }, + { "key": "OLLAMA_URL", "value": "${OLLAMA_URL}" }, + { "key": "VISION_MODEL", "value": "${VISION_MODEL}" }, + { "key": "VALIDATOR_MODEL", "value": "${VALIDATOR_MODEL}" }, + { "key": "BRANDFETCH_API_KEY", "value": "${BRANDFETCH_API_KEY}" }, + { "key": "SQLITE_DB_PATH", "value": "/data/app.db" }, + { "key": "CORS_ORIGINS", "value": "https://sap-drawio.alexzaw.dev" } + ], + "volumes": [ + { "hostPath": "${APP_DATA_DIR}/data", "containerPath": "/data", "readOnly": false } + ], + "healthCheck": { + "test": "python -c \"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8011/api/health', timeout=5).status==200 else 1)\"", + "interval": "30s", + "timeout": "10s", + "retries": 3, + "startPeriod": "40s" + } + } + ] +} diff --git a/apps/sap-drawio/metadata/description.md b/apps/sap-drawio/metadata/description.md new file mode 100644 index 0000000..79205c4 --- /dev/null +++ b/apps/sap-drawio/metadata/description.md @@ -0,0 +1,32 @@ +# Sketch to SAP BTP + +Turn a rough architecture idea into a polished **SAP BTP architecture diagram** +in seconds. Feed it either a hand-drawn/whiteboard **sketch (image)** or a +**Mermaid diagram**, and it produces a clean draw.io XML diagram (plus an +inline SVG preview) styled in SAP's official BTP solution-diagram visual +language — complete with an LLM-generated architecture review calling out +gaps, risks, and suggestions. + +## Two ways in + +- **Image sketch** — upload a photo or screenshot of a whiteboard sketch; the + vision model reads the shapes, labels, and connections and maps them to the + matching SAP BTP services/icons. +- **Mermaid** — paste a Mermaid diagram and get the same SAP BTP-styled + draw.io output without needing to draw anything by hand. + +Every conversion is saved to a local history so past diagrams and their +review notes can be revisited later, exported again, or refined further. + +## Access note + +This app has **no built-in authentication** of its own — access control is +handled at the edge (Authentik SSO via the homeserver's forward-auth gate). +Anyone who reaches the app through the gate can drive the full pipeline. + +## Dependencies + +Requires a reachable Ollama-compatible LLM endpoint (configured via the +"Ollama URL" field) with the configured vision and validator models +available. An optional Brandfetch API key enables looking up third-party +brand logos for non-SAP components in a diagram. diff --git a/apps/sap-drawio/metadata/logo.jpg b/apps/sap-drawio/metadata/logo.jpg new file mode 100644 index 0000000..0d9033e Binary files /dev/null and b/apps/sap-drawio/metadata/logo.jpg differ