Files
admin/.wiki/concepts/ocis-on-vds-deploy-recipe.md
vitya 68706d0bee tasks(owncloud-vds-deploy): close 🟢 — 26 GB live + 60s timeout finding
Import complete: 25 044 objs / 26 GB. 99.97% залито rclone'ом (22.2 GB
за ~2.5 ч), оставшиеся 6 файлов (4.04 GB) — 4× .pat ~221 MB + 2× .seospider
1.55+1.7 GB — упали с 502/500 на каждом rclone PUT из-за 60-секундного
HTTP timeout в reva v2.27 datagateway (hardcoded в Go http.Client.Timeout,
не env-configurable).

Workaround: VDS-side curl PUT loopback через throwaway sftp key.
Local network 142 MB/s → 4.04 GB за 28 секунд, все PROPFIND size match.

Wiki: §Gotcha 5 в concepts/ocis-on-vds-deploy-recipe.md документирует
finding + recipe для workaround. Traefik buffering middleware пробован,
не помог (likely vulcand/oxy buffer bug на больших телах) — откатил.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 07:26:07 +03:00

12 KiB
Raw Permalink Blame History

title, type, tags, sources, updated
title type tags sources updated
oCIS на VDS — deploy recipe + non-obvious gotchas concept
recipe
owncloud
ocis
docker
traefik
webdav
libregraph
decomposedfs
../../.tasks/owncloud-vds-deploy.md
2026-05-21

oCIS на VDS — deploy recipe + non-obvious gotchas

Recipe для разворачивания ownCloud Infinite Scale (oCIS) — Go-based замены classic ownCloud Server 10 — в Docker за traefik с LetsEncrypt. Применимо на vds-kzntsv и других хостах с тем же pattern'ом (/opt/stacks/<name>/, traefik network proxy, LE certresolver letsEncrypt).

Validated 2026-05-21 на image owncloud/ocis:7.1.0. Container запустился, traefik routes 200 OK, LibreGraph API + WebDAV работают, init создаёт admin + idm.json в data/idm/.

Когда применять

  • Личный personal-cloud для одного пользователя (single binary, no external DB)
  • Replace мёртвого / legacy ownCloud Server 10 (oCIS = main line от ownCloud GmbH, OC10 deprecating)
  • Files-only use-case (calendar/contacts не нужны — для тех нужен Nextcloud)
  • На хосте с уже работающим traefik + LE certresolver (recipe рассчитан на termination upstream)

Когда не применять

  • Multi-tenant корпоративная инсталляция → нужны external IDP, OIDC chain, не личный setup
  • Нужны calendar / contacts / mail / collabora → Nextcloud, не oCIS
  • Block-level dedup критичен → Seafile (oCIS использует file-level decomposedfs)
  • Host UID = 1000 (image uid 1000 matches → user: override не нужен)

Compose recipe

/opt/stacks/owncloud/docker-compose.yml:

services:
  ocis:
    image: owncloud/ocis:7.1.0
    container_name: owncloud
    restart: unless-stopped
    user: "1001:1001"                 # ← gotcha 1, см. ниже
    entrypoint: /bin/sh
    command: ["-c", "ocis init || true; exec ocis server"]
    environment:
      OCIS_URL: https://owncloud.kzntsv.site
      OCIS_LOG_LEVEL: info
      OCIS_LOG_COLOR: "false"
      PROXY_TLS: "false"              # ← gotcha 2
      PROXY_ENABLE_BASIC_AUTH: "true" # ← gotcha 3
      OCIS_INSECURE: "false"
      IDM_CREATE_DEMO_USERS: "false"
      IDM_ADMIN_PASSWORD: ${OCIS_ADMIN_PASSWORD}
    volumes:
      - ./data:/var/lib/ocis
      - ./config:/etc/ocis
    networks: [proxy]
    labels:
      - traefik.enable=true
      - "traefik.http.routers.owncloud.rule=Host(`owncloud.kzntsv.site`)"
      - traefik.http.routers.owncloud.entrypoints=websecure
      - traefik.http.routers.owncloud.tls.certresolver=letsEncrypt
      - traefik.http.services.owncloud.loadbalancer.server.port=9200

networks:
  proxy: { external: true }

/opt/stacks/owncloud/.env (chmod 600):

OCIS_ADMIN_PASSWORD=<32+ random chars, store в pass owncloud/admin-password>

Gotcha 1: UID mismatch image vs host

Image owncloud/ocis runs as UID 1000 (ocis-user внутри контейнера). Если host-side user-owner mounted dirs ≠ 1000 — контейнер падает в restart-loop с:

Could not create config: open /etc/ocis/ocis.yaml: permission denied
The jwt_secret has not been set properly in your config for ocis.

«jwt_secret has not been set» — обманка, симптом сидит на ровном месте perm-deny при попытке записать ocis.yaml на первом ocis init пробеге.

Fix: user: "<host-uid>:<host-gid>" override в compose. На vds-kzntsv host user vitya = UID 1001 → user: "1001:1001". Альтернатива (chown 1000 host-dirs) отвергнута: создаёт orphan UID на хосте, путает auditing + backup tools.

Diagnose UID: stat -c "%u:%g %U:%G" /opt/stacks/owncloud/config.

Gotcha 2: PROXY_TLS=false за reverse proxy

Дефолт oCIS — PROXY_TLS=true (HTTPS на proxy port 9200 self-signed). За traefik, который сам терминирует HTTPS, это даёт двойную TLS-обёртку — traefik пытается сделать backend TLS, не получается.

Per ownCloud docs: «If a reverse proxy is used to terminate HTTPS, PROXY_TLS can be set to false, though this means communication between the proxy and Infinite Scale will be unencrypted.» На том же хосте, в bridge network — acceptable.

Gotcha 3: PROXY_ENABLE_BASIC_AUTH=true для WebDAV + admin API

Дефолтно oCIS — OIDC-only (browser-based login). Для rclone / ownCloud-desktop / curl LibreGraph API нужен basic auth — иначе 401 везде, включая GET /graph/v1.0/me.

Trade-off: basic auth не имеет MFA/refresh-tokens. Для personal use acceptable. Для shared deployment — обернуть LDAP / OIDC отдельно.

Gotcha 4: User-create via LibreGraph API (нет CLI)

oCIS не имеет ocis idm user add или подобной CLI команды. Создание users — только через:

  1. Web UI как admin → Settings → Users
  2. LibreGraph API: POST /graph/v1.0/users с basic auth admin'а

API recipe:

curl -u "admin:$ADMIN_PWD" -X POST \
  "https://owncloud.kzntsv.site/graph/v1.0/users" \
  -H "Content-Type: application/json" \
  -d '{
    "displayName": "Vitya",
    "onPremisesSamAccountName": "vitya",
    "mail": "vitya@kzntsv.site",
    "passwordProfile": {"password": "<random-32>"},
    "accountEnabled": true
  }'

→ HTTP 201 с user JSON (с id UUID).

List users: GET /graph/v1.0/users.

Storage layout (decomposedfs)

После ocis init data dir выглядит так:

/opt/stacks/owncloud/data/
├── idm/           — internal IDM (LDAP-style) state
├── idp/           — OIDC IdP private keys (private-key.pem etc)
├── nats/          — internal pub-sub messaging
├── search/        — indexer state (bleve)
└── storage/
    ├── metadata/spaces/<2>/<rest>/  — space metadata
    └── users/spaces/<2>/<rest>/     — actual user files (decomposedfs nodes)

Каждый user space = <2-char-prefix>/<26-char-rest-of-uuid>/ (split UUID для FS spread). Files внутри хранятся как plain bytes + sidecar metadata (xattrs / .meta.json). Backup-friendly: tar+rsync захватит всё корректно, восстановление = просто разархивировать обратно.

Internal KV-store (storage/users/spaces/.../metadata/, idm/idm.json, etc.) — JSON files, не БД. Это и есть «no external DB needed» — oCIS использует filesystem + NATS вместо Postgres/Redis.

Backup integration

/opt/stacks/owncloud (whole dir) добавлен в rsync source list vds-backup-rsync-kreknin:

   /opt/stacks/backup \
+  /opt/stacks/owncloud \
   /etc/ssh \

