Commit Graph
5397 Commits
Author SHA1 Message Date
Alex fe68fec69e feat: docsgpt backup and docsgpt restore
`docsgpt backup` writes one archive holding a pg_dump of the database, a tar
of each data volume and a manifest of what it came from; `docsgpt restore`
puts it back over an install. The settings file is left out unless
--with-settings asks for it, since it holds the install's secrets, and a
backup taken with a newer DocsGPT is refused without --force.

The volume tars go through the image the install already runs, so a backup
pulls nothing extra, and compose calls can now redirect stdout and stdin so
the dump never passes through this process.
2026-09-16 21:33:33 +01:00
Alex 4abd2c0c9f Merge pull request #2788 from arc53/feat/install-3-distribution
One-command installers: curl docs.ac/install | bash, irm docs.ac/install.ps1 | iex
2026-09-16 16:06:41 +01:00
Alex 6e452ca5cc chore: 0.21.0 2026-09-16 14:23:26 +01:00
Alex 2010af34c6 Merge pull request #2787 from arc53/feat/install-2-docsgpt-up
docsgpt up: run and manage DocsGPT on Docker from the Python package
2026-09-16 10:09:42 +01:00
Alex d2c6b5a731 Merge pull request #2786 from arc53/feat/install-1-image-compose
Serve the UI from the backend image; one-port standalone Compose stack
2026-09-16 10:09:19 +01:00
Alex d993aaced0 fix: fail on a nonzero uv installer exit; POSIX quoting for the sg handoff
The Windows installer only checked that uv.exe exists after running the uv
installer, so a failed install that left an older uv.exe behind was accepted;
it now fails on a nonzero exit code.

sg runs its command with /bin/sh, which need not be bash, so the handoff
after installing Docker quotes each argument as POSIX single quotes instead
of with bash's printf %q.
2026-09-16 01:10:11 +01:00
Alex 7e80f7a091 fix: check the uv installer against a pinned sha256 before running it
Both installers download the pinned uv installer to a file and run it only
when its sha256 matches the value pinned next to UV_VERSION; bumping the
version means bumping the hash. Astral publishes checksums for the uv
binaries but not for the installer scripts, so the hash is pinned here.

get.docker.com is still only downloaded in full before running: its content
changes over time and it publishes no checksum.
2026-09-16 01:10:11 +01:00
Alex 0e1963552c ci: each generated secret must appear exactly once 2026-09-16 01:10:11 +01:00
Alex 065adaa101 fix: the Windows installer fails when docsgpt up fails 2026-09-16 01:10:11 +01:00
Alex 28cbd268f2 ci: the installer check keeps both generated secrets 2026-09-16 01:10:11 +01:00
Alex 6afce45913 ci: the installer check requires non-empty secrets 2026-09-16 01:10:11 +01:00
Alex 49823a6859 fix: installer review follow-ups
- install.sh saves the get.docker.com and uv installers to a file and runs
  them only after the download finished, so a cut-off transfer runs nothing.
- Neither installer prints DOCSGPT_PACKAGE, which may be a URL with
  credentials.
- The CI step assigns the wheel path before exporting it, so a missing wheel
  fails instead of installing from PyPI.
- Docker-Deploying shows one code block per platform; Quickstart names the
  /opt/docsgpt home used for root on Linux.
2026-09-16 01:10:11 +01:00
Alex bd35281259 fix: plain if in the installer's uv lookup (shellcheck SC2015) 2026-09-16 01:10:11 +01:00
Alex 4f0bf2cca8 feat: one-command installers for macOS, Linux and Windows
deployment/install.sh (curl | bash) and install.ps1 (irm | iex) check for
Docker, install uv when it is missing or older than 0.8 (pinned 0.12.15 via
Astral's installer), install or upgrade the docsgpt package with
`uv tool install`, and hand the terminal to `docsgpt up` with any arguments.
On Linux without Docker the shell installer offers get.docker.com. Both run
entirely inside a function, so a download cut short runs nothing.

Releases attach both scripts next to the Compose file, which is where
docs.ac/install and docs.ac/install.ps1 will point. installer-lint.yml runs
shellcheck and the PowerShell parser; docker-image-verify.yml now installs
through install.sh. README, Quickstart, Docker-Deploying and the changelog
lead with the one-liner.
2026-09-16 01:10:11 +01:00
Alex 6b6bd1b0fb test: assert the plain-HTTP warning is printed before compose starts 2026-09-16 01:10:09 +01:00
Alex 63de66722e fix: warn about plain HTTP before the stack starts, not only afterwards
Network mode publishes the port on every interface and its access token
travels as readable text, so `docsgpt up` says so before starting rather
than in the summary at the end. The health poll's except clause says why it
swallows the error.
2026-09-16 00:53:03 +01:00
Alex db1e382502 ci: each generated secret must appear exactly once 2026-09-16 00:35:29 +01:00
Alex 9b5f1196fe ci: the repeated startup must keep both generated secrets 2026-09-16 00:20:43 +01:00
Alex 303f239fcd fix: tighten an existing .env to 0600 before rewriting it; CI checks secrets have values
envfile.update only applied 0600 when it created the file, so an existing
.env with a wider mode kept it while secrets were written into it. The mode
is now set on the open descriptor before the file is truncated and written.

The CI step now requires POSTGRES_PASSWORD and JWT_SECRET_KEY to have values:
an empty one falls back to a default without any check noticing.
2026-09-16 00:11:38 +01:00
Alex f6bf4fefd1 fix: docsgpt up review follow-ups
- wait_healthy starts no request once the deadline is reached, and neither
  its pauses nor the requests after the first run past it; the first attempt
  still always runs (status uses a zero timeout).
- envfile writes $ as $$ inside double quotes, which Compose interpolates,
  and reads $$ back as $, so a value such as pa$w'rd reaches the container
  unchanged.
- Upgrading no longer describes the working directory as the data home.
2026-09-15 23:32:37 +01:00
Alex 5e699b0168 ci: no uv cache in the image verify job (zizmor cache-poisoning) 2026-09-15 23:20:16 +01:00
Alex c7af5f873a fix: docsgpt up --adopt recreates every container
Compose keeps containers whose configuration did not change, so after a
takeover Redis and Postgres still carried the other folder's working
directory label, and the next `docsgpt up` asked for --adopt again.
2026-09-15 23:11:32 +01:00
Alex 0bd0afc1f8 fix: docsgpt up removes Caddy when leaving a domain; clearer Docker permission error
Caddy sits behind the https profile, so once COMPOSE_PROFILES no longer
enables it, `up --remove-orphans` left it running on ports 80 and 443 and
`down -v` left its volumes. `up` now removes Caddy when an install moves
off its domain, and down and uninstall name the profile explicitly.

A user outside the docker group was told Docker is not running; the error
now says how to get access to the socket.
2026-09-15 23:06:27 +01:00
Alex 68d9772f2e docs: docsgpt up and the new data home; CI runs up against the built image
Docker-Deploying gains a `docsgpt up` section, Pip-Install and Upgrading
describe the ~/.docsgpt/server data home, and the changelog covers both.

docker-image-verify.yml installs the wheel and runs `docsgpt up`, `status`,
a second `up` that must keep the secrets, and `uninstall --purge` against
the image it built. The standalone Compose file maps host.docker.internal
to the host gateway, so a model server on a Linux host is reachable the way
`docsgpt up` suggests.
2026-09-15 23:00:23 +01:00
Alex aa0e4ea280 feat: docsgpt up runs and manages DocsGPT on Docker
`docsgpt up` copies the standalone Compose file shipped with this package
version into the stack directory (~/.docsgpt/server by default), writes its
.env and starts the stack on the images of the same version. A first run
asks who should reach DocsGPT (this computer, the network with a token, or a
domain with HTTPS) and which model provider to use; flags answer the same
questions for scripts. Re-running keeps secrets and settings and moves the
image tag, and the database password is only generated for a new database.

Also: down, status, logs, token, open, env, upgrade (uv tool installs
upgrade themselves and run `up` again) and uninstall (keeps settings and
data unless --purge). The commands import no Flask, Celery or settings.

The wheel carries deployment/docker-compose-standalone.yaml as
docsgpt/deploy/docker-compose.yaml; the sdist includes the source file.
2026-09-15 22:57:00 +01:00
Alex ac06527a8d feat: installed package keeps its data in ~/.docsgpt/server
Outside a checkout the data home was the working directory, so running
`docsgpt api` from another folder silently used different settings and data.
It is now ~/.docsgpt/server (/opt/docsgpt for root on Linux); DOCSGPT_HOME
and a checkout still take precedence. The API and worker commands create
the home and point out a .env left in the working directory.
2026-09-15 22:45:54 +01:00
Alex 95fd4bdefe feat: serve the UI from the backend image, one-port standalone stack
The backend image builds the web UI with scripts/build_frontend.sh and
serves it through docsgpt/ui.py, so the standalone Compose file drops the
frontend container. UI and API share port 7091, published on 127.0.0.1
unless DOCSGPT_BIND says otherwise. POSTGRES_PASSWORD is configurable, and
an optional https profile puts Caddy in front of a public domain.

docker-image-verify.yml starts the standalone stack on the image it built
and checks the API, the UI, /config.js and a client-side route on one port.
2026-09-15 22:41:53 +01:00
Alex 0fcec68b67 Merge pull request #2784 from arc53/fix/air-gapped-review-followups
fix: air-gapped review follow-ups
2026-09-15 21:42:58 +01:00
Alex 02f2fa198a fix: disabled STT also covers live finish; clarify model cache location 2026-09-15 19:28:19 +01:00
Pavel 94255863d1 Merge pull request #2783 from arc53/feat/air-gapped-deployment
feat: air-gapped deployment guide, no implicit downloads
2026-09-15 20:05:08 +02:00
Alex 7da46c2bea feat: air-gapped deployment guide, no implicit downloads
- Ship tiktoken's cl100k_base inside the package and build the encoding
  from it, so token counting never downloads anything.
- Default EMBEDDINGS_CACHE_DIR to <data home>/models instead of FastEmbed's
  temp dir, and read tokenizer.json and repo metadata from that cache, so
  a model downloads once and survives reboots.
- TTS_PROVIDER=none and STT_PROVIDER=none switch the speech features off:
  the endpoints return 404, audio files fail to ingest with a clear
  message, /api/config reports tts_available/stt_available, and the UI
  hides the Speak and microphone buttons.
- Drop the Google Fonts Roboto import from the web UI.
- prefetch-models fills the cache the app reads; verify-offline checks the
  packaged encoding.
- Docs: new Air-Gapped Deployment guide, settings and cache notes.
2026-09-15 17:54:24 +01:00
Alex ca69d1ea29 Merge pull request #2780 from arc53/fix/connector-oauth-postmessage-origin
fix: stop leaking connector OAuth session tokens to other origins
2026-09-15 15:22:50 +01:00
Alex 3dfe3b6112 fix: slight hardening 2026-09-15 08:44:37 +01:00
Alex e8305ef137 fix: more mini connector hardening 2026-09-14 22:49:34 +01:00
Alex 08e8de7370 fix: mini connector fixes 2026-09-14 22:29:26 +01:00
Alex 41b3afed14 fix: stop leaking connector OAuth session tokens to other origins
The connector OAuth popup posted the session token to window.opener with
a '*' target origin, so any page that opened the popup received it. An
attacker with an account on a multi-user deployment could start a flow for
their own pending session, get a victim to finish the provider consent, and
receive a token backed by the victim's Drive/SharePoint/Confluence tokens.

- Post popup results only to allowed frontend origins: the callback origin,
  OIDC_FRONTEND_URL, the new CONNECTOR_ALLOWED_ORIGINS, and localhost:5173
  when the callback runs on a loopback host.
- Render the success page from the callback itself so the token never
  appears in a URL; callback-status ignores session_token/user_email params.
- ConnectorAuth accepts messages only from the popup it opened, on the
  callback origin reported by /api/connectors/auth.
- /api/connectors/disconnect requires auth and only deletes the caller's
  session.
- /api/connectors/sync and /api/remote reject session tokens the caller
  does not own.

Fixes #2766
2026-09-14 21:46:09 +01:00
Alex d121eec2f5 Merge pull request #2771 from arc53/fix/durable-non-text-uploads
fix: handle non text uploads more carefully
2026-09-14 21:23:22 +01:00
Alex ad201f8318 fix: refuse oversized TIFF/BMP attachments before converting them
A deflate-compressed TIFF under 1 MB can declare 144 million pixels and
take 1.2 GB to convert to PNG, and Pillow only warns below 179 million.
Read the dimensions from the header and refuse images over 40 million
pixels before any pixel data is decoded. Pillow's DecompressionBombError
is now raised as DocumentParseError, so the upload fails once instead of
being retried.
2026-09-14 17:45:43 +01:00
Alex 3030aba39d fix: handle non text uploads more carefully 2026-09-14 17:27:13 +01:00
Alex 82691314ca Merge pull request #2769 from arc53/feat/async-starlette-streams
feat(api): serve long-lived streams on the event loop
2026-09-14 16:22:36 +01:00
Alex 1bf7380aa5 fix: mini test 2026-09-14 14:12:21 +01:00
Alex 72904b0b95 fix: Make ticket claim atomic. 2026-09-14 14:06:59 +01:00
Alex fb66a0b230 fix: stability improvements on event loop 2026-09-14 12:52:15 +01:00
Alex e889392eba fix(api): address review on the event-loop streams
- Per-user SSE cap uses per-connection leases in a sorted set instead of
  a shared INCR/DECR counter with a TTL. A stream that outlived the TTL
  could let the counter expire, then decrement another stream's slot or
  drive the count negative. Leases refresh while a stream sends frames
  and age out when a stream dies without cleanup.
- Shielded cleanup is bounded per step: stream close and on_close in
  ClosingStreamingResponse, unsubscribe and close in AsyncTopic, lease
  release, and the replay-budget check, so a dead Redis connection can't
  hold a request or a graceful shutdown.
- Artifact downloads send an ASCII filename plus an RFC 5987 filename*
  for non-ASCII names, build headers before opening the file, and close
  disk handles off the event loop.
- Type hints and docstrings on the new helpers.
2026-09-14 10:19:59 +01:00
Alex 54b540ea42 feat(api): serve long-lived streams on the event loop
Move GET /api/events, the remote-device command stream and artifact
downloads from Flask to Starlette routes mounted ahead of the Flask
catch-all. On Flask each held an a2wsgi threadpool slot for as long as
its response stayed open, and because uvicorn drops writes after a
client disconnects, a closed tab never released it.

- asgi_auth: one JWT/OIDC gate for Starlette routes; the chat reconnect
  reader uses it too
- ClosingStreamingResponse closes the body iterator and releases the
  SSE slot or file handle even when the client leaves before the first
  frame
- AsyncTopic liveness probe replaces the sync client's socket_timeout
  guard against half-open pub/sub sockets
- ASYNC_REDIS_MAX_CONNECTIONS sizes the async Redis pool; every open
  stream holds a connection and redis-py defaults to 100
2026-09-14 08:33:46 +01:00
Alex 38773471a8 Update docsgpt.yaml 2026-09-13 14:48:28 +01:00
Alex eae635b6dd Merge pull request #2765 from arc53/fix/release-recovery-and-attestations
ci: fix PyPI publishing from the release chain, make image builds recoverable
2026-09-12 23:50:26 +01:00
Alex 12016baaf2 ci: resolve the manual version under refs/tags
The version input reached checkout as a bare ref, so a manual run given a
branch name would have built that branch and published it as an image tag,
`latest` included. It also meant a version that happens to match a branch name
would silently resolve to the branch rather than the tag.

Resolve it under refs/tags/ instead. Plain versions from backend-release keep
working, and anything that is not a tag fails at checkout.
2026-09-12 23:41:43 +01:00
Alex 99b8508bdc ci: build the source the version names
A manual run takes the version as an input but the checkout had no ref, so it
built whatever the run was dispatched from. Dispatching off main to rebuild an
older release would have published current main under that release's tags, and
moved `latest` to it.

Check out the version instead, falling back to the run's own ref when there is
no version, which is the push that publishes `develop`. The release and call
paths already checked out the right commit; this makes them explicit and fails
loudly on a version with no tag rather than mislabelling an image. The compose
file attached to the release now comes from the tag too.
2026-09-12 23:37:14 +01:00
Alex 55e619b812 ci: let the image workflows be run by hand
None of the three image workflows could be started manually, so when 0.20.0
tagged before the release chain knew about the frontend and sandbox images,
there was no way to build those images for the tag short of recreating the
release. Add a workflow_dispatch trigger taking the version to build.

A manual run also gets a `move_latest` switch, so rebuilding an older tag does
not drag `latest` backwards. The rule for the moving tag now lives in the
manifest step's shell rather than in a nested expression: `latest` follows the
build unless the release is a prerelease, the run asked it not to, or it is the
rolling `develop` build.
2026-09-12 23:29:33 +01:00