From 02c516af746a27d8c5937ab48c9a7844db29f5d1 Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Fri, 25 Sep 2026 11:07:25 +0000 Subject: [PATCH] desktop: embed GitDXD too (Forgejo needs [cors] X_FRAME_OPTIONS = unset) --- README.md | 8 ++++---- opencode.md | 21 ++++++++++++--------- src/app/components/WindowsDesktop.tsx | 16 +++++++++------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index daa9dd7..62446d6 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,10 @@ heatmap**, a gym SBD panel and links to friends' sites. - **Live service status** — each self-hosted service is health-checked through the site's own API; Minecraft is checked through mcstatus.io. - **Embedded live services** — opening a frameable self-hosted service (Llama.cpp/Open WebUI, - Jellyfin, Navidrome) from the desktop shows the real site inside the window (an iframe). A - service must not send `X-Frame-Options`: Navidrome sends `DENY` unless it runs with - `ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL`. Forgejo/GitDXD (`SAMEORIGIN`) and Discord (`DENY`) - keep a link card / their own app card instead. + Jellyfin, Navidrome, GitDXD) from the desktop shows the real site inside the window (an + iframe). A service must not send a restrictive `X-Frame-Options`: Navidrome needs + `ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL` and Forgejo needs `[cors] X_FRAME_OPTIONS = unset`. + Discord sends `DENY` and keeps a link card instead. - **Minecraft-style SMP window** — the SMP shortcut opens a join menu modelled on Minecraft's multiplayer screen, with live status, player count, MOTD and version pulled from mcstatus.io through the site's own ping API. diff --git a/opencode.md b/opencode.md index 8ea1895..8bc95fd 100644 --- a/opencode.md +++ b/opencode.md @@ -271,15 +271,18 @@ list reordering, not free placement. Don't confuse the two. `createWebsiteApp({ embed: true })` (the shortcut's `embed` flag), but the browser refuses to frame a site that sends `X-Frame-Options` / `frame-ancestors`, leaving a blank window. `embed` is set on Llama.cpp (Open - WebUI), Jellyfin and Navidrome. Llama.cpp/Jellyfin allow framing; Navidrome - only does when its host runs with `ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL` - (Navidrome maps `HTTPHeaders.FrameOptions` to that env var and it overrides - its built-in `FrameDeny`; any unrecognised value is ignored by the browser). - For a scoped, clickjacking-safe alternative, remove the header at the edge - (Cloudflare "Modify Response Header" / nginx) and add - `Content-Security-Policy: frame-ancestors`. Forgejo/GitDXD (`SAMEORIGIN`) - keeps its dedicated app card. Always verify headers before enabling `embed` - on a new service. + WebUI), Jellyfin, Navidrome and GitDXD. Llama.cpp/Jellyfin allow framing out + of the box; the other two need their host configured so the header is an + unrecognised value (browsers ignore unknown X-Frame-Options values): + Navidrome via `ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL` (it maps + `HTTPHeaders.FrameOptions` to that env var and it overrides the built-in + `FrameDeny`), and Forgejo via `[cors] X_FRAME_OPTIONS = unset` (Forgejo reads + this from the `[cors]` section, **not** `[security]`) followed by a Forgejo + restart. For a scoped, clickjacking-safe alternative, remove the header at the + edge (Cloudflare "Modify Response Header" / nginx) and add + `Content-Security-Policy: frame-ancestors`. Discord keeps a link card because + it sends `DENY` and can't be configured. Always verify headers before enabling + `embed` on a new service. - Reduced motion is handled with a near-zero `animation-duration` (not `animation: none`) so `animationend` still fires and windows unmount. diff --git a/src/app/components/WindowsDesktop.tsx b/src/app/components/WindowsDesktop.tsx index 4f6c7ec..bd01826 100644 --- a/src/app/components/WindowsDesktop.tsx +++ b/src/app/components/WindowsDesktop.tsx @@ -41,19 +41,21 @@ interface IconEntry { // `embed: true` renders the real site in an iframe, which the browser refuses // (blank window) when the server sends X-Frame-Options / frame-ancestors. -// Llama.cpp and Jellyfin allow framing out of the box. Navidrome sends -// `X-Frame-Options: DENY` unless it is started with -// `ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL`, which replaces that header with an -// unrecognised value the browser ignores (that env var is set on the Navidrome -// host — without it this window is blank again). Forgejo (GitDXD) sends -// `SAMEORIGIN`, so it keeps its dedicated app card. +// Llama.cpp and Jellyfin allow framing out of the box; Navidrome and Forgejo +// need to be told to stop sending a restrictive value: +// - Navidrome: `ND_HTTPHEADERS_FRAMEOPTIONS=ALLOWALL` +// - Forgejo: `[cors] X_FRAME_OPTIONS = unset` (Forgejo reads this from +// [cors], NOT [security]) then restart Forgejo. +// Any X-Frame-Options value a browser doesn't recognise is ignored, so the +// frame is allowed. These live on the service hosts — without them the windows +// go blank again. const SERVICE_SHORTCUTS: Shortcut[] = [ { id: "svc-proxmox", icon: "🖥️", label: "Proxmox", appId: "alacritty", color: "#1257a0", desc: "Server Management" }, { id: "svc-ai", icon: "🦾", label: "Llama.cpp", url: "https://ai.dvorakmortonxd.dev/", color: "#157347", desc: "Local AI Server", embed: true }, { id: "svc-jellyfin", icon: "🎬", label: "Jellyfin", url: "https://jelly.dvorakmortonxd.dev/", color: "#6a4fb3", desc: "Local Media Server", embed: true }, { id: "svc-smp", icon: "🎮", label: "SMP", appId: "smp", color: "#c0392b", desc: "My Survival Minecraft Server" }, { id: "svc-navidrome", icon: "🎸", label: "Navidrome", url: "https://navidrome.dvorakmortonxd.dev/", color: "#b3005f", desc: "Music Streaming", embed: true }, - { id: "svc-gitdxd", icon: "🐙", label: "GitDXD", appId: "gitdxd", color: "#6a4fb3", desc: "Best Git Hosting OAT" }, + { id: "svc-gitdxd", icon: "🐙", label: "GitDXD", url: "https://git.dvorakmortonxd.dev", color: "#6a4fb3", desc: "Best Git Hosting OAT", embed: true }, ]; const FRIEND_SHORTCUTS: Shortcut[] = friends.map((f, i) => ({