diff --git a/README.md b/README.md index b6150f9..453398e 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Each links to its own README for variables, ports, and storage. | [alloy](alloy/README.md) | Grafana Alloy shipping host and Docker telemetry to Grafana Cloud | | [code-server](code-server/README.md) | VS Code in the browser from the official image | | [code-server-lsio](code-server-lsio/README.md) | VS Code in the browser from the LinuxServer image, as a remote dev box | +| [collabora](collabora/README.md) | Collabora Online CODE, an online office suite for WOPI hosts | | [couchbase](couchbase/README.md) | Couchbase Server | | [diun](diun/README.md) | Image-update notifier, reading the Docker API through a read-only proxy | | [gitea](gitea/README.md) | Gitea backed by PostgreSQL | @@ -86,10 +87,14 @@ Each links to its own README for variables, ports, and storage. | [goclaw](goclaw/README.md) | Multi-tenant AI agent gateway, with pgvector PostgreSQL | | [hermes](hermes/README.md) | Hermes Agent with its built-in web dashboard | | [litellm](litellm/README.md) | LiteLLM proxy in front of many LLM providers, with PostgreSQL and Redis | +| [nextcloud](nextcloud/README.md) | Nextcloud file sync and share, with PostgreSQL, Redis and a cron container | +| [onlyoffice](onlyoffice/README.md) | ONLYOFFICE Docs Community Edition, an online document editor | | [open-webui](open-webui/README.md) | Open WebUI chat interface for OpenAI-compatible and Ollama providers | | [openclaw](openclaw/README.md) | OpenClaw AI agent gateway, with built-in browser automation | +| [opencloud](opencloud/README.md) | OpenCloud file sync and share, single container with built-in identity provider | | [owncloud](owncloud/README.md) | ownCloud file sync and share, with MariaDB and Redis | | [paseo](paseo/README.md) | Paseo coding-agent daemon and web UI | +| [seafile](seafile/README.md) | Seafile CE file sync and share, with MariaDB, Redis and the notification server | | [traffmonetizer](traffmonetizer/README.md) | TraffMonetizer bandwidth-sharing client | | [webtop](webtop/README.md) | Ubuntu XFCE desktop in the browser | diff --git a/collabora/.env.example b/collabora/.env.example new file mode 100644 index 0000000..9afaba8 --- /dev/null +++ b/collabora/.env.example @@ -0,0 +1,18 @@ +# Copy to .env and fill in. Never commit .env. +# +# cp .env.example .env + +# WOPI host allowed to open documents, as scheme://host:port. Further +# comma-separated entries are aliases of the same host. +COLLABORA_ALIASGROUP1=https://cloud.example.com:443 + +# Admin console login, at /browser/dist/admin/admin.html. +# Generate a password with: openssl rand -base64 24 +COLLABORA_ADMIN_USERNAME=admin +COLLABORA_ADMIN_PASSWORD= + +# Public hostname, without scheme. +COLLABORA_SERVER_NAME=office.example.com + +# Spell-check languages loaded at start. +# COLLABORA_DICTIONARIES=de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru diff --git a/collabora/README.md b/collabora/README.md new file mode 100644 index 0000000..8e3f6df --- /dev/null +++ b/collabora/README.md @@ -0,0 +1,82 @@ +# collabora + +[Collabora Online](https://www.collaboraonline.com/code/) Development Edition +(CODE): an online office suite for documents, spreadsheets and presentations. +It holds no files itself; a WOPI host, a file server with Collabora +integration, opens documents in it. + +One container: `collabora`. + +## Setup + +1. Set `COLLABORA_ALIASGROUP1` to the WOPI host's URL, + `COLLABORA_ADMIN_PASSWORD` and `COLLABORA_SERVER_NAME`. +2. Map the domain to port `9980` and deploy. +3. In the WOPI host, set the Collabora server URL to the public `https://` + address of this domain. + +Discovery: `GET /hosting/discovery` returns XML once the server is up. The +image ships its own health check (`coolwsd --probe`, against `/livez`), so the +compose file declares none. + +WebSocket editing sessions go through Traefik's default routing; no extra +labels are needed. + +## Environment + +| Variable | Default | Purpose | +| --- | --- | --- | +| `COLLABORA_ALIASGROUP1` | — | Allowed WOPI host, `scheme://host:port`; later comma-separated entries are aliases of it | +| `COLLABORA_ADMIN_USERNAME` / `COLLABORA_ADMIN_PASSWORD` | `admin` / — | Admin console at `/browser/dist/admin/admin.html` | +| `COLLABORA_SERVER_NAME` | empty | Public hostname; empty derives it from each request | +| `COLLABORA_DICTIONARIES` | optional | Space-separated spell-check languages; upstream's default list when unset | + +`aliasgroup1` is the only access control on document editing: with no group +set, CODE trusts whichever host connects first after each start. Entries are +regular expressions, so `https://.*\.example\.com:443` allows a whole domain. +A second, unrelated WOPI host needs its own `aliasgroup2` line in +`compose.yml`. + +`extra_params` is fixed in `compose.yml`: `--o:ssl.enable=false` serves plain +HTTP on `9980` for Traefik to terminate TLS in front of it, and +`--o:ssl.termination=true` makes CODE build `https://` and `wss://` URLs +anyway. Without it the editor loads over HTTPS but fails on mixed-content +WebSocket URLs. + +The admin password fails fast if unset, because the console is served on the +public domain. + +## Storage + +None. Documents stay on the WOPI host; CODE's jails and cache are rebuilt on +every start. + +## Isolation + +CODE isolates each document process in a jail, choosing the first that works: +a mount namespace, then a chroot built by `coolforkit-caps`, then landlock. + +The compose file adds no capabilities and no `security_opt`. Docker's default +seccomp profile blocks the `unshare` a mount namespace needs, so CODE falls back +to the chroot. `coolforkit-caps` carries file capabilities `CAP_CHOWN`, +`CAP_FOWNER` and `CAP_SYS_CHROOT`, all in Docker's default set, so it works +unprivileged. `MKNOD`, which older CODE images needed, is no longer used. + +The price is speed, not safety: without `CAP_SYS_ADMIN` the `coolmount` helper +cannot bind-mount the system template, so each new jail copies it, and +opening a document takes a little longer. The alternatives cost more: +`cap_add: SYS_ADMIN` grants the container broad host-kernel powers, and +upstream's `cool-seccomp-profile.json`, Docker's default list plus the six +namespace and mount syscalls needed, must exist as a file on the host, which a Coolify compose deploy does +not place there. + +The startup log line `creating usernamespace for mount user failed` is this +fallback, not a fault. Do not set `no-new-privileges`: `coolforkit-caps` gains +its capabilities on exec, and without them CODE drops to the weaker landlock +jail. + +## Image + +`collabora/code:latest` is the only moving tag upstream publishes; releases are +versioned `YY.MM.x`. The container keeps no state, so a new release needs only +a redeploy. diff --git a/collabora/compose.yml b/collabora/compose.yml new file mode 100644 index 0000000..4bc354e --- /dev/null +++ b/collabora/compose.yml @@ -0,0 +1,11 @@ +services: + collabora: + image: collabora/code:latest + restart: unless-stopped + environment: + - aliasgroup1=${COLLABORA_ALIASGROUP1:?required} + - extra_params=--o:ssl.enable=false --o:ssl.termination=true + - username=${COLLABORA_ADMIN_USERNAME:-admin} + - password=${COLLABORA_ADMIN_PASSWORD:?required} + - server_name=${COLLABORA_SERVER_NAME:-} + # - dictionaries=${COLLABORA_DICTIONARIES:-de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru} diff --git a/nextcloud/.env.example b/nextcloud/.env.example new file mode 100644 index 0000000..6bc2f1b --- /dev/null +++ b/nextcloud/.env.example @@ -0,0 +1,23 @@ +# Copy to .env and fill in. Never commit .env. +# +# cp .env.example .env + +# Admin account, created on first start only. +# Generate a password with: openssl rand -base64 24 +NEXTCLOUD_ADMIN_USER=admin +NEXTCLOUD_ADMIN_PASSWORD= + +# Public hostname, without scheme. The only trusted domain and the CLI URL. +NEXTCLOUD_DOMAIN=nextcloud.example.com + +# Space-separated proxy addresses or CIDRs allowed to set X-Forwarded-* headers. +TRUSTED_PROXIES=10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 + +# PostgreSQL credentials, used by the database and by the first-start install. +POSTGRES_DB=nextcloud +POSTGRES_USER=nextcloud +POSTGRES_PASSWORD= + +# PHP limits for the web server. +PHP_MEMORY_LIMIT=1G +PHP_UPLOAD_LIMIT=16G diff --git a/nextcloud/README.md b/nextcloud/README.md new file mode 100644 index 0000000..63cbe60 --- /dev/null +++ b/nextcloud/README.md @@ -0,0 +1,85 @@ +# nextcloud + +[Nextcloud](https://github.com/nextcloud/docker) Server: file sync and share, +calendar, contacts and office apps, with web, desktop and mobile clients. + +## Containers + +| Service | Image | Internal port | Role | +| --- | --- | --- | --- | +| `nextcloud` | `nextcloud:35-apache` | 80 | Web app | +| `cron` | `nextcloud:35-apache` | none | Background jobs, via `/cron.sh` | +| `db` | `postgres:18-alpine` | 5432 | Metadata, users and shares | +| `cache` | `redis:8-alpine` | 6379 | File locking and the distributed cache | + +In Coolify, give `nextcloud` the domain from `NEXTCLOUD_DOMAIN` on port `80`. +`nextcloud` and `cron` wait for `db` and `cache` to pass their health checks. + +## Setup + +1. Set `NEXTCLOUD_DOMAIN`, `NEXTCLOUD_ADMIN_PASSWORD` and `POSTGRES_PASSWORD`. +2. Map the domain to port `80` and deploy. The first start installs Nextcloud + and creates the admin account. +3. Log in with `NEXTCLOUD_ADMIN_USER` / `NEXTCLOUD_ADMIN_PASSWORD`. The first + run of `cron` switches background jobs to **Cron** on its own. + +Office editing is an app from the app store plus an external document server, +both set up in the admin UI, not in this compose file. + +## Variables + +| Variable | Default | Purpose | +| --- | --- | --- | +| `NEXTCLOUD_ADMIN_USER` / `NEXTCLOUD_ADMIN_PASSWORD` | `admin` / — | Admin account | +| `NEXTCLOUD_DOMAIN` | — | Public hostname, without scheme | +| `TRUSTED_PROXIES` | private IPv4 ranges | Proxies allowed to set `X-Forwarded-*` | +| `POSTGRES_DB` / `POSTGRES_USER` / `POSTGRES_PASSWORD` | `nextcloud` / `nextcloud` / — | Database credentials, read by `db` and by the install | +| `PHP_MEMORY_LIMIT` | `1G` | PHP `memory_limit` | +| `PHP_UPLOAD_LIMIT` | `16G` | PHP `upload_max_filesize` and `post_max_size` | + +`NEXTCLOUD_DOMAIN` fills `NEXTCLOUD_TRUSTED_DOMAINS` and `OVERWRITECLIURL`. +Nextcloud rejects requests for any host not on the trusted list. + +The admin and database variables are read only by the first-start install. +The installer creates its own database role and writes it to `config.php`, so +changing them later changes nothing; use the web UI or `occ`. + +## Storage + +| Volume | Mount | Holds | +| --- | --- | --- | +| `nextcloud-html` | `/var/www/html` | Nextcloud code, apps, `config.php` and user files | +| `db-data` | `/var/lib/postgresql` | The database | +| `cache-data` | `/data` | Redis locks and cache | + +`cron` mounts the same volume at the same path as `nextcloud`; the two must +match for background jobs to see the same installation. + +The Redis contents are rebuilt after a restart, but the `redis` image declares +`/data` a volume, so without a named one Docker creates an anonymous volume on +every recreate. + +## Choices + +- **Behind a TLS-terminating proxy.** The proxy speaks HTTP to port 80, so + `OVERWRITEPROTOCOL=https` keeps generated links and redirects on HTTPS. + `APACHE_DISABLE_REWRITE_IP=1` with `TRUSTED_PROXIES` makes Nextcloud read + the client IP and host from `X-Forwarded-*`, which brute-force protection + and the admin overview's proxy check rely on. No port is published, so only + containers on the app's networks can reach port 80; the private ranges cover + whatever subnet the proxy network gets. +- **Larger PHP limits.** The image defaults both to `512M`. The web UI and + the desktop and mobile clients upload in chunks, but WebDAV clients that send + a file in one request hit `PHP_UPLOAD_LIMIT`, and preview generation for big + images needs more memory. +- **`cron` container.** Nextcloud's recommended background job mode is system + cron; the image ships `/cron.sh`, which runs `cron.php` as `www-data` every + five minutes. +- **`nextcloud:35-apache`.** The major tag takes point releases; Nextcloud + can only upgrade one major at a time, so a new major is a deliberate change. + The `apache` variant serves PHP itself, with no separate web server. +- **`postgres:18-alpine`** is the version the Nextcloud 35 admin manual + recommends. Postgres 18 images keep data under `/var/lib/postgresql/18/`, + so the volume mounts at `/var/lib/postgresql`. A new Postgres major cannot + read an older data directory without a dump and restore. +- **`redis:8-alpine`** matches the Redis line upstream's example uses. diff --git a/nextcloud/compose.yml b/nextcloud/compose.yml new file mode 100644 index 0000000..c88399f --- /dev/null +++ b/nextcloud/compose.yml @@ -0,0 +1,72 @@ +services: + nextcloud: + image: nextcloud:35-apache + restart: unless-stopped + environment: + - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin} + - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:?required} + - NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_DOMAIN:?required} + - OVERWRITEPROTOCOL=https + - OVERWRITECLIURL=https://${NEXTCLOUD_DOMAIN:?required} + - TRUSTED_PROXIES=${TRUSTED_PROXIES:-10.0.0.0/8 172.16.0.0/12 192.168.0.0/16} + - APACHE_DISABLE_REWRITE_IP=1 + - POSTGRES_HOST=db + - POSTGRES_DB=${POSTGRES_DB:-nextcloud} + - POSTGRES_USER=${POSTGRES_USER:-nextcloud} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?required} + - REDIS_HOST=cache + - PHP_MEMORY_LIMIT=${PHP_MEMORY_LIMIT:-1G} + - PHP_UPLOAD_LIMIT=${PHP_UPLOAD_LIMIT:-16G} + volumes: + - nextcloud-html:/var/www/html + depends_on: + db: + condition: service_healthy + cache: + condition: service_healthy + + # Runs Nextcloud background jobs every five minutes. + cron: + image: nextcloud:35-apache + restart: unless-stopped + entrypoint: /cron.sh + volumes: + - nextcloud-html:/var/www/html + depends_on: + db: + condition: service_healthy + cache: + condition: service_healthy + + # PostgreSQL for file metadata, users and shares. + db: + image: postgres:18-alpine + restart: unless-stopped + environment: + - POSTGRES_DB=${POSTGRES_DB:-nextcloud} + - POSTGRES_USER=${POSTGRES_USER:-nextcloud} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?required} + volumes: + - db-data:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -h localhost -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] + interval: 5s + timeout: 5s + retries: 10 + + # Redis for file locking and the distributed cache. + cache: + image: redis:8-alpine + restart: unless-stopped + volumes: + - cache-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 10 + +volumes: + nextcloud-html: + db-data: + cache-data: diff --git a/onlyoffice/.env.example b/onlyoffice/.env.example new file mode 100644 index 0000000..a702d66 --- /dev/null +++ b/onlyoffice/.env.example @@ -0,0 +1,10 @@ +# Copy to .env and fill in. Never commit .env. +# +# cp .env.example .env + +# Shared secret for signing editor requests; the storage app must use the same. +# Generate one with: openssl rand -hex 32 +JWT_SECRET= + +# Allow downloads from and callbacks to private IP addresses. +# ALLOW_PRIVATE_IP_ADDRESS=false diff --git a/onlyoffice/README.md b/onlyoffice/README.md new file mode 100644 index 0000000..632d595 --- /dev/null +++ b/onlyoffice/README.md @@ -0,0 +1,60 @@ +# onlyoffice + +[ONLYOFFICE Docs](https://github.com/ONLYOFFICE/Docker-DocumentServer) +Community Edition: an online editor for documents, spreadsheets and +presentations. It has no file storage or user accounts of its own; a storage +app opens files in it and receives the saved result. + +One container, `onlyoffice`. Map the domain to port `80`. + +## Setup + +1. Set `JWT_SECRET`. +2. Map the domain to port `80` and deploy. Every start regenerates the font + list, so the health check takes a minute or two to pass. +3. Give the storage app the public URL and the same `JWT_SECRET`. + +Health check: `GET /healthcheck`. It answers `200` with `false` when a +dependency is down, so the compose healthcheck matches the body `true` rather +than relying on the status code. + +## Environment + +| Variable | Default | Purpose | +| --- | --- | --- | +| `JWT_SECRET` | — | Secret for signing requests between the editor and the storage app | +| `ALLOW_PRIVATE_IP_ADDRESS` | `false` | Allow fetching files from, and calling back to, private IP addresses | + +`JWT_ENABLED` is fixed to `true`. `JWT_SECRET` fails fast if unset: the image +otherwise generates a random secret on every start, which breaks the storage +app's connection after each redeploy. + +`ALLOW_PRIVATE_IP_ADDRESS` is needed only when the storage app is reached over +a private network, such as a container hostname or a LAN address. The editor +refuses those addresses by default, so leave it off when the storage app uses +a public URL. + +## Storage + +| Volume | Mount | Holds | +| --- | --- | --- | +| `onlyoffice-data` | `/var/www/onlyoffice/Data` | Certificates and generated WOPI keys | +| `onlyoffice-lib` | `/var/lib/onlyoffice` | Document cache and converted files | +| `onlyoffice-logs` | `/var/log/onlyoffice` | Logs | + +Nothing here is the documents themselves; those stay in the storage app. The +volumes keep the cache and keys across redeploys. + +## Images + +`onlyoffice/documentserver:latest`: upstream publishes `X.Y` and `X.Y.Z` tags +but no major tag. Back up the volumes before a pull that crosses a major. + +The 9.x images up to 9.4 still run PostgreSQL, RabbitMQ and Redis inside the +container for the Community Edition, in volumes the image declares itself; +upstream's source has since dropped them from the Community build. Either way +no separate database, broker or cache container is needed, and none of their +connection variables are set. + +`stop_grace_period: 60s` follows upstream's compose, giving the editor time to +save open documents back to the storage app on shutdown. diff --git a/onlyoffice/compose.yml b/onlyoffice/compose.yml new file mode 100644 index 0000000..afba3e7 --- /dev/null +++ b/onlyoffice/compose.yml @@ -0,0 +1,24 @@ +services: + onlyoffice: + image: onlyoffice/documentserver:latest + restart: unless-stopped + stop_grace_period: 60s + environment: + - JWT_ENABLED=true + - JWT_SECRET=${JWT_SECRET:?required} + # - ALLOW_PRIVATE_IP_ADDRESS=${ALLOW_PRIVATE_IP_ADDRESS:-false} + volumes: + - onlyoffice-data:/var/www/onlyoffice/Data + - onlyoffice-lib:/var/lib/onlyoffice + - onlyoffice-logs:/var/log/onlyoffice + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost/healthcheck | grep -qx true"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 120s + +volumes: + onlyoffice-data: + onlyoffice-lib: + onlyoffice-logs: diff --git a/opencloud/.env.example b/opencloud/.env.example new file mode 100644 index 0000000..c8fed0c --- /dev/null +++ b/opencloud/.env.example @@ -0,0 +1,13 @@ +# Copy to .env and fill in. Never commit .env. +# +# cp .env.example .env + +# Public hostname, without scheme. +OC_DOMAIN=opencloud.example.com + +# Password of the built-in admin account, set on first start only. +# Generate one with: openssl rand -base64 24 +INITIAL_ADMIN_PASSWORD= + +# OC_LOG_LEVEL=info +# PROXY_ENABLE_BASIC_AUTH=false diff --git a/opencloud/README.md b/opencloud/README.md new file mode 100644 index 0000000..1a673b7 --- /dev/null +++ b/opencloud/README.md @@ -0,0 +1,70 @@ +# opencloud + +[OpenCloud](https://docs.opencloud.eu/) Server: file sync and share with web, +desktop and mobile clients, spaces and WebDAV. + +One container, `opencloud`, running every OpenCloud service in one process, +including the built-in identity provider, user directory and NATS. Files and +metadata live on disk, so there is no database container. + +## Setup + +1. Set `OC_DOMAIN` and `INITIAL_ADMIN_PASSWORD`. +2. Map the domain to port `9200` over HTTPS and deploy. +3. Log in as `admin` with `INITIAL_ADMIN_PASSWORD`. + +Health check: `GET http://127.0.0.1:9205/healthz`, the proxy's debug endpoint, +also the compose healthcheck. It listens on loopback only. + +## Environment + +| Variable | Default | Purpose | +| --- | --- | --- | +| `OC_DOMAIN` | — | Public hostname, without scheme; becomes `OC_URL` | +| `INITIAL_ADMIN_PASSWORD` | — | Password of the `admin` account | +| `OC_LOG_LEVEL` | `info` | Optional. Log level | +| `PROXY_ENABLE_BASIC_AUTH` | `false` | Optional. Basic auth for WebDAV clients without OpenID Connect | + +`OC_URL` must be the exact HTTPS URL the browser uses: the built-in identity +provider uses it as its issuer and redirect target. + +`INITIAL_ADMIN_PASSWORD` is read only on first start, when the admin account +is created. Changing it later does not change the password; use the web UI. + +`PROXY_TLS=false`, `PROXY_HTTP_ADDR` and `OC_INSECURE=false` are fixed in +`compose.yml`. The platform proxy terminates TLS and forwards plain HTTP to +port `9200`, and the certificate it serves is a real one, so certificate +checks stay on. + +## Storage + +| Volume | Mount | Holds | +| --- | --- | --- | +| `opencloud-config` | `/etc/opencloud` | `opencloud.yaml`, with the generated secrets | +| `opencloud-data` | `/var/lib/opencloud` | User files, spaces, the user directory and search index | + +The two volumes belong together. `opencloud.yaml` holds the secrets the data +was written with; a data volume restored without its config volume, or the +reverse, does not start cleanly. Back them up as a pair. + +The image creates both directories owned by uid `1000`, the user it runs as, +so fresh named volumes are writable without an init step. + +## Choices + +- **`opencloud init || true; opencloud server`.** `init` writes + `opencloud.yaml` with random secrets on first start and fails harmlessly + once it exists, as in upstream's own compose. +- **No config files.** OpenCloud's built-in CSP and app list cover the web UI + and the built-in identity provider. Upstream's `csp.yaml` and `apps.yaml` + only add origins for an external identity provider, office server and + optional web apps. +- **No office integration.** Editing documents in the browser needs a + separate office server on its own domain, OpenCloud's collaboration service, + and a custom `csp.yaml` allowing that domain. The office server also needs + extra kernel capabilities and a WOPI proof key. None of that can be switched + on by variables alone, so it is left out. +- **`opencloudeu/opencloud:7`.** The `opencloud` repository carries the + production releases, and `7` tracks the 7.x line. The `opencloud-rolling` + repository, which upstream's compose defaults to, ships a new major every + few months. diff --git a/opencloud/compose.yml b/opencloud/compose.yml new file mode 100644 index 0000000..cbbaaed --- /dev/null +++ b/opencloud/compose.yml @@ -0,0 +1,28 @@ +services: + opencloud: + image: opencloudeu/opencloud:7 + restart: unless-stopped + # Writes the config with generated secrets on first start, then runs the server. + entrypoint: ["/bin/sh"] + command: ["-c", "opencloud init || true; opencloud server"] + environment: + - OC_URL=https://${OC_DOMAIN:?required} + - IDM_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD:?required} + - PROXY_TLS=false + - PROXY_HTTP_ADDR=0.0.0.0:9200 + - OC_INSECURE=false + # - OC_LOG_LEVEL=${OC_LOG_LEVEL:-info} + # - PROXY_ENABLE_BASIC_AUTH=${PROXY_ENABLE_BASIC_AUTH:-false} + volumes: + - opencloud-config:/etc/opencloud + - opencloud-data:/var/lib/opencloud + healthcheck: + test: ["CMD", "curl", "-fsS", "http://127.0.0.1:9205/healthz"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 60s + +volumes: + opencloud-config: + opencloud-data: diff --git a/seafile/.env.example b/seafile/.env.example new file mode 100644 index 0000000..bab02d4 --- /dev/null +++ b/seafile/.env.example @@ -0,0 +1,21 @@ +# Copy to .env and fill in. Never commit .env. +# +# cp .env.example .env + +# Public hostname, without scheme. +SEAFILE_SERVER_HOSTNAME=seafile.example.com + +# Shared secret between Seafile and the notification server, 32+ characters. +# Generate one with: openssl rand -hex 32 +JWT_PRIVATE_KEY= + +# Admin account, created on first start only. +INIT_SEAFILE_ADMIN_EMAIL=admin@example.com +INIT_SEAFILE_ADMIN_PASSWORD= + +# MariaDB passwords. Seafile uses the root password on first start to create +# the seafile user and its three databases. +DB_PASSWORD= +DB_ROOT_PASSWORD= + +TIME_ZONE=Etc/UTC diff --git a/seafile/README.md b/seafile/README.md new file mode 100644 index 0000000..c573762 --- /dev/null +++ b/seafile/README.md @@ -0,0 +1,98 @@ +# seafile + +[Seafile](https://manual.seafile.com/13.0/) Community Edition 13: file sync +and share with libraries, web, desktop and mobile clients. Based on the +official 13.0 Docker compose, without its bundled Caddy; the platform proxy +terminates TLS. + +Four containers: `seafile` (Seahub web UI and file server), `notification` +(real-time change notifications over WebSocket), `db` (MariaDB) and `cache` +(Redis). + +## Setup + +1. Set `SEAFILE_SERVER_HOSTNAME`, `JWT_PRIVATE_KEY`, `INIT_SEAFILE_ADMIN_EMAIL`, + `INIT_SEAFILE_ADMIN_PASSWORD`, `DB_PASSWORD` and `DB_ROOT_PASSWORD`. +2. Map the domains, both on the same host: + + | Container | Domain | Port | + | --- | --- | --- | + | `seafile` | `https://seafile.example.com` | `80` | + | `notification` | `https://seafile.example.com/notification` | `8083` | + + Keep the app's strip-prefix setting on (the default), so the notification + server receives `/` rather than `/notification`. +3. Deploy. The first start creates the databases and the admin account; log in + with `INIT_SEAFILE_ADMIN_EMAIL` / `INIT_SEAFILE_ADMIN_PASSWORD`. + +Health check: `curl -f http://localhost:80` inside `seafile`, from the official +compose. Its start period is two minutes instead of the official ten seconds: +the first start creates the databases before Seahub answers, and +`notification` waits for `seafile` to be healthy. + +## Environment + +| Variable | Default | Purpose | +| --- | --- | --- | +| `SEAFILE_SERVER_HOSTNAME` | — | Public hostname, without scheme; also builds the notification URL | +| `JWT_PRIVATE_KEY` | — | Shared secret between `seafile` and `notification`, 32+ characters | +| `INIT_SEAFILE_ADMIN_EMAIL` / `INIT_SEAFILE_ADMIN_PASSWORD` | — | Admin account, first start only | +| `DB_PASSWORD` | — | Password of the `seafile` MariaDB user | +| `DB_ROOT_PASSWORD` | — | MariaDB root password, used by the first start to create the user and databases | +| `TIME_ZONE` | `Etc/UTC` | Server time zone | + +`SEAFILE_SERVER_PROTOCOL` is fixed to `https`: TLS ends at the proxy, but the +generated links, CSRF origins and notification URL must use the public scheme. + +The `INIT_*` variables and `DB_ROOT_PASSWORD` take effect only on the first +start. Changing them later does not change the admin account or the database +passwords. + +Logs go to stdout (`SEAFILE_LOG_TO_STDOUT=true`) so they show in the +platform's log view instead of only under `/shared/seafile/logs`. + +## Storage + +| Volume | Mount | Holds | +| --- | --- | --- | +| `seafile-data` | `/shared` | Libraries, config, logs | +| `db-data` | `/var/lib/mysql` | The ccnet, seafile and seahub databases | +| `cache-data` | `/data` | Redis cache | + +`notification` has no volume: it reads its settings from the environment and, +with `SEAFILE_LOG_TO_STDOUT=true`, writes no log file, so the log mount of the +official compose is not needed. + +The `redis` image declares `/data` a volume, so without a named one Docker +creates an anonymous volume on every recreate. + +## Left out + +- **SeaDoc** (`ENABLE_SEADOC=false`). It needs `/sdoc-server/` with the prefix + stripped and `/socket.io/` with it kept, on the same host. The proxy's + strip-prefix setting applies to the whole app, so both cannot be routed at + once. +- **Thumbnail server.** It needs `/thumbnail/` unstripped and + `/thumbnail/ping` rewritten to `/ping`, the same conflict. Without it, Seahub + generates thumbnails itself. +- **SeaSearch, Seafile AI, metadata server.** Not needed for file sync; + SeaSearch publishes no arm64 image. +- **Redis password.** The official compose passes an empty one by default; + Redis is reachable only on the app's internal network. + +## Images + +`seafileltd/seafile-mc:13.0-latest` and +`seafileltd/notification-server:13.0-latest` track the 13.0 line. Seafile +publishes no `:13` tag, `latest` has not moved since 2025, and 14 is still a +testing release. A Seafile major upgrade runs database migrations; back up +both volumes first. + +`mariadb:10.11` is the version the official 13.0 compose pins, and the tag +follows its patch releases. `MARIADB_AUTO_UPGRADE=1` (from the official compose) runs +`mariadb-upgrade` after a minor update. A MariaDB data directory cannot move +back to an older major. + +`redis:8` is the major the official compose's unpinned `redis` resolves to +today; the tag keeps it there instead of following a future major with no +review. Redis holds only cache, so a version change loses nothing. diff --git a/seafile/compose.yml b/seafile/compose.yml new file mode 100644 index 0000000..58c31d3 --- /dev/null +++ b/seafile/compose.yml @@ -0,0 +1,93 @@ +services: + seafile: + image: seafileltd/seafile-mc:13.0-latest + restart: unless-stopped + environment: + - SEAFILE_SERVER_HOSTNAME=${SEAFILE_SERVER_HOSTNAME:?required} + - SEAFILE_SERVER_PROTOCOL=https + - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?required} + - INIT_SEAFILE_ADMIN_EMAIL=${INIT_SEAFILE_ADMIN_EMAIL:?required} + - INIT_SEAFILE_ADMIN_PASSWORD=${INIT_SEAFILE_ADMIN_PASSWORD:?required} + - SEAFILE_MYSQL_DB_HOST=db + - SEAFILE_MYSQL_DB_PORT=3306 + - SEAFILE_MYSQL_DB_USER=seafile + - SEAFILE_MYSQL_DB_PASSWORD=${DB_PASSWORD:?required} + - INIT_SEAFILE_MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:?required} + - SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db + - SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db + - SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db + - CACHE_PROVIDER=redis + - REDIS_HOST=cache + - REDIS_PORT=6379 + - ENABLE_NOTIFICATION_SERVER=true + - INNER_NOTIFICATION_SERVER_URL=http://notification:8083 + - NOTIFICATION_SERVER_URL=https://${SEAFILE_SERVER_HOSTNAME:?required}/notification + - ENABLE_SEADOC=false + - TIME_ZONE=${TIME_ZONE:-Etc/UTC} + - SEAFILE_LOG_TO_STDOUT=true + volumes: + - seafile-data:/shared + depends_on: + db: + condition: service_healthy + cache: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 2m + + # Notification server: pushes library changes to web and desktop clients over WebSocket. + notification: + image: seafileltd/notification-server:13.0-latest + restart: unless-stopped + environment: + - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?required} + - SEAFILE_MYSQL_DB_HOST=db + - SEAFILE_MYSQL_DB_PORT=3306 + - SEAFILE_MYSQL_DB_USER=seafile + - SEAFILE_MYSQL_DB_PASSWORD=${DB_PASSWORD:?required} + - SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db + - SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db + - SEAFILE_LOG_TO_STDOUT=true + depends_on: + db: + condition: service_healthy + seafile: + condition: service_healthy + + # MariaDB for the ccnet, seafile and seahub databases. + db: + image: mariadb:10.11 + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:?required} + - MYSQL_LOG_CONSOLE=true + - MARIADB_AUTO_UPGRADE=1 + volumes: + - db-data:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--mariadbupgrade", "--innodb_initialized"] + interval: 20s + start_period: 30s + timeout: 5s + retries: 10 + + # Redis for the Seahub cache. + cache: + image: redis:8 + restart: unless-stopped + volumes: + - cache-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + seafile-data: + db-data: + cache-data: