6 Commits
Author SHA1 Message Date
Alex 4976b3104c fix: give each native install its own services, and guard the port
From the outside-diff findings on #2800:

- Service names are derived from the install directory. A service manager has
  one namespace per user, so two installs in different --dir directories wrote
  over each other's units and down, status and uninstall acted on whichever was
  written last. The default install keeps the readable names; another directory
  gets a digest suffix.
- `up --native` refuses when a Docker stack in another directory publishes the
  same port: its API would answer the health check while these services failed
  to bind. The check degrades quietly when Docker is absent, which is exactly
  the machine a native install targets.
- A Redis database path is required to be ASCII digits: str.isdigit() is true
  for characters int() then refuses.
- DOCSGPT_PORT from a hand-edited .env is validated before conversion, and the
  error names where the bad value came from.
- Percent signs are doubled in systemd values, arguments and log paths, since
  systemd expands specifiers in all of them.
2026-09-16 23:51:20 +01:00
Alex 76d81caaa4 fix: refuse control characters in the values of a service file
Quoting cannot carry a newline into a unit file or a plist: the line ends and
whatever follows becomes another directive. Every value bound for a service
file — the working directory, the log path, environment names and values, and
the command arguments — is checked before any of it is rendered, on both
launchd and systemd.
2026-09-16 23:10:53 +01:00
Alex 4d9f1d47a9 fix: make a native install recoverable, honest and safe to quote
From the outside-diff findings on #2800:

- install.json is written before the services are installed and started. A
  service that fails to start used to leave units behind in a directory that
  status, down and uninstall no longer recognised as a native install, so
  nothing could clean them up.
- systemd stop and removal propagate failures: `down` reporting success while
  the unit still runs, or `uninstall` dropping the unit file and the record
  while systemd still runs the service, is worse than an error. Removing a unit
  that is already gone stays harmless.
- WorkingDirectory and each Environment value are quoted and escaped for
  systemd. `--dir` takes a free-form path, and one with a space in it is not
  hypothetical: this checkout lives in one.
- Native status checks and prints http://localhost:<port>, which is what the
  units bind. With a LAN DOCSGPT_BIND it used to poll an address nothing
  listened on and call a healthy install dead.
2026-09-16 22:58:52 +01:00
Alex a96734da07 fix: run the module when the docsgpt command is not on PATH
Refusing to write the service units when `docsgpt` is not on PATH was wrong. A
package installed in a virtualenv is runnable whether or not its console script
is on PATH, and CI runs pytest as `python -m pytest`, where argv[0] is a module
file: the refusal failed thirteen native tests there.

The launcher now prefers the command on PATH, resolved to an absolute path
since PATH can hold relative entries, then an argv[0] that can be executed, and
otherwise this interpreter with `-m docsgpt`, which works wherever the package
is importable. `python -m docsgpt` became an entrypoint of its own and has a
test that runs it.
2026-09-16 22:45:54 +01:00
Alex f9f52e99f0 fix: make a native install take effect on systemd and stay out of Docker's way
From review of #2800:

- systemd `enable --now` starts nothing when the unit is already active, so a
  second `up --native` kept the old ExecStart and left the API on its previous
  port. start enables and then restarts, as the launchd path already did by
  booting the job out first.
- An explicit `home` now wins over XDG_CONFIG_HOME, which is what callers pass
  it for.
- The ExecStart program must be a real executable: when `docsgpt` is not on
  PATH, sys.argv[0] is accepted only if it can be run, and otherwise the
  failure is raised before any unit is written.
- `up --native` over a directory holding a Docker install now refuses and says
  how to proceed, instead of starting native services beside containers that
  down, status and uninstall would no longer see.
- Docs: without a terminal only --postgres-uri is required, and the Windows
  fallback names `docsgpt beat`, which the worker cannot embed there.

SystemdServices was the least covered part of the module and cannot be run on
this machine, so it now has tests for install, start, stop, remove, is_running
and a failing systemctl.
2026-09-16 22:35:14 +01:00
Alex f23a32d9c5 feat: docsgpt up --native
Run DocsGPT without Docker: the API and the worker each become a service
on the machine itself, a launchd agent on macOS and a systemd user unit
on Linux, pointed at a PostgreSQL and a Redis that already run.

`docsgpt up --native --postgres-uri ... --redis-url ...` writes the same
.env a Docker install uses, applies the migrations and starts both
services. status, logs, down and uninstall work on a native install the
same way they do on a Docker one, and never touch the database or Redis:
they were the user's to begin with.

One Redis URL covers the broker, the result backend and the cache on
three consecutive databases, starting at the one the URL names, so a
Redis that already holds something else can be shared.

Windows has neither service manager, so native mode refuses it and says
what to do instead.
2026-09-16 21:51:43 +01:00