Skip to content

Add HedgeDoc at docs.mathewcsims.uk for personal Markdown authoring - #58

Merged
mathewcsims merged 2 commits into
mainfrom
add-hedgedoc-docs
Aug 7, 2026
Merged

Add HedgeDoc at docs.mathewcsims.uk for personal Markdown authoring#58
mathewcsims merged 2 commits into
mainfrom
add-hedgedoc-docs

Conversation

@mathewcsims

Copy link
Copy Markdown
Owner

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


🤖 Generated with Claude Code

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>
@mathewcsims mathewcsims closed this Aug 6, 2026
@mathewcsims mathewcsims reopened this Aug 6, 2026
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>
@mathewcsims mathewcsims closed this Aug 6, 2026
@mathewcsims mathewcsims reopened this Aug 6, 2026
@mathewcsims mathewcsims closed this Aug 7, 2026
@mathewcsims mathewcsims reopened this Aug 7, 2026
@mathewcsims
mathewcsims merged commit ca6e309 into main Aug 7, 2026
7 checks passed
@mathewcsims
mathewcsims deleted the add-hedgedoc-docs branch August 7, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant