# Installing DocWright

DocWright installs as **one cohesive, isolated unit** under a single prefix
(default `/opt/docwright`), owned by a dedicated system user, with its own
PostgreSQL database and role, its own PHP-FPM master, and namespaced systemd
units on configurable ports. It **never mutates shared system config** — it
ships templates that the installer renders into the prefix — and it provides a
matching `uninstall.sh`.

This guide reflects the actual behaviour of `install.sh`, `uninstall.sh`,
`bootstrap-vendor.sh`, and the `Makefile`.

---

## 1. Prerequisites

| Component | Version | Notes |
|---|---|---|
| **PHP** | 8.3+ (8.4 recommended) | The systemd unit invokes `php-fpm8.4`; `doctor` requires ≥ 8.3. Needs extensions `pdo_pgsql`, `mbstring`, `json`, `openssl`, `sodium` (required) and `gd`, `zip`, `intl` (recommended). |
| **Python** | 3.11+ | For the worker venv (rendering, imaging, conversion, BIM, NLP). Created by `bootstrap-vendor.sh`. |
| **PostgreSQL** | 16+ | A dedicated database + role are created by the installer; reached over the local unix socket with peer auth. |
| **nginx** | current | Reverse proxy in front of the dedicated PHP-FPM socket. Sample vhost in `ops/nginx/`. (Caddy also works — bring your own vhost.) |
| **git** | current | The per-project sovereign store shells out to the `git` CLI. |
| **certbot** | current | For TLS via DNS-01 (see [§6](#6-tls-via-dns-01-cloudns)). |

The heavy render/convert tools (Chromium, Tectonic, Pandoc, LibreOffice, Noto
fonts, Hunspell, libvips) are **bundled** into `vendor-bin/` by
`bootstrap-vendor.sh` and are used in preference to any system copies; `doctor`
also accepts a system-installed equivalent if a bundled one is absent. See
[`THIRD_PARTY.md`](../THIRD_PARTY.md).

Verify your environment at any point with:

```bash
php bin/console doctor
```

`doctor` checks the PHP version and extensions, `DOCWRIGHT_APP_KEY` /
`DOCWRIGHT_SECRET_KEY`, writable data/log dirs, PostgreSQL reachability, the
migrations table, and the presence + executability of each render tool
(preferring `vendor-bin/`). It reports precisely what to fix and never fails
silently; its exit code is non-zero only if a **required** check fails.

---

## 2. The install flow at a glance

```
git clone …                    # get the code
./bootstrap-vendor.sh          # Python venv + pinned vendor-bin/ binaries (NFR-8)
sudo ./install.sh              # isolated user + DB/role + PHP-FPM + systemd + migrate
# (installer generates .env for you; edit if needed)
php bin/console doctor         # verify
make nginx  DOMAIN=…           # render + enable the reverse-proxy vhost
make cert   DOMAIN=…           # obtain TLS via DNS-01
bin/console user:create admin --role=admin   # first admin
```

For **development** you can skip the system install entirely — see
[§8](#8-development-mode).

---

## 3. Bootstrap the bundled runtime (`bootstrap-vendor.sh`)

```bash
./bootstrap-vendor.sh
```

This step (safe to re-run):

1. Creates an isolated **Python worker venv** at `./.venv` (no global pip) and
   installs `workers/requirements.txt` if present (WeasyPrint, pikepdf,
   openpyxl, …).
2. Fetches **pinned, checksum-verified** heavy binaries into `vendor-bin/` —
   Chrome-for-Testing/Chromium (primary PDF via Paged.js), Tectonic (LaTeX),
   Pandoc (DOCX), a portable headless LibreOffice (DOCX fidelity), Noto fonts,
   and Hunspell dictionaries. Exact versions and SHA-256 hashes live in
   `vendor-bin/MANIFEST.json`; each artifact is downloaded only if absent and
   the checksum **must** match.

The running app uses these bundled copies exclusively and never mutates
system-wide equivalents (upholds isolation in both directions). An offline /
air-gapped install is supported by shipping a pre-populated release bundle so no
network access is needed at install time.

> In this scaffold the heavy-binary step documents the intended components and
> reads its pins from `vendor-bin/MANIFEST.json`; wire real pinned URLs/hashes
> there for a production release.

---

## 4. Run the installer (`install.sh`)

```bash
sudo ./install.sh [--prefix DIR] [--domain HOST] [--db NAME] [--user NAME]
```

Defaults: `--prefix /opt/docwright`, `--domain docwright.jsp.net.in`,
`--db docwright`, `--user docwright`. The installer is idempotent and must run as
root. It performs, in order:

1. **Dedicated system user** (`docwright`, `--system`, `nologin`) if missing.
2. **Deploys code** to the prefix via `rsync` (excluding `.git`, `var/*`,
   `.env`); creates `var/log`, `var/run`, `var/cache`, and `data/`.
3. **Dedicated PostgreSQL role + database** owned by that role, and grants on the
   `public` schema. (No shared tables; peer auth via the service user.)
4. **Generates `.env`** from `.env.example` if none exists, filling in
   production defaults, the prefix-derived paths, a socket-based passwordless
   peer DSN, and freshly generated `DOCWRIGHT_APP_KEY` and
   `DOCWRIGHT_SECRET_KEY` (32-byte, base64). The file is `chmod 640`.
5. **Renders the dedicated PHP-FPM master config** from
   `ops/php-fpm/php-fpm.conf.template` into `<prefix>/runtime/php-fpm.conf`, and
   the systemd unit from `ops/systemd/docwright-web.service` into
   `/etc/systemd/system/`.
6. Sets **ownership** (`var/` and `data/` to the service user; `.env` to
   `root:docwright`).
7. **Applies migrations + seed** (`bin/console migrate`, run as the service
   user) — core schema then any plugin migrations.
8. **Starts** `docwright-web.service` (a complete standalone PHP-FPM master, on
   the unix socket `/run/docwright/docwright.sock`).
9. Renders the optional `docwright-worker@` / `docwright-realtime` unit templates
   if present, and runs `doctor`.

It finishes by printing the next steps (DNS, TLS, nginx, create admin).

### How the isolation works

- **Dedicated user** `docwright` owns the prefix; nothing runs as a shared web
  user.
- **Dedicated DB + role** `docwright` — all objects in the app's own database,
  reached over `pgsql:host=/var/run/postgresql;dbname=docwright` (no password;
  peer auth).
- **Dedicated PHP-FPM master** — `ops/php-fpm/php-fpm.conf.template` is a full
  standalone FPM master (not a pool added to the system php-fpm), with its own
  pid/log, its own socket `/run/docwright/docwright.sock` (mode `0660`, group
  `www-data`), bounded `pm.max_children = 12`, and `request_terminate_timeout`.
  Nothing under `/etc/php` is touched.
- **systemd unit** `docwright-web.service` runs `php-fpm8.4 --fpm-config
  <prefix>/runtime/php-fpm.conf` with hardening (`NoNewPrivileges`,
  `ProtectSystem=full`, `PrivateTmp`, `ReadWritePaths` limited to
  `var/`, `data/`, `/run/docwright`). Planned `docwright-realtime` and
  `docwright-worker@` units follow the same namespaced pattern on **configurable**
  ports (e.g. `DOCWRIGHT_REALTIME_PORT=8807`).
- **No global config mutation** — the installer only renders templates into the
  prefix and drops unit files; the nginx vhost is enabled explicitly by you via
  `make nginx`.

---

## 5. Configure `.env`

The installer generates `.env`; edit it to taste. Every knob is documented in
`.env.example`. Key groups:

- **Application** — `DOCWRIGHT_ENV`, `DOCWRIGHT_DEBUG`, `DOCWRIGHT_URL`,
  `DOCWRIGHT_PREFIX`.
- **Paths** — `DOCWRIGHT_DATA_DIR` (per-project Git repos), `DOCWRIGHT_VAR_DIR`,
  `DOCWRIGHT_VENDOR_BIN`.
- **Database** — `DOCWRIGHT_DB_DSN`, `DOCWRIGHT_DB_USER`, `DOCWRIGHT_DB_PASSWORD`
  (empty for peer auth), `DOCWRIGHT_DB_SCHEMA`.
- **Realtime** — `DOCWRIGHT_REALTIME_HOST/PORT/PUBLIC_PATH` (default port
  `8807`, non-well-known).
- **Workers** — `DOCWRIGHT_WORKER_CONCURRENCY`, `DOCWRIGHT_JOB_NICE`,
  `DOCWRIGHT_JOB_IONICE_CLASS`.
- **Sessions (§14.7)** — cookie name, idle timeout (default 3600s), absolute cap
  (43200s), `Secure`/`SameSite`, remember-me TTL, step-up TTL.
- **Secrets** — `DOCWRIGHT_SECRET_KEY` (AEAD key for encrypting per-user
  integration creds), `DOCWRIGHT_APP_KEY` (session/CSRF signing).
- **Argon2id** — memory/time/threads for password hashing.
- **Mail** — optional SMTP for password reset / notifications.
- **Quotas** — default priority tier, max concurrency, storage.

Regenerate the two keys at any time with:

```bash
php bin/console key:generate --write   # writes APP_KEY + SECRET_KEY into .env
```

---

## 6. TLS via DNS-01 (ClouDNS)

The host uses Let's Encrypt with a **DNS-01** challenge (works without exposing
port 80 to the validator and supports wildcard-style setups). Hooks are in
`ops/cloudns/`:

- `docwright-cloudns-auth.sh` — publishes the `_acme-challenge` TXT via the
  ClouDNS API, then polls `1.1.1.1`, `8.8.8.8`, `9.9.9.9` until they all see it
  (Let's Encrypt uses multi-perspective validation).
- `docwright-cloudns-cleanup.sh` — removes the challenge TXT afterwards.

Credentials come from env (`CLOUDNS_AUTH_ID`, `CLOUDNS_AUTH_PW`, `CLOUDNS_ZONE`).
The `Makefile` `cert` target wires them into certbot:

```bash
make cert DOMAIN=docwright.jsp.net.in
# → certbot certonly --manual --preferred-challenges dns \
#     --manual-auth-hook   ops/cloudns/docwright-cloudns-auth.sh \
#     --manual-cleanup-hook ops/cloudns/docwright-cloudns-cleanup.sh \
#     -d <DOMAIN> --key-type ecdsa
```

Then install the reverse-proxy vhost:

```bash
make nginx DOMAIN=docwright.jsp.net.in PREFIX=/opt/docwright
```

The `nginx` target renders `ops/nginx/docwright.conf.template` (substituting
`@DOMAIN@`, `@PREFIX@`, `@SOCK@`), symlinks it into `sites-enabled`, runs
`nginx -t`, and reloads. The vhost serves `/css`, `/js`, `/assets`, `/docs`
straight from disk, throttles the login POST, proxies `/realtime` to the CRDT
WebSocket service, and routes everything else through the front controller on
the DocWright FPM socket. It references
`/etc/letsencrypt/live/<domain>/` for the certificate.

---

## 7. Create the first admin

```bash
cd /opt/docwright
sudo -u docwright php bin/console user:create admin --role=admin
```

`user:create <username> [--email=] [--name=] [--role=editor] [--password=]`
creates the user (prompting for a password on a TTY if `--password` is omitted;
minimum 8 characters) and assigns the role. Related commands:

- `bin/console user:list` — list users and their roles.
- `bin/console role:assign <username> <role> [--remove]` — grant/revoke a role.

The five built-in system roles are `admin`, `manager`, `editor`, `reviewer`,
`viewer` (seeded by `migrations/0010_bootstrap_data.sql`, deny-by-default).

---

## 8. Development mode

No system install needed — run the built-in PHP dev server against a local
Postgres:

```bash
cp .env.example .env
php bin/console key:generate --write     # APP_KEY + SECRET_KEY
# edit .env DB settings for your local Postgres
php bin/console migrate                  # apply schema + seed
php bin/console doctor                    # preflight
php bin/console serve 127.0.0.1:8080      # http://127.0.0.1:8080
```

`serve` runs PHP's built-in server (`php -S`) with docroot `app/public`.
`make serve` is the shortcut.

---

## 9. Useful commands

| Command | Purpose |
|---|---|
| `php bin/console migrate` | Apply pending migrations (core + plugins) |
| `php bin/console migrate:status` | Show applied vs pending migrations |
| `php bin/console doctor` | Environment preflight |
| `php bin/console key:generate [--write]` | Generate APP_KEY + SECRET_KEY |
| `php bin/console plugin:list` | List discovered plugins and load status |
| `php bin/console plugin:enable\|disable <id>` | Toggle a plugin (persisted in `plugins`) |
| `php bin/console serve [addr]` | Dev server |
| `make install \| uninstall \| nginx \| cert \| migrate \| doctor \| serve \| lint \| test \| deploy` | Operator entry points |

`make deploy` rsyncs updated code to the prefix (excluding `.git`, `runtime`,
`var`, `data`, `.env`), re-runs migrations, and reloads `docwright-web.service`.

---

## 10. Uninstall

```bash
sudo ./uninstall.sh [--prefix DIR] [--domain HOST] [--db NAME] [--purge]
```

By default it stops and removes the systemd units, removes the nginx vhost, and
deletes the app/runtime/vendor-bin code — but **keeps the database and
`data/`** (your projects). Pass `--purge` to also drop the database and role,
remove the entire prefix, and delete the service user. TLS certificates under
`/etc/letsencrypt` are always left intact.

---

## 11. Backups & ops

- **Database** — `pg_dump docwright` (all app state and indexes).
- **Projects** — back up `DOCWRIGHT_DATA_DIR` (each project is a self-contained
  Git repo; you can `git clone` any of them elsewhere and still own the work).
- **Logs** — under `<prefix>/var/log` (`docwright.log`, `php-fpm.log`,
  `php-error.log`); rotate as usual.
- **Health** — the app serves `/healthz` (liveness) via `HealthController` for
  readiness checks behind the proxy.
