Add HedgeDoc at docs.mathewcsims.uk for personal Markdown authoring - #58
Merged
Conversation
A LAN/tailnet-gated authoring space with real accounts, a document list, and notes that are owner-only until deliberately shared. Family on the tailnet can be given accounts later. It has both a LAN gate and its own login, and that combination is the point rather than belt-and-braces: the gate admits any device on the LAN or the tailnet, which is the availability wanted, but it is not an identity — it cannot tell one tailnet device from another, and these are personal drafts. This replaces an Etherpad instance built and torn down the same day. Etherpad's requireAuthentication is HTTP Basic only (native browser prompt, credentials cached by the browser, no usable logout), it has no per-note permissions at all, and its own OIDC provider cannot serve pad login — core accepts only Basic, and the provider's endpoints sit behind that same gate. None of it was fixable by configuration. Two bugs found on the way out were filed upstream as ether/etherpad#8109 and ether/ep_comments_page#454. Everything claimed in the comments was verified by running it behind a Caddy carrying this repo's real security_headers, not reasoned about: - Renders correctly under X-Frame-Options: DENY. HedgeDoc's editor is not iframe-based, so unlike Etherpad it needs no per-site header override. - Real login form; with CMD_ALLOW_EMAIL_REGISTER=false the register option disappears. Real logout that actually ends the session. - A note left at the default Private returns 403 to a different signed-in account and does not appear in that account's History. - The /socket.io/ upgrade works through Caddy untouched — typed content was confirmed present in the Postgres Notes table, not merely in the browser. - /status and /metrics are blocked at the proxy: verified 200 for /, connection closed for both, and normal traffic (/new -> 302 to login) unaffected. The `route` wrapper pins directive order so the abort cannot be reordered after reverse_proxy, the same lesson the Owl block records. Two things that would each have shipped a silent fault: - The image contains no wget, curl, nc or python3 — only node. The planned wget healthcheck would have reported the container permanently unhealthy while the service was fine, which is exactly the bug shipped on the Etherpad instance. It uses node's fetch, against 127.0.0.1 rather than localhost. - HedgeDoc builds absolute asset URLs from CMD_DOMAIN/CMD_PROTOCOL_USESSL/ CMD_URL_ADDPORT rather than from the request. Getting them wrong produces a completely unstyled page; hit exactly that during testing, so the values and the reason are documented in both compose.yaml and SETUP.md. Known limit, recorded rather than papered over: permissions are owner / all-signed-in / guests, so a note cannot be shared with just one person. Sharing a note shares it with everyone holding an account. Not yet done, deliberately: the Pass item (agent tokens are read-only for item creation, so that script runs under Mathew's own session), the DNS records, and copying the Caddyfile to the Pi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first deploy crash-looped. `cap_drop: ALL` with nothing added meant the entrypoint could not drop from root to the `hedgedoc` user: error: failed switching to "hedgedoc": operation not permitted The comment I shipped claimed this was "a plain node process that never changes ownership or binds a privileged port, so a bare cap_drop: ALL is safe here". That was an assertion, not a finding — every local test had run with no capability restrictions at all, so the one configuration that mattered was the one never exercised. The same shape of mistake as testing an app directly instead of through the reverse proxy. The set is now established by probing rather than copied from BookStack: cap_drop ALL fails; +SETUID,SETGID starts; adding CHOWN, DAC_OVERRIDE and FOWNER changes nothing, so they are not included. Re-verified with the uploads bind mount attached, since that is the one place a chown could have been required — it starts and the directory is writable. NET_BIND_SERVICE is not needed as it binds 3000. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
A LAN/tailnet-gated authoring space with real accounts, a document list, and
notes that are owner-only until deliberately shared. Family on the tailnet can
be given accounts later.
It has both a LAN gate and its own login, and that combination is the point
rather than belt-and-braces: the gate admits any device on the LAN or the
tailnet, which is the availability wanted, but it is not an identity — it
cannot tell one tailnet device from another, and these are personal drafts.
This replaces an Etherpad instance built and torn down the same day. Etherpad's
requireAuthentication is HTTP Basic only (native browser prompt, credentials
cached by the browser, no usable logout), it has no per-note permissions at
all, and its own OIDC provider cannot serve pad login — core accepts only
Basic, and the provider's endpoints sit behind that same gate. None of it was
fixable by configuration. Two bugs found on the way out were filed upstream as
ether/etherpad#8109 and ether/ep_comments_page#454.
Everything claimed in the comments was verified by running it behind a Caddy
carrying this repo's real security_headers, not reasoned about:
iframe-based, so unlike Etherpad it needs no per-site header override.
disappears. Real logout that actually ends the session.
account and does not appear in that account's History.
confirmed present in the Postgres Notes table, not merely in the browser.
closed for both, and normal traffic (/new -> 302 to login) unaffected. The
routewrapper pins directive order so the abort cannot be reordered afterreverse_proxy, the same lesson the Owl block records.
Two things that would each have shipped a silent fault:
wget healthcheck would have reported the container permanently unhealthy
while the service was fine, which is exactly the bug shipped on the Etherpad
instance. It uses node's fetch, against 127.0.0.1 rather than localhost.
CMD_URL_ADDPORT rather than from the request. Getting them wrong produces a
completely unstyled page; hit exactly that during testing, so the values and
the reason are documented in both compose.yaml and SETUP.md.
Known limit, recorded rather than papered over: permissions are owner /
all-signed-in / guests, so a note cannot be shared with just one person.
Sharing a note shares it with everyone holding an account.
Not yet done, deliberately: the Pass item (agent tokens are read-only for item
creation, so that script runs under Mathew's own session), the DNS records, and
copying the Caddyfile to the Pi.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
🤖 Generated with Claude Code