seafile: add Pro metadata-server + thumbnail-server

Two new services on LXC 125's docker stack:
- seafile-md-server (Pro extended metadata, internal-only on :8084)
- thumbnail-server (Pro accelerated thumbnails, bound to .185:8081)

Caddy now routes /thumbnail/* to the thumbnail-server; everything else stays
on the main seafile container. End-to-end smoke verified: routing works,
per-request permission checks via INNER_SEAHUB_SERVICE_URL=http://seafile
correctly return 403 for cross-user thumbnail requests.
This commit is contained in:
Claudio on hubris
2026-05-12 19:06:50 +02:00
parent 02ad003e52
commit dc0780bcaf

View File

@@ -14,14 +14,16 @@ Seafile **Pro** Edition 13.0 — exploratory deployment to evaluate as a possibl
## Stack
Docker-compose at `/opt/seafile/`, four services on a private `seafile-net` bridge:
Docker-compose at `/opt/seafile/`, six services on a private `seafile-net` bridge:
| Service | Image | Role |
|---|---|---|
| `seafile` | `seafileltd/seafile-pro-mc:13.0-latest` | Seahub + seaf-server + Apache (Pro flavour; uses Redis for cache) |
| `seafile` | `seafileltd/seafile-pro-mc:13.0-latest` | Seahub + seaf-server + Apache (Pro flavour; uses Redis for cache). Binds `192.168.8.185:80`. |
| `db` | `mariadb:10.11` | ccnet / seafile / seahub databases |
| `redis` | `redis:7-alpine` | session + cache backend |
| `elasticsearch` | `elasticsearch:8.15.0` | full-text search (Pro-only feature) |
| `elasticsearch` | `elasticsearch:8.15.0` | full-text search backend (Pro-only) |
| `seafile-md-server` | `seafileltd/seafile-md-server:13.0-latest` | extended-metadata server (Pro-only). Internal-only on `seafile-md-server:8084` over the docker bridge; no host port. Enabled on a per-library basis via the Seahub UI. |
| `thumbnail-server` | `seafileltd/thumbnail-server:13.0-latest` | offload thumbnail generation from Seahub (Pro-only). Binds `192.168.8.185:8081`; Caddy routes `/thumbnail/*` to it. Calls back into Seahub at `http://seafile` to verify per-request permissions. |
Compose layout:
@@ -29,8 +31,10 @@ Compose layout:
|---|---|
| `/opt/seafile/seafile-server.yml` | upstream-verbatim from `manual.seafile.com/13.0/repo/docker/ce/` (image swapped to `pro-mc` via `.env`) |
| `/opt/seafile/elasticsearch.yml` | upstream-derived; local tweaks for LXC — `bootstrap.memory_lock=false`, `mem_limit: 2g`, `ES_JAVA_OPTS=-Xms1g -Xmx1g`, no `ulimits.memlock` (privileged LXC can't reliably hold memlock=unlimited) |
| `/opt/seafile/md-server.yml` | derived from `manual.seafile.com/13.0/repo/docker/metadata-server/md-server.yml` — the upstream exposes port 8084 on the host; we **drop the host port mapping** (internal-only) and add `depends_on: db (healthy), redis`. Mounts `${SEAFILE_VOLUME}:/shared` so it can read storage objects directly. |
| `/opt/seafile/thumbnail-server.yml` | derived from `manual.seafile.com/13.0/repo/docker/thumbnail-server/thumbnail-server.yml`. Upstream binds port 80 on the host; we **rebind to `192.168.8.185:8081`** to avoid colliding with the main `seafile` container which already owns :80. `INNER_SEAHUB_SERVICE_URL=http://seafile` (the Seahub container's DNS name on the docker bridge) so it can verify perms. |
| `/opt/seafile/docker-compose.override.yml` | local: publishes `192.168.8.185:80`, `extra_hosts: auth.hubris.network:192.168.8.175`, drops bundled-caddy labels (`labels: !reset []`) |
| `/opt/seafile/.env` | mode 600, **not git-tracked**`SEAFILE_IMAGE=seafileltd/seafile-pro-mc:13.0-latest`, `SEAFILE_ELASTICSEARCH_IMAGE=elasticsearch:8.15.0`, `SEAFILE_ELASTICSEARCH_VOLUME=/mnt/library/seafile/elasticsearch`, `COMPOSE_FILE='seafile-server.yml,elasticsearch.yml,docker-compose.override.yml'`, DB passwords, `JWT_PRIVATE_KEY`, `INIT_SEAFILE_ADMIN_*`, `SEAFILE_SERVER_HOSTNAME=files.hubris.network`, `SEAFILE_SERVER_PROTOCOL=https`, `ENABLE_SEADOC=false` |
| `/opt/seafile/.env` | mode 600, **not git-tracked**`SEAFILE_IMAGE=seafileltd/seafile-pro-mc:13.0-latest`, `MD_IMAGE=seafileltd/seafile-md-server:13.0-latest`, `THUMBNAIL_SERVER_IMAGE=seafileltd/thumbnail-server:13.0-latest`, `SEAFILE_ELASTICSEARCH_IMAGE=elasticsearch:8.15.0`, `SEAFILE_ELASTICSEARCH_VOLUME=/mnt/library/seafile/elasticsearch`, `INNER_SEAHUB_SERVICE_URL=http://seafile`, `COMPOSE_FILE='seafile-server.yml,elasticsearch.yml,md-server.yml,thumbnail-server.yml,docker-compose.override.yml'`, DB passwords, `JWT_PRIVATE_KEY`, `INIT_SEAFILE_ADMIN_*`, `SEAFILE_SERVER_HOSTNAME=files.hubris.network`, `SEAFILE_SERVER_PROTOCOL=https`, `ENABLE_SEADOC=false` |
**Do not rotate `JWT_PRIVATE_KEY`** — it invalidates every active session and library access token.
@@ -83,24 +87,38 @@ files.hubris.network {
request_body {
max_size 10GB
}
reverse_proxy 192.168.8.185:80 {
header_up X-Forwarded-Proto https
header_up X-Forwarded-Host {host}
header_up Host {host}
# Strip IETF resumable-upload negotiation headers. The iOS Seafile Pro app
# (>=4.0) sends Upload-Draft-Interop-Version: 6 + Upload-Complete, but the
# bundled seafile-fileserver doesn't speak that draft. Without stripping,
# uploads hang ~60s waiting for an interim response and then cancel.
header_up -Upload-Draft-Interop-Version
header_up -Upload-Complete
header_up -Upload-Offset
header_up -Upload-Length
transport http {
dial_timeout 30s
read_timeout 1h
write_timeout 1h
# /thumbnail/* → Pro thumbnail-server on LXC 125:8081
@thumb path /thumbnail/*
handle @thumb {
reverse_proxy 192.168.8.185:8081 {
header_up X-Forwarded-Proto https
header_up X-Forwarded-Host {host}
header_up Host {host}
transport http {
dial_timeout 30s
read_timeout 1h
write_timeout 1h
}
}
}
# Everything else → main seafile container on LXC 125:80
handle {
reverse_proxy 192.168.8.185:80 {
header_up X-Forwarded-Proto https
header_up X-Forwarded-Host {host}
header_up Host {host}
# Strip IETF resumable-upload negotiation headers (iOS Pro app workaround).
header_up -Upload-Draft-Interop-Version
header_up -Upload-Complete
header_up -Upload-Offset
header_up -Upload-Length
transport http {
dial_timeout 30s
read_timeout 1h
write_timeout 1h
}
flush_interval -1
}
flush_interval -1
}
}
```
@@ -124,6 +142,9 @@ For now: stand up Seafile, kick the tires, decide later whether to:
## Changelog
### 2026-05-12 — add metadata + thumbnail servers
Two more Pro services online: `seafile-md-server` (extended metadata, internal-only on :8084 over the docker bridge, no host port — enabled per-library from the Seahub UI) and `thumbnail-server` (offload thumbnail generation from Seahub, host-bound to `192.168.8.185:8081`). Caddy now splits `/thumbnail/*` → :8081 and everything else → :80; both blocks keep the resumable-upload header strip + 1 h transport timeouts. `INNER_SEAHUB_SERVICE_URL=http://seafile` in `.env` so the thumbnail server can call the Seahub container by DNS name for per-request permission checks (verified end-to-end: admin token → 403 for dtoro's repo via `POST /api/v2.1/internal/repos/.../check-thumbnail/user-token/`, i.e. routing works and perms are enforced).
### 2026-05-12 — CE → Pro upgrade
Image swapped to `seafileltd/seafile-pro-mc:13.0-latest`; added `elasticsearch:8.15.0` as a new service for Pro's full-text search. Free Pro tier (≤3 users, no license). MariaDB dump saved at `/mnt/library/seafile/backups/seafile-all-20260512-183001.sql.gz` (1.1 MB compressed) before the swap; recovery path is `gunzip … | mariadb -uroot` against a fresh CE container if needed. ES tuned for the LXC: `bootstrap.memory_lock=false`, no `ulimits.memlock`, 1 GiB JVM heap, 2 GiB container `mem_limit`. Host `vm.max_map_count` already at 1,048,576 (well above the 262,144 ES minimum). Existing data + JIT users (admin, dtoro) survived the migration; libraries intact (Iphone with 20 files / 53.1 MB carried through).