Skip to content

feat: Docker support, TOTP auth, image rendering, ion-sync fixes, favicon, open-in-new-window#9

Open
s39n wants to merge 43 commits into
MusiCode1:mainfrom
s39n:main
Open

feat: Docker support, TOTP auth, image rendering, ion-sync fixes, favicon, open-in-new-window#9
s39n wants to merge 43 commits into
MusiCode1:mainfrom
s39n:main

Conversation

@s39n

@s39n s39n commented Jun 10, 2026

Copy link
Copy Markdown

Summary

This PR adds Docker/NAS deployment support and fixes several compatibility issues for self-hosted use, particularly around the ion-sync community plugin and plain-HTTP (non-HTTPS) deployments.

Docker & Deployment

  • Add Dockerfile and docker-compose.yml with nginx reverse proxy option
    • Move Obsidian vendor bundle download to container entrypoint (fixes build-time network errors)
    • Support USER_DATA and VAULT_PATH env vars for flexible vault mounting
    • Fix EXDEV rename error by symlinking .tmp into the vendor volume
    • Add .env.example and deployment docs in README

Authentication

  • Replace static AUTH_KEY with TOTP (time-based one-time password) authentication
    • Configurable via TOTP_SECRET env var; auth disabled when unset
    • Add /__totp-setup QR code endpoint for easy authenticator app registration

Crypto / ion-sync (plain HTTP support)

Browsers restrict crypto.subtle to HTTPS/localhost. These polyfills allow ion-sync to work on plain HTTP LAN deployments:

  • SHA-256 and SHA-1 pure-JS polyfills for crypto.subtle.digest
    • AES-GCM-256 polyfill for crypto.subtle.encrypt / decrypt
    • PBKDF2 offloaded to /api/pbkdf2 (Node native crypto — avoids ~10s browser freeze for 100k iterations)
    • crypto.randomUUID polyfill via crypto.getRandomValues
    • safeStorage shim — stores plugin secrets server-side via /api/keytar (Electron's safeStorage unavailable in browser)

Image Rendering

  • Override vault.getResourcePath in boot.js to return /api/fs/read?path=... HTTP URLs instead of app://local/... Electron URLs that browsers can't load
    • Use file.path directly (clean vault-relative path) rather than parsing the Electron URL, avoiding issues with Obsidian's ?timestamp cache-buster suffix
    • Add MutationObserver fallback to rewrite any app:// or file:// img src attributes that slip through
    • /api/fs/read now returns correct MIME types for images, video, audio, and PDF

Image Paste (Clipboard)

  • Capture paste events in electron.js and cache the image data
    • Implement remote.clipboard.readImage(), availableFormats(), hasImage() so Obsidian's paste handler can write pasted images to the vault

IPC / Electron shim fixes

  • Handle open-window / move-to-window / new-window IPC channels by opening the vault in a new browser tab instead of crashing (V0 Electron check)
    • Handle open-vault-manager / manage-vaults channels by navigating to /starter
    • Fix sendSync('starter') to return '/starter' so vault picker navigation works
    • Fix trash endpoint: strip virtual /vault/ prefix before resolving path (was causing ENOENT on ion-sync deletes)
    • Fix file-url endpoint: return /api/fs/read?path=... instead of app://local/...
    • Implement shell.showItemInFolder — opens the file in a new tab via /api/fs/read
    • Fix binary writes: always set Content-Type: application/octet-stream so body-parser parses the body

Other fixes

  • Auto-repair ENOTDIR vault corruption (mkdirRepair): unlinks stale files blocking directory creation
    • Add Obsidian favicon to browser tab (favicon.ico, favicon.svg, apple-touch-icon.png)
    • Fix mobile safe-area top cutoff

s39n and others added 30 commits June 9, 2026 16:05
- Dockerfile: multi-stage build (downloads obsidian + obsidian-mobile
  renderer bundles in stage 1, installs server deps in stage 2)
- docker-compose.yml: base setup, vault persisted via volume
- docker-compose.auth-key.yml: API-key auth overlay (AUTH_KEY env var)
- docker-compose.auth-basic.yml: nginx HTTP Basic Auth overlay
- nginx.conf: nginx reverse proxy with WebSocket support for /api/watch
- .dockerignore: excludes vendor/, node_modules/, .git/, etc.
- src/server/middleware/auth.js: Express middleware for API-key auth
  (login page, cookie session, Bearer token support)
- src/server/index.js: load auth middleware when AUTH_KEY is set
Both auth options are now in one file — easier for Dockhand and other
Docker Compose users to work with a single file:

- Option 1 (API Key): uncomment AUTH_KEY in the env section, or set it
  in Dockhand's stack environment editor
- Option 2 (Basic Auth): docker compose --profile auth-nginx up
  (requires nginx.htpasswd generated with htpasswd)

Removes docker-compose.auth-key.yml and docker-compose.auth-basic.yml.
…error)

