diff --git a/Makefile b/Makefile index 78a8438..5300386 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ whitelabel-check: install ## Build with example overrides and grep for brand le BRAND_COMPANY='Acme Inc.' \ BRAND_CLI=acme \ BRAND_PRODUCT_SLUG=acme \ + BRAND_PACKAGE_PATH_NAME=acme \ BRAND_IMAGE=acme/documentserver \ BRAND_URL=https://acme.example \ BRAND_REPO=https://github.com/acme \ diff --git a/brand.yml b/brand.yml index 13e4507..4536def 100644 --- a/brand.yml +++ b/brand.yml @@ -4,7 +4,8 @@ # 1. Edit the values below, OR # 2. Set the matching BRAND_* environment variables at build time: # BRAND_NAME, BRAND_SHORT_NAME, BRAND_TAGLINE, BRAND_COMPANY, -# BRAND_PRODUCT_SLUG, BRAND_CLI, BRAND_URL, BRAND_REPO, BRAND_DOCS_URL +# BRAND_PRODUCT_SLUG, BRAND_PACKAGE_PATH_NAME, BRAND_CLI, BRAND_URL, +# BRAND_REPO, BRAND_DOCS_URL # 3. Replace docs/assets/logo.svg and docs/assets/favicon.svg with your own. # 4. Override theme colors via BRAND_PRIMARY_COLOR / BRAND_ACCENT_COLOR # (any Material palette name: indigo, deep purple, teal, blue, etc.). @@ -18,6 +19,10 @@ brand: tagline: Your sovereign office company: Euro-Office product_slug: eurooffice + # On-disk/package namespace used in install paths (/etc, /var/log, /var/www, + # /var/lib), the package/artifact name, and example container names. + # Branded builds differ here, e.g. nextcloud-office. + package_path_name: euro-office cli: eurooffice image: ghcr.io/euro-office/documentserver url: https://euro-office.com diff --git a/docs/configuration/commands.md b/docs/configuration/commands.md new file mode 100644 index 0000000..5eaab59 --- /dev/null +++ b/docs/configuration/commands.md @@ -0,0 +1,70 @@ +# Command-line Tools + +Euro-Office Document Server ships a set of `documentserver-*` helper scripts for +administration. On a package install they are on the `PATH` (in `/usr/bin`) and +run as root: + +```bash +sudo documentserver-jwt-status.sh +``` + +In Docker, run them inside the container: + +```bash +docker exec {{ brand.package_path_name }} documentserver-jwt-status.sh +``` + +## Available commands + +| Command | Purpose | +|---|---| +| `documentserver-configure.sh` | Configure the database, message broker, Redis, JWT, and port. Package install only | +| `documentserver-jwt-status.sh` | Print the current JWT enabled state, secret, and header | +| `documentserver-generate-allfonts.sh` | Regenerate the font cache. See [Custom fonts](fonts.md) | +| `documentserver-pluginsmanager.sh` | Install and update editor plugins. See [Installing plugins](plugins.md) | +| `documentserver-flush-cache.sh` | Bump the cache tag so browsers reload editor assets | +| `documentserver-update-securelink.sh` | Set or rotate the nginx secure-link secret | +| `documentserver-static-gzip.sh` | Pre-compress static assets and enable nginx `gzip_static` | +| `documentserver-prepare4shutdown.sh` | Drain the server before a graceful shutdown or restart | +| `documentserver-letsencrypt.sh` | Obtain and configure a Let's Encrypt certificate | + +## Common tasks + +**Check JWT configuration** — confirm whether token validation is on and which secret is in use: + +```bash +sudo documentserver-jwt-status.sh +``` + +**Configure connections** (package install) — point the server at external services: + +```bash +sudo documentserver-configure.sh \ + --databasetype postgres \ + --databasehost db.internal \ + --databasename eurooffice \ + --databaseuser eurooffice \ + --databasepassword your-password \ + --jwtsecret at-least-32-chars-long-for-hs256 +``` + +Run `documentserver-configure.sh --help` to see all options. The package +supports `postgres`, `mariadb`, and `mysql`. + +**Issue a TLS certificate** — pass the contact email and domain: + +```bash +sudo documentserver-letsencrypt.sh admin@example.com docs.example.com +``` + +**Rotate the secure-link secret** — omit the value to generate a random one: + +```bash +sudo documentserver-update-securelink.sh --secure_link_secret a-random-secret-for-nginx-secure-link +``` + +**Drain before maintenance** — stop accepting new editing sessions before restarting: + +```bash +sudo documentserver-prepare4shutdown.sh +``` diff --git a/docs/configuration/fonts.md b/docs/configuration/fonts.md new file mode 100644 index 0000000..485bd69 --- /dev/null +++ b/docs/configuration/fonts.md @@ -0,0 +1,42 @@ +# Installing Custom Fonts + +Euro-Office Document Server ships with a built-in font set. To make additional fonts available in the editors, add them to the server and regenerate the font cache. + +Supported formats: TrueType (`.ttf`, `.tte`), OpenType (`.otf`, `.otc`, `.ttc`), and Web Open Font Format (`.woff`, `.woff2`). + +## Bare-metal (deb/rpm) + +1. Copy your font files to `/usr/share/fonts/`: + + ```bash + sudo cp myfont.ttf /usr/share/fonts/ + ``` + +2. Regenerate the font cache: + + ```bash + sudo documentserver-generate-allfonts.sh + ``` + +The new fonts appear in the editor font list. No restart is required. + +## Docker + +Mount a host directory with your fonts into the container and let it regenerate the cache on startup: + +```bash +docker run -d \ + --name {{ brand.package_path_name }} \ + --restart=unless-stopped \ + -p 80:80 \ + -e JWT_ENABLED=true \ + -e JWT_SECRET=at-least-32-chars-long-for-hs256 \ + -v /path/on/your/host/fonts:/usr/share/fonts/custom \ + ghcr.io/euro-office/documentserver:latest +``` + +Fonts are picked up automatically because `GENERATE_FONTS` is enabled by default. To add fonts to a running container, drop the files into the host directory and restart it: + +```bash +docker restart {{ brand.package_path_name }} +``` diff --git a/docs/configuration/logging.md b/docs/configuration/logging.md new file mode 100644 index 0000000..906cfb5 --- /dev/null +++ b/docs/configuration/logging.md @@ -0,0 +1,79 @@ +# Logging + +Euro-Office Document Server uses [log4js](https://log4js-node.github.io/log4js-node/) +for its server-side logs. You control how much is logged with the log level, and +read the output from per-service log files. + +## Log level + +The level is set in the log4js config: + +``` +/etc/{{ brand.package_path_name }}/documentserver/log4js/production.json +``` + +Change `categories.default.level` (default `WARN`) to one of the log4js levels, +from least to most verbose: + +``` +OFF · FATAL · ERROR · WARN · INFO · DEBUG · TRACE +``` + +```json +{ + "categories": { + "default": { "appenders": ["default"], "level": "DEBUG" } + } +} +``` + +### Package install (deb/rpm) + +Edit `production.json`, then restart the services to apply: + +```bash +sudo systemctl restart ds-docservice ds-converter ds-metrics +``` + +### Docker + +Do not edit the file by hand. Set the log level with an environment variable and +recreate the container; the entrypoint writes it into `production.json`: + +```bash +docker run -d \ + --name {{ brand.package_path_name }} \ + --restart=unless-stopped \ + -p 80:80 \ + -e DS_LOG_LEVEL=DEBUG \ + ghcr.io/euro-office/documentserver:latest +``` + +!!! warning + `DEBUG` and `TRACE` are verbose. Use them to diagnose a problem, then set the + level back to `WARN` for normal operation. + +## Log files + +Logs are written per service under `/var/log/{{ brand.package_path_name }}/documentserver/`: + +| File | Component | +|---|---| +| `docservice/out.log`, `docservice/err.log` | DocService (collaboration server) | +| `converter/out.log` | FileConverter (document conversion) | +| `metrics/out.log` | Metrics | +| `adminpanel/out.log` | Admin panel | +| `nginx.access.log` | nginx access log | + +### Viewing logs in Docker + +The application logs are written to the files above inside the container, not to +`docker logs`. Tail them directly: + +```bash +docker exec {{ brand.package_path_name }} tail -f /var/log/{{ brand.package_path_name }}/documentserver/docservice/out.log +``` + +To keep logs on the host, mount the log directory as a volume +(`-v /path/to/logs:/var/log/{{ brand.package_path_name }}/documentserver`) as shown in the +[Docker installation guide](../installation/docker.md#persistent-data). diff --git a/docs/configuration/plugins.md b/docs/configuration/plugins.md new file mode 100644 index 0000000..0fbcb6e --- /dev/null +++ b/docs/configuration/plugins.md @@ -0,0 +1,115 @@ +# Installing Plugins + +Plugins extend the editors with extra panels and tools. Once installed on the +server, a plugin is available to all users of that document server. This guide +applies to self-hosted Euro-Office installations. + +Plugins live in the `sdkjs-plugins` directory of the document server: + +``` +/var/www/{{ brand.package_path_name }}/documentserver/sdkjs-plugins/ +``` + +## Finding plugins + +!!! note "No app directory yet" + + Euro-Office does not yet have an in-product app directory for browsing and + installing plugins. Until then, install plugins manually as described below. + For a curated list of plugins known to work with Euro-Office, see + [Plugins](../integration/plugins.md). + +Because Euro-Office is a fork of ONLYOFFICE, plugins built for the ONLYOFFICE +editors are generally compatible. Each plugin is a folder containing a +`config.json` manifest and its assets. The upstream plugins are maintained +together in one repository, with each plugin under +[`sdkjs-plugins/content`](https://github.com/ONLYOFFICE/onlyoffice.github.io/tree/master/sdkjs-plugins/content) +(`wordscounter`, `thesaurus`, `translator`, and others). Before installing, +check that the plugin supports your editor version. + +## Package install (deb/rpm) + +1. Get the plugin folder you want. Since the upstream plugins live in a single + repository, clone it and copy the individual plugin into the plugins + directory: + + ```bash + git clone --depth 1 https://github.com/ONLYOFFICE/onlyoffice.github.io + sudo cp -r onlyoffice.github.io/sdkjs-plugins/content/wordscounter \ + /var/www/{{ brand.package_path_name }}/documentserver/sdkjs-plugins/ + ``` + + For a plugin distributed in its own repository, clone it directly into a + folder under `sdkjs-plugins/` instead. + +2. Register it and reload the editors: + + ```bash + sudo documentserver-pluginsmanager.sh + ``` + + This fixes file ownership, restarts the document service, and flushes the + editor cache so the plugin appears. + +## Docker + +Mount the plugin from the host so it survives container recreation. Clone the +plugin folder to a directory on the host and bind-mount it into the document +server's `sdkjs-plugins` directory: + +```bash +git clone --depth 1 https://github.com/ONLYOFFICE/onlyoffice.github.io +cp -r onlyoffice.github.io/sdkjs-plugins/content/wordscounter \ + /path/on/your/host/plugins/wordscounter +``` + +```bash +docker run -d \ + --name {{ brand.package_path_name }} \ + --restart=unless-stopped \ + -p 80:80 \ + -e JWT_ENABLED=true \ + -e JWT_SECRET=at-least-32-chars-long-for-hs256 \ + -v /path/on/your/host/plugins/wordscounter:/var/www/{{ brand.package_path_name }}/documentserver/sdkjs-plugins/wordscounter \ + ghcr.io/euro-office/documentserver:latest +``` + +Mount each plugin into its own subdirectory under `sdkjs-plugins/` rather than +mounting over the whole directory, which would hide the built-in plugins. After +starting the container, register the plugin so the editors pick it up: + +```bash +docker exec {{ brand.package_path_name }} documentserver-pluginsmanager.sh +``` + +!!! tip + Mounting keeps the plugin on the host, so it persists when the container is + recreated. You only need to re-run the register command after recreating the + container. + +## Per-user plugins from the editor + +The above installs a plugin server-wide for every user. The editors also include +a built-in **Plugins** tab with a plugin manager, where individual users can add +plugins for their own session without server access. This is useful for testing +a plugin before deploying it for everyone. + +## Removing a plugin + +Remove the plugin's folder and re-register so the editors stop loading it. + +=== "Package install" + + ```bash + sudo rm -rf /var/www/{{ brand.package_path_name }}/documentserver/sdkjs-plugins/wordscounter + sudo documentserver-pluginsmanager.sh + ``` + +=== "Docker" + + Remove the `-v` mount for the plugin and recreate the container, then delete + the plugin folder from the host: + + ```bash + rm -rf /path/on/your/host/plugins/wordscounter + ``` diff --git a/docs/configuration/server.md b/docs/configuration/server.md new file mode 100644 index 0000000..30e38e8 --- /dev/null +++ b/docs/configuration/server.md @@ -0,0 +1,139 @@ +# Server Configuration + +Euro-Office Document Server is configured in one of two ways depending on how it +was installed: + +- **Package install (deb/rpm)** — edit a JSON config file on disk. +- **Docker** — set environment variables; the container writes the config file for you on startup. + +Both end up writing the same file: `local.json`. + +## The configuration file + +Settings live in `/etc/{{ brand.package_path_name }}/documentserver/`, loaded in this order, with +later files overriding earlier ones: + +``` +default.json → production-linux.json → local.json +``` + +!!! warning + Do not edit `default.json` or `production-linux.json`. They are replaced on + upgrade. Put all of your changes in `local.json`. + +Create `local.json` next to `default.json` and include only the keys you are +changing, keeping the full nested structure. For example, to set the JWT secret +and point at an external PostgreSQL database: + +```json +{ + "services": { + "CoAuthoring": { + "secret": { + "inbox": { "string": "at-least-32-chars-long-for-hs256" }, + "outbox": { "string": "at-least-32-chars-long-for-hs256" }, + "session": { "string": "at-least-32-chars-long-for-hs256" } + }, + "sql": { + "type": "postgres", + "dbHost": "db.internal", + "dbName": "eurooffice", + "dbUser": "eurooffice", + "dbPass": "your-password" + } + } + } +} +``` + +The JWT secret must be at least **32 characters** long. This is required by the +HS256 signing algorithm used by the document server and the Nextcloud integration. +A secret shorter than 32 characters will be rejected when signing or verifying +tokens. Generate a suitable secret with: + +```bash +openssl rand -hex 32 +``` + +Restart the server to apply changes: + +```bash +sudo supervisorctl restart all +``` + +## Common settings + +| Setting | Key path in `local.json` | +|---|---| +| JWT enabled (incoming) | `services.CoAuthoring.token.enable.browser`, `…token.enable.request.inbox` | +| JWT enabled (outgoing) | `services.CoAuthoring.token.enable.request.outbox` | +| JWT secret | `services.CoAuthoring.secret.{inbox,outbox,session}.string` | +| JWT header / in-body | `services.CoAuthoring.token.inbox.header`, `…token.inbox.inBody` | +| Database | `services.CoAuthoring.sql.{type,dbHost,dbPort,dbName,dbUser,dbPass}` | +| Redis | `services.CoAuthoring.redis.{host,port}` | +| RabbitMQ | `rabbitmq.url` | +| WOPI | `wopi.enable` | +| Allow private-IP requests | `services.CoAuthoring.request-filtering-agent.allowPrivateIPAddress` | + +## Docker + +In Docker you do not edit `local.json` directly. Set environment variables and +the container generates `local.json` on startup. To change a setting later, +update the variable and recreate the container. + +```bash +docker run -d \ + --name {{ brand.package_path_name }} \ + --restart=unless-stopped \ + -p 80:80 \ + -e JWT_SECRET=at-least-32-chars-long-for-hs256 \ + -e DB_TYPE=postgres \ + -e DB_HOST=db.internal \ + -e DB_NAME=eurooffice \ + -e DB_USER=eurooffice \ + -e DB_PWD=your-password \ + ghcr.io/euro-office/documentserver:latest +``` + +### Environment variables + +| Variable | Default | Description | +|---|---|---| +| `JWT_ENABLED` | `true` | Enable JWT validation | +| `JWT_SECRET` | random | Shared JWT secret (see note below) | +| `JWT_SECRET_INBOX` / `JWT_SECRET_OUTBOX` | `JWT_SECRET` | Separate secrets per direction | +| `JWT_HEADER` | `Authorization` | HTTP header carrying the token | +| `JWT_IN_BODY` | `false` | Accept the token in the request body | +| `DB_TYPE` | `postgres` | Database engine. The standalone image supports `postgres` only; other engines require the cluster image | +| `DB_HOST` | `localhost` | Database host | +| `DB_PORT` | `5432` | Database port | +| `DB_NAME` | `eurooffice` | Database name | +| `DB_USER` | `eurooffice` | Database user | +| `DB_PWD` | — | Database password | +| `AMQP_HOST` | `localhost` | RabbitMQ host | +| `AMQP_PORT` | `5672` | RabbitMQ port | +| `AMQP_USER` / `AMQP_PWD` | `guest` | RabbitMQ credentials | +| `REDIS_SERVER_HOST` | `localhost` | Redis host | +| `REDIS_SERVER_PORT` | `6379` | Redis port | +| `REDIS_SERVER_PASS` | — | Redis password | +| `WOPI_ENABLED` | `false` | Enable WOPI protocol support | +| `PLUGINS_ENABLED` | `true` | Enable editor plugins | +| `METRICS_ENABLED` | `false` | Send StatsD metrics | +| `GENERATE_FONTS` | `true` | Regenerate the font cache on startup | +| `ALLOW_PRIVATE_IP_ADDRESS` | `false` | Allow fetching documents from private IPs | +| `NGINX_WORKER_PROCESSES` | `1` | Number of nginx worker processes | + +!!! note "Persisting the JWT secret" + If `JWT_SECRET` is not set, a random secret is generated on first start and + stored under `/var/www/{{ brand.package_path_name }}/Data/.private/`. Mount the `Data` + directory as a volume to keep it stable across container restarts, or set + `JWT_SECRET` explicitly. + +!!! warning "JWT secret length" + When using the Nextcloud integration, the JWT secret must be at least + **32 characters**. A shorter secret will cause token generation to fail with + "JWT secret key is too short". Generate a suitable secret with: + + ```bash + openssl rand -hex 32 + ``` diff --git a/docs/development/building.md b/docs/development/building.md index 6338fdc..b4d1d19 100644 --- a/docs/development/building.md +++ b/docs/development/building.md @@ -73,7 +73,7 @@ docker run -d \ --restart=unless-stopped \ -p 8080:80 \ -e JWT_ENABLED=true \ - -e JWT_SECRET=dev-secret \ + -e JWT_SECRET=at-least-32-chars-long-for-hs256 \ ghcr.io/euro-office/documentserver:latest ``` @@ -93,7 +93,7 @@ docker run -d \ --restart=unless-stopped \ -p 8080:80 \ -e JWT_ENABLED=true \ - -e JWT_SECRET=dev-secret \ + -e JWT_SECRET=at-least-32-chars-long-for-hs256 \ -e EXAMPLE_ENABLED=true \ ghcr.io/euro-office/documentserver:latest ``` diff --git a/docs/index.md b/docs/index.md index d191770..0ee1207 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,43 +15,44 @@ It is not a standalone application; it requires a compatible storage backend to