--- title: oCIS на VDS — deploy recipe + non-obvious gotchas type: concept tags: [recipe, owncloud, ocis, docker, traefik, webdav, libregraph, decomposedfs] sources: [../../.tasks/owncloud-vds-deploy.md] updated: 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//`, 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`: ```yaml 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: ":"` 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: ```bash 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": ""}, "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>// — space metadata └── users/spaces/<2>// — 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]]: ```diff /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 ```bash 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 ``` ## Related - [[admin-infra-project]] — общий context для admin-infra repo - [[vds-kzntsv]] — host entity (owncloud стек добавлен в его software stack) - [[future-resilient-architecture-goals]] — RTO/RPO targets (oCIS — single-user, RTO/RPO defer)