From 4f0bf2cca8a23f0bcdeeb53b60a62da8fabaaff8 Mon Sep 17 00:00:00 2001
From: Alex
Date: Tue, 15 Sep 2026 23:17:58 +0100
Subject: [PATCH 01/10] 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.
---
.github/workflows/ci.yml | 11 +-
.github/workflows/docker-image-verify.yml | 47 +++---
.github/workflows/installer-lint.yml | 47 ++++++
README.md | 28 +++-
deployment/install.ps1 | 94 +++++++++++
deployment/install.sh | 169 ++++++++++++++++++++
docs/content/Deploying/Docker-Deploying.mdx | 14 +-
docs/content/changelog.mdx | 7 +
docs/content/quickstart.mdx | 139 ++++++++--------
9 files changed, 455 insertions(+), 101 deletions(-)
create mode 100644 .github/workflows/installer-lint.yml
create mode 100644 deployment/install.ps1
create mode 100755 deployment/install.sh
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5cd9c85e..fd042048 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -206,9 +206,16 @@ jobs:
ref: ${{ inputs.version && format('refs/tags/{0}', inputs.version) || github.ref }}
persist-credentials: false
- - name: Attach the standalone compose file to the release
+ # The installers are served from these assets: docs.ac/install redirects to
+ # releases/latest/download/install.sh (and install.ps1), so the script a
+ # user runs always comes from the newest release.
+ - name: Attach the standalone compose file and the installers to the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ env.RELEASE_TAG }}
run: |
- gh release upload "$TAG" deployment/docker-compose-standalone.yaml --clobber
+ gh release upload "$TAG" \
+ deployment/docker-compose-standalone.yaml \
+ deployment/install.sh \
+ deployment/install.ps1 \
+ --clobber
diff --git a/.github/workflows/docker-image-verify.yml b/.github/workflows/docker-image-verify.yml
index f592febf..a2825d52 100644
--- a/.github/workflows/docker-image-verify.yml
+++ b/.github/workflows/docker-image-verify.yml
@@ -23,6 +23,7 @@ on:
- 'frontend/**'
- 'scripts/build_frontend.sh'
- 'deployment/docker-compose-standalone.yaml'
+ - 'deployment/install.sh'
- 'docsgpt/deploy/**'
- 'docsgpt/cli.py'
- 'docsgpt/core/paths.py'
@@ -98,41 +99,29 @@ jobs:
curl -fsS "$base/settings" | grep -q 'src="/config.js"'
echo "API and UI served on $base"
- - name: Set up uv
- if: matrix.variant == ''
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
- with:
- # No cache: a cache restored into a job that runs the built image is a poisoning risk.
- enable-cache: false
-
- - name: docsgpt up runs the same image from the installed package
+ - name: The installer runs docsgpt up on the same image
if: matrix.variant == ''
+ env:
+ DOCSGPT_NO_MODIFY_PATH: "1"
run: |
set -euo pipefail
# Same Compose project name as the step above; stop that stack first.
docker compose -f deployment/docker-compose-standalone.yaml down -v
- uv build --wheel --out-dir "$RUNNER_TEMP/dist"
- uv venv "$RUNNER_TEMP/venv"
- uv pip install --python "$RUNNER_TEMP/venv/bin/python" "$RUNNER_TEMP"/dist/*.whl
- docsgpt="$RUNNER_TEMP/venv/bin/docsgpt"
- stack="$RUNNER_TEMP/stack"
- "$docsgpt" up --yes --dir "$stack" --image-tag verify
- "$docsgpt" status --dir "$stack"
+ pipx run build --wheel --outdir "$RUNNER_TEMP/dist"
+ export DOCSGPT_PACKAGE="$(ls "$RUNNER_TEMP"/dist/docsgpt-*.whl)"
+ # No uv is set up beforehand, so the installer's pinned uv download runs too.
+ # Without a terminal the installer passes --yes to docsgpt up.
+ bash deployment/install.sh --image-tag verify [!Note]
-> Make sure you have [Docker](https://docs.docker.com/engine/install/) installed
+> DocsGPT runs on [Docker](https://docs.docker.com/engine/install/). The installer checks for it first.
-A more detailed [Quickstart](https://docs.docsgpt.cloud/quickstart) is available in our documentation
+**macOS and Linux:**
+
+```bash
+curl -fsSL https://docs.ac/install | bash
+```
+
+**Windows (PowerShell):**
+
+```powershell
+irm https://docs.ac/install.ps1 | iex
+```
+
+The installer gets [uv](https://docs.astral.sh/uv/), installs the `docsgpt` Python package with it, and runs `docsgpt up`. That asks who should reach DocsGPT (only this computer, your network, or a domain with HTTPS) and which model provider to use, then starts it, at http://localhost:7091 for a local install. Afterwards, `docsgpt status`, `docsgpt logs`, `docsgpt upgrade`, `docsgpt down` and `docsgpt uninstall` manage it.
+
+To read the script before running it:
+
+```bash
+curl -fsSL https://docs.ac/install -o install.sh
+less install.sh
+bash install.sh
+```
+
+A more detailed [Quickstart](https://docs.docsgpt.cloud/quickstart) is available in our documentation.
+
+### From a clone, with the setup script
1. **Clone the repository:**
diff --git a/deployment/install.ps1 b/deployment/install.ps1
new file mode 100644
index 00000000..0c722b3b
--- /dev/null
+++ b/deployment/install.ps1
@@ -0,0 +1,94 @@
+# DocsGPT installer for Windows.
+#
+# irm https://docs.ac/install.ps1 | iex
+#
+# Installs uv when it is missing or too old, installs the docsgpt Python
+# package with it, then runs `docsgpt up`, which sets up DocsGPT on Docker
+# Desktop and starts it. Running it again upgrades the package and keeps your
+# settings. To pass options to `docsgpt up`:
+#
+# & ([scriptblock]::Create((irm https://docs.ac/install.ps1))) --domain docs.example.com --yes
+#
+# Environment:
+# DOCSGPT_VERSION package version to install (default: the latest release)
+# DOCSGPT_PACKAGE install this instead of docsgpt from PyPI (a wheel path or URL)
+# DOCSGPT_NO_MODIFY_PATH set to 1 to leave PATH alone
+#
+# Everything runs inside a function, so a download cut short runs nothing, and
+# nothing calls `exit`, which would close the window `iex` runs in.
+
+function Install-DocsGPT {
+ param([string[]]$UpArguments)
+
+ $ErrorActionPreference = 'Stop'
+ $UvVersion = '0.12.15'
+ $UvMinVersion = [version]'0.8.0'
+
+ function Say([string]$Message) { Write-Host "==> $Message" }
+
+ if (-not (Get-Command docker -ErrorAction SilentlyContinue)) {
+ throw 'DocsGPT runs on Docker. Install Docker Desktop (https://docs.docker.com/desktop/setup/install/windows-install/), start it, and run this again.'
+ }
+
+ # uv installs and upgrades the package, and brings Python 3.12 when the system has none.
+ $uv = $null
+ $candidates = @(
+ (Get-Command uv -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source -First 1),
+ (Join-Path $HOME '.local\bin\uv.exe'),
+ (Join-Path $HOME '.cargo\bin\uv.exe')
+ ) | Where-Object { $_ -and (Test-Path $_) }
+ foreach ($candidate in $candidates) {
+ $found = "$(& $candidate --version 2>$null)" -replace '^uv\s+([0-9.]+).*$', '$1'
+ if ($found -match '^\d+\.\d+(\.\d+)?$' -and [version]$found -ge $UvMinVersion) {
+ $uv = $candidate
+ break
+ }
+ }
+ if (-not $uv) {
+ $uvDir = Join-Path $HOME '.local\bin'
+ Say "Installing uv $UvVersion into $uvDir"
+ $env:UV_INSTALL_DIR = $uvDir
+ $env:UV_NO_MODIFY_PATH = '1'
+ $env:UV_PRINT_QUIET = '1'
+ # A child PowerShell, so nothing the uv installer does can end this session.
+ $shell = (Get-Process -Id $PID).Path
+ & $shell -NoProfile -ExecutionPolicy Bypass -Command "irm https://astral.sh/uv/$UvVersion/install.ps1 | iex"
+ $uv = Join-Path $uvDir 'uv.exe'
+ if (-not (Test-Path $uv)) { throw "uv did not install into $uvDir" }
+ }
+
+ if ($env:DOCSGPT_VERSION -and $env:DOCSGPT_PACKAGE) {
+ throw 'Set DOCSGPT_VERSION or DOCSGPT_PACKAGE, not both.'
+ }
+ if ($env:DOCSGPT_PACKAGE) {
+ Say "Installing docsgpt from $env:DOCSGPT_PACKAGE"
+ & $uv tool install --reinstall --python 3.12 $env:DOCSGPT_PACKAGE
+ } elseif ($env:DOCSGPT_VERSION) {
+ Say "Installing docsgpt $env:DOCSGPT_VERSION"
+ & $uv tool install --force --python 3.12 "docsgpt==$env:DOCSGPT_VERSION"
+ } else {
+ Say 'Installing the latest docsgpt'
+ & $uv tool install --upgrade --python 3.12 docsgpt
+ }
+ if ($LASTEXITCODE -ne 0) { throw 'Installing the docsgpt package failed.' }
+
+ $binDir = "$(& $uv tool dir --bin)".Trim()
+ $docsgpt = Join-Path $binDir 'docsgpt.exe'
+ if (-not (Test-Path $docsgpt)) { throw "The docsgpt command is missing from $binDir." }
+ if (($env:Path -split ';') -notcontains $binDir) {
+ if ($env:DOCSGPT_NO_MODIFY_PATH -eq '1') {
+ Say "Add $binDir to PATH to run docsgpt from a new terminal"
+ } else {
+ & $uv tool update-shell *> $null
+ Say "Added $binDir to PATH for new terminals"
+ }
+ $env:Path = "$binDir;$env:Path"
+ }
+
+ & $docsgpt up @UpArguments
+ if ($LASTEXITCODE -ne 0) {
+ Write-Error "docsgpt up exited with code $LASTEXITCODE. Run it again after fixing the problem above: docsgpt up" -ErrorAction Continue
+ }
+}
+
+Install-DocsGPT -UpArguments $args
diff --git a/deployment/install.sh b/deployment/install.sh
new file mode 100755
index 00000000..95ed1401
--- /dev/null
+++ b/deployment/install.sh
@@ -0,0 +1,169 @@
+#!/usr/bin/env bash
+# DocsGPT installer for macOS and Linux.
+#
+# curl -fsSL https://docs.ac/install | bash
+#
+# Installs uv when it is missing or too old, installs the `docsgpt` Python
+# package with it, then runs `docsgpt up`, which sets up DocsGPT on Docker and
+# starts it. Running it again upgrades the package and keeps your settings.
+# Arguments go to `docsgpt up` (see `docsgpt up --help`):
+#
+# curl -fsSL https://docs.ac/install | bash -s -- --domain docs.example.com --yes
+#
+# Environment:
+# DOCSGPT_VERSION package version to install (default: the latest release)
+# DOCSGPT_PACKAGE install this instead of docsgpt from PyPI (a wheel path or URL)
+# DOCSGPT_NO_MODIFY_PATH set to 1 to leave shell profiles alone
+# DOCSGPT_INSTALL_DOCKER set to 1 to install Docker on Linux without asking
+#
+# Everything runs inside main(), so a download cut short runs nothing.
+
+UV_VERSION="0.12.15"
+UV_MIN_VERSION="0.8.0"
+
+main() {
+ set -euo pipefail
+
+ local bold="" red="" reset=""
+ if [ -t 2 ]; then
+ bold=$'\033[1m' red=$'\033[31m' reset=$'\033[0m'
+ fi
+ say() { printf '%s==>%s %s\n' "$bold" "$reset" "$*" >&2; }
+ die() { printf '%serror:%s %s\n' "$red" "$reset" "$*" >&2; exit 1; }
+ has() { command -v "$1" >/dev/null 2>&1; }
+ have_tty() { (exec /dev/null; }
+ ask_yes() {
+ local answer
+ printf '%s [y/N] ' "$1" >/dev/tty
+ read -r answer = B for dotted version numbers.
+ version_ge() {
+ local -a left right
+ IFS=. read -r -a left <<<"$1"
+ IFS=. read -r -a right <<<"$2"
+ local i x y
+ for i in 0 1 2; do
+ x="${left[i]:-0}" y="${right[i]:-0}"
+ x="${x%%[!0-9]*}" y="${y%%[!0-9]*}"
+ if (( 10#${x:-0} > 10#${y:-0} )); then return 0; fi
+ if (( 10#${x:-0} < 10#${y:-0} )); then return 1; fi
+ done
+ return 0
+ }
+
+ local os
+ os="$(uname -s)"
+ case "$os" in
+ Linux | Darwin) ;;
+ *) die "this installer is for macOS and Linux. On Windows, in PowerShell: irm https://docs.ac/install.ps1 | iex" ;;
+ esac
+
+ # Docker first: without it nothing below is useful.
+ local docker_group_pending=0
+ if ! has docker; then
+ if [ "$os" = Darwin ]; then
+ die "DocsGPT runs on Docker. Install Docker Desktop (https://docs.docker.com/desktop/setup/install/mac-install/) or OrbStack (https://orbstack.dev), start it, and run this again."
+ fi
+ if [ "${DOCSGPT_INSTALL_DOCKER:-}" = 1 ] || { have_tty && ask_yes "Docker is not installed. Install it now with Docker's script from get.docker.com?"; }; then
+ local sudo=""
+ if [ "$(id -u)" -ne 0 ]; then
+ has sudo || die "installing Docker needs root. Install it (https://docs.docker.com/engine/install/) and run this again."
+ sudo="sudo"
+ fi
+ say "Installing Docker"
+ download https://get.docker.com | $sudo sh
+ $sudo systemctl enable --now docker >/dev/null 2>&1 || true
+ if [ -n "$sudo" ]; then
+ $sudo usermod -aG docker "$(id -un)"
+ docker_group_pending=1
+ fi
+ else
+ die "DocsGPT runs on Docker. Install it (https://docs.docker.com/engine/install/) and run this again."
+ fi
+ fi
+
+ # uv installs and upgrades the package, and brings Python 3.12 when the system has none.
+ local uv="" candidate found
+ for candidate in "$(command -v uv 2>/dev/null || true)" "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv"; do
+ [ -n "$candidate" ] && [ -x "$candidate" ] || continue
+ found="$("$candidate" --version 2>/dev/null | awk '{print $2}')" || continue
+ if [ -n "$found" ] && version_ge "$found" "$UV_MIN_VERSION"; then
+ uv="$candidate"
+ break
+ fi
+ done
+ if [ -z "$uv" ]; then
+ local uv_dir="${XDG_BIN_HOME:-$HOME/.local/bin}"
+ say "Installing uv $UV_VERSION into $uv_dir"
+ download "https://astral.sh/uv/$UV_VERSION/install.sh" | env UV_INSTALL_DIR="$uv_dir" UV_NO_MODIFY_PATH=1 UV_PRINT_QUIET=1 sh
+ uv="$uv_dir/uv"
+ [ -x "$uv" ] || die "uv did not install into $uv_dir"
+ fi
+
+ if [ -n "${DOCSGPT_VERSION:-}" ] && [ -n "${DOCSGPT_PACKAGE:-}" ]; then
+ die "set DOCSGPT_VERSION or DOCSGPT_PACKAGE, not both"
+ fi
+ if [ -n "${DOCSGPT_PACKAGE:-}" ]; then
+ say "Installing docsgpt from $DOCSGPT_PACKAGE"
+ "$uv" tool install --reinstall --python 3.12 "$DOCSGPT_PACKAGE"
+ elif [ -n "${DOCSGPT_VERSION:-}" ]; then
+ say "Installing docsgpt $DOCSGPT_VERSION"
+ "$uv" tool install --force --python 3.12 "docsgpt==$DOCSGPT_VERSION"
+ else
+ say "Installing the latest docsgpt"
+ "$uv" tool install --upgrade --python 3.12 docsgpt
+ fi
+
+ local bin_dir docsgpt
+ bin_dir="$("$uv" tool dir --bin)"
+ docsgpt="$bin_dir/docsgpt"
+ [ -x "$docsgpt" ] || die "the docsgpt command is missing from $bin_dir"
+ case ":$PATH:" in
+ *":$bin_dir:"*) ;;
+ *)
+ if [ "${DOCSGPT_NO_MODIFY_PATH:-}" = 1 ]; then
+ say "Add $bin_dir to PATH to run docsgpt from a new terminal"
+ else
+ "$uv" tool update-shell >/dev/null 2>&1 || true
+ say "Added $bin_dir to PATH for new terminals"
+ fi
+ ;;
+ esac
+
+ if [ "$docker_group_pending" = 1 ]; then
+ if has sg; then
+ # The docker group applies to new logins; sg gives it to this command now.
+ local command
+ command="$(printf '%q ' "$docsgpt" up "$@")"
+ if have_tty; then
+ exec sg docker -c "$command
+ To read the script before running it, download it first: `curl -fsSL https://docs.ac/install -o install.sh`, then `bash install.sh`. On Windows: `irm https://docs.ac/install.ps1 -OutFile install.ps1`, then `.\install.ps1`.
+
+
+**Options.** Arguments after `bash -s --` go to `docsgpt up`, so a server can be set up without questions:
+
+```bash
+curl -fsSL https://docs.ac/install | bash -s -- --yes --domain docs.example.com --provider openai --api-key "$OPENAI_API_KEY"
+```
+
+`DOCSGPT_VERSION` installs a specific release, and `DOCSGPT_NO_MODIFY_PATH=1` leaves your shell profile alone. `docsgpt up --help` lists every option.
+
+**Afterwards:**
+
+| Command | What it does |
+| --- | --- |
+| `docsgpt status` | Version, address, and whether DocsGPT answers |
+| `docsgpt logs -f` | Follow the logs |
+| `docsgpt token` | The access token, for installs reachable beyond this computer |
+| `docsgpt up --reconfigure` | Ask the setup questions again |
+| `docsgpt upgrade` | Upgrade to the latest release, keeping your settings and data |
+| `docsgpt down` | Stop DocsGPT |
+| `docsgpt uninstall` | Remove it; `--purge` also deletes settings and data |
+
+Running the install command again also upgrades. See [Run it with `docsgpt up`](/Deploying/Docker-Deploying#run-it-with-docsgpt-up) for the details.
+
+## From a clone, with the setup script
+
+To work from the source tree, for example to build the images yourself, use `setup.sh` (macOS and Linux) or `setup.ps1` (Windows).
+
+1. **Clone the repository:**
```bash
git clone https://github.com/arc53/DocsGPT.git
cd DocsGPT
```
-2. **Run the `setup.sh` script:**
-
- Navigate to the DocsGPT directory in your terminal and execute the `setup.sh` script:
+2. **Run the setup script:**
```bash
./setup.sh
```
-3. **Follow the interactive setup:**
+ On Windows:
- The `setup.sh` script will guide you through an interactive menu with the following options:
+ ```powershell
+ PowerShell -ExecutionPolicy Bypass -File .\setup.ps1
+ ```
+
+3. **Follow the interactive setup:**
```
Welcome to DocsGPT Setup!
@@ -47,73 +98,29 @@ The easiest way to launch DocsGPT is using the provided `setup.sh` script. This
Choose option (1-5):
```
- Let's break down each option:
+ * **1) Use DocsGPT Public API Endpoint (simple and free):** This is the simplest option to get started. It utilizes the DocsGPT public API, requiring no API keys or local model downloads.
- * **1) Use DocsGPT Public API Endpoint (simple and free):** This is the simplest option to get started. It utilizes the DocsGPT public API, requiring no API keys or local model downloads. Choose this for a quick and easy setup.
+ * **2) Serve Local (with Ollama):** Runs a Large Language Model locally using [Ollama](https://ollama.com/). You'll be prompted to choose between CPU or GPU for Ollama and select a model to download.
- * **2) Serve Local (with Ollama):** This option allows you to run a Large Language Model locally using [Ollama](https://ollama.com/). You'll be prompted to choose between CPU or GPU for Ollama and select a model to download. This is a good option for local processing and experimentation.
+ * **3) Connect Local Inference Engine:** If you already run a local inference engine like Llama.cpp, Text Generation Inference (TGI), vLLM, or others, choose this option and provide the connection details.
- * **3) Connect Local Inference Engine:** If you are already running a local inference engine like Llama.cpp, Text Generation Inference (TGI), vLLM, or others, choose this option. You'll be asked to select your engine and provide the necessary connection details. This is for users with existing local LLM infrastructure.
+ * **4) Connect Cloud API Provider:** Connect DocsGPT to a Cloud API provider such as OpenAI, Google (Vertex AI/Gemini), Anthropic (Claude), Groq, HuggingFace Inference API, or Azure OpenAI. You will need an API key from your chosen provider.
- * **4) Connect Cloud API Provider:** This option lets you connect DocsGPT to a commercial Cloud API provider such as OpenAI, Google (Vertex AI/Gemini), Anthropic (Claude), Groq, HuggingFace Inference API, or Azure OpenAI. You will need an API key from your chosen provider. Select this if you prefer to use a powerful cloud-based LLM.
+ * **5) Modify DocsGPT's source code and rebuild the Docker images locally.** Instead of pulling prebuilt images from Docker Hub, you build the backend and frontend from source, to customize how DocsGPT works internally or to run it in an environment without internet access.
- * **5) Modify DocsGPT's source code and rebuild the Docker images locally.** Instead of pulling prebuilt images from Docker Hub or using the hosted/public API, you build the entire backend and frontend from source, customizing how DocsGPT works internally, or run it in an environment without internet access.
+ After selecting an option and providing any required information (like API keys or model names), the script configures your `.env` file and starts DocsGPT using Docker Compose.
- After selecting an option and providing any required information (like API keys or model names), the script will configure your `.env` file and start DocsGPT using Docker Compose.
+4. **Access DocsGPT in your browser:** open [http://localhost:5173/](http://localhost:5173/).
-4. **Access DocsGPT in your browser:**
-
- Once the setup is complete and Docker containers are running, navigate to [http://localhost:5173/](http://localhost:5173/) in your web browser to access the DocsGPT web application.
-
-5. **Stopping DocsGPT:**
-
- To stop DocsGPT, simply open a new terminal in the `DocsGPT` directory and run:
+5. **Stopping DocsGPT:** in the `DocsGPT` directory, run the `docker compose down` command the script printed at the end, for example:
```bash
docker compose -f deployment/docker-compose-hub.yaml down
```
- (or the specific `docker compose` command shown at the end of the `setup.sh` execution, which may include optional compose files depending on your choices).
-## Launching DocsGPT (Windows)
+**Important for Windows:** Ensure Docker Desktop is installed and running before you start. The script tries to start Docker if it is not running, but you may need to start it manually.
-For Windows users, we provide a PowerShell script that offers the same functionality as the macOS/Linux setup script.
-
-**Steps:**
-
-1. **Download the DocsGPT Repository:**
-
- First, you need to download the DocsGPT repository to your local machine. You can do this using Git:
-
- ```powershell
- git clone https://github.com/arc53/DocsGPT.git
- cd DocsGPT
- ```
-
-2. **Run the `setup.ps1` script:**
-
- Execute the PowerShell setup script:
-
- ```powershell
- PowerShell -ExecutionPolicy Bypass -File .\setup.ps1
- ```
-
-3. **Follow the interactive setup:**
-
- Just like the Linux/macOS script, the PowerShell script will guide you through setting DocsGPT.
- The script will handle environment configuration and start DocsGPT based on your selections.
-
-4. **Access DocsGPT in your browser:**
-
- Once the setup is complete and Docker containers are running, navigate to [http://localhost:5173/](http://localhost:5173/) in your web browser to access the DocsGPT web application.
-
-5. **Stopping DocsGPT:**
-
- To stop DocsGPT run the Docker Compose down command displayed at the end of the setup script's execution.
-
-**Important for Windows:** Ensure Docker Desktop is installed and running correctly on your Windows system before proceeding. The script will attempt to start Docker if it's not running, but you may need to start it manually if there are issues.
-
-**Alternative Method:**
-If you prefer a more manual approach, you can follow our [Docker Deployment documentation](/Deploying/Docker-Deploying) for detailed instructions on setting up DocsGPT on Windows using Docker commands directly.
+**Alternative Method:** To run the pre-built images with Docker Compose yourself, follow the [Docker Deployment documentation](/Deploying/Docker-Deploying).
## Advanced Configuration
From bd352812593a9388a0f798cd5c5832c34dd2dcfc Mon Sep 17 00:00:00 2001
From: Alex
Date: Tue, 15 Sep 2026 23:20:02 +0100
Subject: [PATCH 02/10] fix: plain if in the installer's uv lookup (shellcheck
SC2015)
---
deployment/install.sh | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/deployment/install.sh b/deployment/install.sh
index 95ed1401..509a04a2 100755
--- a/deployment/install.sh
+++ b/deployment/install.sh
@@ -96,7 +96,9 @@ main() {
# uv installs and upgrades the package, and brings Python 3.12 when the system has none.
local uv="" candidate found
for candidate in "$(command -v uv 2>/dev/null || true)" "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv"; do
- [ -n "$candidate" ] && [ -x "$candidate" ] || continue
+ if [ -z "$candidate" ] || [ ! -x "$candidate" ]; then
+ continue
+ fi
found="$("$candidate" --version 2>/dev/null | awk '{print $2}')" || continue
if [ -n "$found" ] && version_ge "$found" "$UV_MIN_VERSION"; then
uv="$candidate"
From 49823a685992c064b4a77c9272418004d96bae04 Mon Sep 17 00:00:00 2001
From: Alex
Date: Tue, 15 Sep 2026 23:29:19 +0100
Subject: [PATCH 03/10] 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.
---
.github/workflows/docker-image-verify.yml | 4 +++-
deployment/install.ps1 | 2 +-
deployment/install.sh | 20 +++++++++++++++++---
docs/content/Deploying/Docker-Deploying.mdx | 11 +++++++++--
docs/content/quickstart.mdx | 2 +-
5 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/docker-image-verify.yml b/.github/workflows/docker-image-verify.yml
index a2825d52..9a9ad057 100644
--- a/.github/workflows/docker-image-verify.yml
+++ b/.github/workflows/docker-image-verify.yml
@@ -108,7 +108,9 @@ jobs:
# Same Compose project name as the step above; stop that stack first.
docker compose -f deployment/docker-compose-standalone.yaml down -v
pipx run build --wheel --outdir "$RUNNER_TEMP/dist"
- export DOCSGPT_PACKAGE="$(ls "$RUNNER_TEMP"/dist/docsgpt-*.whl)"
+ # Assigned before export, so a missing wheel fails here instead of installing from PyPI.
+ DOCSGPT_PACKAGE="$(ls "$RUNNER_TEMP"/dist/docsgpt-*.whl)"
+ export DOCSGPT_PACKAGE
# No uv is set up beforehand, so the installer's pinned uv download runs too.
# Without a terminal the installer passes --yes to docsgpt up.
bash deployment/install.sh --image-tag verify "$script"; then
+ rm -f "$script"
+ die "could not download $url"
+ fi
+ "$@" "$script" || status=$?
+ rm -f "$script"
+ return "$status"
+ }
# version_ge A B: A >= B for dotted version numbers.
version_ge() {
local -a left right
@@ -82,7 +96,7 @@ main() {
sudo="sudo"
fi
say "Installing Docker"
- download https://get.docker.com | $sudo sh
+ run_downloaded https://get.docker.com $sudo sh
$sudo systemctl enable --now docker >/dev/null 2>&1 || true
if [ -n "$sudo" ]; then
$sudo usermod -aG docker "$(id -un)"
@@ -108,7 +122,7 @@ main() {
if [ -z "$uv" ]; then
local uv_dir="${XDG_BIN_HOME:-$HOME/.local/bin}"
say "Installing uv $UV_VERSION into $uv_dir"
- download "https://astral.sh/uv/$UV_VERSION/install.sh" | env UV_INSTALL_DIR="$uv_dir" UV_NO_MODIFY_PATH=1 UV_PRINT_QUIET=1 sh
+ run_downloaded "https://astral.sh/uv/$UV_VERSION/install.sh" env UV_INSTALL_DIR="$uv_dir" UV_NO_MODIFY_PATH=1 UV_PRINT_QUIET=1 sh
uv="$uv_dir/uv"
[ -x "$uv" ] || die "uv did not install into $uv_dir"
fi
@@ -117,7 +131,7 @@ main() {
die "set DOCSGPT_VERSION or DOCSGPT_PACKAGE, not both"
fi
if [ -n "${DOCSGPT_PACKAGE:-}" ]; then
- say "Installing docsgpt from $DOCSGPT_PACKAGE"
+ say "Installing docsgpt from DOCSGPT_PACKAGE"
"$uv" tool install --reinstall --python 3.12 "$DOCSGPT_PACKAGE"
elif [ -n "${DOCSGPT_VERSION:-}" ]; then
say "Installing docsgpt $DOCSGPT_VERSION"
diff --git a/docs/content/Deploying/Docker-Deploying.mdx b/docs/content/Deploying/Docker-Deploying.mdx
index 400c0207..b4c5f87f 100644
--- a/docs/content/Deploying/Docker-Deploying.mdx
+++ b/docs/content/Deploying/Docker-Deploying.mdx
@@ -24,9 +24,16 @@ you. It needs Docker with Compose 2.24 or newer. The installer gets
[uv](https://docs.astral.sh/uv/), installs the package with it and runs
`docsgpt up`:
+macOS and Linux:
+
```bash
-curl -fsSL https://docs.ac/install | bash # macOS and Linux
-irm https://docs.ac/install.ps1 | iex # Windows (PowerShell)
+curl -fsSL https://docs.ac/install | bash
+```
+
+Windows (PowerShell):
+
+```powershell
+irm https://docs.ac/install.ps1 | iex
```
Both scripts are attached to every [release](https://github.com/arc53/DocsGPT/releases)
diff --git a/docs/content/quickstart.mdx b/docs/content/quickstart.mdx
index 63917cfb..3b134fe6 100644
--- a/docs/content/quickstart.mdx
+++ b/docs/content/quickstart.mdx
@@ -34,7 +34,7 @@ The installer:
* **Who should reach DocsGPT:** only this computer; other machines on your network (plain HTTP, with an access token); or a domain name with HTTPS (a certificate from Let's Encrypt, with an access token).
* **Which model provider:** the DocsGPT public API (no key needed), OpenAI, Anthropic, Google Gemini, OpenRouter, Groq, or an OpenAI-compatible server such as Ollama or vLLM.
-It then starts DocsGPT and prints its address, [http://localhost:7091](http://localhost:7091) for a local install. Settings and generated secrets are in `~/.docsgpt/server/.env`.
+It then starts DocsGPT and prints its address, [http://localhost:7091](http://localhost:7091) for a local install. Settings and generated secrets are in `~/.docsgpt/server/.env`, or `/opt/docsgpt/.env` when the installer runs as root on Linux; `docsgpt up --dir` or `DOCSGPT_HOME` choose another folder.
To read the script before running it, download it first: `curl -fsSL https://docs.ac/install -o install.sh`, then `bash install.sh`. On Windows: `irm https://docs.ac/install.ps1 -OutFile install.ps1`, then `.\install.ps1`.
From 6afce45913c0dd459d53094bcbc1535f6e2d3b48 Mon Sep 17 00:00:00 2001
From: Alex
Date: Wed, 16 Sep 2026 00:11:42 +0100
Subject: [PATCH 04/10] ci: the installer check requires non-empty secrets
---
.github/workflows/docker-image-verify.yml | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docker-image-verify.yml b/.github/workflows/docker-image-verify.yml
index 9a9ad057..fb8d3bb2 100644
--- a/.github/workflows/docker-image-verify.yml
+++ b/.github/workflows/docker-image-verify.yml
@@ -118,9 +118,10 @@ jobs:
stack="$HOME/.docsgpt/server"
"$docsgpt" status
curl -fsS http://127.0.0.1:7091/ | grep -q 'src="/config.js"'
- grep -q '^POSTGRES_PASSWORD=' "$stack/.env"
+ # The secrets must have values: an empty one would fall back to a default silently.
+ grep -Eq '^POSTGRES_PASSWORD=.+$' "$stack/.env"
# Running the installer again upgrades in place and keeps the generated secrets.
- before=$(grep '^JWT_SECRET_KEY=' "$stack/.env")
+ before=$(grep -E '^JWT_SECRET_KEY=.+$' "$stack/.env")
bash deployment/install.sh --image-tag verify
Date: Wed, 16 Sep 2026 00:20:49 +0100
Subject: [PATCH 05/10] ci: the installer check keeps both generated secrets
---
.github/workflows/docker-image-verify.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/docker-image-verify.yml b/.github/workflows/docker-image-verify.yml
index fb8d3bb2..468c0dc8 100644
--- a/.github/workflows/docker-image-verify.yml
+++ b/.github/workflows/docker-image-verify.yml
@@ -118,12 +118,12 @@ jobs:
stack="$HOME/.docsgpt/server"
"$docsgpt" status
curl -fsS http://127.0.0.1:7091/ | grep -q 'src="/config.js"'
- # The secrets must have values: an empty one would fall back to a default silently.
- grep -Eq '^POSTGRES_PASSWORD=.+$' "$stack/.env"
- # Running the installer again upgrades in place and keeps the generated secrets.
- before=$(grep -E '^JWT_SECRET_KEY=.+$' "$stack/.env")
+ # Both secrets must have values: an empty one falls back to a default silently.
+ secrets=$(grep -E '^(POSTGRES_PASSWORD|JWT_SECRET_KEY)=.+$' "$stack/.env" | sort)
+ [ "$(printf '%s\n' "$secrets" | wc -l)" -eq 2 ]
+ # Running the installer again upgrades in place and keeps both secrets.
bash deployment/install.sh --image-tag verify
Date: Wed, 16 Sep 2026 00:27:42 +0100
Subject: [PATCH 06/10] fix: the Windows installer fails when docsgpt up fails
---
deployment/install.ps1 | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/deployment/install.ps1 b/deployment/install.ps1
index 2eb2b71f..e8ea9a96 100644
--- a/deployment/install.ps1
+++ b/deployment/install.ps1
@@ -87,7 +87,8 @@ function Install-DocsGPT {
& $docsgpt up @UpArguments
if ($LASTEXITCODE -ne 0) {
- Write-Error "docsgpt up exited with code $LASTEXITCODE. Run it again after fixing the problem above: docsgpt up" -ErrorAction Continue
+ # throw, not Write-Error: the caller (and any automation) must see this fail.
+ throw "docsgpt up exited with code $LASTEXITCODE. Fix the problem above and run it again: docsgpt up"
}
}
From 0e1963552ce9bec19ebc06555c440ccc2dee79fc Mon Sep 17 00:00:00 2001
From: Alex
Date: Wed, 16 Sep 2026 00:35:35 +0100
Subject: [PATCH 07/10] ci: each generated secret must appear exactly once
---
.github/workflows/docker-image-verify.yml | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docker-image-verify.yml b/.github/workflows/docker-image-verify.yml
index 468c0dc8..552764c0 100644
--- a/.github/workflows/docker-image-verify.yml
+++ b/.github/workflows/docker-image-verify.yml
@@ -118,11 +118,18 @@ jobs:
stack="$HOME/.docsgpt/server"
"$docsgpt" status
curl -fsS http://127.0.0.1:7091/ | grep -q 'src="/config.js"'
- # Both secrets must have values: an empty one falls back to a default silently.
+ # Each secret must appear exactly once with a value: a missing or empty one
+ # falls back to a default silently.
+ check_secrets() {
+ for key in POSTGRES_PASSWORD JWT_SECRET_KEY; do
+ [ "$(grep -Ec "^$key=.+$" "$stack/.env")" -eq 1 ]
+ done
+ }
+ check_secrets
secrets=$(grep -E '^(POSTGRES_PASSWORD|JWT_SECRET_KEY)=.+$' "$stack/.env" | sort)
- [ "$(printf '%s\n' "$secrets" | wc -l)" -eq 2 ]
# Running the installer again upgrades in place and keeps both secrets.
bash deployment/install.sh --image-tag verify
Date: Wed, 16 Sep 2026 00:44:06 +0100
Subject: [PATCH 08/10] 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.
---
deployment/install.ps1 | 20 +++++++++++++++++---
deployment/install.sh | 35 +++++++++++++++++++++++++++++------
2 files changed, 46 insertions(+), 9 deletions(-)
diff --git a/deployment/install.ps1 b/deployment/install.ps1
index e8ea9a96..f62edc70 100644
--- a/deployment/install.ps1
+++ b/deployment/install.ps1
@@ -23,6 +23,9 @@ function Install-DocsGPT {
$ErrorActionPreference = 'Stop'
$UvVersion = '0.12.15'
$UvMinVersion = [version]'0.8.0'
+ # sha256 of https://astral.sh/uv/$UvVersion/install.ps1, checked before it runs. Bump it with
+ # $UvVersion: (Invoke-WebRequest "https://astral.sh/uv//install.ps1").Content | …
+ $UvInstallerSha256 = '63f2d7e2ccc347cc018127b22f56f569080e5730a390dcb702b654daf6822193'
function Say([string]$Message) { Write-Host "==> $Message" }
@@ -50,9 +53,20 @@ function Install-DocsGPT {
$env:UV_INSTALL_DIR = $uvDir
$env:UV_NO_MODIFY_PATH = '1'
$env:UV_PRINT_QUIET = '1'
- # A child PowerShell, so nothing the uv installer does can end this session.
- $shell = (Get-Process -Id $PID).Path
- & $shell -NoProfile -ExecutionPolicy Bypass -Command "irm https://astral.sh/uv/$UvVersion/install.ps1 | iex"
+ # Downloaded and checked before it runs, and run in a child PowerShell so nothing the
+ # uv installer does can end this session.
+ $installer = Join-Path ([System.IO.Path]::GetTempPath()) "uv-installer-$UvVersion.ps1"
+ try {
+ Invoke-WebRequest -Uri "https://astral.sh/uv/$UvVersion/install.ps1" -OutFile $installer -UseBasicParsing
+ $actual = (Get-FileHash -Path $installer -Algorithm SHA256).Hash.ToLower()
+ if ($actual -ne $UvInstallerSha256) {
+ throw "The uv installer does not match its pinned sha256: got $actual, expected $UvInstallerSha256. Refusing to run it."
+ }
+ $shell = (Get-Process -Id $PID).Path
+ & $shell -NoProfile -ExecutionPolicy Bypass -File $installer
+ } finally {
+ Remove-Item $installer -Force -ErrorAction SilentlyContinue
+ }
$uv = Join-Path $uvDir 'uv.exe'
if (-not (Test-Path $uv)) { throw "uv did not install into $uvDir" }
}
diff --git a/deployment/install.sh b/deployment/install.sh
index 018bb761..c7b81dee 100755
--- a/deployment/install.sh
+++ b/deployment/install.sh
@@ -20,6 +20,9 @@
UV_VERSION="0.12.15"
UV_MIN_VERSION="0.8.0"
+# sha256 of https://astral.sh/uv/$UV_VERSION/install.sh, checked before it runs. Bump it with
+# UV_VERSION: curl -fsSL https://astral.sh/uv//install.sh | shasum -a 256
+UV_INSTALLER_SHA256="716a1d6844740756c68770fcec2f79c2013fb9b03869a113f61e15f6f482a6a1"
main() {
set -euo pipefail
@@ -47,16 +50,33 @@ main() {
die "curl or wget is needed to download $1"
fi
}
- # run_downloaded URL COMMAND...: save URL to a file, then run COMMAND with the file as its last
- # argument. A transfer cut short fails before anything runs.
+ sha256_of() {
+ if has shasum; then
+ shasum -a 256 "$1" | awk '{print $1}'
+ elif has sha256sum; then
+ sha256sum "$1" | awk '{print $1}'
+ else
+ die "neither shasum nor sha256sum is available to check $1"
+ fi
+ }
+ # run_downloaded URL SHA256 COMMAND...: save URL to a file, check it against SHA256 ("-" to
+ # skip), then run COMMAND with the file as its last argument. A transfer cut short, or content
+ # that does not match, fails before anything runs.
run_downloaded() {
- local url="$1" script status=0
- shift
+ local url="$1" expected="$2" script status=0 actual
+ shift 2
script="$(mktemp)"
if ! download "$url" >"$script"; then
rm -f "$script"
die "could not download $url"
fi
+ if [ "$expected" != "-" ]; then
+ actual="$(sha256_of "$script")"
+ if [ "$actual" != "$expected" ]; then
+ rm -f "$script"
+ die "$url does not match its pinned sha256: got $actual, expected $expected. Refusing to run it."
+ fi
+ fi
"$@" "$script" || status=$?
rm -f "$script"
return "$status"
@@ -96,7 +116,9 @@ main() {
sudo="sudo"
fi
say "Installing Docker"
- run_downloaded https://get.docker.com $sudo sh
+ # Docker's script changes over time and publishes no checksum, so it is only downloaded
+ # in full before it runs.
+ run_downloaded https://get.docker.com - $sudo sh
$sudo systemctl enable --now docker >/dev/null 2>&1 || true
if [ -n "$sudo" ]; then
$sudo usermod -aG docker "$(id -un)"
@@ -122,7 +144,8 @@ main() {
if [ -z "$uv" ]; then
local uv_dir="${XDG_BIN_HOME:-$HOME/.local/bin}"
say "Installing uv $UV_VERSION into $uv_dir"
- run_downloaded "https://astral.sh/uv/$UV_VERSION/install.sh" env UV_INSTALL_DIR="$uv_dir" UV_NO_MODIFY_PATH=1 UV_PRINT_QUIET=1 sh
+ run_downloaded "https://astral.sh/uv/$UV_VERSION/install.sh" "$UV_INSTALLER_SHA256" \
+ env UV_INSTALL_DIR="$uv_dir" UV_NO_MODIFY_PATH=1 UV_PRINT_QUIET=1 sh
uv="$uv_dir/uv"
[ -x "$uv" ] || die "uv did not install into $uv_dir"
fi
From d993aaced0cc11d7fa3fb1428bf65a7536c45332 Mon Sep 17 00:00:00 2001
From: Alex
Date: Wed, 16 Sep 2026 00:57:43 +0100
Subject: [PATCH 09/10] 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.
---
deployment/install.ps1 | 4 ++++
deployment/install.sh | 13 +++++++++++--
2 files changed, 15 insertions(+), 2 deletions(-)
diff --git a/deployment/install.ps1 b/deployment/install.ps1
index f62edc70..5b900eb3 100644
--- a/deployment/install.ps1
+++ b/deployment/install.ps1
@@ -64,6 +64,10 @@ function Install-DocsGPT {
}
$shell = (Get-Process -Id $PID).Path
& $shell -NoProfile -ExecutionPolicy Bypass -File $installer
+ if ($LASTEXITCODE -ne 0) {
+ # Without this, a uv.exe left behind by an older install would be accepted below.
+ throw "The uv installer exited with code $LASTEXITCODE."
+ }
} finally {
Remove-Item $installer -Force -ErrorAction SilentlyContinue
}
diff --git a/deployment/install.sh b/deployment/install.sh
index c7b81dee..2eac7ee6 100755
--- a/deployment/install.sh
+++ b/deployment/install.sh
@@ -50,6 +50,14 @@ main() {
die "curl or wget is needed to download $1"
fi
}
+ # POSIX single-quote encoding: what needs quoting is decided here, not by the shell that runs it.
+ shell_quote() {
+ local arg out=""
+ for arg in "$@"; do
+ out="$out'$(printf '%s' "$arg" | sed "s/'/'\\\\''/g")' "
+ done
+ printf '%s' "$out"
+ }
sha256_of() {
if has shasum; then
shasum -a 256 "$1" | awk '{print $1}'
@@ -182,9 +190,10 @@ main() {
if [ "$docker_group_pending" = 1 ]; then
if has sg; then
- # The docker group applies to new logins; sg gives it to this command now.
+ # The docker group applies to new logins; sg gives it to this command now. sg runs the
+ # command with /bin/sh, which need not be bash, so quote for POSIX sh rather than with %q.
local command
- command="$(printf '%q ' "$docsgpt" up "$@")"
+ command="$(shell_quote "$docsgpt" up "$@")"
if have_tty; then
exec sg docker -c "$command
Date: Wed, 16 Sep 2026 14:23:26 +0100
Subject: [PATCH 10/10] chore: 0.21.0
---
docs/content/changelog.mdx | 2 +-
docsgpt/version.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/content/changelog.mdx b/docs/content/changelog.mdx
index 13cdc752..6e59a1f5 100644
--- a/docs/content/changelog.mdx
+++ b/docs/content/changelog.mdx
@@ -11,7 +11,7 @@ The notable changes in each release. Every release on GitHub also carries
[auto-generated notes](https://github.com/arc53/DocsGPT/releases) listing every merged pull
request, and [Upgrading](/upgrading) covers the steps an existing deployment has to take.
-## Unreleased
+## 0.21.0
### Install with one command
diff --git a/docsgpt/version.py b/docsgpt/version.py
index c6e10fde..c327b39b 100644
--- a/docsgpt/version.py
+++ b/docsgpt/version.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-__version__ = "0.20.0"
+__version__ = "0.21.0"
def get_version() -> str: