Repository navigation
feat(shares): reach S3 and WebDAV through remote access, at /s3 and /dav - #402
Merged
Merged
Conversation
The Cloudflare tunnel and HTTPS serve only the web app, and the web app forwarded only the API, MCP and a few other paths, so the file shares (S3 on 9555, WebDAV on 9556) couldn't be reached from outside the network, and not at all on Cloudron. Now the web app relays them at its own address: /s3 to the S3 server and /dav to the WebDAV server. Off until an admin turns on "At the web app's address too" in Settings > File shares (shares.remote, default false). Until then the share servers refuse requests that came through the web app before any sign-in; the shares' own ports work as before. Choices made: - nextjs-frontend/lens-shares.js relays before Next sees the request: Next answers only the usual methods (WebDAV needs PROPFIND) and redirects paths ending in a slash. lens-server.js uses it in the production image; NODE_OPTIONS --require adds it to next dev (dev compose) and next start (Cloudron). - Path and Host pass through unchanged, since S3 clients sign both; X-Forwarded-Prefix tells the share server the request came this way and which prefix to strip and to put in WebDAV's links. - Failed WebDAV sign-ins are counted per visitor from X-Forwarded-For as trusted proxies report it (server.trusted_proxies, LENS_TRUSTED_PROXY_HOSTS, loopback), so one visitor can't lock out everyone arriving through the web app. The LENS_TRUSTED_PROXY_HOSTS lookup moved from app/api/deps.py to ipgroups.proxy_hosts() so the worker can use it. - LENS_S3_URL / LENS_WEBDAV_URL say where the share servers are (http://worker:9555 and :9556 in every compose file); unset, this machine's ports, which fits Cloudron and microsandbox. - GET /api/v1/shares/s3 gains `remote`; a new access key's dialog then gives https://<web app>/s3 and https://<web app>/dav/. - MQTT, syslog, SMB and NFS aren't HTTP and stay on their own ports; docs/remote-access.md now lists what the tunnel reaches. - Stored data: a new settings key with a default, merged on read; no upgrade step. Verified: botocore (list, ranged get, presigned link) and rclone (S3 and WebDAV) through next dev with the preload and through lens-server.js; backend tests use a relay that does what lens-shares.js does. Refine later (docs/file-tree.md): virtual-host S3 addressing at the web app's address, for clients that can't take an endpoint with a path. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
# Conflicts: # CHANGELOG.md
…hares The web app's relay will carry other servers Lens runs besides the API (MQTT over WebSockets next), so its name says what it does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
…-ppx7di # Conflicts: # CHANGELOG.md # threat-model.yaml
…-ppx7di # Conflicts: # CHANGELOG.md # threat-model.yaml
…-ppx7di Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
…-ppx7di Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
…them, so a cut connection is tried again The microsandbox install stopped at "Getting Node 22" when the connection to nodejs.org was cut partway: curl can't retry once it has written to a pipe, so tar got half an archive. SurrealDB and Node are now downloaded to a file (curl retries any error), Node's checked against its SHASUMS256, then unpacked. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
Merged
16 of 22 tasks
adeelahmad
added a commit
that referenced
this pull request
Oct 15, 2026
…pages and notes as built (#424) Move usage analytics (#274) and assets and people (#273) out of the README's In progress list, and add what merged since #383: a namespace's own MCP server, chat completions and one-namespace API keys; editing and merging entities and topics with undo, from agents too; entity pages at wiki addresses; Connect with Google and rclone sign-in on the server; the notes tree, today's note, pins, search and Recently deleted; backups that keep Lens's files; and grouped, searchable settings. In progress now lists the open feature PRs (#398, #400, #402, #408, #419), and the docs index links usage analytics. Claude-Session: https://claude.ai/code/session_01HDx7PPUauyjMn1LZs8JAsS Co-authored-by: Adeel Ahmad <noreply@anthropic.com>
…-ppx7di Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
…-ppx7di Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
…-ppx7di Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
…-ppx7di Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa
This was referenced Oct 17, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Adeel · project thread
Description
Before: the Cloudflare tunnel (Settings › Remote access) and HTTPS serve only the web app, and the web app forwards only the API, MCP and a few other paths. So the file shares (S3 on 9555, WebDAV on 9556) couldn't be reached from outside the network, and not at all on Cloudron, which publishes only the web app.
After: the web app relays them at its own address, S3 at
/s3and WebDAV at/dav, so they work wherever the web app opens. It's off until an admin turns on At the web app's address too in Settings › File shares (shares.remote, default off). Once it's on, a new access key's dialog giveshttps://<web app>/s3andhttps://<web app>/dav/. Signing in is unchanged: a person's own access key, read-only.MQTT, syslog, SMB and NFS aren't HTTP, so they stay on their own ports;
docs/remote-access.mdnow has a table of what the tunnel reaches (the web app, API, MCP and HTTP sensor push already did). MQTT over WebSockets follows in its own PR.How:
nextjs-frontend/lens-relay.jsrelays requests under/s3and/davbefore Next sees them, because Next answers only the usual methods (WebDAV needs PROPFIND) and redirects paths that end in a slash.lens-server.jsuses it in the production image, andNODE_OPTIONS=--requireadds it tonext dev(dev compose) andnext start(Cloudron).Hostpass through unchanged, since S3 clients sign both.X-Forwarded-Prefixtells the share server the request came through the web app; it refuses such requests before sign-in unlessshares.remoteis on, strips the prefix, and puts it in WebDAV's links.X-Forwarded-For, as trusted proxies report it (server.trusted_proxies,LENS_TRUSTED_PROXY_HOSTS, loopback). That way one visitor can't lock out everyone who arrives through the web app. TheLENS_TRUSTED_PROXY_HOSTSlookup moved fromapp/api/deps.pytoipgroups.proxy_hosts()so the worker can use it.LENS_S3_URLandLENS_WEBDAV_URLsay where the share servers are (http://worker:9555and:9556in every compose file). Unset, they default to this machine's ports, which fits Cloudron and microsandbox.Motivation and Context
Adeel asked whether remote access also exposes S3, WebDAV, MCP and the other protocols, and to add what's missing. MCP already did; S3 and WebDAV didn't, and they can run over HTTPS with the same auth.
Steps to reproduce (if appropriate):
Verified locally with the real share servers over a seeded archive:
next devwith the preload, andlens-server.js: botocore listed buckets and objects, did a ranged get and fetched a presigned link athttp://127.0.0.1:3000/s3.lsd,lsandcatover both S3 (endpoint = …/s3) and WebDAV (url = …/dav/)./dav/and/dav/pods/Collections/General/answered 207 with/dav/…hrefs, and normal pages still render.Types of changes
!in the title)Checklist:
make hookschecks it on commit).docs/accordingly.## UnreleasedinCHANGELOG.md.Done questions (AGENTS.md)
PUT /api/v1/settings/shares {"remote": true}(admin), and the assistant's settings tool, which always asks first forshares.GET /api/v1/shares/s3reportsremote..envline.shares.remoteis in Settings › File shares, default off, and not in the wizard.lens-relay.js. Docs are updated, and the OpenAPI schema and client were regenerated with tooling.mc).🤖 Generated with Claude Code
https://claude.ai/code/session_01CXpr1EWCfUD5tb1SmSwfDa