Commit graph

36 commits

Author SHA1 Message Date
Anish Sarkar
259ffbfcf3 docs(docker): the Docker proposal becomes architecture/docker.md
The CPU image is built, so what is true now moves from the proposal into
docs/architecture/docker.md, written from the code: the container, the
entries, Caddy and HTTPS, the frontend without Electron, sign-ins, the
image, Compose, the dev stack, CI and releases, with what is not built as
Known gaps. The GPU images, not built, stay a proposal, as
proposals/docker-gpu.md. Every link and code comment that named the
proposal points at the new doc.
2026-10-28 18:28:54 +05:30
Anish Sarkar
4bd584f869 feat(docker): every instance has a password, set up on first visit, and opens to the server's networks
Sign-in is always on. Whoever opens a new instance first creates its
password, from any browser, with one field and an eye, as Immich and Open
WebUI have their first account. SURFSENSE_PASSWORD presets it, and the app
shows a password set there locked; without the variable it becomes the
app's to change. SURFSENSE_AUTH=none turns sign-in off for a server whose
own proxy or VPN decides who reaches it.

Compose now publishes on every network the server is on, so phones and
laptops reach a fresh install at once. docs/architecture/docker-access.md
holds every case, from a home server to a public domain.
2026-10-28 18:12:19 +05:30
Anish Sarkar
31032e2bdc feat(frontend): the dev server's tab says SurfSense Dev and wears the DEV icon
The SPA gets a favicon, the app's icon at 16, 32 and 48 px, so the Docker
image's tab shows SurfSense instead of a globe. Vite's dev server swaps in the
DEV icon and the title SurfSense Dev, as Electron's dev build names itself, so
the dev stack's tab is told apart from an install's.
2026-10-28 04:52:41 +05:30
Anish Sarkar
e5cbf7d81a feat(docker): a dev stack runs the image with the checkout mounted in
docker/surfsense/dev/compose.yml runs the image's new `app` stage, everything
but the SPA, with the source mounted over it, on 127.0.0.1:4930 beside a real
install on 4929. Vite runs as its own service and Caddy proxies the SPA's route
to it, so the frontend hot-reloads; under --watch a backend, sidecar, entry or
Caddy change restarts the app container in a few seconds instead of a rebuild.

