diff --git a/claude_desktop/CHANGELOG.md b/claude_desktop/CHANGELOG.md index 3f6a13d3e9..9dfed9c476 100644 --- a/claude_desktop/CHANGELOG.md +++ b/claude_desktop/CHANGELOG.md @@ -1,3 +1,15 @@ +## 1.4 (07-07-2026) + +- Persist Claude Desktop sign-in: bundle gnome-keyring/libsecret/dbus-x11 and start an unlocked Secret Service in the desktop session, and launch with --password-store=gnome-libsecret. Fixes "Your sign-in won't be saved on this device. Install and unlock a system keyring". The keyring DB lives on persistent storage (/config/data), so the session survives restarts. + +## 1.3 (07-07-2026) + +- Fix startup crash loop ("All subprocesses terminated. Exiting."): + - Make the Selkies desktop init oneshots (init-video, init-selkies-config) tolerant so a partially-permitted device/permission op in the HA sandbox no longer fails add-on bringup. + - Pre-create /tmp/selkies_js.log so the base image's "chmod 777 /tmp/selkies*" calls never fail on an empty glob. + - Reconcile XDG_RUNTIME_DIR to the tmpfs runtime dir instead of persistent storage. +- Run Claude Desktop with --disable-dev-shm-usage and drop the misplaced shm_size env var (Home Assistant ignores shm_size), fixing Electron renderer crashes on the default 64 MB /dev/shm. + ## 1.2 (07-07-2026) - Add baked-in git/GitHub CLI support with optional startup credential configuration. diff --git a/claude_desktop/Dockerfile b/claude_desktop/Dockerfile index 6634d2afb6..ce94689de0 100644 --- a/claude_desktop/Dockerfile +++ b/claude_desktop/Dockerfile @@ -56,7 +56,13 @@ RUN if [ ! -f /bin/sh ] && [ -f /usr/bin/sh ]; then ln -s /usr/bin/sh /bin/sh; f RUN curl -fsSLo /usr/share/keyrings/claude-desktop-archive-keyring.asc https://downloads.claude.ai/claude-desktop/key.asc && \ echo "deb [arch=amd64,arm64 signed-by=/usr/share/keyrings/claude-desktop-archive-keyring.asc] https://downloads.claude.ai/claude-desktop/apt/stable stable main" > /etc/apt/sources.list.d/claude-desktop.list && \ apt-get update && \ - apt-get install -y --no-install-recommends claude-desktop python3-pip git gh && \ + apt-get install -y --no-install-recommends \ + claude-desktop \ + python3-pip \ + gnome-keyring \ + libsecret-1-0 \ + dbus-x11 \ + git gh && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* diff --git a/claude_desktop/SIGN_IN.md b/claude_desktop/SIGN_IN.md new file mode 100644 index 0000000000..87b89e0f19 --- /dev/null +++ b/claude_desktop/SIGN_IN.md @@ -0,0 +1,118 @@ +# Claude Desktop sign-in — fix plan (not implemented) + +Two related sign-in problems when Claude Desktop runs inside the LinuxServer Selkies +streamed desktop. + +**Status:** +- **Problem B (keyring persistence) — IMPLEMENTED** in v1.4 (Dockerfile + `rootfs/defaults/autostart`). +- **Problem A (in-desktop browser for OAuth) — PLAN ONLY, intentionally not implemented.** + The image ships no browser; complete the login with the user-side workaround below. + +--- + +## Problem A — Cannot complete login + +Symptoms: +- **"Continue with Google"** does nothing. +- **"Continue with email"** sends a link that "must be opened from the same machine". + +### Root cause +Claude Desktop signs in through an **external web page** (Google OAuth or the email magic +link) and then hands the token back to the app via the **`claude://` URL scheme** +(auto-registered on first launch). Both paths need: +1. a **web browser inside the streamed desktop** to render the auth page, and +2. a **default-browser association** so the app's `xdg-open ` call goes somewhere. + +The Selkies base image ships **no browser** and no default handler, so `xdg-open` has +nothing to dispatch to → Google "does nothing", and the email link's final `claude://…` +redirect only works on the machine running the app (the container), which currently can't +render/handle it. + +### Fix (image level) +In `claude_desktop/Dockerfile`, install a browser and register defaults so the whole flow +completes inside the session: +- `apt-get install -y --no-install-recommends chromium` (Debian bookworm package name; + `firefox-esr` is an alternative). +- Set the default browser and confirm the scheme handlers, e.g. as an s6 oneshot or in the + desktop `autostart` (runs as the session user with `$DISPLAY`/dbus available): + ```sh + xdg-settings set default-web-browser chromium.desktop + xdg-mime default claude-desktop.desktop x-scheme-handler/claude + ``` +- Verifying expectations (for reference, not a test step): + `x-scheme-handler/https` → `chromium.desktop`, `x-scheme-handler/claude` → + `claude-desktop.desktop`. + +Then login is done entirely in the streamed desktop: **Continue with Google** opens +Chromium in-session and the `claude://` redirect lands back in the app; or paste the email +magic link into the in-session Chromium (not a phone). + +### User-side workaround (no rebuild) +- Add-on Configuration → `additional_apps: chromium`, restart (installed by + `rootfs/etc/cont-init.d/80-configuration.sh`). +- Run the two `xdg-settings`/`xdg-mime` commands once in an in-session terminal, or add them + to the custom script `/addon_configs/db21ed7f_claude-desktop/claude-desktop.sh`. + +--- + +## Problem B — "Your sign-in won't be saved on this device" + +Symptom: +> Install and unlock a system keyring (such as GNOME Keyring), then restart the app. + +### Root cause +Claude Desktop (Electron) persists its auth token with `safeStorage`, which on Linux +encrypts via the **Secret Service API** (`org.freedesktop.secrets`) provided by +gnome-keyring/kwallet. The Selkies base runs only a **system** D-Bus +(`svc-dbus` → `dbus-daemon --system`) and has **no keyring daemon**, so `safeStorage` is +unavailable and the token cannot be stored → the app warns and forgets the session on +restart. + +Note: the desktop itself already runs under a **session** bus — +`root/defaults/startwm.sh` launches `dbus-launch --exit-with-session openbox-session`, so +apps started from the openbox `autostart` inherit `DBUS_SESSION_BUS_ADDRESS`. The keyring +must be started **inside that session** so it registers the secrets service on the same bus +Claude Desktop uses. No extra `dbus-launch` is needed. + +### Fix (image level) +1. `claude_desktop/Dockerfile`: install + `gnome-keyring libsecret-1-0 dbus-x11` (dbus-x11 provides `dbus-launch`, already used by + startwm; libsecret-1-0 is the backend Electron dlopens). +2. `claude_desktop/rootfs/defaults/autostart`: start & unlock an empty-password keyring + before launching the app, and force the libsecret backend. Proposed content: + ```sh + # Start + unlock a gnome-keyring secrets service so Claude Desktop can persist sign-in. + # Runs inside the openbox session's dbus (startwm.sh: dbus-launch --exit-with-session). + eval "$(printf '' | gnome-keyring-daemon --login)" 2>/dev/null || true + eval "$(gnome-keyring-daemon --start --components=secrets)" 2>/dev/null || true + dbus-update-activation-environment --all >/dev/null 2>&1 || true + + claude-desktop --no-sandbox --disable-dev-shm-usage --password-store=gnome-libsecret + ``` + - `--login` reads the password from stdin (empty here), creating and unlocking the login + keyring on first run and starting the daemon in login mode; `--start --components=secrets` + then exposes the Secret Service and exports `GNOME_KEYRING_CONTROL`/`SSH_AUTH_SOCK`. + - `--password-store=gnome-libsecret` forces Electron to use the libsecret backend instead + of falling back to plaintext. +3. Persistence: the keyring DB lives in `$HOME/.local/share/keyrings/` and `HOME=/config/data` + (persistent add-on storage), so the empty-password login keyring survives restarts and is + re-unlocked automatically each boot by the same `autostart` line — the sign-in then sticks. + +### User-side workaround (no rebuild) +- Add-on Configuration → `additional_apps: gnome-keyring, libsecret-1-0, dbus-x11`, restart. +- Add the keyring-start lines above to the custom script + `/addon_configs/db21ed7f_claude-desktop/claude-desktop.sh`, and relaunch Claude Desktop + with `--password-store=gnome-libsecret` (e.g. edit the in-session openbox autostart). + +--- + +## Files this plan would touch (when implemented) +- `claude_desktop/Dockerfile` — install `chromium`, `gnome-keyring`, `libsecret-1-0`, + `dbus-x11`; set default browser + scheme handlers. +- `claude_desktop/rootfs/defaults/autostart` — keyring bootstrap + `--password-store` flag. +- `claude_desktop/CHANGELOG.md` / `config.yaml` version bump. + +## Open questions before implementing +- Bundle **chromium** vs **firefox-esr** as the in-image browser? +- Empty-password keyring (fully unattended) vs. reuse the add-on `PASSWORD` option to lock + the keyring with the same password? diff --git a/claude_desktop/config.yaml b/claude_desktop/config.yaml index a06f3b6663..6ec8cf444f 100644 --- a/claude_desktop/config.yaml +++ b/claude_desktop/config.yaml @@ -18,7 +18,6 @@ environment: SELKIES_FRAMERATE: "30" START_DOCKER: "false" TITLE: Claude Desktop - shm_size: 1gb image: ghcr.io/alexbelgium/claude_desktop-{arch} ingress: true init: false @@ -79,5 +78,5 @@ slug: claude_desktop tmpfs: true udev: true url: https://github.com/alexbelgium/hassio-addons -version: "1.2" +version: "1.4" video: true diff --git a/claude_desktop/rootfs/defaults/autostart b/claude_desktop/rootfs/defaults/autostart index a76d2f564e..bc6b9e0dcf 100644 --- a/claude_desktop/rootfs/defaults/autostart +++ b/claude_desktop/rootfs/defaults/autostart @@ -1 +1,16 @@ -claude-desktop --no-sandbox +#!/usr/bin/env sh +# Persist Claude Desktop sign-in across restarts. +# Claude Desktop (Electron) stores its auth token via safeStorage, which on Linux needs the +# Secret Service API (org.freedesktop.secrets). Provide it with gnome-keyring on the openbox +# session bus (startwm.sh runs `dbus-launch --exit-with-session`), unlocked with an empty +# password so it comes back automatically after a restart. The keyring DB lives under +# $HOME/.local/share/keyrings, and HOME=/config/data is persistent add-on storage, so the +# saved sign-in survives add-on restarts. +eval "$(gnome-keyring-daemon --start --components=secrets 2>/dev/null)" || true +printf '' | gnome-keyring-daemon --unlock 2>/dev/null || true +export GNOME_KEYRING_CONTROL SSH_AUTH_SOCK +dbus-update-activation-environment --all >/dev/null 2>&1 || true + +# --password-store=gnome-libsecret forces Electron onto the libsecret backend instead of +# falling back to plaintext (and warning that the sign-in will not be saved). +claude-desktop --no-sandbox --disable-dev-shm-usage --password-store=gnome-libsecret diff --git a/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh b/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh index 4eb5030c3e..917a4952bd 100755 --- a/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh +++ b/claude_desktop/rootfs/etc/cont-init.d/20-folders.sh @@ -70,6 +70,11 @@ mkdir -p "$LOCATION" /tmp/cache "$XDG_RUNTIME_DIR" chmod 755 /tmp/cache chmod 700 "$XDG_RUNTIME_DIR" +# Pre-create the Selkies joystick log so the base image's "chmod 777 /tmp/selkies*" +# calls (in init-selkies-config and svc-de) never fail on an empty glob. +touch /tmp/selkies_js.log +chmod 777 /tmp/selkies_js.log + if [ -e "$LOCATION/.cache" ] && [ ! -L "$LOCATION/.cache" ]; then rm -rf "$LOCATION/.cache" fi @@ -78,3 +83,24 @@ ln -sfn /tmp/cache "$LOCATION/.cache" bashio::log.info "Setting ownership to $PUID:$PGID" chown -R "$PUID":"$PGID" "$LOCATION" /tmp/cache "$XDG_RUNTIME_DIR" chmod -R 700 "$LOCATION" + +# The base init-selkies-config script overrides XDG_RUNTIME_DIR to $HOME/.XDG, which lands +# on persistent storage and conflicts with the tmpfs runtime dir set above. Re-assert the +# tmpfs value at the end of that oneshot so the app and desktop agree on one valid dir. +SELKIES_CONFIG_RUN="/etc/s6-overlay/s6-rc.d/init-selkies-config/run" +if [ -f "$SELKIES_CONFIG_RUN" ] && ! grep -q 'XDG_RUNTIME_DIR override reconciled' "$SELKIES_CONFIG_RUN"; then + printf '\n# XDG_RUNTIME_DIR override reconciled\nprintf "%%s" "%s" > /run/s6/container_environment/XDG_RUNTIME_DIR\n' "$XDG_RUNTIME_DIR" >> "$SELKIES_CONFIG_RUN" +fi + +# The Selkies desktop init oneshots do best-effort device/permission setup (mknod +# /dev/input/*, chmod /tmp/selkies*, /dev/dri perms) that is only partially permitted in the +# HA add-on sandbox. A non-zero exit from a oneshot fails add-on bringup and crash-loops the +# container, so make these two tolerant and always report success. Longruns (svc-*) are left +# untouched so s6 keeps supervising them with their real exit codes. +for oneshot in init-video init-selkies-config; do + run="/etc/s6-overlay/s6-rc.d/$oneshot/run" + if [ -f "$run" ] && ! grep -q '^set +e$' "$run"; then + sed -i "1a set +e" "$run" + printf '\nexit 0\n' >> "$run" + fi +done