WebDAV: fix 500, perms, bind path; drop broken depth middleware
ci/woodpecker/push/woodpecker Pipeline was successful Details

- Remove limit_propfind_depth middleware (was breaking response chain)
- Entrypoint: mkdir/chmod /data, chmod -R a+rwX, umask 0000
- Compose: bind webdav data to ${HOME}/dev/piconfigurator/bin
- Add __pycache__ to .gitignore

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leopere 2026-02-24 16:16:02 -05:00
parent 5431fdbbfb
commit 00c0e073d1
Signed by: colin
SSH Key Fingerprint: SHA256:nRPCQTeMFLdGytxRQmPVK9VXY3/ePKQ5lGRyJhT5DY8
5 changed files with 84 additions and 6 deletions

4
.gitignore vendored
View File

@ -9,5 +9,9 @@ macmini-tunnel
macmini-tunnel.pub
webdav-data/
# Python
__pycache__/
*.pyc
# OS
.DS_Store

View File

@ -2,7 +2,7 @@
# Server (ingress) is assumed configured for this domain and key.
#
# What works:
# - WebDAV: no auth in app; uploads go to ./webdav-data. Rebuild after app changes: --build.
# - WebDAV: no auth in app; uploads go to ~/dev/piconfigurator/bin. Rebuild after app changes: --build.
# - Tunnel: uses ~/.ssh/ca-userkey (same key as all other tunnel clients).
# - Auth: TUNNEL_AUTH_USER/PASS (genghis/genghis) = HTTP Basic at the tunnel; WebDAV behind it is open.
# - network_mode: service:webdav so tunnel forwards to localhost:80 inside the webdav container.
@ -11,8 +11,14 @@ services:
webdav:
build: ./webdav
restart: always
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:80/')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
volumes:
- ./webdav-data:/data
- ${HOME}/dev/piconfigurator/bin:/data
tunnel-client:
image: git.nixc.us/colin/better-argo-tunnels:client-production-arm64

View File

@ -4,7 +4,8 @@ WORKDIR /app
RUN pip install --no-cache-dir wsgidav cheroot
COPY app.py .
RUN mkdir -p /data
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
EXPOSE 80
CMD ["python", "-u", "app.py"]
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -1,25 +1,83 @@
"""Minimal WebDAV server. Serves /data for uploads. Auth handled at reverse tunnel."""
"""Minimal WebDAV server. Serves /data for uploads. Auth at reverse tunnel; app allows anonymous."""
import os
import sys
import traceback
from wsgidav.fs_dav_provider import FilesystemProvider
from wsgidav.wsgidav_app import WsgiDAVApp
from wsgidav.dir_browser import WsgiDavDirBrowser
from wsgidav.error_printer import ErrorPrinter
from wsgidav.request_resolver import RequestResolver
from wsgidav.http_authenticator import HTTPAuthenticator
from wsgidav.mw.cors import Cors
from cheroot.wsgi import Server as WSGIServer
ROOT = "/data"
PORT = 80
def ensure_data_dir_writable() -> None:
os.makedirs(ROOT, exist_ok=True)
probe = os.path.join(ROOT, ".write_probe")
try:
with open(probe, "w") as f:
f.write("")
os.remove(probe)
except OSError as e:
print(f"FATAL: cannot write to {ROOT}: {e}", file=sys.stderr)
sys.exit(1)
def catch_all(app):
"""Catch uncaught exceptions so one bad request doesn't kill the server."""
def wrapper(environ, start_response):
try:
return app(environ, start_response)
except Exception:
traceback.print_exc()
start_response("500 Internal Server Error", [("Content-Type", "text/plain")])
return [b"Internal Server Error"]
return wrapper
ensure_data_dir_writable()
config = {
"host": "0.0.0.0",
"port": PORT,
"provider_mapping": {"/": FilesystemProvider(ROOT, readonly=False)},
"middleware_stack": [Cors, ErrorPrinter, WsgiDavDirBrowser, RequestResolver],
"middleware_stack": [
Cors,
ErrorPrinter,
HTTPAuthenticator,
WsgiDavDirBrowser,
RequestResolver,
],
"http_authenticator": {
"domain_controller": None,
"accept_basic": True,
"accept_digest": True,
"default_to_digest": False,
},
"simple_dc": {
"user_mapping": {"*": True},
},
"hotfixes": {
"emulate_win32_lastmod": True,
"treat_root_options_as_asterisk": True,
},
"add_header_MS_Author_Via": True,
"verbose": 3,
}
app = WsgiDAVApp(config)
app = catch_all(app)
if __name__ == "__main__":
server = WSGIServer((config["host"], config["port"]), app)
# Avoid Finder/hung clients holding connections forever
if hasattr(server, "connection_limit"):
server.connection_limit = 20
if hasattr(server, "timeout"):
server.timeout = 30
server.start()

9
webdav/entrypoint.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/sh
set -e
mkdir -p /data
chmod 1777 /data
# Fix perms on existing content so host user can read/write
chmod -R a+rwX /data 2>/dev/null || true
# New files/dirs world-readable/writable so host user can manage them
umask 0000
exec python -u app.py