diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index a7d4f94..6986bf9 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,6 +1,6 @@ name: e2e -# Tier 3: Playwright against the seeded dev stack (docs/development.md). +# Tier 3: Playwright against the seeded dev stack (docs/internals/development.md). # Always on main and on demand; on pull requests only where the change can # affect the browser flow. on: diff --git a/README.md b/README.md index a5e46c6..f3f2b96 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ Collection; a Document author fills structured fields in a leading - **injector**: a small reverse proxy in front of Outline that adds a "Download PDF" button without forking Outline. -See [CONTEXT.md](CONTEXT.md) for the vocabulary and -[docs/adr](docs/adr) for architecture decisions. +See the [vocabulary](CONTEXT.md) and the +[architecture decision records](docs/adr/README.md). ## Quick start @@ -44,14 +44,15 @@ task e2e:run # optional: the browser tests against that stack ``` The log-in, the seed data and what to click are described in -[docs/development.md](docs/development.md). To try only the renderer, see +[Development stack and end-to-end tests](docs/internals/development.md). To try only the renderer, see "Development" below. ## Documentation -Start at the index, [docs/README.md](docs/README.md). Template authors begin -with the [Template author guide](docs/template-author-guide.md). The research -behind the design is in [docs/research](docs/research/README.md). +The docs are at and in [docs/](docs/README.md), in +five sections: Write, Configure, Deploy, Templates and Internals. Template authors +begin with the [Template author guide](docs/templates/template-author-guide.md). The research +behind the design is in the [research notes](docs/research/README.md). ## Status @@ -76,7 +77,7 @@ task e2e:run # tier 3: browser tests against that stack ``` The local stack, its login, the seeding and the end-to-end tests are described -in [docs/development.md](docs/development.md). +in [Development stack and end-to-end tests](docs/internals/development.md). Render a letter with the shipped Template and Configuration: @@ -97,31 +98,30 @@ docker run --rm outprint validate # the packages shipped in the outprint validate --templates-root ./templates --configs-root ./configurations ``` -See [docs/metadata.md](docs/metadata.md) for the Metadata Block authors write -(syntax, layering, field types, errors), -[docs/packages.md](docs/packages.md) for the package format, field types, -typed patterns, the TeX deny-list, the drift guard and `validate`, and -[docs/standard-fields.md](docs/standard-fields.md) for the Standard Fields. -See [docs/images.md](docs/images.md) for images: formats, sizes, captions, -limits, error kinds and the `imageHosts` setting. -See [docs/binding.md](docs/binding.md) for the `binding:` block a -Collection admin uses to choose the Template and Configuration. -See [docs/worker.md](docs/worker.md) for the Worker pipeline: phases, filters, -engine lockdown, errors, logging and the debug switch. -See [docs/architecture.md](docs/architecture.md) for the Gateway and Worker -roles, the job bundle, the request flow and the error mapping, and -[docs/deploy-compose.md](docs/deploy-compose.md) for running both with Docker -Compose and the Worker security baseline. -See [docs/injector.md](docs/injector.md) for the injector, the download button, -compression and CSP handling, routing and the security notes. -See [docs/security.md](docs/security.md) for the security layers, what each -proves, the limits and how to run the suite, and -[docs/testing.md](docs/testing.md) for the test tiers and the CI schedule. -See [docs/limits.md](docs/limits.md) for the limits and admission control -(slots, queue, per-user limit, and how to configure them). -See [docs/sanitize.md](docs/sanitize.md) for the sanitisation gate and the -pre-publish audit, and [docs/publishing.md](docs/publishing.md) for the -maintainer's checklist. +The reference for each part is in the docs: the [Metadata Block reference](docs/templates/metadata-reference.md), +[Packages](docs/templates/packages.md) (format, field types, typed patterns, +the TeX deny-list, the drift guard and `validate`) and +[Standard Fields](docs/templates/standard-fields.md); [Images in the pipeline](docs/internals/images.md); +[Choose a Template and Configuration](docs/configure/choose-a-template.md) for the +`binding:` block of a Collection admin; the [Worker pipeline](docs/internals/worker.md); +[Architecture](docs/internals/architecture.md); [Docker Compose](docs/deploy/docker-compose.md) +with the Worker security baseline; the [Injector and download button](docs/deploy/injector.md); +[Security](docs/internals/security.md) and [Testing](docs/testing.md) for the test tiers +and the CI schedule; [Limits and admission control](docs/deploy/limits-and-admission.md); +and, for the maintainer, the [Sanitisation gate](docs/sanitize.md) and the +[Publishing checklist](docs/publishing.md). + +### Documentation site + +The pages in `docs/` are also published as a site (`website/`, Docusaurus). + +```sh +task docs:build # strict build of the site (broken links fail) +task docs:catalogue # regenerate the Template catalogue pages from the packages +task docs:catalogue-check # fail if the generated catalogue pages are out of date +task docs:catalogue-preview # re-render the catalogue preview images (needs Docker) +task docs:errors-check # fail if the error catalogue and the source codes differ +``` ## License diff --git a/THIRD_PARTY_NOTICES b/THIRD_PARTY_NOTICES index dad5d7c..1028e8a 100644 --- a/THIRD_PARTY_NOTICES +++ b/THIRD_PARTY_NOTICES @@ -218,9 +218,9 @@ and similar licences; each is listed with its licence in the image. file, fontconfig resolved the generic families `sans-serif`, `serif`, `monospace` to TeX Gyre Adventor and `Helvetica` to TeX Gyre Heros, so SVG text is drawn with TeX Gyre fonts of section 5.2, unless the SVG's font name - matches an installed font (see docs/images.md). No proprietary font is + matches an installed font (see docs/internals/images.md). No proprietary font is shipped. Do not add fonts to Templates or Configurations unless their licence - allows redistribution; see docs/template-author-guide.md. + allows redistribution; see docs/templates/template-author-guide.md. 6. Go dependencies @@ -261,7 +261,7 @@ modules were checked by hand.) The browser test suite (e2e/package.json) uses @playwright/test, pdfjs-dist, typescript and @types/node as development dependencies. They are installed -from npm by `make e2e-install`, are not part of any image or release, and are +from npm by `task e2e:install`, are not part of any image or release, and are licensed under Apache-2.0 (Playwright, pdf.js, TypeScript) and MIT (@types/node), as their packages state; not re-verified in this run. @@ -269,7 +269,7 @@ licensed under Apache-2.0 (Playwright, pdf.js, TypeScript) and MIT 7. Development and end-to-end test stack (not distributed) ---------------------------------------------------------- -`make dev` and `make e2e` start these images from public registries on the +`task dev` and `task e2e:run` start these images from public registries on the developer's or the CI machine, from deploy/dev/versions.env. This repository contains only configuration files for them. They are separate containers that talk to our code over HTTP and OIDC. They are not copied into, built into or diff --git a/Taskfile.yml b/Taskfile.yml index 2a0d80a..fe02df7 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -98,7 +98,7 @@ tasks: if: '! command -v node >/dev/null 2>&1' screenshots: - desc: Regenerate the documentation screenshots (needs `task dev`; see docs/development.md) + desc: Regenerate the documentation screenshots (needs `task dev`; see docs/internals/development.md) cmds: - task: e2e:screenshots @@ -151,7 +151,7 @@ tasks: desc: Security layers 2 and 3 (needs a Docker daemon) summary: | The Worker image run with the flags of the shipped Compose file against - a hostile pipeline (docs/security.md). The "sandbox" image stage holds + a hostile pipeline (docs/internals/security.md). The "sandbox" image stage holds the test-only probe; the release image must not, and the suite checks that. The log is always shown before the skip guard runs. cmds: diff --git a/cmd/sandbox-probe/main.go b/cmd/sandbox-probe/main.go index f31b0cf..bcd3bfa 100644 --- a/cmd/sandbox-probe/main.go +++ b/cmd/sandbox-probe/main.go @@ -1,7 +1,7 @@ //go:build linux // Command sandbox-probe is the test-only adversary of the sandbox security -// suite (docs/security.md, layers 2 and 3). It runs hostile actions as the +// suite (docs/internals/security.md, layers 2 and 3). It runs hostile actions as the // Worker user, inside the container flags the shipped Compose file sets, and // reports what was contained. It is built into the "sandbox" image stage // only; the release image must not contain it. diff --git a/cmd/seed/main.go b/cmd/seed/main.go index f0e5d80..ef04164 100644 --- a/cmd/seed/main.go +++ b/cmd/seed/main.go @@ -1,6 +1,6 @@ // Command seed provisions the development stack: the fictional user, a bound // and an unbound Collection, each with a Document. `task dev` and `task e2e:run` -// both run it. See docs/development.md. +// both run it. See docs/internals/development.md. package main import ( diff --git a/config/service.yaml b/config/service.yaml index 392254a..e7c4793 100644 --- a/config/service.yaml +++ b/config/service.yaml @@ -16,11 +16,11 @@ defaultBinding: # Hosts besides the Outline origin that an image redirect may lead to, for # example the file storage behind Outline's attachments. Empty by default: # only the Outline origin is allowed. A host name matches ports 80 and 443; -# write host:port for another port. See docs/images.md. +# write host:port for another port. See docs/deploy/image-fetching.md. # imageHosts: # - files.example.invalid -# Admission control of the Gateway (docs/limits.md). One Worker runs one job, +# Admission control of the Gateway (docs/deploy/limits-and-admission.md). One Worker runs one job, # so keep slots equal to the number of Worker replicas. Every key is optional; # the values below are the defaults. Environment variables override the file: # OUTPRINT_ADMISSION_SLOTS, _QUEUE_DEPTH, _PER_USER and _MAX_WAIT. diff --git a/deploy/compose/compose.yaml b/deploy/compose/compose.yaml index 3dae531..17bd0b8 100644 --- a/deploy/compose/compose.yaml +++ b/deploy/compose/compose.yaml @@ -1,10 +1,10 @@ -# Gateway and Worker from one image (docs/deploy-compose.md, ADR 0001). +# Gateway and Worker from one image (docs/deploy/docker-compose.md, ADR 0001). # # OUTLINE_URL=https://wiki.example.test docker compose \ # -f deploy/compose/compose.yaml up --build # # The worker service block is the shipped Worker baseline. Security tests -# read it (see docs/deploy-compose.md): weaken it and the build breaks. +# read it (see docs/deploy/docker-compose.md): weaken it and the build breaks. name: outprint @@ -70,7 +70,7 @@ services: # Required: the base URL of your Outline. The gateway refuses to start # without it. OUTLINE_URL: ${OUTLINE_URL:-} - # Admission control (docs/limits.md). Empty keeps the service + # Admission control (docs/deploy/limits-and-admission.md). Empty keeps the service # configuration's value. Scale the slots with the Worker replicas. OUTPRINT_ADMISSION_SLOTS: ${OUTPRINT_ADMISSION_SLOTS:-} OUTPRINT_ADMISSION_QUEUE_DEPTH: ${OUTPRINT_ADMISSION_QUEUE_DEPTH:-} @@ -102,7 +102,7 @@ services: retries: 3 start_period: 5s - # The injector sits in front of Outline (docs/injector.md). It is a + # The injector sits in front of Outline (docs/deploy/injector.md). It is a # separate, slim image without the TeX toolchain and is not part of the # Worker baseline. injector: diff --git a/deploy/dev/Taskfile.yml b/deploy/dev/Taskfile.yml index b29f06c..89bd186 100644 --- a/deploy/dev/Taskfile.yml +++ b/deploy/dev/Taskfile.yml @@ -1,6 +1,6 @@ # https://taskfile.dev # -# Development stack (docs/development.md): Outline with login, the injector, +# Development stack (docs/internals/development.md): Outline with login, the injector, # Gateway and Worker, seeded with a fictional user, a bound and an unbound # Collection. DEV ONLY. Included by the root Taskfile as `dev:`; tasks run in # the repository root. diff --git a/deploy/dev/compose.yaml b/deploy/dev/compose.yaml index e0dcb5f..4b8bac5 100644 --- a/deploy/dev/compose.yaml +++ b/deploy/dev/compose.yaml @@ -23,7 +23,7 @@ x-outline-env: &outline-env FILE_STORAGE: local FILE_STORAGE_UPLOAD_MAX_SIZE: "26214400" # Sign-in with the dev Dex. The browser is sent to localhost:5556; Outline - # itself reaches Dex by its compose name. See docs/development.md. + # itself reaches Dex by its compose name. See docs/internals/development.md. OIDC_CLIENT_ID: outline OIDC_AUTH_URI: http://localhost:5556/dex/auth OIDC_TOKEN_URI: http://dex:5556/dex/token @@ -66,7 +66,7 @@ services: target: /etc/dex/config.yaml read_only: true ports: - # The browser signs in here; see docs/development.md. + # The browser signs in here; see docs/internals/development.md. - "127.0.0.1:5556:5556" networks: [frontend] healthcheck: @@ -102,7 +102,7 @@ services: environment: OUTLINE_URL: http://outline:3000 # Outline names itself http://localhost:8090 in the redirect to a - # stored file; the Gateway fetches that from OUTLINE_URL (docs/images.md). + # stored file; the Gateway fetches that from OUTLINE_URL (docs/deploy/image-fetching.md). OUTLINE_PUBLIC_URL: http://localhost:8090 OUTPRINT_SERVICE_CONFIG: /opt/outprint/service-dev.yaml volumes: diff --git a/docs/README.md b/docs/README.md index 59e7347..543261e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,42 +1,26 @@ -# Documentation +# Outprint documentation -## Using and running it +Outprint turns Outline documents into typeset PDFs. Pick the section that fits +your job. The same files make up the [documentation site](https://outprint.task.media). -| Page | What it covers | -|------|----------------| -| [Metadata](metadata.md) | the Metadata Block a Document author writes: syntax, layering, field types, errors | -| [Binding](binding.md) | the block a Collection admin uses to choose a Template and Configuration | -| [Images](images.md) | images and logos: formats, sizes, captions, limits, errors | -| [Deploying with Docker Compose](deploy-compose.md) | running Gateway and Worker with Docker Compose | -| [Injector and download button](injector.md) | the reverse proxy that adds the Download PDF button | -| [Limits and admission control](limits.md) | limits and admission control | - -## Writing Templates +| Section | For | What you find | +|---------|-----|---------------| +| [Write](write/README.md) | people who write documents in Outline | download a PDF, fill in the details at the top of a document, add images, fix a message | +| [Configure](configure/README.md) | admins of a Collection | choose a Template and a Configuration for a Collection | +| [Deploy](deploy/README.md) | people who run Outline | prerequisites, Docker Compose, the injector, limits, security notes, upgrading | +| [Templates](templates/README.md) | people who make Templates | the catalogue, the author guide, the package format, the Standard Fields | +| [Internals](internals/README.md) | reviewers and contributors | architecture, the Worker, images, security, the local stack, decision records | -| Page | What it covers | -|------|----------------| -| [Template author guide](template-author-guide.md) | start here: package format, fields, patterns, deny-list, `validate`, `render`, goldens, a worked example | -| [Packages](packages.md) | reference for packages, field types, typed patterns, TeX deny-list, drift guard | -| [Standard Fields](standard-fields.md) | the Standard Fields | - -## How it works - -| Page | What it covers | -|------|----------------| -| [Architecture](architecture.md) | Gateway and Worker roles, the job bundle, request flow, error mapping | -| [Worker pipeline](worker.md) | the Worker pipeline, filters, engine lockdown | -| [Security](security.md) | the security layers and what each proves | -| [Architecture decision records](adr/README.md) | architecture decision records | -| [Vocabulary](https://github.com/taskmedia/outprint/blob/main/CONTEXT.md) | the vocabulary | +## Only on GitHub -## Developing and publishing +These pages are for the maintainers and are not part of the site. | Page | What it covers | |------|----------------| -| [Development stack and end-to-end tests](development.md) | the seeded local stack with Outline | -| [Testing](https://github.com/taskmedia/outprint/blob/main/docs/testing.md) | test tiers and CI | -| [Sanitisation gate](https://github.com/taskmedia/outprint/blob/main/docs/sanitize.md) | the sanitisation gate and the pre-publish audit | -| [Publishing checklist](https://github.com/taskmedia/outprint/blob/main/docs/publishing.md) | the maintainer's checklist before the repository goes public | -| [Releasing](https://github.com/taskmedia/outprint/blob/main/docs/releasing.md) | the maintainer's release procedure: release PR, image publishing, one-time settings | -| [Research notes](https://github.com/taskmedia/outprint/blob/main/docs/research/README.md) | the research notes behind the design | -| [Third-party notices](https://github.com/taskmedia/outprint/blob/main/THIRD_PARTY_NOTICES) | third-party licences and the Pandoc source offer | +| [Testing](testing.md) | test tiers and CI | +| [Sanitisation gate](sanitize.md) | the sanitisation gate and the pre-publish audit | +| [Publishing checklist](publishing.md) | the checklist before the repository goes public | +| [Releasing](releasing.md) | the release procedure: release PR, image publishing, one-time settings | +| [Research notes](research/README.md) | the research behind the design | +| [Vocabulary](../CONTEXT.md) | the words with a fixed meaning | +| [Third-party notices](../THIRD_PARTY_NOTICES) | third-party licences and the Pandoc source offer | diff --git a/docs/assets/screens/template-neutral.png b/docs/assets/screens/template-neutral.png new file mode 100644 index 0000000..5995634 Binary files /dev/null and b/docs/assets/screens/template-neutral.png differ diff --git a/docs/binding.md b/docs/binding.md deleted file mode 100644 index 8479748..0000000 --- a/docs/binding.md +++ /dev/null @@ -1,79 +0,0 @@ -# Binding - -A **Binding** pairs one Template with one Template Configuration. A -Collection admin chooses it for every Document in the Collection, nested -Documents included, with a fenced `yaml` block holding a `binding:` key in the -Collection overview. Authors cannot choose or override it, and neither can a request. - -## The block - -````markdown -# Town Council - -Notes and letters of the council. - -```yaml -binding: - template: neutral - configuration: example -``` -```` - -- The block is a fenced code block (backticks or tildes, three or more, - indented at most three spaces) whose info string is exactly `yaml`. Outline - keeps only the first word of an info string, so the block is told apart from - other YAML by its root key `binding:`, not by a second word. -- It may be anywhere in the overview. The first block with a `binding:` key - counts; a second one is a `binding_duplicate` error. Other `yaml` blocks are - ordinary text. -- The content is YAML with exactly one key, `binding`, holding a mapping with - exactly the keys `template` and `configuration`, each the name of an - installed package, as a string. -- A `binding:` inside another fenced block, in inline code, in an indented code - block or nested below another key is not a block. -- A block spelled with the old info string `yaml binding` is not recognised. -- The pair must be on the installation's allow-list (the `bindings` of the - service configuration). - -## Which Binding applies - -1. The overview has a block: that block. Any problem with it is an error. -2. The overview has no block at all: the installation's default Binding. -3. No block and no default Binding: an error (`no_binding`). - -There is no silent fallback. A broken block never falls back to the default, -and a Collection that cannot be read is an error, not a default render. -The overview is read on every render, so an edit takes effect on the next -click. Only people who can edit the Collection can change it. - -## Errors - -Binding errors are part of the common error body (status 422), with -`source: "collection"` and `line` counting from the first line of the -overview: - -```json -{"errors": [ - {"field": "font", "source": "collection", "code": "binding_malformed", - "message": "unknown key \"font\": the binding block has exactly the keys template and configuration", - "line": 8} -]} -``` - -| Code | Meaning | -|------|---------| -| `binding_malformed` | block not closed, broken YAML, `binding` is not a mapping, a missing, extra or repeated key, or a value that is not a name | -| `binding_unknown` | no installed Template or Configuration of that name (`field` names the key) | -| `binding_not_allowed` | both exist but the pair is not on the allow-list | -| `binding_duplicate` | a second block with a `binding:` key | -| `no_binding` | no block and no default Binding | - -All problems are reported at once. A Collection the user cannot read answers -403 or 404 with a message that names the Collection. - -## The button - -The Document view shows the button where the Collection has a block (valid or -not) or the installation has a default Binding, see the status endpoint in -[Architecture](architecture.md#status-endpoint). A block with a typo keeps -the button visible so that the author sees the error on click. diff --git a/docs/configure/README.md b/docs/configure/README.md new file mode 100644 index 0000000..c86ff11 --- /dev/null +++ b/docs/configure/README.md @@ -0,0 +1,27 @@ +--- +sidebar_position: 1 +--- + +# Configure + +You look after a Collection in Outline. This section shows how to give it a PDF +look. + +Outprint needs two choices for a Collection: + +- a **Template**, which is the page design: layout, fonts and the kinds of + document, such as a letter or a report; +- a **Configuration**, which holds the details of your organisation: name, logo + and colour. + +The pair is the **Binding**. You write it once, in the description of the +Collection. After that every document in the Collection can make a PDF with that +look. The authors do not choose it. + +| Page | What you learn | +|------|----------------| +| [Choose a Template and Configuration](choose-a-template.md) | The block to copy, the rules for it, and the errors you may see. | + +Before you start, check that someone has [deployed Outprint](../deploy/README.md) +for your Outline. If a document shows no **Download PDF** button in a Collection +that has a block, ask that person. diff --git a/docs/configure/_category_.json b/docs/configure/_category_.json new file mode 100644 index 0000000..5146a97 --- /dev/null +++ b/docs/configure/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Configure", + "position": 2, + "collapsed": false +} diff --git a/docs/configure/choose-a-template.md b/docs/configure/choose-a-template.md new file mode 100644 index 0000000..2d18a2c --- /dev/null +++ b/docs/configure/choose-a-template.md @@ -0,0 +1,95 @@ +--- +sidebar_position: 2 +--- + +# Choose a Template and Configuration + +You give a Collection its PDF look by adding a short block to the Collection +description in Outline. The block names one Template and one Configuration. The +pair is called a **Binding**. + +You need to be allowed to edit the Collection. You also need the names of an +installed Template and Configuration. Ask the person who runs Outprint, or look +at the [Template catalogue](../templates/catalogue/README.md). The first +Template, `neutral`, comes with a Configuration called `example`. + +## Add the block + +Open the Collection in Outline and edit its description. Add the block below +anywhere in the description. In the example the description is a title, one +sentence and the block. The part you add is the fenced `yaml` block. + +````markdown +# Town Council + +Notes and letters of the council. + +```yaml +binding: + template: neutral + configuration: example +``` +```` + +To make the block in Outline, type three backticks, choose **YAML** as the +language, and type the lines inside. Then save. + +The change counts at once. Every document in the Collection, including documents +in folders below it, now shows the **Download PDF** button. + +![The binding block in the description of a Collection in Outline](../assets/screens/collection-binding-block.png) + +## The rules + +- The block has exactly one key, `binding`. Below it, exactly two keys: + `template` and `configuration`. Both hold a name. +- The names must match an installed Template and Configuration. Spell them + exactly. +- The pair must be one that the person who runs Outprint allows. If you choose + another pair, you get the error [`binding_not_allowed`](../write/when-something-goes-wrong.md#binding_not_allowed). +- The language of the block must be `yaml`, nothing else. +- Use one block per Collection. A second block is the error + [`binding_duplicate`](../write/when-something-goes-wrong.md#binding_duplicate). +- Other `yaml` blocks in the description are ordinary text. Only the block with + the key `binding` counts. +- Authors cannot change the Binding. Only people who can edit the Collection can. + +## What happens without a block + +1. The Collection has a block: Outprint uses it. A mistake in the block is an + error, not a silent fallback. +2. The Collection has no block, and the installation has a default pair: + Outprint uses the default pair. +3. Neither exists: the author gets the error + [`no_binding`](../write/when-something-goes-wrong.md#no_binding). + +A document in a Collection without a Binding and without a default shows no +**Download PDF** button. + +## If something is wrong + +Mistakes in the block show up when an author clicks **Download PDF**. The button +stays visible on purpose, so that the mistake is seen. The error says +`collection` and gives the line, counted from the first line of the description. + +| Code | What it means | +|------|---------------| +| [`binding_malformed`](../write/when-something-goes-wrong.md#binding_malformed) | The block is not closed, is not valid YAML, or has a missing, extra or repeated key | +| [`binding_unknown`](../write/when-something-goes-wrong.md#binding_unknown) | No installed Template or Configuration has that name | +| [`binding_not_allowed`](../write/when-something-goes-wrong.md#binding_not_allowed) | Both names exist, but this pair is not allowed | +| [`binding_duplicate`](../write/when-something-goes-wrong.md#binding_duplicate) | The description holds a second block | +| [`no_binding`](../write/when-something-goes-wrong.md#no_binding) | No block, and no default pair | + +All problems are reported at once. A person who may not read the Collection +gets a message that names the Collection. + +## Try it + +1. Add the block to a test Collection. +2. Open a document in it. The **Download PDF** button appears at the bottom + right. +3. Click it. Open the PDF. The letterhead shows the name and logo of the + Configuration. + +If a document starts with a block of its own at the top, the author can add +details such as a recipient. See [Fill in the document](../write/fill-in-the-document.md). diff --git a/docs/deploy/README.md b/docs/deploy/README.md new file mode 100644 index 0000000..e4365c6 --- /dev/null +++ b/docs/deploy/README.md @@ -0,0 +1,68 @@ +--- +sidebar_position: 1 +--- + +# Deploy + +This section is for the person who runs Outline and Outprint. It shows how to +install Outprint next to an Outline you host. + +## Prerequisites + +Check these before you start. If one is missing, Outprint cannot work. + +1. **Outline must be self-hosted.** You need control over the web server in + front of Outline. Hosted Outline (the cloud service of the Outline company) + cannot be used, because you cannot put anything in front of it. +2. **The injector is a required step, not an option.** Outline has no plug-in + point for a toolbar button, and Outprint does not change Outline. The + injector is a small reverse proxy that adds one ` @@ -150,8 +154,9 @@ prints a note when it is not; CI always runs it. ## Routing example -Traefik in front of the injector, with invented hosts. TLS ends at Traefik, which -also compresses (the injector sends HTML uncompressed): +The next example puts Traefik in front of the injector, with invented hosts. TLS +ends at Traefik, which also compresses (the injector sends HTML uncompressed). +This is the Traefik configuration: ```yaml # dynamic configuration @@ -169,6 +174,8 @@ http: - url: http://injector.internal.example.invalid:8090 ``` +These are the injector's settings for that setup: + ```sh OUTPRINT_INJECTOR_UPSTREAM=http://outline.internal.example.invalid:3000 OUTPRINT_INJECTOR_GATEWAY_URL=http://gateway.internal.example.invalid:8080 @@ -179,4 +186,4 @@ Outline keeps its public URL (`URL=https://wiki.example.invalid`) and its With Docker Compose, `deploy/compose/compose.yaml` has an `injector` service next to `gateway` and `worker`. It publishes port 8090 (`OUTPRINT_INJECTOR_PORT`) and needs -`OUTPRINT_INJECTOR_UPSTREAM`; see [Deploying with Docker Compose](deploy-compose.md). +`OUTPRINT_INJECTOR_UPSTREAM`; see [Deploying with Docker Compose](docker-compose.md). diff --git a/docs/limits.md b/docs/deploy/limits-and-admission.md similarity index 98% rename from docs/limits.md rename to docs/deploy/limits-and-admission.md index 6c63f0b..d86b7e5 100644 --- a/docs/limits.md +++ b/docs/deploy/limits-and-admission.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 5 +--- + # Limits and admission control The renderer bounds what one request can cost, and what all requests together @@ -18,7 +22,7 @@ truncated: a Document that does not fit fails, it is not cut short. | Pages | 500 | Worker | 413 `the document has too many pages; shorten it` | `OUTPRINT_MAX_PAGES` (Worker, lower only in practice) | | PDF size | 50 MiB | Worker, Gateway | 413 `the resulting PDF is too large; remove or shrink images` | `OUTPRINT_MAX_PDF_BYTES` (Worker, up to 50 MiB) | | TeX runs | 2 | Worker | | no | -| Worker memory, CPUs, PIDs, scratch space | 1 GiB, 1, 128, 256 MiB | container runtime ([Deploying with Docker Compose](deploy-compose.md)) | 413 `resource` | Compose file | +| Worker memory, CPUs, PIDs, scratch space | 1 GiB, 1, 128, 256 MiB | container runtime ([Deploying with Docker Compose](docker-compose.md)) | 413 `resource` | Compose file | | Concurrent renders | 1 | Gateway admission control | 429 | see below | | Queue depth | 4 | Gateway admission control | 429 | see below | | Renders per user | 1 | Gateway admission control | 429 | see below | diff --git a/docs/deploy/podman.md b/docs/deploy/podman.md new file mode 100644 index 0000000..063b5ac --- /dev/null +++ b/docs/deploy/podman.md @@ -0,0 +1,16 @@ +--- +sidebar_label: Podman (coming soon) +sidebar_position: 10 +--- + +# Podman + +**Status: coming soon.** A guide for running Outprint with Podman is planned. + +The services are ordinary OCI images, so Podman should work. What is missing is +a tested guide, in particular for the Worker settings that the +[Compose file](docker-compose.md) applies: no network, a read-only root file +system, dropped capabilities and a memory limit. Until a tested guide exists, +use [Docker Compose](docker-compose.md). + +Progress is tracked in [issue #51](https://github.com/taskmedia/outprint/issues/51). diff --git a/docs/deploy/reverse-proxy-recipes.md b/docs/deploy/reverse-proxy-recipes.md new file mode 100644 index 0000000..f55d87d --- /dev/null +++ b/docs/deploy/reverse-proxy-recipes.md @@ -0,0 +1,18 @@ +--- +sidebar_label: Reverse-proxy recipes (coming soon) +sidebar_position: 9 +--- + +# Reverse-proxy recipes + +**Status: coming soon.** Recipes for nginx, Traefik and Caddy are planned. + +Each recipe will show the edge proxy in front of the injector: TLS, compression, +and the forwarding headers that the injector expects. The shape is always the +same: `edge proxy -> injector -> Outline`, with `/pdf/*` going to the Gateway. + +Today there is one worked example, for Traefik, in +[Injector and download button](injector.md#routing-example). The rules in +[Where the injector sits](injector.md) hold for every proxy. + +Progress is tracked in [issue #51](https://github.com/taskmedia/outprint/issues/51). diff --git a/docs/deploy/security-for-operators.md b/docs/deploy/security-for-operators.md new file mode 100644 index 0000000..7f9fd24 --- /dev/null +++ b/docs/deploy/security-for-operators.md @@ -0,0 +1,65 @@ +--- +sidebar_position: 6 +--- + +# Security notes for operators + +What to expose, what to keep inside, and what to check after the install. The +reasoning and the tests are in [Security](../internals/security.md). The +Compose file already follows everything on this page. + +## What to expose + +- **Publish the injector, and nothing else.** The injector is the only service + that browsers should reach. Do not publish the Gateway or the Worker. +- **Keep one address.** The Gateway sends no CORS headers and has no login of + its own. The Outline session cookie is the credential, so it has to come from + the Outline address. Route `/pdf/*` to the Gateway through the injector. The + `ports` entry of the Gateway in the Compose file is for trying it out. +- **Terminate TLS in front.** The edge proxy ends TLS and sets the forwarding + headers. A client that reaches the injector directly can forge + `X-Forwarded-For`. + +## What to keep inside + +- **The Worker has no network.** It sits on an internal network that only the + Gateway can reach. It has no route to the internet, to Outline or to the host. + Keep it that way. +- **The Worker is locked down.** Non-root user, no extra privileges, all + capabilities dropped, a read-only root file system, memory, process and disk + limits. These are the settings of the shipped Compose file. The test suite + fails if one of them is weakened, so merge your changes with care. At start + the Worker logs `worker posture is weaker than the shipped baseline` if it + finds a weaker setting. +- **Set a token between Gateway and Worker.** `OUTPRINT_WORKER_TOKEN` is + optional. Set it when the network between the two is shared with other + services. Do not reuse the value anywhere else. + +## What the Gateway sends out + +- **To Outline:** the user's cookie, so that Outline decides what the user may + read. Nothing else gets the cookie. +- **To image hosts:** nothing with the cookie. Images come from Outline. A + redirect is followed only to hosts you list in `imageHosts`, and only to public + addresses. See [Image fetching](image-fetching.md). +- **To the Worker:** the document, its images and its settings. + +The Gateway keeps no copy of a document or a PDF. Responses carry +`Cache-Control: no-store`. + +## What the logs hold + +The Gateway logs one line per request: job, document id, phase, kind, status and +duration. The Worker logs one line per failed job. Neither logs document text, +metadata values or cookies. Error messages shown to users carry no log excerpts +and no internal addresses. + +## Check after the install + +1. Reach the Gateway's port from outside. It must not answer. +2. Reach the Worker from outside, and from the Gateway's host network. It must + not answer. +3. Open Outline through the injector. The **Download PDF** button appears in a + Collection with a Binding. +4. Check the Content-Security-Policy of Outline if the button is missing. See + [Injector and download button](injector.md#content-security-policy). diff --git a/docs/deploy/upgrading.md b/docs/deploy/upgrading.md new file mode 100644 index 0000000..ae87045 --- /dev/null +++ b/docs/deploy/upgrading.md @@ -0,0 +1,54 @@ +--- +sidebar_position: 7 +--- + +# Upgrading + +There is no release yet, so there is no upgrade path to describe. This page +lists what to check when versions exist. It will grow with the first releases. + +## What is safe to know now + +- **Outprint keeps no state.** There is no database and no cache of PDFs. An + upgrade replaces containers. Nothing needs to be migrated. +- **The three services belong together.** The Gateway and the Worker run from + one image, and the injector is built from the same tag. Upgrade all three to + the same version. +- **Templates and Configurations are files.** They are mounted into the + services, or built into the image. A new version can ship new shipped + packages. Your own packages are not touched. + +## Before you upgrade + +1. Read the release notes (`CHANGELOG.md`) for the versions in between. Before + version 1.0, a minor version can change settings or packages. +2. Read this documentation in the version that you install. The site has one + version of the docs for every minor release. +3. Check your settings against that version. The service configuration is + strict. An unknown key stops the Gateway at startup with a message, so a + renamed setting cannot go unnoticed. The same holds for the `OUTPRINT_*` + variables that are checked at startup. +4. Check your own Template and Configuration packages with the new version: + + ```sh + docker run --rm -v "$PWD/templates:/opt/outprint/templates:ro" \ + -v "$PWD/configurations:/opt/outprint/configurations:ro" \ + outprint validate + ``` + + `validate` reports every problem of a package. Fix them before you roll out. +5. Check the [Compose file](docker-compose.md) of the new version against yours. + The Worker keeps its security settings in that file. The build of Outprint + checks them, so do not weaken them while you merge changes. + +## After you upgrade + +1. Open a document in a Collection with a Binding and click **Download PDF**. +2. If an author reports a message you do not know, look up its code in + [When something goes wrong](../write/when-something-goes-wrong.md). + The codes are stable per version. + +## Roll back + +Start the previous image tags again. Nothing was migrated, so nothing needs to +be reverted. diff --git a/docs/internals/README.md b/docs/internals/README.md new file mode 100644 index 0000000..2b63789 --- /dev/null +++ b/docs/internals/README.md @@ -0,0 +1,35 @@ +--- +sidebar_position: 1 +--- + +# Internals + +This section is for people who want to know how Outprint works: security +reviewers, contributors and the curious. You do not need it to use or run +Outprint. + +| Page | What it covers | +|------|----------------| +| [Architecture](architecture.md) | Gateway and Worker roles, the job bundle, the request flow, error mapping | +| [Worker pipeline](worker.md) | The phases of a render, the filters, the engine lockdown | +| [Images in the pipeline](images.md) | How images and SVG files are checked, converted and placed | +| [Security](security.md) | The security layers, what each one proves, the limits of the guarantees | +| [Development stack and end-to-end tests](development.md) | The seeded local stack with Outline, and the browser tests | +| [Architecture decision records](../adr/README.md) | Why the system is built the way it is | + +## Vocabulary + +The words **Template**, **Configuration**, **Collection**, **Binding**, +**Document Type** and the rest have a fixed meaning. The vocabulary lives in +`CONTEXT.md` at the root of the repository, so that the code and the docs share +one source. It is not copied into this site. Read +[the vocabulary on GitHub](https://github.com/taskmedia/outprint/blob/main/CONTEXT.md). + +## Repo-only pages + +Some pages are for the maintainers and are not published on this site: the +test tiers, the sanitisation gate, the publishing checklist, the release +procedure and the research notes. They are in the +[docs folder on GitHub](https://github.com/taskmedia/outprint/tree/main/docs). +Third-party licences are in the +[third-party notices](https://github.com/taskmedia/outprint/blob/main/THIRD_PARTY_NOTICES). diff --git a/docs/internals/_category_.json b/docs/internals/_category_.json new file mode 100644 index 0000000..85e4ac1 --- /dev/null +++ b/docs/internals/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Internals", + "position": 5, + "collapsed": false +} diff --git a/docs/architecture.md b/docs/internals/architecture.md similarity index 91% rename from docs/architecture.md rename to docs/internals/architecture.md index 8de7e36..abade53 100644 --- a/docs/architecture.md +++ b/docs/internals/architecture.md @@ -1,7 +1,11 @@ +--- +sidebar_position: 2 +--- + # Architecture One image, `outprint`, runs in two roles. The reasons for the split are in -[ADR 0001](adr/0001-split-renderer-into-gateway-and-worker.md); this page +[ADR 0001](../adr/0001-split-renderer-into-gateway-and-worker.md); this page describes the roles, the contract between them, the request flow and how errors are mapped. @@ -16,7 +20,7 @@ browser --> injector --> Outline (adds the button script to HTML pages) ``` A third deployable, the **injector**, is a separate slim image -([Injector and download button](injector.md)). It is not a role of the outprint image. +([Injector and download button](../deploy/injector.md)). It is not a role of the outprint image. ## Roles @@ -39,11 +43,11 @@ file. The endpoint changes nothing on the server, and the response is `Cache-Control: no-store`. `GET /healthz` answers 200. `GET /button.js` serves the button script (embedded from `web/button.js`; `text/javascript`, `nosniff`, `max-age=300`, no cookie needed). The Gateway sends no CORS headers: it is reached through the injector's -`/pdf` prefix, on the origin of Outline ([Injector and download button](injector.md)). +`/pdf` prefix, on the origin of Outline ([Injector and download button](../deploy/injector.md)). `GET /status/{documentId}` tells the button script whether to show the button (see [Status endpoint](#status-endpoint)). The Template and Configuration come -from the Collection's Binding block ([Binding](binding.md)) and nowhere +from the Collection's Binding block ([Binding](../configure/choose-a-template.md)) and nowhere else: no query parameter or header selects or overrides them. Configuration (environment): @@ -51,13 +55,13 @@ Configuration (environment): | Variable | Meaning | Default | |----------|---------|---------| | `OUTLINE_URL` | base URL of Outline; the only origin the cookie is sent to | required | -| `OUTLINE_PUBLIC_URL` | Outline's own `URL` setting, when `OUTLINE_URL` is an internal address; a redirect to this origin is fetched from `OUTLINE_URL` ([Images](images.md#outline-behind-an-internal-address)) | none | +| `OUTLINE_PUBLIC_URL` | Outline's own `URL` setting, when `OUTLINE_URL` is an internal address; a redirect to this origin is fetched from `OUTLINE_URL` ([Image fetching](../deploy/image-fetching.md#outline-behind-an-internal-address)) | none | | `OUTPRINT_WORKER_URL` | base URL of the Worker | required | | `OUTPRINT_WORKER_TOKEN` | optional shared bearer token | none | | `OUTPRINT_LISTEN` | listen address | `:8080` | | `OUTPRINT_TIMEOUT` | render limit; the Gateway waits 30 s longer for the Worker | `60s` | | `OUTPRINT_SERVICE_CONFIG` | service configuration file | | -| `OUTPRINT_ADMISSION_SLOTS`, `_QUEUE_DEPTH`, `_PER_USER`, `_MAX_WAIT` | override the `admission` keys of the service configuration, see [Limits and admission control](limits.md) | `1`, `4`, `1`, `60s` | +| `OUTPRINT_ADMISSION_SLOTS`, `_QUEUE_DEPTH`, `_PER_USER`, `_MAX_WAIT` | override the `admission` keys of the service configuration, see [Limits and admission control](../deploy/limits-and-admission.md) | `1`, `4`, `1`, `60s` | | `OUTPRINT_LOG_LEVEL` | `debug`, `info`, `warn`, `error` | `info` | ### Worker @@ -108,7 +112,7 @@ to the Worker. It is JSON, `Content-Type: application/json`: | `values` | the merged, typed field values of the Document Type: string, list of strings or bool | | `template`, `configuration` | package names; the Worker also checks the pairing against its allow-list | | `documentType` | one of the Template's Document Types | -| `images` | image files by bundle name (`images/` in the Markdown). Fetched and canonicalised by the Gateway (an SVG is checked and passed on as `.svg`; the Worker converts it to a vector PDF), see [Images](images.md) | +| `images` | image files by bundle name (`images/` in the Markdown). Fetched and canonicalised by the Gateway (an SVG is checked and passed on as `.svg`; the Worker converts it to a vector PDF), see [Images in the pipeline](images.md) | There is no field for a cookie, a URL or a secret, and unknown fields are rejected, so none can be added by accident. A test serialises a bundle built @@ -146,7 +150,7 @@ are named in one place, `internal/limits`; see [Security](security.md). unknown name or a pair off the allow-list is a 422 with `source: collection`, never a fallback to the default. No block and no default is a 422 `no_binding`. A Document without a Collection has no block. See - [Binding](binding.md). + [Binding](../configure/choose-a-template.md). 5. `metadata.Resolve` merges the Metadata Block with the derived values: `title` is the Outline title and `date` is the last-modified date (`updatedAt`, in UTC, as an ISO date). @@ -175,7 +179,7 @@ id, 401 without a cookie or when Outline answers 401, 403 and 404 when the user cannot read the Document or its Collection, 500 otherwise. A client must treat any non-200 answer as "do not decide" and may keep the button hidden. -Admission control ([Limits and admission control](limits.md)) sits in `gateway.process`. After the +Admission control ([Limits and admission control](../deploy/limits-and-admission.md)) sits in `gateway.process`. After the Binding is selected the Gateway asks Outline for the confirmed user id (`auth.info`) and counts the render against that user, before the images are fetched; a slot of the global limit is taken right before the Worker call. Both @@ -197,7 +201,7 @@ Content-Disposition: attachment; filename="Gruesse aus Koeln.pdf"; filename*=UTF ## Error mapping -Validation problems are the structured body of [Metadata](metadata.md): +Validation problems are the structured body of [Metadata Block reference](../templates/metadata-reference.md): `{"errors":[{"field","source","code","message","line"}]}`. Every other error is `{"message", "phase", "job_id"}`, where the message is a fixed sentence. No response carries a log excerpt, TeX output, Document content or a cause. @@ -210,13 +214,13 @@ response carries a log excerpt, TeX output, Document content or a cause. | Outline answers 404 | 404 | | | metadata errors | 422 | the error list; all problems at once | | no Binding | 422 | one error with `source: collection`, `code: no_binding` | -| invalid Binding block | 422 | errors with `source: collection` and a `binding_*` code, see [Binding](binding.md) | +| invalid Binding block | 422 | errors with `source: collection` and a `binding_*` code, see [Binding](../configure/choose-a-template.md) | | Outline answers 403 or 404 for the Collection | 403 / 404 | `phase: binding`, no fallback | | Markdown, bundle or PDF over a limit | 413 | | | too many pages (`pages_exceeded`), PDF too large (`pdf_too_large`) | 413 | `phase: output` | | the job ran out of memory or scratch space (`resource`) | 413 | the Worker restarts | | Worker busy | 429 | with `Retry-After` | -| renderer at capacity (`busy`), user already has a render (`user_limit`) | 429 | admission control, with `Retry-After`; error list with `source: service`, see [Limits and admission control](limits.md) | +| renderer at capacity (`busy`), user already has a render (`user_limit`) | 429 | admission control, with `Retry-After`; error list with `source: service`, see [Limits and admission control](../deploy/limits-and-admission.md) | | Outline does not confirm the user | 401 / 403 / 500 | never counted by address | | Worker timeout | 504 | `phase: timeout` | | Outline or Worker unreachable, TeX error, any other failure | 500 | only phase and job id | diff --git a/docs/development.md b/docs/internals/development.md similarity index 98% rename from docs/development.md rename to docs/internals/development.md index c01395a..bfbb4fb 100644 --- a/docs/development.md +++ b/docs/internals/development.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 7 +--- + # Development stack and end-to-end tests One command gives you Outline with a login, a bound and an unbound Collection @@ -82,7 +86,7 @@ Outline answers `/api/attachments.redirect?id=...` with a redirect to Gateway container `localhost` is the Gateway itself, so the Gateway gets `OUTLINE_PUBLIC_URL=http://localhost:8090` and fetches that redirect from `OUTLINE_URL=http://outline:3000` instead. See -[Images](images.md#outline-behind-an-internal-address). No `imageHosts` is +[Image fetching](../deploy/image-fetching.md#outline-behind-an-internal-address). No `imageHosts` is needed. ### Secrets @@ -138,7 +142,7 @@ Outline and both tracked: download, check the `%PDF` header and the text (the opening sentence, the name of the example Configuration, a table cell). It also fetches the export of the captioned Document through the API and checks that the captions are - the alt text (the verification recorded in [Images](images.md)). + the alt text (the verification recorded in [Images in the pipeline](images.md#notes)). 2. **No Binding, no button.** Open *Garden notes*, wait for the status answer (`available: false`) and check that the button is absent. diff --git a/docs/images.md b/docs/internals/images.md similarity index 57% rename from docs/images.md rename to docs/internals/images.md index 3efb1e8..75373fe 100644 --- a/docs/images.md +++ b/docs/internals/images.md @@ -1,57 +1,13 @@ -# Images +--- +sidebar_position: 4 +--- -Images uploaded to Outline appear in the PDF at the size the author chose. -The Gateway fetches and checks them; the Worker only sees clean files. +# Images in the pipeline -## For authors - -**Supported formats**: PNG, JPEG, WebP, static GIF and SVG, detected by -content, not by name. Animated GIF and animated WebP are rejected. Upload the image to Outline; images linked from other sites, -`data:` and `file:` addresses are rejected. - -**What happens to a file**: a raster image is decoded and written again. PNG -stays lossless, JPEG is saved at quality 95, WebP and GIF become PNG. A JPEG's -EXIF orientation is applied, so a photo taken sideways is upright. All other -metadata (camera, location, colour profile) is dropped. An SVG is checked and -then kept as it is; see [SVG](#svg). - -**Size**: Outline's size syntax is respected. Pixels are converted at 96 dpi -(a pixel is 0.75 pt). - -| In Outline | Result | -|------------|--------| -| no size | natural size | -| `=400x300` | 400 by 300 px | -| `=400` or `=400x` | width 400 px, height from the aspect ratio | -| `=x300` | height 300 px, width from the aspect ratio | - -An image that does not fit is scaled down, aspect kept, to the text width -and to 0.9 of the text height. It is never cut off. - -**Captions**: an image alone in its paragraph, with alt text, becomes a -numbered figure and the alt text is the caption. An image without alt text, or -inside a line of text, has no caption. Alt text is plain text: markup in it is -shown as written. - -**Limits**: 50 images per Document, 10 MiB per file (as uploaded and after -conversion), 50 MiB in total, 40 megapixels per image. - -**Problems** are all reported at once, each with its line, its alt text and a -kind: - -| Kind | Meaning | -|------|---------| -| `missing` | no address or attachment id, or the attachment was not found | -| `forbidden` | not an Outline attachment (other site, `data:`, `file:`, a relative path), not readable by you, or stored on a host the service may not fetch from | -| `too_large` | over 10 MiB (an SVG: over 2 MiB), over 50 images, or over 50 MiB in total | -| `format` | not PNG, JPEG, WebP, a static GIF or an SVG; the message names what was found | -| `svg_rejected` | an SVG that is not allowed or cannot be used; the message says why | -| `decode` | the file is damaged | -| `pixels` | more than 40 megapixels | -| `syntax` | the image Markdown spans lines; put it on one line | - -Do not use Pandoc attribute blocks (`{width=...}`) after an image: they are -ignored. +How the Gateway checks, converts and places images, and how SVG is handled. +Authors find the short version in [Images and logos](../write/images-and-logos.md); +operators find the settings in [Image fetching](../deploy/image-fetching.md). +The limits are listed in [Limits and admission control](../deploy/limits-and-admission.md). ## SVG @@ -142,67 +98,6 @@ Raw HTML `` is not an image: the Worker drops it like all raw HTML. The `render` command runs the same pipeline with images read from the directory of the input file (no `..`, no links out of it). -## For operators - -**Fetching images is the Gateway's only outbound request besides Outline's -API.** The model: - -- The user's cookie is sent to the Outline origin and nowhere else. The first - request goes to the configured Outline URL, with the cookie. -- Redirects are followed by hand, at most 5, **without the cookie**, and only - to hosts on the allow-list: the Outline origin and `imageHosts`. Only - `http` and `https`, no credentials in the URL. -- Names are resolved by the Gateway, once per request. Every answer must be a - public address: loopback, private, link-local (including the cloud metadata - address), carrier-grade NAT, multicast, reserved and documentation ranges, - and IPv4 written as IPv6, NAT64, 6to4 and Teredo forms are refused. The - connection goes to the address that was checked, and the address is checked - again when the socket connects, so a name that changes its answer cannot - redirect the request (DNS rebinding). -- One exception: the configured Outline origin itself (host and port) may be - at a private address, which the local development stack needs. No other - host gets this. -- No proxy from the environment is used. A request is bounded by 30 s and a - body by 10 MiB. - -**`imageHosts`** in the service configuration lists the other hosts a redirect -may lead to, for example the file storage of an Outline that keeps files in -S3-compatible storage. It is empty by default, so such an Outline shows -`forbidden` with the host's name until the operator adds it: - -```yaml -imageHosts: - - files.example.invalid # ports 80 and 443 - - storage.example.invalid:9000 # this port only -``` - -Entries are lower-case host names, optionally with a port: no scheme, path or -wildcard. A redirect target needs a public address, so a storage host on a -private network cannot be listed. - -### Outline behind an internal address - -When the Gateway reaches Outline at an address such as `http://outline:3000` -while Outline's own `URL` setting is public (`https://wiki.example.invalid`, or -`http://localhost:8090` in the dev stack), Outline's redirect to a stored file -points at the public URL. Set `OUTLINE_PUBLIC_URL` on the Gateway to that -public URL: a redirect to exactly that origin is then fetched from -`OUTLINE_URL` instead, still without the cookie and under the same checks on -the address of the configured origin. It adds no host to the allow-list: a -redirect to any other host is refused as before. Public and internal URL must -use the same path prefix, if any. Not needed when `OUTLINE_URL` is the public -URL, or when files live in S3-compatible storage (use `imageHosts`). - -**Templates** must scale images to the page. The Gateway gives every image -an explicit size in points, so only the Template knows the text area. The -`neutral` Template redefines `\includegraphics` to scale an image down to the -line width and `0.9\textheight`, keeping the aspect ratio, and sets figure -captions through the `caption` package. Its `letter` has no float: the KOMA -letter class has none, so the Template defines a `figure` environment that -keeps the figure in place on one page, with the same caption style and -numbering as the report. A Template that shows Document images -must do the same. - ## Notes - Verified: images, links and tables are found at the right source range, and @@ -225,7 +120,7 @@ must do the same. names Outline by its public URL (the `URL` setting). When the Gateway reaches Outline at an internal address, that host is not the configured origin and would be refused, so set `OUTLINE_PUBLIC_URL` on the Gateway; see - [below](#outline-behind-an-internal-address). + [Image fetching](../deploy/image-fetching.md#outline-behind-an-internal-address). - SVG: the Gateway pre-check is in `internal/images/svg*.go`; the conversion is the first phase of the Worker (`internal/worker/svg.go`). The attack corpus that backs the table above is `internal/svgcorpus` (see diff --git a/docs/security.md b/docs/internals/security.md similarity index 98% rename from docs/security.md rename to docs/internals/security.md index 644bbfe..e240f1f 100644 --- a/docs/security.md +++ b/docs/internals/security.md @@ -1,9 +1,13 @@ +--- +sidebar_position: 5 +--- + # Security The renderer turns documents written by many people into PDFs with a TeX toolchain, so it assumes a hostile Document. The reasoning and the split into Gateway and Worker are in -[ADR 0001](adr/0001-split-renderer-into-gateway-and-worker.md). This page lists +[ADR 0001](../adr/0001-split-renderer-into-gateway-and-worker.md). This page lists the layers, what each one proves, the limits and how to run the tests. The principle: **the Worker's container sandbox is the security boundary, the @@ -29,7 +33,7 @@ CI fails if any test of the security suite reports a skip. ## The shipped baseline `deploy/compose/compose.yaml`, service `worker` (see -[Deploying with Docker Compose](deploy-compose.md)): +[Deploying with Docker Compose](../deploy/docker-compose.md)): - fixed non-root user `10001:10001`, `no-new-privileges`, all capabilities dropped, Docker's default seccomp profile, read-only root file system diff --git a/docs/worker.md b/docs/internals/worker.md similarity index 98% rename from docs/worker.md rename to docs/internals/worker.md index 358216e..ee83d33 100644 --- a/docs/worker.md +++ b/docs/internals/worker.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 3 +--- + # Worker pipeline The Worker turns one job into one PDF. The same pipeline runs behind the @@ -5,7 +9,7 @@ The Worker turns one job into one PDF. The same pipeline runs behind the (`outprint worker`, see [Architecture](architecture.md)). This page describes what it does, how it is locked down, what it logs and where it is limited. The reasons for the two-role split are in -[ADR 0001](adr/0001-split-renderer-into-gateway-and-worker.md). +[ADR 0001](../adr/0001-split-renderer-into-gateway-and-worker.md). ## Phases @@ -167,7 +171,7 @@ in production, so a write through a link fails. The `neutral` fonts show the consequence. The luaotfload font name database cannot be built under paranoid reads, so a Template must load its fonts by -file (`Path=./fonts/`, see [Packages](packages.md)). The default Latin +file (`Path=./fonts/`, see [Packages](../templates/packages.md)). The default Latin Modern fonts that LaTeX loads for code and math are found by file name and work. @@ -295,7 +299,7 @@ a deployment. - Text inside SVGs, Document images and logos alike, uses the fonts of the image; a missing font is substituted silently (`sans-serif` and unknown names become a TeX Gyre font). Export text as paths. See - [Images](images.md#svg). + [Images in the pipeline](images.md#svg). - The Worker does not repeat the Gateway's SVG pre-check. It relies on librsvg's own limits, the limits above and the container; the attack corpus is run against exactly this (see [Security](security.md)). diff --git a/docs/research/README.md b/docs/research/README.md index c099549..0efefc8 100644 --- a/docs/research/README.md +++ b/docs/research/README.md @@ -10,11 +10,11 @@ source, **[S]** read in source code or documentation, **[I]** inferred. | Note | Question | Outcome in short | |------|----------|------------------| -| [license.md](license.md) | Which licence for the code, and what does shipping Pandoc, TeX and fonts require? Includes the GPL FAQ recheck. | MIT; `THIRD_PARTY_NOTICES` with a source offer | -| [lualatex-safer.md](lualatex-safer.md) | Can `lualatex --safer` harden the engine? | No; paranoid reads and writes inside the job directory instead ([worker.md](../worker.md)) | -| [markdown-latex-fidelity.md](markdown-latex-fidelity.md) | What breaks when Outline Markdown goes through Pandoc to LaTeX, and how to fix each? | Fixtures, goldens and Worker filters | -| [outline-surface.md](outline-surface.md) | Where can a Collection Binding live, and can the UI be extended without a fork? | Overview block plus a reverse-proxy injector; info-string caveat in issue #42 | -| [test-environment.md](test-environment.md) | How to run Outline locally and verify PDFs? | Three tiers, Compose dev stack, text goldens | +| [Licence](license.md) | Which licence for the code, and what does shipping Pandoc, TeX and fonts require? Includes the GPL FAQ recheck. | MIT; `THIRD_PARTY_NOTICES` with a source offer | +| [LuaLaTeX --safer](lualatex-safer.md) | Can `lualatex --safer` harden the engine? | No; paranoid reads and writes inside the job directory instead ([Worker pipeline](../internals/worker.md)) | +| [Markdown to LaTeX fidelity](markdown-latex-fidelity.md) | What breaks when Outline Markdown goes through Pandoc to LaTeX, and how to fix each? | Fixtures, goldens and Worker filters | +| [Outline surface](outline-surface.md) | Where can a Collection Binding live, and can the UI be extended without a fork? | Overview block plus a reverse-proxy injector; info-string caveat in issue #42 | +| [Test environment](test-environment.md) | How to run Outline locally and verify PDFs? | Three tiers, Compose dev stack, text goldens | The versions in the notes (Outline 1.11, TeX Live 2026 on a laptop) are the ones of the day. The pinned versions of the shipped image are in the diff --git a/docs/research/lualatex-safer.md b/docs/research/lualatex-safer.md index 936dfd8..fe07180 100644 --- a/docs/research/lualatex-safer.md +++ b/docs/research/lualatex-safer.md @@ -120,6 +120,6 @@ Net: `--safer` adds little security for this pipeline and costs the font loader. ## Outcome -- `--safer` was rejected, as recommended; the Worker never passes it (see [../worker.md](../worker.md#engine-lockdown)). -- The section 4 finding that `openin_any` is "no longer a control" was **not** accepted as final. The Worker runs LuaLaTeX with `openin_any=p` (paranoid reads) and makes that work by linking the TeX trees into the job directory, so that reads are confined to the job and the TeX installation. See [../worker.md](../worker.md#reading-below-the-job-directory-only) for the mechanism and [../security.md](../security.md) for the tests that prove it. Note that the setting is only effective together with that layout; the container sandbox stays mandatory. +- `--safer` was rejected, as recommended; the Worker never passes it (see [Worker pipeline](../internals/worker.md#engine-lockdown)). +- The section 4 finding that `openin_any` is "no longer a control" was **not** accepted as final. The Worker runs LuaLaTeX with `openin_any=p` (paranoid reads) and makes that work by linking the TeX trees into the job directory, so that reads are confined to the job and the TeX installation. See [Worker pipeline](../internals/worker.md#reading-below-the-job-directory-only) for the mechanism and [Security](../internals/security.md) for the tests that prove it. Note that the setting is only effective together with that layout; the container sandbox stays mandatory. - Fonts are loaded by file path (`Path=./fonts/`) because the luaotfload name database cannot be built under paranoid reads, which confirms section 6. The cache is built at image build time (`renderer warm-cache`). diff --git a/docs/research/markdown-latex-fidelity.md b/docs/research/markdown-latex-fidelity.md index e5d4b50..3b56d83 100644 --- a/docs/research/markdown-latex-fidelity.md +++ b/docs/research/markdown-latex-fidelity.md @@ -257,4 +257,4 @@ For the Pandoc LaTeX writer output to compile in a `scrlttr2` template: ## Outcome -The failure modes of section 3 are covered by fixtures in `testdata/fixtures/` and goldens in `testdata/golden/` (see [../testing.md](../testing.md)), and by the Worker filters described in [../worker.md](../worker.md): reader flags, math neutralisation, `
` handling, table widths, headings, image size pre-processing and attachment download ([../images.md](../images.md)). The open questions of section 6 were resolved by implementation; where a behaviour differs from the proposals above, the docs linked here are authoritative. One known gap: a captioned figure in the `neutral` letter Template is tracked in issue #43. +The failure modes of section 3 are covered by fixtures in `testdata/fixtures/` and goldens in `testdata/golden/` (see [Testing](../testing.md)), and by the Worker filters described in [Worker pipeline](../internals/worker.md): reader flags, math neutralisation, `
` handling, table widths, headings, image size pre-processing and attachment download ([Images in the pipeline](../internals/images.md)). The open questions of section 6 were resolved by implementation; where a behaviour differs from the proposals above, the docs linked here are authoritative. One known gap: a captioned figure in the `neutral` letter Template is tracked in issue #43. diff --git a/docs/research/outline-surface.md b/docs/research/outline-surface.md index f408d0c..f483af6 100644 --- a/docs/research/outline-surface.md +++ b/docs/research/outline-surface.md @@ -238,11 +238,11 @@ Feasibility of injecting a "Render with template X" item via an external script: ## Outcome -The hybrid shape of section 7 was implemented: the service configuration is the allow-list, a fenced block in the Collection overview selects a Template ([../binding.md](../binding.md)), and the Download PDF button is injected by a reverse proxy ([../injector.md](../injector.md)). +The hybrid shape of section 7 was implemented: the service configuration is the allow-list, a fenced block in the Collection overview selects a Template ([Choose a Template and Configuration](../configure/choose-a-template.md)), and the Download PDF button is injected by a reverse proxy ([Injector and download button](../deploy/injector.md)). One assumption of section 2 turned out to be wrong when tested against a real Outline in the seeded dev stack: the editor keeps only the first word of a code fence's info string, so a block opened with an info string of two words is stored with the first word alone. This affects the markers used for the Binding and the Metadata Block and was tracked in issue #42. -Resolution (issue #42): both blocks are plain `yaml` fences. The Metadata Block is the first thing in the Document; the Binding block is told apart by its root key `binding:` ([../binding.md](../binding.md), [../metadata.md](../metadata.md)). Checked against Outline 1.12.0 in the dev stack, through the paths the editor uses: +Resolution (issue #42): both blocks are plain `yaml` fences. The Metadata Block is the first thing in the Document; the Binding block is told apart by its root key `binding:` ([Choose a Template and Configuration](../configure/choose-a-template.md), [Metadata Block reference](../templates/metadata-reference.md)). Checked against Outline 1.12.0 in the dev stack, through the paths the editor uses: - `collections.update` with `data` (a ProseMirror `code_block` with `language: yaml`) comes back from `collections.info` (without `x-api-version`) as a `yaml` fence with the content unchanged, nested indentation included. - `documents.update` with `text` starting with a `yaml` fence that holds a comment, a list and nested keys comes back from `documents.info` with the fence still at the top and its content unchanged, comment and key order included. diff --git a/docs/research/test-environment.md b/docs/research/test-environment.md index 5a6cce7..1a81d4e 100644 --- a/docs/research/test-environment.md +++ b/docs/research/test-environment.md @@ -118,4 +118,4 @@ Fixtures are shared across tiers 2 and 3; tier 3 should assert only a few string ## Outcome -Implemented as planned, with these differences. The tiers are described in [../testing.md](../testing.md) and the local stack in [../development.md](../development.md). Dex, Postgres, Redis and Outline run as pinned images in Compose (`deploy/dev/versions.env`); the seeding is one Go implementation shared with the browser tests (`task dev:seed`); PDF goldens compare `pdfinfo`/`pdffonts` facts and `pdftotext -layout` text only. A rasterised PNG diff was not implemented (see [../template-author-guide.md](../template-author-guide.md#goldens-for-template-authors)). +Implemented as planned, with these differences. The tiers are described in [Testing](../testing.md) and the local stack in [Development stack and end-to-end tests](../internals/development.md). Dex, Postgres, Redis and Outline run as pinned images in Compose (`deploy/dev/versions.env`); the seeding is one Go implementation shared with the browser tests (`task dev:seed`); PDF goldens compare `pdfinfo`/`pdffonts` facts and `pdftotext -layout` text only. A rasterised PNG diff was not implemented (see [Template author guide](../templates/template-author-guide.md#goldens-for-template-authors)). diff --git a/docs/templates/README.md b/docs/templates/README.md new file mode 100644 index 0000000..cdb3260 --- /dev/null +++ b/docs/templates/README.md @@ -0,0 +1,22 @@ +--- +sidebar_position: 1 +--- + +# Templates + +This section is for people who make or choose Templates. + +A **Template** is a page design: layout, fonts and the kinds of document it +offers. A **Configuration** adapts a Template to an organisation with a name, a +logo and colours. Both are folders of files. You can write your own. + +| Page | What it covers | +|------|----------------| +| [Template catalogue](catalogue/README.md) | The Templates that ship with Outprint: a preview, a block to copy, every field, and the errors to expect. Generated from the packages. | +| [Template author guide](template-author-guide.md) | Start here to write a Template: package format, fields, patterns, the TeX deny-list, `validate`, `render`, a worked example. | +| [Packages](packages.md) | Reference for packages, field types, typed patterns, the TeX deny-list and the drift guard. | +| [Standard Fields](standard-fields.md) | Common field names that Templates should reuse, so Configurations stay portable. | +| [Metadata Block reference](metadata-reference.md) | How the block at the top of a document is read: layering, field types, errors, the `render` command. | + +Authors who only fill in a document read [Write](../write/README.md). Admins +who choose a Template for a Collection read [Configure](../configure/README.md). diff --git a/docs/templates/_category_.json b/docs/templates/_category_.json new file mode 100644 index 0000000..325ee55 --- /dev/null +++ b/docs/templates/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Templates", + "position": 4, + "collapsed": false +} diff --git a/docs/templates/catalogue/README.md b/docs/templates/catalogue/README.md new file mode 100644 index 0000000..e28d0b8 --- /dev/null +++ b/docs/templates/catalogue/README.md @@ -0,0 +1,18 @@ +--- +title: Template catalogue +--- + + + +# Template catalogue + +One page for every Template that ships with Outprint. Each page shows what the +result looks like, a block you can copy into a document, every field, and the +errors you can expect. + +| Template | Document types | +|---|---| +| [neutral](neutral.md) | `letter`, `report` | + +The pages are generated from the Template packages, so they match this version of Outprint. +To add your own Template to the list, see the [Template author guide](../template-author-guide.md). diff --git a/docs/templates/catalogue/_category_.json b/docs/templates/catalogue/_category_.json new file mode 100644 index 0000000..6448796 --- /dev/null +++ b/docs/templates/catalogue/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Catalogue", + "position": 1, + "collapsed": true +} diff --git a/docs/templates/catalogue/neutral.md b/docs/templates/catalogue/neutral.md new file mode 100644 index 0000000..b35cadc --- /dev/null +++ b/docs/templates/catalogue/neutral.md @@ -0,0 +1,147 @@ +--- +title: neutral +sidebar_position: 1 +--- + + + +# The neutral Template + +Document types: `letter`, `report`. + +![First page of a letter set with the neutral Template and the example configuration](../../assets/screens/template-neutral.png) + +The picture is the first page of the `letter` example below, typeset with the real pipeline and the `example` configuration. + +To use the Template, a Collection admin binds it to a Collection; see [Configure](../../configure/README.md). An author then starts a document with the block below. + +## Document type `letter` + +Copy this block to the very start of a document and replace the values. Lines that start with `#` are optional: remove the `#` to use them. + +````markdown +```yaml +type: letter +# title: Spring plant sale +# date: 2026-03-02 +language: en +author: Robin Sample +recipient: + - Ada Example + - 1 Sample Lane + - 12345 Exampleton +sender: + - 2 Orchard Road + - 54321 Exampleton +subject: Spring plant sale +opening: Dear Ms Example, +closing: With best wishes +reference: PS-2026-03 +enclosures: + - Price list + - Map of the greenhouse +cc: + - Treasurer + - Sale committee +``` +```` + +### Fields + +Types are explained in [Fill in the document](../../write/fill-in-the-document.md#what-each-kind-of-field-looks-like). + +| Field | Type | Required | Default | Meaning | +|---|---|---|---|---| +| `title` | string | no | the Outline title | Document title. Defaults to the title of the Outline document. | +| `date` | date | no | the last-modified date | Date of the document. Defaults to the last-modified date. An ISO date (YYYY-MM-DD) is written out in the document language; any other text is printed as written. | +| `language` | string | no | `en` | Document language, which sets how dates are written and the PDF language. Allowed: `^(en\|de)$`. | +| `author` | string | no | none | Author. Signs a letter and appears in the title block of a report. | +| `recipient` | list | no | none | Address lines. The address window is shown only when this is set. | +| `sender` | list | no | none | Sender address lines, printed under the organisation name. | +| `subject` | string | no | none | Subject line. | +| `opening` | string | no | none | Salutation. | +| `closing` | string | no | none | Complimentary close. | +| `reference` | string | no | none | Reference line, for example a file number. | +| `enclosures` | list | no | none | Enclosures, listed under the closing. | +| `cc` | list | no | none | Copy recipients, listed under the closing. | + +The configuration `example` supplies defaults for `language` (en), `opening` (Dear Sir or Madam,), `closing` (Kind regards). A document can override each of them. + +These fields are fixed by the configuration `example`. A document cannot set them, so they are not in the block. + +| Field | Type | Value | Meaning | +|---|---|---|---| +| `name` | string | `Example Garden Society` | Organisation name, shown in the letterhead and the footer. | +| `logo` | asset | `logo.svg` | Logo shown in the letterhead, an SVG file in the Configuration. | +| `accent_color` | color | `2F6F5E` | Colour of the margin stripe and the footer rule, six hex digits. | + +### Errors you can expect + +Each message below was produced by the real checker from the mistake shown. The code links to its entry in [When something goes wrong](../../write/when-something-goes-wrong.md). + +| Message | Cause | Fix | +|---|---|---| +| autor: unknown key "autor": not a field of document type "letter" (did you mean "author"?) ([unknown](../../write/when-something-goes-wrong.md#unknown)) | A field name is misspelled, or the field does not exist in this document type. | Use the name the message suggests, or one from the field table. | +| language: "fr" does not match the field pattern ^(en\|de)$ ([type](../../write/when-something-goes-wrong.md#type)) | The value does not match the allowed format of the field. | Use a value that matches the format in the field table. | +| author: must be text, not a list ([type](../../write/when-something-goes-wrong.md#type)) | A list was written where one line of text is expected. | Write a single line of text. | +| type: unknown document type "memo" (available: letter, report) ([unknown](../../write/when-something-goes-wrong.md#unknown)) | The type is not a document type of this Template. | Use one of the document types listed at the top of this page. | +| name: "name" is a Fixed Value of the Configuration and cannot be set in a Document ([unknown](../../write/when-something-goes-wrong.md#unknown)) | The field is fixed by the configuration of the Collection. | Remove the line. To change the value, ask the Collection admin to change the configuration. | +| the metadata block is not closed (missing closing fence) ([block](../../write/when-something-goes-wrong.md#block)) | The block has no closing line of three backticks. | Add a line with three backticks after the last field. | + +## Document type `report` + +Copy this block to the very start of a document and replace the values. Lines that start with `#` are optional: remove the `#` to use them. + +````markdown +```yaml +type: report +title: Spring plant sale +# date: 2026-03-02 +language: en +author: Robin Sample +subtitle: Results and plans +version: "1.0" +abstract: A short summary of the report, in *one* or two sentences. +toc: true +``` +```` + +### Fields + +Types are explained in [Fill in the document](../../write/fill-in-the-document.md#what-each-kind-of-field-looks-like). + +| Field | Type | Required | Default | Meaning | +|---|---|---|---|---| +| `title` | string | yes | the Outline title | Document title. Defaults to the title of the Outline document. | +| `date` | date | no | the last-modified date | Date of the document. Defaults to the last-modified date. An ISO date (YYYY-MM-DD) is written out in the document language; any other text is printed as written. | +| `language` | string | no | `en` | Document language, which sets how dates are written and the PDF language. Allowed: `^(en\|de)$`. | +| `author` | string | no | none | Author. Signs a letter and appears in the title block of a report. | +| `subtitle` | string | no | none | Subtitle under the title. | +| `version` | string | no | none | Version of the report, printed under the date. | +| `abstract` | text | no | none | Summary printed before the body. Inline Markdown is allowed. | +| `toc` | bool | no | none | Print a table of contents. | + +The configuration `example` supplies defaults for `language` (en). A document can override each of them. + +These fields are fixed by the configuration `example`. A document cannot set them, so they are not in the block. + +| Field | Type | Value | Meaning | +|---|---|---|---| +| `name` | string | `Example Garden Society` | Organisation name, shown in the letterhead and the footer. | +| `logo` | asset | `logo.svg` | Logo shown in the letterhead, an SVG file in the Configuration. | +| `accent_color` | color | `2F6F5E` | Colour of the margin stripe and the footer rule, six hex digits. | + +### Errors you can expect + +Each message below was produced by the real checker from the mistake shown. The code links to its entry in [When something goes wrong](../../write/when-something-goes-wrong.md). + +| Message | Cause | Fix | +|---|---|---| +| autor: unknown key "autor": not a field of document type "report" (did you mean "author"?) ([unknown](../../write/when-something-goes-wrong.md#unknown)) | A field name is misspelled, or the field does not exist in this document type. | Use the name the message suggests, or one from the field table. | +| title: required field "title" is empty ([required](../../write/when-something-goes-wrong.md#required)) | A required field is empty or missing. | Give the field a value. | +| language: "fr" does not match the field pattern ^(en\|de)$ ([type](../../write/when-something-goes-wrong.md#type)) | The value does not match the allowed format of the field. | Use a value that matches the format in the field table. | +| title: must be text, not a list ([type](../../write/when-something-goes-wrong.md#type)) | A list was written where one line of text is expected. | Write a single line of text. | +| type: unknown document type "memo" (available: letter, report) ([unknown](../../write/when-something-goes-wrong.md#unknown)) | The type is not a document type of this Template. | Use one of the document types listed at the top of this page. | +| name: "name" is a Fixed Value of the Configuration and cannot be set in a Document ([unknown](../../write/when-something-goes-wrong.md#unknown)) | The field is fixed by the configuration of the Collection. | Remove the line. To change the value, ask the Collection admin to change the configuration. | +| the metadata block is not closed (missing closing fence) ([block](../../write/when-something-goes-wrong.md#block)) | The block has no closing line of three backticks. | Add a line with three backticks after the last field. | + diff --git a/docs/metadata.md b/docs/templates/metadata-reference.md similarity index 96% rename from docs/metadata.md rename to docs/templates/metadata-reference.md index ff7f196..9203067 100644 --- a/docs/metadata.md +++ b/docs/templates/metadata-reference.md @@ -1,4 +1,11 @@ -# Metadata +--- +sidebar_position: 5 +--- + +# Metadata Block reference + +This page is the reference for Template authors and operators. Document authors +find a plain-language version in [Fill in the document](../write/fill-in-the-document.md). A Document author sets field values, and picks the Document Type, in a **Metadata Block** at the very start of the Document. The block is removed diff --git a/docs/packages.md b/docs/templates/packages.md similarity index 98% rename from docs/packages.md rename to docs/templates/packages.md index 742d11b..646ab77 100644 --- a/docs/packages.md +++ b/docs/templates/packages.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 3 +--- + # Packages The renderer reads two kinds of packages, each from its own root directory: @@ -50,7 +54,7 @@ templates/neutral/ ``` Fonts are loaded by file path, never by name: the Worker's TeX engine cannot -use the system font database (see [Worker pipeline](worker.md)). A Template loads +use the system font database (see [Worker pipeline](../internals/worker.md)). A Template loads its fonts with the fontspec option `Path=./fonts/`: ```tex @@ -237,7 +241,7 @@ fields: warning. How a Document's Metadata Block, the Configuration Defaults and the manifest -defaults combine into field values is described in [Metadata](metadata.md). +defaults combine into field values is described in [Metadata Block reference](metadata-reference.md). ## Path confinement diff --git a/docs/standard-fields.md b/docs/templates/standard-fields.md similarity index 98% rename from docs/standard-fields.md rename to docs/templates/standard-fields.md index 617d65a..6ba9053 100644 --- a/docs/standard-fields.md +++ b/docs/templates/standard-fields.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 4 +--- + # Standard Fields Standard Fields are a documented set of common field names. Templates are diff --git a/docs/template-author-guide.md b/docs/templates/template-author-guide.md similarity index 90% rename from docs/template-author-guide.md rename to docs/templates/template-author-guide.md index 7bf2054..9d97468 100644 --- a/docs/template-author-guide.md +++ b/docs/templates/template-author-guide.md @@ -1,3 +1,7 @@ +--- +sidebar_position: 2 +--- + # Template author guide This guide is for people who write a **Template** (a style family such as a @@ -10,10 +14,12 @@ Reference pages this guide builds on: - [Packages](packages.md): package format, field types, typed patterns, the TeX deny-list, drift guard, `validate` - [Standard Fields](standard-fields.md): the Standard Fields -- [Metadata](metadata.md): what a Document author can write and how values - are layered -- [Worker pipeline](worker.md): what the engine allows, and why -- [Images](images.md): images and logos +- [Metadata Block reference](metadata-reference.md): what a Document author can write + and how values are layered +- [Template catalogue](catalogue/README.md): the shipped Templates, as the + authors see them +- [Worker pipeline](../internals/worker.md): what the engine allows, and why +- [Images in the pipeline](../internals/images.md): how images are checked and placed ## The model in one minute @@ -130,7 +136,7 @@ does): Fonts are loaded by **file path** with `Path=./fonts/`, never by name (the engine cannot build the font name database under paranoid reads; see -[Worker pipeline](worker.md#reading-below-the-job-directory-only)): +[Worker pipeline](../internals/worker.md#reading-below-the-job-directory-only)): ```tex \setmainfont{texgyrepagella-regular.otf}[Path=./fonts/, @@ -146,8 +152,18 @@ repository. ### Images and logos A logo is an `asset` field. SVG files are converted to vector PDF; raster -formats are limited as described in [Images](images.md). Text inside an SVG -is drawn with the fonts of the Worker image, not the Template's fonts. +formats are limited as described in [Images in the pipeline](../internals/images.md). Text inside +an SVG is drawn with the fonts of the Worker image, not the Template's fonts. + +**Templates** must scale images to the page. The Gateway gives every image +an explicit size in points, so only the Template knows the text area. The +`neutral` Template redefines `\includegraphics` to scale an image down to the +line width and `0.9\textheight`, keeping the aspect ratio, and sets figure +captions through the `caption` package. Its `letter` has no float: the KOMA +letter class has none, so the Template defines a `figure` environment that +keeps the figure in place on one page, with the same caption style and +numbering as the report. A Template that shows Document images +must do the same. ## The TeX deny-list @@ -168,7 +184,7 @@ Every TeX file of a Template (`.latex`, `.tex`, `.sty`, `.cls`, `.def`, `.cfg`, The full list is in [Packages](packages.md#tex-deny-list). It is a safety net against accidents and careless Templates, not a sandbox: the Worker still runs without network or secrets and with shell escape off -([Security](security.md)). +([Security](../internals/security.md)). ## Standard Fields @@ -244,8 +260,8 @@ docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/data" outprint render \ ``` `--debug-tex-log` prints the full TeX log (it may contain document content; -see [Worker pipeline](worker.md)); `--json-errors` prints the JSON error body on -stdout ([Metadata](metadata.md)). +see [Worker pipeline](../internals/worker.md)); `--json-errors` prints the JSON error body on +stdout ([Metadata Block reference](metadata-reference.md)). ### Drift guard @@ -338,7 +354,7 @@ with `validate` and `render` against the image of this repository. listing the field and it is an error. 5. Render a Document that sets it. The Metadata Block of a Document is a - `yaml` block at its start ([Metadata](metadata.md)): + `yaml` block at its start ([Metadata Block reference](metadata-reference.md)): ````markdown ```yaml diff --git a/docs/testing.md b/docs/testing.md index 2fd68d9..1eb7156 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -6,15 +6,15 @@ |------|---------|-------|--------| | 1 | `task test` | Go | Unit tests, the Gateway HTTP API with fakes, the Compose baseline check, the Gateway half of the SVG attack corpus | | 2 | `task test-golden` | Docker | PDF goldens, the Worker tests and security layer 1 inside the pinned outprint image | -| 2 | `task test-sandbox` | Docker | Security layers 2 and 3: the Worker under the shipped container flags (see [Security](security.md)) | -| 3 | `task e2e:run` | Docker, Go, Node | Browser end-to-end tests (Playwright) against the seeded dev stack, see [Development stack and end-to-end tests](development.md) | +| 2 | `task test-sandbox` | Docker | Security layers 2 and 3: the Worker under the shipped container flags (see [Security](internals/security.md)) | +| 3 | `task e2e:run` | Docker, Go, Node | Browser end-to-end tests (Playwright) against the seeded dev stack, see [Development stack and end-to-end tests](internals/development.md) | `task test-security` runs layers 1 to 3. No test of tier 2 skips: a missing tool, image or Docker daemon fails the run. ## Tier 3 in CI -`.github/workflows/e2e.yml` runs tier 3 on pushes to `main`, on demand and on pull requests that touch the injector, the Outline client, the Gateway, the seed, `deploy/`, `web/` or `e2e/`. A weekly, non-blocking run (`e2e-latest-outline.yml`) uses the newest stable Outline release and opens an issue when it fails. Both upload the Playwright report, traces and compose logs on failure. Details: [Development stack and end-to-end tests](development.md#ci). +`.github/workflows/e2e.yml` runs tier 3 on pushes to `main`, on demand and on pull requests that touch the injector, the Outline client, the Gateway, the seed, `deploy/`, `web/` or `e2e/`. A weekly, non-blocking run (`e2e-latest-outline.yml`) uses the newest stable Outline release and opens an issue when it fails. Both upload the Playwright report, traces and compose logs on failure. Details: [Development stack and end-to-end tests](internals/development.md#ci). ## CI diff --git a/docs/write/README.md b/docs/write/README.md new file mode 100644 index 0000000..c4ce38b --- /dev/null +++ b/docs/write/README.md @@ -0,0 +1,45 @@ +--- +sidebar_position: 1 +--- + +# Write + +You write documents in Outline. Outprint turns a document into a PDF with a +letterhead, a footer and a fixed layout. You do not need to design anything. + +This section is for you if you write documents. It does not need any technical +knowledge. + +| Page | What you learn | +|------|----------------| +| [Download a PDF](download-a-pdf.md) | Get your first PDF in five minutes. | +| [Fill in the document](fill-in-the-document.md) | Add the details a letter or report needs, such as the recipient or the subject. | +| [Images and logos](images-and-logos.md) | Add pictures, and what to do when one is rejected. | +| [When something goes wrong](when-something-goes-wrong.md) | Find your message and see what to do. | +| [Limits you may hit](limits-you-may-hit.md) | How large a document can be. | + +## Four words you will meet + +Outprint uses a few words with a fixed meaning. The +[vocabulary](https://github.com/taskmedia/outprint/blob/main/CONTEXT.md) lists +them all. These four are enough to start. + +**Template** +: The look of a PDF: page layout, fonts, and the kinds of document it can make, + such as a letter or a report. Think of it as letter paper that is ready to + use. + +**Configuration** +: The details of your organisation that go on every PDF: name, logo and colour. + A Template plus a Configuration gives your own letterhead. You cannot change + a Configuration in a document. This keeps every PDF looking the same. + +**Collection** +: A collection in Outline, shown in the left sidebar. Every document in a + Collection, including documents in nested folders, gets the same PDF look. + +**Binding** +: The choice of one Template and one Configuration for a Collection. A + Collection admin makes it once. You do not choose it yourself. If your + Collection has no Binding, there is no Download PDF button. See + [Configure](../configure/README.md). diff --git a/docs/write/_category_.json b/docs/write/_category_.json new file mode 100644 index 0000000..0166f59 --- /dev/null +++ b/docs/write/_category_.json @@ -0,0 +1,5 @@ +{ + "label": "Write", + "position": 1, + "collapsed": false +} diff --git a/docs/write/download-a-pdf.md b/docs/write/download-a-pdf.md new file mode 100644 index 0000000..4c61c37 --- /dev/null +++ b/docs/write/download-a-pdf.md @@ -0,0 +1,54 @@ +--- +sidebar_position: 2 +--- + +# Download a PDF + +This takes about five minutes. You need an Outline account and a document in a +Collection that has a PDF look. + +## 1. Open a document + +Open any document in your Collection. If the Collection is set up for PDFs, a +**Download PDF** button appears at the bottom right of the page. + +![A document in Outline with the Download PDF button at the bottom right](../assets/screens/bound-document-with-button.png) + +## 2. Click the button + +Click **Download PDF**. The button is disabled while the PDF is made. This takes +a few seconds. Your browser then saves the file, like any other download. + +Open the PDF. The title of the document is the title of the PDF. The date is the +day the document was last changed. + +## 3. If there is no button + +No button means that the Collection has no PDF look yet. + +![A document in a Collection without a PDF look: there is no Download PDF button](../assets/screens/unbound-document-no-button.png) + +Ask the admin of the Collection to set one up. The admin finds the steps in +[Configure](../configure/README.md). + +## 4. Add the details + +A plain document already makes a PDF. A letter also needs a recipient and a +subject. You write them in a short block at the top of the document. See +[Fill in the document](fill-in-the-document.md). + +## 5. If a message appears + +If something is wrong with the document, a message appears instead of the +download. It says what to fix and where. + +![The message an author sees when the details at the top of the document are wrong](../assets/screens/error-shown-to-author.png) + +Fix the problem and click the button again. Find your message in +[When something goes wrong](when-something-goes-wrong.md). + +## Good to know + +- The PDF shows the document as it is saved now. +- Nothing is stored. Every click makes a new PDF. +- You can only download PDFs of documents you are allowed to read. diff --git a/docs/write/fill-in-the-document.md b/docs/write/fill-in-the-document.md new file mode 100644 index 0000000..b496e78 --- /dev/null +++ b/docs/write/fill-in-the-document.md @@ -0,0 +1,173 @@ +--- +sidebar_position: 3 +--- + +# Fill in the document + +A letter needs a recipient and a subject. A report needs a title and maybe a +table of contents. You write these details in a small block at the very start of +your document. Outprint calls it the **metadata block**. It does not appear in +the PDF as text. Outprint reads it and puts each detail in the right place. + +## Start from a ready-made block + +Do not write the block from scratch. Copy one. + +1. Ask your Collection admin which Template your Collection uses, or look at the + [Template catalogue](../templates/catalogue/README.md). Every Template has a + page there. +2. Open the page of your Template and copy the block of the document type you + want, for example `letter`. +3. Paste it at the very start of your document, above everything else. +4. Replace the example values with your own. + +The first Template that ships with Outprint is +[neutral](../templates/catalogue/neutral.md). Its letter block looks like this: + +````markdown +```yaml +type: letter +# title: Spring plant sale +# date: 2026-03-02 +language: en +author: Robin Sample +recipient: + - Ada Example + - 1 Sample Lane + - 12345 Exampleton +sender: + - 2 Orchard Road + - 54321 Exampleton +subject: Spring plant sale +opening: Dear Ms Example, +closing: With best wishes +reference: PS-2026-03 +enclosures: + - Price list + - Map of the greenhouse +cc: + - Treasurer + - Sale committee +``` +```` + +The text of your letter follows below the block, as in any Outline document. + +![The metadata block at the top of a document in Outline](../assets/screens/document-metadata-block.png) + +The result is a letter with the recipient in the address window, the subject in +bold, and your text below. + +![The first page of the letter made from the block above](../assets/screens/example-letter.png) + +## How to make the block in Outline + +The block is a code block. In Outline, type three backticks to start one, then +choose **YAML** as its language. Type or paste the lines inside. + +Three rules keep it working: + +- The block must be the first thing in the document. A blank line before it is + fine. Text before it is not. +- The language must be **YAML**, nothing else. +- Only the first block counts. A second YAML block further down is shown as + normal text. That is useful when you want to show an example in a document. + +A document without a block still works, as long as the Template does not need +any detail that has no default. + +## Lines that start with `#` + +A line that starts with `#` is a comment. Outprint ignores it. The ready-made +blocks use comments for details that Outprint fills in for you, such as the +title and the date. Remove the `#` to set the value yourself. + +## What each kind of field looks like + +Every line in the block is a **field**: a name, a colon, a space and a value. +The name stays as it is. You change the value. A field has one of these kinds. + +| Kind | What it is | How you write it | +|------|------------|------------------| +| One line of text | A subject, a reference, an author | `subject: Spring plant sale` | +| A longer text | A summary of a report, with **bold**, *italics*, links and lists | `abstract: A short summary of *this* report.` | +| A list | Address lines, enclosures, copy recipients | One `-` per item, indented by two spaces, as in `recipient` above. You can also write several lines of text; each line becomes one item. | +| A date | The date of the document | `date: 2026-03-02` (year, month, day), or any text such as `date: spring 2026`, which is printed as you wrote it | +| Yes or no | A switch, such as the table of contents | `toc: true` or `toc: false` | +| A choice | A value from a short list of allowed values | The page of your Template lists the values. Write one of them exactly. | +| Colour, logo | Set by the Configuration, not by you | You do not write these | + +A value must fit its field. Outprint never guesses. A list where one line of +text is expected is a mistake, and Outprint tells you. + +### Text that contains a colon + +A colon followed by a space has a special meaning in the block. If your text +contains one, put the whole value in quotation marks: + +````markdown +```yaml +type: letter +subject: "Re: spring plant sale" +``` +```` + +Without the quotation marks, the block is broken and Outprint reports a +[`block`](when-something-goes-wrong.md#block) problem. Quotation marks are also +a good idea for a value that starts with a special character such as `#`, `*`, +`&`, `[` or `{`. + +### A plain text field is plain + +Only longer text fields accept formatting. In a one-line field, stars, dollar +signs and backslashes print as you typed them. This protects the layout. + +## The kind of document + +The line `type: letter` chooses the kind of document. `letter` and `report` +are the kinds the neutral Template offers. The kind decides which other fields +exist. If your Collection admin has chosen a default kind, you can leave `type` +out. A kind that the Template does not know is an error. + +## Defaults, and what you cannot change + +Details come from several places. When two places set the same field, the first +place in this list wins. + +1. **The Configuration.** Some fields are fixed, for example the organisation + name, the logo and the colour. You cannot set them in a document. If you try, + Outprint says so. +2. **Your block.** What you write. +3. **The Configuration's defaults.** A Configuration may set a salutation or a + closing for you. Your block wins over these. +4. **Outprint.** The title of the Outline document and the date of the last + change. +5. **The Template.** Its own defaults, such as the language. + +To remove a default, leave the value empty: `closing:` removes the closing. +If the field is required, an empty value is an error. + +## Common fields + +These names mean the same in every Template that uses them. The Template page in +the [catalogue](../templates/catalogue/README.md) lists the exact fields of each +Template. + +| Field | Used for | +|-------|----------| +| `title` | The title of the document | +| `date` | The date of the document | +| `language` | The language of the document, for example `en` or `de`. It sets how dates are written. | +| `author` | Who wrote it | +| `recipient`, `sender` | Address lines of a letter | +| `subject`, `reference` | Subject line and reference of a letter | +| `opening`, `closing` | Salutation and closing of a letter | +| `enclosures`, `cc` | Lists below a letter | +| `subtitle`, `version`, `abstract`, `toc` | Parts of a report | + +## Check your block + +Click **Download PDF**. Outprint checks the whole block and reports all problems +at once, each with its line. A name that is almost right gets a hint, such as +`did you mean "recipient"?`. Find the message in +[When something goes wrong](when-something-goes-wrong.md). diff --git a/docs/write/images-and-logos.md b/docs/write/images-and-logos.md new file mode 100644 index 0000000..269c0b6 --- /dev/null +++ b/docs/write/images-and-logos.md @@ -0,0 +1,92 @@ +--- +sidebar_position: 4 +--- + +# Images and logos + +Pictures you add to a document in Outline appear in the PDF. This page tells +you what works and what to do when a picture is refused. + +## Add a picture + +Upload the picture to the document in Outline. Drag it in, paste it, or use the +image button of the editor. Pictures that you only link from another website +do not work. + +Outprint accepts these formats: + +- PNG +- JPEG +- WebP +- GIF, if it is not animated +- SVG, the format of drawings and diagrams + +Outprint looks at the content of the file, not at its name. A file called +`photo.png` that is really something else is refused. + +## Size + +Resize the picture in Outline. The PDF uses the size you chose. A picture that +is too big for the page is scaled down to fit. It is never cut off. + +If you do not choose a size, the picture has its natural size. + +## Captions + +A picture on a line of its own gets a number and a caption in the PDF. The +caption is the text you type under the picture in Outline. + +A picture without a caption text, or one that sits inside a line of text, has no +caption. + +## What happens to your file + +Outprint saves a clean copy of every picture. A photo taken sideways is turned +upright. Details such as the camera model or the place of the photo are +removed. PNG stays sharp, and JPEG stays at high quality. + +## Limits + +- 50 pictures in one document. +- 10 MiB for one picture (an SVG: 2 MiB). +- 50 MiB for all pictures together. +- 40 megapixels in one picture. That is, for example, 8000 by 5000 pixels. + +See [Limits you may hit](limits-you-may-hit.md) for the other limits. + +## Drawings and diagrams (SVG) + +An SVG stays sharp at every size. Two things help. + +- **Turn text into outlines.** Outprint draws text in an SVG with its own fonts. + If your font is missing, it uses another one without telling you. The picture + can then look different from the screen. In your drawing program, choose + "Object to path" or "Outline text" before you export. The text then looks as + you drew it. +- **Export plain SVG.** Pictures with scripts, links to other files or + external styles are refused for safety. + +## The logo + +The logo on your letterhead is not part of your document. The admin of the +Collection sets it once. You cannot change it in a document. See +[Fill in the document](fill-in-the-document.md#defaults-and-what-you-cannot-change). + +## When a picture is refused + +The message lists every picture that has a problem, with its line. Find the +code of the message in [When something goes wrong](when-something-goes-wrong.md). + +| Code | What it means | What to do | +|------|---------------|------------| +| [`missing`](when-something-goes-wrong.md#missing) | The picture has no address, or it was deleted | Upload it again | +| [`forbidden`](when-something-goes-wrong.md#forbidden) | The picture comes from another website, or you may not read it | Upload your own copy | +| [`too_large`](when-something-goes-wrong.md#too_large) | A size or count limit was passed | Shrink it or use fewer pictures | +| [`format`](when-something-goes-wrong.md#format) | The format is not supported, or the picture is an animation | Export a PNG or JPEG | +| [`decode`](when-something-goes-wrong.md#decode) | The file is damaged | Save it again and upload the new file | +| [`pixels`](when-something-goes-wrong.md#pixels) | The picture has too many pixels | Scale it down | +| [`svg_rejected`](when-something-goes-wrong.md#svg_rejected) | The SVG holds something not allowed | Export a plain SVG, or a PNG | +| [`syntax`](when-something-goes-wrong.md#syntax) | The picture is written across several lines | Put it on a line of its own | + +Do not add curly-bracket settings such as `{width=50%}` after a picture. They +are ignored. Use the size handles in Outline. diff --git a/docs/write/limits-you-may-hit.md b/docs/write/limits-you-may-hit.md new file mode 100644 index 0000000..f50ab6a --- /dev/null +++ b/docs/write/limits-you-may-hit.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 6 +--- + +# Limits you may hit + +Outprint keeps a PDF within fixed sizes, so that one large document cannot slow +everybody down. A document that does not fit fails with a message. Outprint +never cuts a document short. + +The person who runs Outprint can change some of these values. The numbers +below are the standard ones. + +| What | Limit | If you pass it | +|------|-------|----------------| +| Text of the document | 1 MiB of text | [`too_large`](when-something-goes-wrong.md#too_large) | +| Pictures in one document | 50 | [`too_large`](when-something-goes-wrong.md#too_large) | +| One picture | 10 MiB (an SVG: 2 MiB) | [`too_large`](when-something-goes-wrong.md#too_large) | +| All pictures together | 50 MiB | [`too_large`](when-something-goes-wrong.md#too_large) | +| Pixels of one picture | 40 million | [`pixels`](when-something-goes-wrong.md#pixels) | +| Time to make the PDF | 60 seconds | [`timeout`](when-something-goes-wrong.md#timeout) | +| Pages of the PDF | 500 | [`pages_exceeded`](when-something-goes-wrong.md#pages_exceeded) | +| Size of the PDF | 50 MiB | [`pdf_too_large`](when-something-goes-wrong.md#pdf_too_large) | + +## Only a few PDFs at a time + +Making a PDF takes effort. Outprint makes a few at a time and lets the others +wait in a short line. + +- You can have **one PDF in progress**. If you click the button twice, the + second click gets [`user_limit`](when-something-goes-wrong.md#user_limit). +- If many people click at once, the line can fill up. The message is + [`busy`](when-something-goes-wrong.md#busy). Wait a few seconds and try again. + +Messages about a full line are not your fault. Nothing in your document needs to +change. + +## No copy is kept + +Outprint makes a new PDF at every click and keeps nothing. A PDF reflects the +document as it is saved now. You can only download PDFs of documents that you +may read in Outline. + +## If a document is too big + +- Split it into two documents. Each gets its own PDF. +- Scale pictures down before you upload them. +- Remove pictures that the reader does not need. diff --git a/docs/write/when-something-goes-wrong.md b/docs/write/when-something-goes-wrong.md new file mode 100644 index 0000000..fe09c2b --- /dev/null +++ b/docs/write/when-something-goes-wrong.md @@ -0,0 +1,498 @@ +--- +sidebar_position: 5 +--- + +# When something goes wrong + +If Outprint cannot make your PDF, it shows a message instead of the download. +Find the message on this page. Each entry says what the message means and what +to do. + +![The message an author sees when the details at the top of the document are wrong](../assets/screens/error-shown-to-author.png) + +Every kind of problem has a short name, called its **code**. The message +usually shows the code. Search this page for it. The code is also the link +target: `#no_binding` leads to the entry for `no_binding`. + +## How to read a message + +- **Line numbers** count from the first line of the document. The line is where + the problem starts. +- **All problems come at once.** Fix everything the message lists, then click + the button again. +- **A hint** such as `did you mean "recipient"?` is almost always right. +- **A job number** appears for some failures. Give it to the person who runs + Outprint. It finds the failure in the log. + +The first word of a problem tells you who fixes it. + +| Source | Who fixes it | +|--------|--------------| +| `document` | You. The problem is in your document. | +| `collection` | The admin of your Collection. See [Configure](../configure/README.md). | +| `configuration`, `template` | The admin of your Collection, or the person who made the Template. | +| `service` | Nobody. Wait a moment and try again. | + +## How this list was made + +The list is complete. It is derived from the source code, not written from +memory. A code is any short name that Outprint can put in an error list (the +`code` of a problem), or use as the kind of any other failure. The script +`scripts/docs-error-codes.sh` collects them from these files: + +- `internal/metadata/errors.go`: problems in the block at the top of a document. +- `internal/images/kinds.go`: problems with images. +- `internal/binding/binding.go`: problems in the PDF setup of a Collection. +- `internal/gateway/admit.go` and `internal/gateway/errors.go`: a busy service, + and failures of the service that are not about your document. +- `internal/workerapi/workerapi.go` and `internal/worker/error.go`: failures + while the PDF is made. + +The task `task docs:errors-check` compares that list with the headings of this +page. It fails if a code has no entry here, or if an entry names a code that +does not exist. A heading is the anchor of its entry, so links to it stay valid. + +## Problems in your document + +These messages come as a list, one line per problem, each with a line number. + +### `block` + +**Message:** `the metadata block is not closed (missing closing fence)`, or +`invalid YAML in the metadata block: ...`, or `key "subject" appears twice`. + +**Cause:** The block at the top of the document is broken. A closing line of +three backticks is missing, a line is not written as `name: value`, or a field +name appears twice. + +**What to do:** Check the lines the message points to. Close the block with +three backticks on a line of their own. Put a value that contains a colon and a +space in quotation marks. See [Fill in the document](fill-in-the-document.md). + +### `required` + +**Message:** `required field "title" is missing`, or `required field "title" is empty`. + +**Cause:** The Template needs this detail and the document has none. An empty +value counts as missing. + +**What to do:** Add the field to the block and give it a value. + +### `unknown` + +**Message:** `unknown key "recipent": not a field of document type "letter" (did you mean "recipient"?)`. + +Other messages with this code: `unknown document type "memo" (available: letter, report)` +and `"name" is a Fixed Value of the Configuration and cannot be set in a Document`. + +**Cause:** Outprint does not know the name. You misspelled a field, used a field +that this kind of document does not have, or tried to set a detail that the +Collection fixes, such as the organisation name or the logo. + +**What to do:** Use the name from the hint, or one from the page of your +Template in the [catalogue](../templates/catalogue/README.md). For a fixed +detail, delete the line. To change it, ask your Collection admin. + +### `type` + +**Message:** `must be text, not a list`, `must be a single line`, `must be true or false`, +or `"fr" does not match the field pattern ^(en|de)$`. + +**Cause:** The value has the wrong shape for its field. Outprint never guesses. +A list where one line of text belongs is a mistake, and so is a value outside +the allowed format. + +**What to do:** Write the value the way the field expects. The page of your +Template shows the kind of each field. See +[what each kind of field looks like](fill-in-the-document.md#what-each-kind-of-field-looks-like). + +### `enum` + +**Message:** `"middle" is not one of left, right`. + +**Cause:** The field accepts only a fixed set of values. + +**What to do:** Write one of the values the message lists, exactly as shown. + +### `missing` + +**Message:** `the image has no address`, `the image was not found; it may have been deleted`, +or `the attachment id is not valid`. + +**Cause:** An image in the document points to nothing, or the file is gone from +Outline. + +**What to do:** Delete the image and upload it again. See +[Images and logos](images-and-logos.md). + +### `forbidden` + +**Message:** `this address is not an Outline attachment; ...`, +`images from other sites are not allowed; ...`, or `you are not allowed to read this image`. + +**Cause:** Outprint only uses images that you uploaded to Outline. An image +linked from another website is refused. So is an image you may not read. + +**What to do:** Download the picture and upload it to the document. If you may +not read the image, ask its owner for access. + +The same code appears as a single sentence: `you are not allowed to read this document`. +That means Outline refused you the document itself. Ask the document owner for +access. + +### `too_large` + +**Message:** `the image is larger than 10 MiB`, `the document has more than 50 images`, +`the images of this document together exceed 50 MiB`, or `the document is too large to render`. + +**Cause:** A limit was passed. See [Limits you may hit](limits-you-may-hit.md). + +**What to do:** Shrink the image, use fewer images, or split the document in two. +The message says which limit it is. + +### `format` + +**Message:** `this is not a supported image (detected: ...)`, or `animated GIF is not supported; ...`. + +**Cause:** Outprint accepts PNG, JPEG, WebP, GIF (not animated) and SVG. The +file is something else, or it is an animation. + +**What to do:** Export the picture as PNG or JPEG and upload it again. + +### `decode` + +**Message:** `the png image could not be decoded`, or `the jpeg image header is not valid`. + +**Cause:** The file has a supported format but is damaged. + +**What to do:** Open the image on your computer. If it opens, save it again and +upload the new file. + +### `pixels` + +**Message:** `the image is 9000 x 6000 pixels (54 megapixels), at most 40 megapixels`. + +**Cause:** The picture has more pixels than Outprint processes. + +**What to do:** Scale the picture down before you upload it. Half the width is +usually still sharp on paper. + +### `svg_rejected` + +**Message:** Starts with `the SVG` and names the reason, for example +`the SVG has a