The SPA's route moves into caddy/spa.caddy, the one file the dev stack replaces;
the shipped image is unchanged and passes image.test.ts.
2026-10-28 04:46:48 +05:30
Anish Sarkar
9bd794761b fix(docker): a sign-in's callback needs no session
OpenAI and the plugin host return the browser to 127.0.0.1, and a browser
signed in on localhost holds no cookie for that name, so the access
service answered 401 and the sign-in never finished. /callback and
/plugins/oauth/callback are guarded by their own state and PKCE, so only
/api/ needs the session now; the host checks still apply. With nothing
cross-site needing the cookie, it is SameSite=Strict again.
2026-10-28 03:14:41 +05:30
Anish Sarkar
36ddda54e5 fix(docker): the image carries what Office support's LibreOffice links against
TDF's Linux build, unpacked into /data, could not start: a minimal Ubuntu
lacks NSS, D-Bus, cairo, GLib, CUPS, a few X11 libraries and any font.
The image installs them, about 21 MB, found by converting in throwaway
containers until it worked, and an image test checks they are present.
2026-10-28 03:14:33 +05:30
Anish Sarkar
9ddb923424 feat(frontend): dictation says the microphone needs HTTPS where the browser withholds it
Outside a secure context, as the Docker image is from another device over
plain HTTP, the browser offers no microphone, and the strip said only that
it couldn't start. It reads window.isSecureContext, a browser rule rather
than a Docker one.
2026-10-28 03:14:17 +05:30
Anish Sarkar
fd13496110 feat(local): the Docker image keeps no usage statistics
Nothing in the image reports them, so the API there neither counts
activity nor serves /usage-statistics. Before, only Electron's absence
kept the image from reporting.
2026-10-28 03:14:05 +05:30
Anish Sarkar
a79a8aaab1 refactor(frontend): one IN_CONTAINER flag for what the Docker build does differently
Five places compared VITE_SURFSENSE_RUNTIME to "docker" themselves. They
read IN_CONTAINER now, and the variable is typed "docker" | undefined, so
a misspelt comparison fails typecheck. ADR 0075 records the rule: what
only Electron can do is gated on its bridge, what the Docker build does
differently on this flag, and the backend on in_container().
2026-10-28 03:13:46 +05:30
Anish Sarkar
ff310b5dad feat(frontend): in Docker a source downloads from its menu, and a click only previews
An upload's file lives in the container, where no app on the user's
computer can open it or show its folder. Its menu offers Download, from
GET .../documents/{id}/original, and the preview's button reads Download
file. A click previews a PDF and does nothing on other files.
2026-10-28 01:43:30 +05:30
Anish Sarkar
1f05cc1ed2 feat(local): plugin sign-ins from another device return to that device
In the Docker image the API trusts the browser's Host, which Caddy and
the access service have vetted, and never on the desktop. A LAN name
registers dynamically with its own callback; the public domain signs in
as the instance's own client document. The session cookie is Lax, so the
provider's redirect back carries it past Caddy.
2026-10-28 01:43:12 +05:30
Anish Sarkar
d11955d1ee feat(docker): the image runs llama.cpp, sd.cpp and audio.cpp on the CPU
The desktop's staging scripts take --out, and llama.cpp's and sd.cpp's
--variant cpu, so the image stages each runtime from the same source:
llama.cpp from upstream's CPU build, sd.cpp compiled without Vulkan,
audio.cpp as the desktop's Linux build. The bundled Kokoro voice ships
too, so podcasts work from the first start.
2026-10-28 01:42:56 +05:30
Anish Sarkar
b555decbcd docs(docker): the image's sign-in is a SurfSense page that Caddy enforces
ADR 0074 replaces step 10's basic_auth: the browser's dialog has no page,
no sign-out and no place to set the password but .env. Caddy stays the one
door and asks an access service in the headless entry about every request
through forward_auth; the service keeps one scrypt-hashed password and
signed session cookies, slows guessing, and without a password still
answers only the host, by its Host header and its client address. The
proposal's access table, files and step 10 follow.
2026-10-27 22:58:40 +05:30
Anish Sarkar
a105de3247 fix(docker): publish on loopback only until the image takes a password
A Host header is the sender's to choose, so Caddy's local-only check stops
a browser on the LAN and DNS rebinding, not a client that sends
Host: localhost. With SURFSENSE_BIND widening the port, an instance with no
password was reachable that way. Compose now publishes on 127.0.0.1 only;
SURFSENSE_BIND returns with the password in step 10.
2026-10-27 21:52:18 +05:30
Anish Sarkar
c755e9a735 fix(docker): Claude Code installs where the backend looks for it
docker compose exec ran with HOME=/home/surfsense, so the dialog's install
command put Claude Code outside the volume, where the backend, on
HOME=/data/home, never found it and an image update lost it; and the image
had no curl, while the pipe still exited 0. The image now sets HOME on
/data for every process with its .local/bin on PATH, ships curl, and the
command runs under bash with pipefail.
2026-10-27 21:52:03 +05:30
Anish Sarkar
b1f8b01ab6 docs(release): release the Docker image with the desktop app
The same tag starts Release Docker, which pushes X.Y.Z, and publishing
moves latest and X.Y to it. The runbook now waits for its image job before
publishing, checks latest afterwards, makes the GHCR package public on the
first release, and names the image's own dry run. A failed image job does
not hold up the desktop.
2026-10-27 20:59:44 +05:30
Anish Sarkar
1fb84ef570 ci: keep caches for dev, build the image from GHCR, run unit tests in parallel
uv and pnpm caches are restored everywhere but saved only by dev pushes, so
pull request and tag caches, never reused, stop pushing dev's out of the
repository's 10 GB. pnpm comes from pnpm/setup, which can restore without
saving, as setup-node cannot. The image's build cache moves to GHCR, which
every branch, pull request and tag can read: dev pushes and release tags
write it, pull requests never do. Backend unit tests run one worker per
core with pytest-xdist and list the slowest; integration tests stay serial
until the ones that clash in parallel share an xdist_group. One test named
its parameters by a PDF's bytes, whose ID differs per worker; it names them
by file now.
2026-10-27 20:59:42 +05:30
Anish Sarkar
cf8eddd60b ci(docker): build the image on pull requests only for what can break it
A pull request builds the image, and runs the backend in it, only when it
touches the Dockerfile, Caddy, compose, headless, sidecars, opencode's
staging, the bundled models' downloads, a dependency file, the frontend's
build config, VERSION or the plugin bundle. A release's pull request
carries the VERSION bump, so it always does; one of docs or www does not.
Every dev push and manual run still runs every job, so ordinary code that
breaks the image shows on the merge into dev.
2026-10-27 19:39:25 +05:30
Anish Sarkar
0b57a8653f ci(docker): test the Docker entry, the image and the backend inside it
headless/ becomes its own package, typechecked and tested in Docker Tests
rather than through Electron's run. image.test.ts replaces smoke.sh: off
unless SURFSENSE_TEST_IMAGE names an image, it checks health through
Caddy, the app and its script, the 403s, a clean stop, and that a new
container on the same /data keeps its secret. Docker Tests adds jobs for
headless, the frontend's docker-tagged tests and the backend's image tests
in the test stage, and checks the compose file; Release Docker runs the
same image tests before it pushes.
2026-10-27 19:23:07 +05:30
Anish Sarkar
6a4e9fda8b ci(docker): separate the image's tests from its release
As the desktop has desktop-tests.yml and release-local.yml, the image now
has docker-tests.yml, which builds and smokes it on pull requests and dev
pushes and never pushes, and release-docker.yml, which pushes X.Y.Z on a
tag and moves latest and X.Y when the release is published. Both run
docker/surfsense/smoke.sh, which also runs against a local image. dev
pushes no longer publish dev and git-<sha> images.
2026-10-27 18:14:33 +05:30
Anish Sarkar
10ad9b76b7 feat(docker): About shows the image's version
The image has no Electron bridge, so About showed neither the version nor
the release notes link. Its frontend is now built with
VITE_SURFSENSE_VERSION from surfsense_local/VERSION, the one version the
image shares with the desktop app, and a VERSION bump rebuilds the image.
2026-10-27 00:52:50 +05:30
Anish Sarkar
7353fd934e feat(docker): the CPU image, compose and Caddy, with sign-ins on the host
Step 5 of the Docker proposal. docker/surfsense/ builds one image from the
repository root: the backend's venv, the SPA built for /api, opencode, the
bundled embedding, parser and voice-activity models, Node, Caddy and tini,
run as a non-root user over a /data volume. Compose publishes it on
127.0.0.1:4929, apart from the legacy stack's 3929 so both can run while
data moves over.

