mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-08-20 11:57:19 +02:00
Merge pull request #2868 from alexbelgium/claude/claude-desktop-permissions-cm4k7w
fix(claude_desktop): align abc runtime identity so Claude Desktop can start
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
## 1.26 (15-07-2026)
|
||||
|
||||
- Fix startup permission failures that prevented Claude Desktop from starting: storage was chowned to a hardcoded `1000:1000`, but the shared `abc` desktop user was never mapped to that UID. During init `abc` was still the image default (`911`), so TokenSave (`.claude.json.new`), RTK (`RTK.md`), nginx, PulseAudio, the Mesa shader cache, and Claude Desktop itself all hit `Permission denied`; the base image's `init-adduser` then remapped `abc` to root mid-startup (PUID/PGID were read from add-on options where they did not exist, falling back to `0`), which also made Claude Code reject `permission_mode: bypass`.
|
||||
- Add `PUID`/`PGID` add-on options (default `1000:1000`) and remap `abc` to that identity at the very start of folder setup, before any ownership is applied and before any service resolves the user. The base image's `init-adduser` is pinned to the same effective identity so it can no longer remap `abc` mid-startup.
|
||||
- In `permission_mode: bypass`, a configured `PUID: 0` automatically falls back to UID `1000` (Claude Code refuses bypass permissions as root), retaining the configured group.
|
||||
- Fix `bashio::config.array: command not found` in the TokenSave repository setup, tools configuration, and `claude-tools-doctor.sh`: the function only exists in the repo's standalone bashio, not in the real bashio shipped in the image. Use `bashio::config`, which prints list entries one per line.
|
||||
- Return managed Claude configuration files to the effective `abc` identity instead of the raw configured `PUID`/`PGID` (which previously fell back to `0` and left the files root-owned).
|
||||
- Pre-create `/tmp/.X11-unix` with the standard sticky mode so Xorg, which runs as the non-root `abc` user on a tmpfs `/tmp`, no longer fails to create its socket directory (`_XSERVTransmkdir: euid != 0`).
|
||||
|
||||
## 1.25 (15-07-2026)
|
||||
- Minor bugs fixed
|
||||
## 1.24 (15-07-2026)
|
||||
|
||||
@@ -35,9 +35,10 @@ PATH tools.
|
||||
`/usr/bin/claude` directly, the session remains functional and still has the
|
||||
shared permission mode and Headroom MCP tools, but transparent proxy
|
||||
compression cannot be injected.
|
||||
- When `permission_mode: bypass` is selected while `PUID` is `0`, the add-on
|
||||
automatically remaps the shared `abc` desktop account to an unused non-root
|
||||
UID before Selkies and Claude Desktop start. Claude Code refuses bypass mode
|
||||
- The shared `abc` desktop account runs under the configured `PUID`/`PGID`
|
||||
(default `1000:1000`). When `permission_mode: bypass` is selected while
|
||||
`PUID` is `0`, the add-on automatically falls back to UID `1000` before
|
||||
Selkies and Claude Desktop start, because Claude Code refuses bypass mode
|
||||
under an effective root UID.
|
||||
- **gnome-keyring** provides the Secret Service backend Electron needs to
|
||||
persist sign-in and dispatch permission grants across restarts.
|
||||
@@ -88,7 +89,7 @@ Git synchronization hooks. A repository is indexed only when it is listed in
|
||||
|
||||
| Option | Default | Description |
|
||||
| ------ | ------- | ----------- |
|
||||
| `PUID` / `PGID` | `0` / `0` | Numeric user and group applied by LinuxServer initialization. In bypass mode, a root `PUID` is automatically replaced at runtime by an unused non-root UID while the configured group is retained. |
|
||||
| `PUID` / `PGID` | `1000` / `1000` | Numeric user and group of the shared `abc` desktop account that owns the data location and runs Claude Desktop. In bypass mode, a root `PUID` is automatically replaced at runtime by UID `1000` while the configured group is retained. |
|
||||
| `TZ` | | Optional timezone, for example `Europe/Brussels`. |
|
||||
| `KEYBOARD` | | Optional Selkies keyboard layout. |
|
||||
| `PASSWORD` | | Optional password for direct Selkies ports. |
|
||||
@@ -131,11 +132,11 @@ permission_mode: auto
|
||||
`--dangerously-skip-permissions` for wrapper-launched sessions.
|
||||
|
||||
Claude Code does not permit bypass mode when its effective UID is `0`. If the
|
||||
add-on is configured with `PUID: 0`, selecting `bypass` remaps only the shared
|
||||
`abc` runtime account to an available non-root UID (preferring `1000`, then
|
||||
`911`) before storage ownership and Desktop startup. Its configured primary
|
||||
GID is retained, so group-based access to mounted Home Assistant paths remains
|
||||
available. Strict and auto modes keep the configured identity unchanged.
|
||||
add-on is configured with `PUID: 0`, selecting `bypass` runs the shared `abc`
|
||||
runtime account as UID `1000` instead, before storage ownership and Desktop
|
||||
startup. Its configured primary GID is retained, so group-based access to
|
||||
mounted Home Assistant paths remains available. Strict and auto modes keep the
|
||||
configured identity unchanged.
|
||||
|
||||
A root shell invoking `/usr/local/bin/claude` in bypass mode is also dropped to
|
||||
the remapped `abc` account. Directly invoking `/usr/bin/claude` as root still
|
||||
|
||||
@@ -34,6 +34,8 @@ name: Claude Desktop
|
||||
options:
|
||||
env_vars: []
|
||||
DNS_server: 8.8.8.8
|
||||
PGID: 1000
|
||||
PUID: 1000
|
||||
data_location: /data/data
|
||||
additional_apps: ""
|
||||
additional_pip: ""
|
||||
@@ -75,6 +77,8 @@ schema:
|
||||
DRINODE: list(/dev/dri/card0|/dev/dri/card1|/dev/dri/card2|/dev/dri/renderD128|/dev/dri/renderD129|)?
|
||||
KEYBOARD: list(da-dk-qwerty|de-de-qwertz|en-gb-qwerty|en-us-qwerty|es-es-qwerty|fr-ch-qwertz|fr-fr-azerty|it-it-qwerty|ja-jp-qwerty|pt-br-qwerty|sv-se-qwerty|tr-tr-qwerty)?
|
||||
PASSWORD: str?
|
||||
PGID: int
|
||||
PUID: int
|
||||
TZ: match([A-Z][a-z]*./[A-Z][a-z]*.)?
|
||||
additional_apps: str?
|
||||
additional_pip: str?
|
||||
@@ -105,5 +109,5 @@ slug: claude_desktop
|
||||
tmpfs: true
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "1.25"
|
||||
version: "1.26"
|
||||
video: true
|
||||
|
||||
@@ -3,10 +3,35 @@
|
||||
# shellcheck disable=SC2046
|
||||
set -e
|
||||
|
||||
# Use the effective shared desktop user identity. In bypass mode an earlier init script may
|
||||
# remap abc away from UID 0 because Claude Code rejects bypass permissions when run as root.
|
||||
# Align the shared desktop user (abc) with the configured PUID/PGID before any storage is
|
||||
# chowned and before any service or s6-setuidgid call resolves abc. The base image's
|
||||
# init-adduser applies the same remap, but it runs after cont-init, so doing it here first is
|
||||
# what lets the tokensave/rtk/git setup in the 8x scripts run under the final identity.
|
||||
PUID="$(if bashio::config.has_value 'PUID'; then bashio::config 'PUID'; else echo '1000'; fi)"
|
||||
PGID="$(if bashio::config.has_value 'PGID'; then bashio::config 'PGID'; else echo '1000'; fi)"
|
||||
|
||||
# Claude Code refuses bypass-permissions mode under an effective root UID, so bypass mode
|
||||
# always needs a non-root desktop user.
|
||||
if [ "$(bashio::config 'permission_mode')" = "bypass" ] && [ "$PUID" -eq 0 ]; then
|
||||
bashio::log.warning "permission_mode: bypass cannot run Claude Code as root; using UID 1000 instead of the configured PUID 0"
|
||||
PUID=1000
|
||||
PGID=1000
|
||||
fi
|
||||
|
||||
groupmod -o -g "$PGID" abc 2> /dev/null || true
|
||||
usermod -o -u "$PUID" abc 2> /dev/null || true
|
||||
if [ "$(id -u abc)" -ne "$PUID" ] || [ "$(id -g abc)" -ne "$PGID" ]; then
|
||||
PUID="$(id -u abc)"
|
||||
PGID="$(id -g abc)"
|
||||
bashio::log.warning "Unable to remap the abc desktop user; continuing with its current identity ${PUID}:${PGID}"
|
||||
fi
|
||||
|
||||
# The base image's init-adduser reads PUID/PGID from the raw add-on options (default 0) and
|
||||
# runs mid-startup, racing the services. Pin it to the effective identity chosen above so it
|
||||
# can never remap abc away from the ownership applied below.
|
||||
ADDUSER_RUN="/etc/s6-overlay/s6-rc.d/init-adduser/run"
|
||||
if [ -f "$ADDUSER_RUN" ]; then
|
||||
sed -i "s|^PUID=.*|PUID=${PUID}|;s|^PGID=.*|PGID=${PGID}|" "$ADDUSER_RUN"
|
||||
fi
|
||||
|
||||
# Check data location
|
||||
LOCATION="$(bashio::config 'data_location')"
|
||||
@@ -71,6 +96,11 @@ mkdir -p "$LOCATION" /tmp/cache "$XDG_RUNTIME_DIR"
|
||||
chmod 755 /tmp/cache
|
||||
chmod 700 "$XDG_RUNTIME_DIR"
|
||||
|
||||
# /tmp is a tmpfs and Xorg runs as the non-root abc user, which cannot create the X11 socket
|
||||
# directory itself (_XSERVTransmkdir: euid != 0). Pre-create it with the standard sticky mode.
|
||||
mkdir -p /tmp/.X11-unix
|
||||
chmod 1777 /tmp/.X11-unix
|
||||
|
||||
# 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
|
||||
@@ -82,7 +112,7 @@ fi
|
||||
ln -sfn /tmp/cache "$LOCATION/.cache"
|
||||
|
||||
bashio::log.info "Setting ownership to $PUID:$PGID"
|
||||
chown -R abc "$LOCATION" /tmp/cache "$XDG_RUNTIME_DIR" /data
|
||||
chown -R "${PUID}:${PGID}" "$LOCATION" /tmp/cache "$XDG_RUNTIME_DIR" /data
|
||||
chmod -R 700 "$LOCATION"
|
||||
|
||||
# The base init-selkies-config script overrides XDG_RUNTIME_DIR to $HOME/.XDG, which lands
|
||||
|
||||
@@ -8,10 +8,14 @@ if ! bashio::config.true 'install_tokensave' || ! command -v git > /dev/null 2>&
|
||||
fi
|
||||
|
||||
declare -A REPOS_SEEN=()
|
||||
while IFS= read -r configured_path; do
|
||||
# bashio::config prints its result without a trailing newline, so the last record arrives
|
||||
# with read returning non-zero; the extra test keeps that final path in the loop.
|
||||
while IFS= read -r configured_path || [ -n "$configured_path" ]; do
|
||||
configured_path="${configured_path#"${configured_path%%[![:space:]]*}"}"
|
||||
configured_path="${configured_path%"${configured_path##*[![:space:]]}"}"
|
||||
[ -n "$configured_path" ] || continue
|
||||
if [ -z "$configured_path" ] || [ "$configured_path" = "null" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
case "$configured_path" in
|
||||
/*) ;;
|
||||
@@ -33,4 +37,6 @@ while IFS= read -r configured_path; do
|
||||
s6-setuidgid abc env HOME="$HOME" git config --global --add safe.directory "$repo_root"
|
||||
bashio::log.info "Marked TokenSave repository as safe for Git: ${repo_root}"
|
||||
fi
|
||||
done < <(bashio::config.array 'tokensave_project_paths')
|
||||
# bashio::config prints list options one entry per line ("null" when the key is absent);
|
||||
# bashio::config.array only exists in the repo's standalone bashio, not in the real bashio here.
|
||||
done < <(bashio::config 'tokensave_project_paths')
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
PUID="$(if bashio::config.has_value 'PUID'; then bashio::config 'PUID'; else echo '0'; fi)"
|
||||
PGID="$(if bashio::config.has_value 'PGID'; then bashio::config 'PGID'; else echo '0'; fi)"
|
||||
# 20-folders.sh already remapped abc to the effective runtime identity (never root in bypass
|
||||
# mode), so follow abc instead of re-reading the raw PUID/PGID options here.
|
||||
RUNTIME_UID="$(id -u abc)"
|
||||
RUNTIME_GID="$(id -g abc)"
|
||||
mkdir -p "$HOME/.claude"
|
||||
|
||||
run_as_runtime_user() {
|
||||
@@ -165,11 +167,15 @@ PY
|
||||
# requires one-time per-project opt-in; an empty list therefore has no startup or storage cost.
|
||||
if $TOKENSAVE_ENABLED; then
|
||||
declare -A TOKENSAVE_REPOS_SEEN=()
|
||||
while IFS= read -r configured_path; do
|
||||
# bashio::config prints its result without a trailing newline, so the last record arrives
|
||||
# with read returning non-zero; the extra test keeps that final path in the loop.
|
||||
while IFS= read -r configured_path || [ -n "$configured_path" ]; do
|
||||
# Trim surrounding whitespace while preserving spaces inside paths.
|
||||
configured_path="${configured_path#"${configured_path%%[![:space:]]*}"}"
|
||||
configured_path="${configured_path%"${configured_path##*[![:space:]]}"}"
|
||||
[ -n "$configured_path" ] || continue
|
||||
if [ -z "$configured_path" ] || [ "$configured_path" = "null" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
case "$configured_path" in
|
||||
/*) ;;
|
||||
@@ -251,7 +257,9 @@ if $TOKENSAVE_ENABLED; then
|
||||
exit 1
|
||||
' _ "$repo_root" \
|
||||
|| bashio::log.warning "TokenSave preparation failed for ${repo_root}"
|
||||
done < <(bashio::config.array 'tokensave_project_paths')
|
||||
# bashio::config prints list options one entry per line ("null" when the key is absent);
|
||||
# bashio::config.array only exists in the repo's standalone bashio, not in the real bashio here.
|
||||
done < <(bashio::config 'tokensave_project_paths')
|
||||
fi
|
||||
|
||||
# Guide Claude to actually use the Headroom compression tools so the MCP integration produces
|
||||
@@ -461,9 +469,9 @@ else
|
||||
fi
|
||||
|
||||
# Startup configuration runs as root, while Claude Desktop runs as abc. Return managed
|
||||
# persistent files to the configured runtime UID/GID after all writes complete.
|
||||
# persistent files to the effective runtime UID/GID after all writes complete.
|
||||
for managed_path in "$HOME/.claude" "$HOME/.claude.json" "$HOME/.config/Claude"; do
|
||||
if [ -e "$managed_path" ]; then
|
||||
chown -R -- "${PUID}:${PGID}" "$managed_path" || bashio::log.warning "Unable to set ownership on $managed_path"
|
||||
chown -R -- "${RUNTIME_UID}:${RUNTIME_GID}" "$managed_path" || bashio::log.warning "Unable to set ownership on $managed_path"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
PUID="$(if bashio::config.has_value 'PUID'; then bashio::config 'PUID'; else echo '0'; fi)"
|
||||
PGID="$(if bashio::config.has_value 'PGID'; then bashio::config 'PGID'; else echo '0'; fi)"
|
||||
# 20-folders.sh already remapped abc to the effective runtime identity (never root in bypass
|
||||
# mode), so follow abc instead of re-reading the raw PUID/PGID options here.
|
||||
RUNTIME_UID="$(id -u abc)"
|
||||
RUNTIME_GID="$(id -g abc)"
|
||||
PERMISSION_MODE="$(bashio::config 'permission_mode')"
|
||||
SETTINGS_PATH="$HOME/.claude/settings.json"
|
||||
STATE_PATH="$HOME/.claude/.addon-permission-mode.json"
|
||||
@@ -86,7 +88,7 @@ case "$PERMISSION_MODE" in
|
||||
;;
|
||||
esac
|
||||
|
||||
chown -- "${PUID}:${PGID}" "$SETTINGS_PATH" 2> /dev/null || true
|
||||
chown -- "${RUNTIME_UID}:${RUNTIME_GID}" "$SETTINGS_PATH" 2> /dev/null || true
|
||||
if [ -e "$STATE_PATH" ]; then
|
||||
chown -- "${PUID}:${PGID}" "$STATE_PATH" 2> /dev/null || true
|
||||
chown -- "${RUNTIME_UID}:${RUNTIME_GID}" "$STATE_PATH" 2> /dev/null || true
|
||||
fi
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
# Earlier configuration scripts intentionally run as root and may use the configured PUID/PGID
|
||||
# values when returning files to the runtime user. In bypass mode PUID can still be configured as
|
||||
# 0 even though 19-claude_bypass_runtime.sh remapped abc to a non-root UID. Reconcile ownership
|
||||
# with the effective desktop identity after all Claude configuration writes are complete.
|
||||
# Earlier configuration scripts intentionally run as root. 20-folders.sh remapped abc to the
|
||||
# effective runtime identity (never root in bypass mode, where Claude Code refuses to run as
|
||||
# root). Reconcile ownership with that identity after all Claude configuration writes are
|
||||
# complete, as a safety net in case any intermediate step re-owned a managed path.
|
||||
RUNTIME_UID="$(id -u abc)"
|
||||
RUNTIME_GID="$(id -g abc)"
|
||||
|
||||
|
||||
@@ -149,8 +149,10 @@ section "TokenSave"
|
||||
if bashio::config.true 'install_tokensave'; then
|
||||
tokensave doctor --agent claude || true
|
||||
tokensave gain --all --range 30d || true
|
||||
while IFS= read -r configured_path; do
|
||||
[ -n "$configured_path" ] || continue
|
||||
while IFS= read -r configured_path || [ -n "$configured_path" ]; do
|
||||
if [ -z "$configured_path" ] || [ "$configured_path" = "null" ]; then
|
||||
continue
|
||||
fi
|
||||
repo_root="$(s6-setuidgid abc env HOME="$HOME" git -c safe.directory='*' -C "$configured_path" rev-parse --show-toplevel 2> /dev/null || true)"
|
||||
if [ -z "$repo_root" ]; then
|
||||
echo "${configured_path}: not a Git repository"
|
||||
@@ -159,7 +161,7 @@ if bashio::config.true 'install_tokensave'; then
|
||||
else
|
||||
echo "${repo_root}: NOT INITIALIZED"
|
||||
fi
|
||||
done < <(bashio::config.array 'tokensave_project_paths')
|
||||
done < <(bashio::config 'tokensave_project_paths')
|
||||
else
|
||||
echo "disabled"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user