Docker build environments have no internet access, so downloading Obsidian
renderer files at build time (EAI_AGAIN / DNS failure) doesn't work.

Instead:
- entrypoint.sh checks for vendor/obsidian/app.js on startup and runs the
  download scripts only if missing (~30s on first start)
- obsidian_vendor Docker volume caches the result — subsequent starts are
  instant
- Dockerfile becomes a single stage with no network dependency at build time
The update scripts extract to /app/.tmp/ then rename() into /app/vendor/.
rename() fails with EXDEV when src and dst are on different filesystems —
which they are in Docker (.tmp = container layer, vendor = named volume).

Fix: at startup, mkdir /app/vendor/.tmp and symlink /app/.tmp -> /app/vendor/.tmp
so both paths live on the same volume filesystem.
docker-compose.yml:
- commented examples for mounting your own vault folder and plugins dir
- AUTH_KEY now loaded from .env file (gitignored) or Dockhand env editor
- env_file with required:false so .env is optional

auth:
- .env.example shows how to configure AUTH_KEY locally
- .gitignore: add .env so real keys are never committed

mobile:
- index.html: add env(safe-area-inset-top) padding on html element
  so content is not hidden under the status bar / notch on iOS and Android
Split into two variables:
- APP_PORT (default 3000): host port for direct access to obsidian-web
- PORT (default 3000): public port nginx listens on

When running the auth-nginx profile, set APP_PORT=3001 so nginx can
take port 3000 without both services fighting over the same host port.
docker-compose: AUTH_KEY was commented out in environment so it never
reached the Node process even when set in Dockhand's env editor or .env.
Now explicitly passed as AUTH_KEY: \ so an empty value is
the default (auth disabled) and any non-empty value enables the login page.

mobile: add env(safe-area-inset-bottom) so the bottom tab bar clears
the home indicator on iOS/Android — previously only top was handled.
- Rewrite src/server/middleware/auth.js with otplib + qrcode
- Login page: 6-digit code entry with auto-advance and auto-submit
- Setup page at /__totp-setup?token=TOTP_SECRET shows QR code + raw secret
- Session cookie derived from TOTP_SECRET (consistent across restarts)
- ±30s clock drift tolerance (window: 1)
- Replace AUTH_KEY with TOTP_SECRET in docker-compose.yml and .env.example
- Update index.js log message and comments
… HTTP

Add importKey, deriveKey (PBKDF2 proxied to server), encrypt, decrypt.

- PBKDF2 with 100k iterations offloaded to POST /api/pbkdf2 (Node native
  crypto.pbkdf2) to avoid freezing the browser for ~10 s in pure JS.
- AES-256 forward cipher and AES-GCM (GHASH + CTR) in pure JS,
  verified byte-for-byte against Node's native AES-256-GCM output.
- digest (SHA-256) polyfill kept from prior commit.

Fixes: crypto.subtle.importKey is not a function at deriveKey (ion-sync:43)
…p - Add Obsidian-style crystal SVG favicon to browser tab (index.html, starter.html)- Handle open-window / move-to-window / new-window IPC channels by opening the same vault in a new browser tab (electron.js shim)- Handle open-vault-manager IPC channels by opening /starter in a new tab - Fix sendSync('starter') server endpoint to return '/starter' so Obsidian's vault picker can navigate to the vault management page
@socket-security

socket-security Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedqrcode@​1.5.410010010082100
Addedotplib@​12.0.110010010089100

View full report

s39n added 6 commits June 10, 2026 15:52
Repeat visits now hit a CacheStorage entry instead of waiting for the
server to return /api/bootstrap. The SW fires a background revalidation
in parallel, keeping the cache fresh for the NEXT visit.

- src/client/sw.js: new SW; intercepts GET /api/bootstrap (not /status),
  serves from cache immediately then updates in background; skipWaiting +
  clients.claim() for immediate takeover on install.
- src/server/index.js: add GET /sw.js route with Service-Worker-Allowed: /
  and Cache-Control: no-cache so the SW can control the full origin and
  always receives updates.
- src/client/boot.js: register /sw.js at the top of the IIFE, before the
  bootstrap fetch, so the SW is in place for subsequent navigations.
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