ENABLE_METADATA_MANAGEMENT=True + METADATA_SERVER_URL=http://seafile-md-server:8084 in seahub_settings.py. Library owners can now toggle metadata per library and create table/gallery/kanban Views over their files.
178 lines
14 KiB
Markdown
178 lines
14 KiB
Markdown
# 125 — `seafile`
|
|
|
|
Seafile **Pro** Edition 13.0 — exploratory deployment to evaluate as a possible [nextcloud (114)](114-nextcloud.md) replacement. Stood up on 2026-05-12, upgraded from CE → Pro the same day (free tier, ≤3 users). **No data migrated from NC**; NC stays untouched.
|
|
|
|
## At a glance
|
|
- **Hostname:** `seafile`
|
|
- **IP:** `192.168.8.185`
|
|
- **Privilege:** privileged (Debian 12, nesting + keyctl)
|
|
- **Resources:** 4 cores / 8 GiB RAM / 32 GiB rootfs / 1 GiB swap
|
|
- **Mounts:** `/mnt/library` ↔ `/mnt/library` (Seafile storage under `/mnt/library/seafile/data`, ES indices under `/mnt/library/seafile/elasticsearch`)
|
|
- **Public hostname:** [`files.hubris.network`](../infrastructure/dns.md) → [caddy (121)](121-caddy.md) → `192.168.8.185:80`
|
|
- **Reachable from:** LAN + Netbird mesh (no public ingress)
|
|
- **Edition:** Pro free tier (no `seafile-license.txt`, ≤3 users limit) — `api2/server-info/` returns `features: [seafile-basic, seafile-pro, client-sso-via-local-browser]`
|
|
|
|
## Stack
|
|
|
|
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). 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 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:
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `/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`, `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.
|
|
|
|
## Storage layout
|
|
- `/mnt/library/seafile/data/` — bind-mounted to `/shared` in the seafile container. Holds config (`seafile/conf/`), logs (`seafile/logs/`), and the heavy block/fs/commit trees (`seafile/seafile-data/storage/`). Group-owned `:media` 2775.
|
|
- `/opt/seafile/mysql/` — mariadb data (small, stays on rootfs).
|
|
|
|
## Metadata / Views
|
|
|
|
Pro's per-library extended metadata (file tags, custom columns, table/gallery/kanban **Views**) is wired up:
|
|
|
|
- `seahub_settings.py` between `# BEGIN-HUBRIS-METADATA` / `# END-HUBRIS-METADATA`:
|
|
```python
|
|
ENABLE_METADATA_MANAGEMENT = True
|
|
METADATA_SERVER_URL = 'http://seafile-md-server:8084'
|
|
```
|
|
- The `seafile-md-server` container (see Stack table above) does the indexing; Seahub calls it via the docker-bridge DNS name `seafile-md-server`.
|
|
|
|
**Per-library activation:** owner of a library opens it in the web UI → top-right menu → **Metadata management** → toggle on. The first PUT to `/api/v2.1/repos/<id>/metadata/` causes the md-server to index the library (see `seaf-md-server.log`: "start initializing the metadata of the repo …"). Once enabled, the library sidebar shows "Views" where the user can create table / gallery / kanban views.
|
|
|
|
Pure-API enable for testing:
|
|
```
|
|
curl -X PUT -H "Authorization: Token <token>" https://files.hubris.network/api/v2.1/repos/<repo_id>/metadata/
|
|
# GET the same path returns: {"enabled":true, "tags_enabled":true, "show_view":true, ...}
|
|
```
|
|
|
|
## Auth
|
|
Native OAuth2/OIDC against [authentik (124)](124-authentik.md). Configuration in `/mnt/library/seafile/data/seafile/conf/seahub_settings.py` between the `# BEGIN-HUBRIS-OAUTH` / `# END-HUBRIS-OAUTH` markers.
|
|
|
|
Authentik side:
|
|
- **Provider:** `Provider for Seafile` (OAuth2/OIDC, confidential, signing key = self-signed cert)
|
|
- **Application slug:** `seafile`
|
|
- **Redirect URI:** `https://files.hubris.network/oauth/callback/` (trailing slash mandatory)
|
|
- **Launch URL:** `https://files.hubris.network/`
|
|
- **Policy binding:** `authentik Admins` group
|
|
- Provisioned via `ak shell` script (idempotent — `OAuth2Provider.objects.get_or_create(name="Provider for Seafile")`)
|
|
|
|
Seahub side:
|
|
- `ENABLE_OAUTH = True`
|
|
- `OAUTH_AUTHORIZATION_URL` / `OAUTH_TOKEN_URL` / `OAUTH_USER_INFO_URL` → `https://auth.hubris.network/application/o/{authorize,token,userinfo}/`
|
|
- `OAUTH_SCOPE = ['openid', 'profile', 'email']`
|
|
- `OAUTH_ATTRIBUTE_MAP = {'sub': (True, 'uid'), 'email': (True, 'email'), 'name': (False, 'name')}` — `sub` is the stable identifier; without it the early `if not uid:` check renders the generic "Error, please contact administrator" page.
|
|
- `OAUTH_CREATE_UNKNOWN_USER = True`, `OAUTH_ACTIVATE_USER_AFTER_CREATION = True` — JIT provisioning enabled.
|
|
- `CLIENT_SSO_VIA_LOCAL_BROWSER = True` — required so the **iOS Seafile Pro app** bounces OAuth through Safari (system browser) instead of the in-app webview; the webview path cannot complete the round-trip back to the app.
|
|
- **Authentik provider scope mappings** — the OAuth2 provider MUST have `openid`, `profile`, `email` scope mappings attached (`property_mappings`), otherwise userinfo returns only `sub` and the OAUTH_ATTRIBUTE_MAP required-claim check fails.
|
|
|
|
The Seahub login page renders a "Single Sign-On" button that 302s through `/sso/` → `/oauth/login/` → Authentik flow.
|
|
|
|
### Reverse-proxy settings (also in seahub_settings.py)
|
|
```python
|
|
SERVICE_URL = "https://files.hubris.network"
|
|
FILE_SERVER_ROOT = "https://files.hubris.network/seafhttp"
|
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
|
CSRF_TRUSTED_ORIGINS = ['https://files.hubris.network']
|
|
```
|
|
|
|
## DNS / mesh
|
|
- LAN clients resolve `files.hubris.network` → `192.168.8.175` via [dnsmasq on LXC 124](../infrastructure/dns.md) (entry: `address=/files.hubris.network/192.168.8.175`).
|
|
- Netbird mesh peers reach `192.168.8.185` through the existing `192.168.8.0/24` route advertised by the hubris peer.
|
|
- The Seafile container itself needs `auth.hubris.network → 192.168.8.175` mapped at the Docker level (Docker's resolver doesn't follow LXC `/etc/hosts` or the dnsmasq split-horizon), so `docker-compose.override.yml` carries `extra_hosts: auth.hubris.network:192.168.8.175`. See [mesh migration](../infrastructure/mesh.md) for the pattern.
|
|
|
|
## Caddy block (LXC 121)
|
|
```
|
|
files.hubris.network {
|
|
tls {
|
|
dns ionos {env.IONOS_AUTH_API_TOKEN}
|
|
}
|
|
encode zstd gzip
|
|
request_body {
|
|
max_size 10GB
|
|
}
|
|
# /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
|
|
}
|
|
}
|
|
}
|
|
```
|
|
No `import authentik` — Seafile handles OAuth natively, forward-auth would block the `/oauth/callback/` round-trip.
|
|
|
|
## Why exploration-only (and not a Nextcloud cutover)
|
|
The [mulita (120)](120-mule-images.md) photo stack is tightly coupled to Nextcloud — it reads storage roots from `/mnt/library/homecloud/{user}/files/`, proxies thumbnails through NC's `/core/preview`, and consumes NC `webhook_listeners` for file events. Migrating off Nextcloud would require re-architecting all three integration points first.
|
|
|
|
For now: stand up Seafile, kick the tires, decide later whether to:
|
|
1. Keep NC for photos and move docs/files to Seafile.
|
|
2. Refactor mulita off NC, then full cutover.
|
|
3. Roll back (Seafile is rollback-safe — NC is untouched throughout).
|
|
|
|
## Related
|
|
- [authentik (124)](124-authentik.md)
|
|
- [caddy (121)](121-caddy.md)
|
|
- [nextcloud (114)](114-nextcloud.md)
|
|
- [DNS](../infrastructure/dns.md)
|
|
- [Mesh](../infrastructure/mesh.md)
|
|
- [Media permissions](../infrastructure/media-permissions.md)
|
|
|
|
## Changelog
|
|
|
|
### 2026-05-12 — enable Metadata management + Views
|
|
Added `ENABLE_METADATA_MANAGEMENT = True` and `METADATA_SERVER_URL = 'http://seafile-md-server:8084'` to `seahub_settings.py` (between BEGIN-HUBRIS-METADATA / END-HUBRIS-METADATA markers), restarted the `seafile` container. Library owners can now enable metadata per-library and create table/gallery/kanban Views. Verified end-to-end by enabling metadata on a throwaway admin-owned library — `GET /api/v2.1/repos/<id>/metadata/` returns `{"enabled":true, "show_view":true, ...}` and `seaf-md-server.log` shows the indexer ran.
|
|
|
|
### 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).
|
|
|
|
### 2026-05-12 — Caddy: strip IETF resumable-upload headers
|
|
Patched `files.hubris.network` block to drop `Upload-Draft-Interop-Version`, `Upload-Complete`, `Upload-Offset`, `Upload-Length` from the upstream-bound request, and bumped reverse_proxy timeouts (`read_timeout 1h`, `write_timeout 1h`). Reason: iOS Seafile Pro 4.0.2 negotiates resumable uploads via these headers; bundled fileserver doesn't speak the draft → uploads stalled ~60s and got cancelled. Stripping forces the client to fall back to plain multipart, which works.
|
|
|
|
### 2026-05-12 — initial deployment
|
|
LXC 125 created (privileged Debian 12, 4 c / 8 GiB / 32 GiB, IP `192.168.8.185`). Seafile CE 13.0 docker-compose stack at `/opt/seafile/`, storage on `/mnt/library/seafile/data`. Authentik OAuth provider + Application provisioned via `ak shell` (slug `seafile`, redirect URI `https://files.hubris.network/oauth/callback/`). Caddy site block added on LXC 121, dnsmasq entry on LXC 124. End-to-end OAuth handshake verified to the Authentik flow page. **No data migration** — exploratory deployment alongside Nextcloud.
|