The headless entry starts Caddy when a Caddyfile is given; Caddy is the one
listener, refuses any host but localhost until a password exists, keeps the
workers' internal routes in, and forwards the sign-in callbacks. Plugin and
ChatGPT sign-ins send the browser back through the published port
(SURFSENSE_LOCAL_PUBLIC_PORT), ChatGPT's listener on a fixed port Caddy
forwards to. In the container the Claude dialog gives the docker compose
commands that install and sign in Claude Code there.
2026-10-27 00:03:22 +05:30
Anish Sarkar
b29a475e09 docs(docker): step 4 is built 2026-10-26 19:12:26 +05:30
Anish Sarkar
8335c08657 feat(plugins): read the signed registry in the container
The Docker image runs the backend from a venv, which `sys.frozen` calls a
checkout, so it would trust the repository's unsigned plugin list and
sign plugins in with a loopback callback. SURFSENSE_LOCAL_SIGNED_REGISTRY,
which the headless entry sets, makes it read the signed list as a
packaged app does.

The Docker proposal no longer says the headless entry turns previews
off: with no desktop app polling, the API already refuses one at once.
2026-10-26 19:12:26 +05:30
Anish Sarkar
ad612a3f8d refactor(local): move the runtime staging scripts to runtimes/
The pinned staging of llama.cpp, sd.cpp, audio.cpp and opencode, with
the helpers they share, leaves electron/scripts/ so the desktop build and
the Docker image stage from one place (ADR 0072). Each script still
stages into electron/<runtime>/, so electron-builder, the build artifacts
and the packaged app do not change; the workflows and docs follow the
new paths.

The electron package now runs runtimes/'s tests through a quoted glob,
which reaches audiocpp/espeak/: its 21 tests were skipped before, since
the shell expanded the unquoted scripts/**/*.test.mjs one level deep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 22:59:54 +05:30
Anish Sarkar
c6e2b3b46f refactor(local): move the sidecar supervisor out of electron/
sidecars/ holds what electron/src/main/sidecars/ held, with net.ts, the
session log's Node-only files and check-sidecars.mjs, so the desktop and
the coming headless entry boot from one place (ADR 0072). Renames only:
the electron package runs and typechecks it, and nothing it does changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 22:56:49 +05:30
Anish Sarkar
d58b88e91c docs(docker): sign-ins land on the browser's own origin
Every OAuth callback returns to the origin the user's browser is on.
Plugins, Notion included, try a CIMD client, then DCR, then the user's
own app, then paste-back; ChatGPT follows the rule as far as OpenAI's
client allows. Access from another machine takes HTTPS. Closes the
proposal's last open question.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:46:09 +05:30
Anish Sarkar
31bb1a8bfe docs(docker): Claude subscriptions in the container
The user installs Claude Code in the container and signs in there; HOME
on /data keeps both across updates. The Claude dialog shows the container's
commands when the API reports it runs in Docker. Closes the proposal's
question about the claude CLI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:33:20 +05:30
Anish Sarkar
59c83bd086 docs(docker): the network picture, why Caddy, and the CUDA driver floor
The Docker proposal maps the container's networks and says why Caddy is
the one listener. The GPU proposal closes its driver question: -cuda
needs 570.26 on Linux and 570.65 on Windows, kept beside the llama.cpp
pin and enforced by the installer, NVIDIA_REQUIRE_CUDA and the GPU check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:25:41 +05:30
Anish Sarkar
d923aae3ae docs(docker): one core with two entries, docker/surfsense/, and one CUDA variant
The Docker proposal moves the supervisor and runtime staging out of
electron/ into sidecars/ and runtimes/, adds the headless entry in
headless/, and puts the image in docker/surfsense/ built from the repo
root. latest moves when the GitHub release is published. The GPU image is
-cuda on CUDA 12.8, with -cuda13 later for arm64, and each runtime keeps
one source shared with the desktop. ADR 0072 and 0073 record the layout
and the variants; 0073 supersedes 0035's CUDA variant names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:20:16 +05:30
Anish Sarkar
64eaffae2f docs(legacy-split): keep the install URLs for the new Docker stack
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 01:42:36 +05:30
Anish Sarkar
a2c00d4b72 chore(legacy-split): point the repo at the legacy branch for the self-hosted stack
- Drop the surfsense_backend ruff and bandit hooks, its proprietary
  path from code-quality.yml, and the legacy CODEOWNERS, .gitignore and
  launch.json entries.
