Nextcloud Mount Sidecar¶
A dedicated core--rclone--sidecar container (alias nextcloud-mounter during naming migration) runs rclone rcd with
SYS_ADMIN and
/dev/fuse. It creates one WebDAV/FUSE mount per platform user under
${NEXTCLOUD_MOUNTS_ROOT}/<machine-name> (default root
/var/lib/scs/nextcloud-mounts). Consumer containers (WissKI, Jupyter) bind that
path with rslave propagation and never receive FUSE capabilities or rc-API
access.
Architecture¶
Host: /var/lib/scs/nextcloud-mounts (self-bound rshared)
├── _disabled/ ← empty fallback bind for stacks without a project folder
├── <user-a>/ ← rclone FUSE mount (whole Drive)
│ └── <project-label>/
└── <user-b>/
nextcloud-mounter (sidecar)
rclone rcd :5572
SYS_ADMIN + /dev/fuse
bind: host root → /mnt/nextcloud (rshared)
nets: nextcloud-mounter-net (internal) + reverse-proxy (WebDAV egress)
▲
│ mount/unmount via rc-API (Basic Auth)
SCS Manager (Drupal) — only consumer of the rc-API
nets: … + nextcloud-mounter-net
bind: host root → /mnt/nextcloud-mounts (rslave)
Design rules:
- One sidecar FUSE mount per platform user (whole Drive under
${NEXTCLOUD_MOUNTS_ROOT}/<machine-name>). - WissKI binds only the project Team Folder subdirectory
(
…/<user>/<project-label>→/opt/drupal/private-files/nextcloud=private://nextcloud), withNEXTCLOUD_MOUNT_MODE=external. - Jupyter binds only Keycloak project Team Folders the user belongs to
(
…/<user>/<project-label>→/home/jovyan/nextcloud/<project-label>,rslave; notebook user is in GID33for www-data ownership on the FUSE tree). Project groups are integer names (entityId + 10000); Hub resolves each group’slabelattribute via Keycloak Admin API at spawn. - Lifecycle owner is SCS Manager (
NextcloudMountManagerreconciler ~1 min cron). - Host bind with
rshared/ consumers withrslave(named volumes cannot propagate FUSE). - Empty fallback:
${NEXTCLOUD_MOUNTS_ROOT}/_disabled(owned33:33) when no project folder is available yet.
Host preparation (once)¶
cd /var/www/deploy/soda_scs_manager_deployment
# One-shot / idempotent
bash 01_scripts/global/setup-nextcloud-mounts.sh
# Persist across reboots (preferred)
sudo cp 01_scripts/global/scs-nextcloud-mounts.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now scs-nextcloud-mounts.service
findmnt -o PROPAGATION --target /var/lib/scs/nextcloud-mounts
# must contain "shared"
start.sh aborts if the mounts root is missing or not shared.
Environment¶
In .env (see example-env):
NEXTCLOUD_MOUNTS_ROOT=/var/lib/scs/nextcloud-mounts
NEXTCLOUD_MOUNTER_RC_USER=scs-manager
NEXTCLOUD_MOUNTER_RC_PASS=<openssl rand -hex 32>
SCS Manager also receives (via stack override):
NEXTCLOUD_MOUNTER_RC_URL=http://nextcloud-mounter:5572
NEXTCLOUD_MOUNTER_RC_USER / NEXTCLOUD_MOUNTER_RC_PASS
NEXTCLOUD_MOUNTS_ROOT
Keep these in 00_custom_configs/scs-manager-stack/docker/docker-compose.override.yml
(copied to scs-manager-stack/ by start.sh): attach scs-manager--drupal to
nextcloud-mounter-net, depends_on: nextcloud-mounter, and bind
${NEXTCLOUD_MOUNTS_ROOT} → /mnt/nextcloud-mounts with rslave.
Start / health¶
docker compose up -d core--rclone--sidecar
docker compose ps core--rclone--sidecar
# Healthy; no host port published for :5572
From the SCS Manager container:
docker exec scs-manager--drupal curl -sf -u "$NEXTCLOUD_MOUNTER_RC_USER:$NEXTCLOUD_MOUNTER_RC_PASS" \
-X POST http://nextcloud-mounter:5572/core/pid
Or via Drush inside Manager:
scs-drush scs:nextcloud-status
scs-drush scs:nextcloud-reconcile
# per user: scs:nextcloud-mount / scs:nextcloud-unmount --user=<name>
WissKI containers must not resolve or reach nextcloud-mounter:5572
(they are not on nextcloud-mounter-net).
Consumers¶
WissKI (project Team Folder)¶
SCS Manager provisions each stack with:
| Env | Typical value |
|---|---|
NEXTCLOUD_MOUNT_MODE |
external |
NEXTCLOUD_USER_MOUNT_SOURCE |
/var/lib/scs/nextcloud-mounts/<owner>/<project-label> (or …/_disabled) |
wisski-base-stack binds that host path to
/opt/drupal/private-files/nextcloud (rslave). The image enables
nextcloud_webdav_mount with operation_mode=external and
external_mount_path=private://nextcloud (status only — no in-container rclone,
no app passwords in the WissKI stack).
Existing stacks need a re-deploy to pick up the external bind after migration from sync mode.
JupyterHub (project Team Folders)¶
jupyterhub/jupyterhub/jupyterhub_config.py reads Keycloak project groups from
OIDC groups (integer names ≥ 10000), resolves each group’s label
attribute, and binds
${NEXTCLOUD_MOUNTS_ROOT}/<username>/<project-label> →
/home/jovyan/nextcloud/<project-label>
with rslave. Personal Drive content and non-project folders (e.g. Documents,
SCS-Share) are not mounted. The Hub override passes NEXTCLOUD_MOUNTS_ROOT,
KC_URL / KC_REALM, and bootstrap admin credentials for label lookup, and
bind-mounts the mounts root (rslave) so existence checks see the host FUSE
tree. If no project folder is available, _disabled is bound to
/home/jovyan/nextcloud.
Respawn after project create / join (not hot)¶
Mounts are fixed when the notebook container is spawned. Creating a project or accepting a membership does not add folders to a running Lab.
- In SCS Manager, users see a warning with a link to stop the notebook
(
/soda-scs-manager/jupyter/restart-notebook), or they can Stop from the Jupyter card health icon. - The confirm form stops the notebook server and warns that unsaved notebook work will be lost (home volume and Nextcloud files are kept).
- The user opens JupyterHub and chooses Start My Server so the Hub re-reads Keycloak project groups and binds the new Team Folders.
Stopping alone does not remount; a Hub spawn is required.
Secret rotation¶
- Set a new
NEXTCLOUD_MOUNTER_RC_PASSin.env. docker compose up -d core--rclone--sidecar scs-manager--drupal- Confirm Manager can still call
core/pid(orscs-drush scs:nextcloud-status).
Troubleshooting¶
| Symptom | Likely cause | Action |
|---|---|---|
Transport endpoint is not connected |
Sidecar crashed; stale FUSE node | docker compose restart core--rclone--sidecar; then scs-drush scs:nextcloud-reconcile |
| Mount empty in running WissKI | Source dir missing at stack start, or wrong NEXTCLOUD_USER_MOUNT_SOURCE |
Ensure owner mount + project folder exist; redeploy stack with correct source |
| WissKI shows disconnected / not mounted | Module not in external mode, or bind still _disabled |
Check NEXTCLOUD_MOUNT_MODE / operation_mode; redeploy after Team Folder exists |
start.sh aborts on propagation |
Host bind not rshared | Re-run setup script / enable systemd unit |
| rc-API 401 | Password drift between sidecar and Manager | Align .env and recreate both services |
Manager cannot resolve nextcloud-mounter |
Not on nextcloud-mounter-net |
Fix override (network + depends_on) and recreate Manager |
Backup¶
Live FUSE mounts are excluded from host backups (backup-containers.sh).
Nextcloud remains the source of truth.
Related¶
- Post-configuration checklist
- WissKI stack
nextcloud_webdav_mountmodule:operation_mode=external(passive path/status)- Manager services:
NextcloudMounterClient,NextcloudMountManager, Drushscs:nextcloud-*