Live container во время rsync — acceptable trade-off (decomposedfs использует atomic rename, отдельные node-files не corrupt'ятся). Если требуется strictly consistent snapshot — docker stop owncloud → rsync → docker start (downtime ~30s).

Первый full backup snapshot после import → +~25 GB на kreknin. Далее hardlink-incremental ≈ 1 GB/day.

Connection chain

DNS owncloud.kzntsv.site → 89.253.255.94 (VDS_IP)
  → ufw 443
  → traefik websecure (LE cert via letsEncrypt resolver)
  → docker network proxy (172.18.0.0/16)
  → container owncloud:9200 (oCIS proxy service)
  → internal services: idp / idm / users / groups / proxy / storage-system / sharing / search ...

Atomic revert

ssh vitya@89.253.255.94 'cd /opt/stacks/owncloud && docker compose down -v && cd .. && rm -rf owncloud'
# rsync source list cleanup (revert):
ssh vitya@89.253.255.94 "sed -i '\\|/opt/stacks/owncloud|d' /opt/stacks/backup/scripts/run.sh"
# secrets cleanup:
pass rm owncloud/admin-password owncloud/user-vitya && pass git push

Симптом: rclone PUT любого файла, который не успевает залиться за 60 секунд через клиентский uplink, падает с 502 (no buffering) или 500 (с buffering middleware). Пороги при разной скорости uplink:

Uplink Cap
3 MiB/s ~180 MB
10 MiB/s ~600 MB
50 MiB/s ~3 GB

В нашем кейсе (rclone 3.35 MiB/s) — упали все файлы ≥221 MB (4× .pat по 221-222 MB, 508 MB zip, 1.55 GB + 1.7 GB seospider).

Где сидит 60s: не traefik (default unlimited). Не на oCIS layer per env-var (нет конфигурируемого ключа). Hardcoded где-то в HTTP stack — кандидаты:

  • Go http.Client.Timeout в reva v2.27 datagateway.go:200 (видно в логах oCIS: Put "http://localhost:9158/data/simple/...": context canceled, time_ns:59944479090 = 59.94s)
  • rclone-side http.Client.Timeout (default Go = no timeout, но в библиотеке WebDAV может быть явно установлен)
  • HTTP/2 default stream timeout

Bumped via env-var не починить — закопано в коде reva. Apgrade oCIS image до новой версии может помочь (newer reva имеет настраиваемый timeout) — defer.

Что НЕ помогло:

  • traefik.http.middlewares.<name>.buffering.maxRequestBodyBytes=0 + memRequestBodyBytes=1048576 — буферинг сам выдаёт 500 на 60s mark (backend_url="-" в access log = traefik так и не доходит до backend). Возможно ошибка в vulcand/oxy buffer на больших телах. Не разбирался — workaround ниже проще.

Workaround: VDS-side direct upload.

Когда у нескольких файлов upload-time > 60s через клиентский uplink:

  1. scp с throwaway-key с restricted authorized_keys на VDS: генерим одноразовый ed25519 на VDS, append'им как restrict,command="internal-sftp" <pub> в ~vitya/.ssh/authorized_keys (sftp-only, no shell, no port forwards), private отдаём агенту на slow-uplink машине. Cleanup ключа после.

  2. Агент sftp -i <key> -b <batch> пушит файлы в /tmp/oc-import/ на VDS. SFTP через ssh не уязвим к 60s timeout — длинная сессия с keep-alive.

  3. C VDS-стороны: curl -T <local-file> <ocis-url> loopback'ом. Local network = >100 MB/s → 1.7 GB файл за 10 секунд, ≪ 60s timeout window. PUT через traefik websecure (https://owncloud.kzntsv.site/dav/files//) с basic auth admin/user. URL-encode Cyrillic / spaces в path. Pattern:

    ENC=$(python3 -c "import urllib.parse,sys; print('/'.join(urllib.parse.quote(p) for p in sys.argv[1].split('/')))" "$REMOTE_PATH")
    curl -sS -u "vitya:$PWD" -T "$SRC" "https://owncloud.kzntsv.site/dav/files/vitya/${ENC}"
    

    → HTTP 201 Created.

  4. Verify через PROPFIND<oc:size> или <d:getcontentlength> должен match'ить stat -c %s source.

  5. Cleanup: rm -rf /tmp/oc-import/, remove throwaway-key line из authorized_keys.

В нашем кейсе для 6 файлов / 4.04 GB local PUT cycle занял ~28 секунд total (0.5-10s per file). vs rclone failed после ~5 минут на каждый retry × 5 retries × 6 файлов = безуспешно вечность.