- Repoint the fastapi skill link at surfsense_local/backend/.venv.
- AGENTS.md, CONTRIBUTING.md and SECURITY.md name the legacy branch for
  the self-hosted stack; drop the legacy commands, tests and boundaries.
- Start the SMTP contract test's Mailpit with docker run.
- Repoint dead doc links at the legacy branch, or at a permalink where
  legacy removed the file too.
- The legacy-split proposal records the branch as cut and the deletion
  as its own pull request before the website split.
2026-10-24 22:25:41 +05:30
Anish Sarkar
8850a6ca57 ci(legacy-split): remove the self-hosted image workflows from dev
docker-build.yml on main published `latest` and `0.0.40` for the
self-hosted images, and every desktop release merge started it, so the
next one would have replaced the 0.0.41 images released from `legacy`.
The self-hosted stack now builds, tests and releases from the `legacy`
branch only.

- Delete docker-build.yml and docker-tests.yml.
- Drop the release-runbook paragraph about the image build on main.
- Point the docs' links to docker-build.yml at the legacy branch, and
  update the comments and CONTRIBUTING lines that named either workflow.
2026-10-24 18:02:18 +05:30
DESKTOP-RTLN3BA\$punk
4dd3ce2f66 docs: cover the work since 2.1.0 so others can pick it up
- chat.md, overview.md: how a streamed token renders only the live reply
  (runSummaries, the 32 ms notice gap, LiveThreadRuntime, the bounded
  highlighter, memoized panels) and that the columns resize.
- model-capabilities.md, agent.md, data-model.md, 07-dev-setup.md: the two
  chat modes, their gates and defaults, where a pick is stored, the 0-id fix,
  and how to re-run the OpenRouter sweep and regenerate capabilities.json.
- RELEASE.md: the steps 2.2.0 was released by, from the bump (with its
  PowerShell form) to the tag on the merge, the Known issues, an optional
  update check and publishing with --latest; dry runs are signed.
- 06-repo-readme.md, AGENTS.md, README.md's comment: the README's structure,
  how to change it and its translations, re-checking the format table, and
  replacing the demo video.
- Stale statements fixed (the Apple agreement, opencode never shipped, three
  languages, Studio's exec(), proposal statuses, the roadmap), and each gap
  still open after the release in its doc's Known gaps, linked to #2219-#2230.
2026-10-11 17:55:51 -07:00
Anish Sarkar
344c2e66cc docs(proposals): order the Docker work as fourteen runnable steps
Each step depends on the one before it and leaves something that runs:
groundwork that keeps the desktop app working, the first CPU container
and its CI, one local runtime at a time, access beyond localhost, then
the GPU images. Docs and AGENTS.md are left to the maintainers once
the work ships.
2026-10-01 11:10:12 +05:30
Anish Sarkar
953ea93100 docs(proposals): run the desktop stack in Docker, with CPU and CUDA images
The desktop app's stack, without Electron, ships as one container in
docker/local/: a supervisor shared with Electron runs the API, the
workers and the model servers on loopback, and Caddy is the only
listener. Without a password it answers only localhost; with one it
uses basic auth, over HTTPS when a domain is set. Images come as CPU,
-cuda (12.8) and -cuda126 (12.6) on amd64 and arm64. The license is the
desktop's offline file. The legacy stack in docker/ is not changed.

ADR 0035 records the decision and supersedes parts of ADR 0004 (Docker
out of scope, the loopback rule) and ADR 0012 (no CUDA), for Docker
only.
2026-10-01 10:58:26 +05:30