Skip to content

Add Synaplan - #5962

Open
cristiangrxs wants to merge 1 commit into
getumbrel:masterfrom
metadist:synaplan
Open

Add Synaplan#5962
cristiangrxs wants to merge 1 commit into
getumbrel:masterfrom
metadist:synaplan

Conversation

@cristiangrxs

Copy link
Copy Markdown

Type

New app

App

App ID: synaplan
Upstream project: https://github.com/metadist/synaplan
Version: 4.0.15

Summary

Synaplan is a self-hosted AI workspace: chat against the provider of your choice,
upload documents and search them by meaning, keep long-term memories, and publish
embeddable chat widgets for your own websites. It is AGPL-3.0, developed by
Metadist, and this package is maintained by us as the upstream project.

The package runs nine services: three app roles that share one image (web,
worker, scheduler), MariaDB, Redis, Centrifugo for realtime, Apache Tika for
document text extraction, and Qdrant for vector search.

Cloud AI only. The reference deployment ships optional Ollama and Whisper
services; they are deliberately absent here. Local inference needs far more memory
than a typical Umbrel device has, and umbrelOS already offers Ollama as its own
app. After the first login the user adds a provider key under Admin > AI Providers.
The manifest description says so near the top, because it is required before the
first answer.

Verification

Umbrel testing performed:

Installed from a clean umbrelOS 1.7.4 instance with this exact package, running
the pinned digest unmodified (docker inspect confirms
ghcr.io/metadist/synaplan@sha256:0aa4123d5e74d683a2f26e904e8f1421629575030f62ab2335fcf4ec93230d7b).

  • All nine containers reach healthy; apps.state.query reports ready.
  • The app opens at its app_proxy port and umbrelOS shows the
    deterministicPassword credentials before launch.
  • Login over plain HTTP works and the session survives the next request:
    Set-Cookie carries HttpOnly; SameSite=Strict and no Secure. This was a real
    blocker — up to 4.0.13 Synaplan marked its auth cookies Secure whenever
    APP_ENV=prod, so on umbrelOS the user logged in and was anonymous again one
    request later. 4.0.14 derives the flag from the APP_URL scheme instead.
  • Adding the first provider key makes the app usable immediately, with no restart:
    the setup banner clears and the first message is answered. This was the second
    blocker, and the reason the pin is 4.0.15 rather than 4.0.14. Fresh accounts are
    created with per-user model bindings that point at a recommended provider, which
    on an App Store install is chosen before any key exists — so the bootstrap
    administrator kept being routed at a provider they never configured, and the
    composer stayed locked behind "no AI provider connected" no matter which key they
    added. 4.0.15 falls back to the working global default and reactivates the stored
    preference once its provider has credentials.
  • Auth boundary with PROXY_AUTH_ADD: "false": an admin endpoint answers 401
    without a session and 200 with one; /widget.js stays public with
    Access-Control-Allow-Origin: *.
  • /connection/websocket upgrades with 101 Switching Protocols through
    app_proxy and Caddy.
  • Document upload: text extracted through Tika, file visible on the host bind
    mount and from the worker container.
  • Restart through Umbrel: all containers healthy again, login works, the uploaded
    document is still listed, and the generated secrets file is byte-identical.
  • Cross-device restore simulation: after replacing the device seed the secrets file
    is byte-identical, the app still opens its database, the original admin password
    still works, and the uploaded document is still there (see Notes).

Environment tested:

  • Umbrel device
  • Local umbrelOS test environment
  • Not runtime tested

Architecture tested:

  • amd64
  • arm64

amd64 is published in the same manifest list and built and tested in our CI, but
the runtime install above was done on arm64 only.

Known lint warnings or caveats:

npm run lint:apps -- synaplan --check-images reports no issues.

Notes

No host access. No privileged mode, no host networking, no Docker socket, no
device or host mounts, no permissions:, no dependencies:. All state lives under
${APP_DATA_DIR}/data/....

PROXY_AUTH_ADD: "false" is deliberate. Synaplan has its own multi-user
accounts with roles, and three of its surfaces cannot carry an Umbrel session
cookie at all: the chat widget embedded on third-party websites, the MCP endpoint
with its OAuth discovery documents, and mobile clients authenticating with a bearer
token. A path whitelist would have to cover most of the API, so the app's own auth
is the boundary — verified above.

Default credentials. admin@umbrel.local with deterministicPassword: true.
BOOTSTRAP_ADMIN_PASSWORD is wired to ${APP_PASSWORD} and read only while no
administrator exists, so a changed value cannot lock anyone out.

Secrets are derived once and then persisted. exports.sh derives six secrets
with derive_entropy and keeps them in data/secrets.env (mode 0600), which wins
on every later start. The reason is a specific failure mode: an umbrelOS backup
contains home and app-data only, while the seed lives in db/umbrel-seed and is
regenerated at random when missing. Restoring this app onto a different device would
otherwise derive a new database password while the restored data directory still
expects the old one — a permanent lockout with no user-visible cause. Happy to drop
this and rely on derive_entropy alone if you prefer.

One cosmetic consequence remains and we could not avoid it: after such a restore
Umbrel derives the password it displays from the new device's seed, while the
account keeps the one it was created with. The data is intact; only the displayed
credential is wrong.

Two environment variables exist for the shared network. Every app on a device
shares umbrel_main_network, so bare service names like redis or centrifugo are
ambiguous between apps. All internal addresses use the injected container names. Two
of them are not plain application config, so upstream gained
REALTIME_UPSTREAM_ADDR (the Caddy upstream for /connection/*) and
SYNAPLAN_WEB_HEALTH_URL (what the worker and scheduler block on) for this purpose.

LAN-only limitations, all documented in the manifest description or expected on
an Umbrel: WhatsApp media delivery and Google/GitHub/Apple sign-in need a publicly
reachable instance; a widget embedded on a public website cannot open its realtime
connection because REALTIME_ALLOWED_ORIGINS is pinned to the Umbrel origin;
reaching the app by IP or through a Tailscale hostname keeps everything working
except that realtime channel.

Screenshots and logo

Not committed — gallery is [] as the packaging guide asks, so these are review
material for you to work from.

Logo (512×512 PNG, source SVG)

Synaplan logo

Screenshots (1600×1000 at 2×, light theme, captured from a running install)

  1. Chat answering from the user's own documents

Chat answering from uploaded documents

  1. Semantic search across the uploaded knowledge base

Semantic document search with similarity scores

  1. Chat widgets, each with its own assistant

Chat widget overview

  1. A website visitor's conversation, with handover to a human

Widget conversation with human handoff

  1. Long-term memories the assistant recalls across conversations

Memory list

cristiangrxs added a commit to metadist/synaplan that referenced this pull request Aug 7, 2026
The package went in at getumbrel/umbrel-apps#5962; the placeholder can
now point at the real pull request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant