Compare commits

...
Author SHA1 Message Date
alexbelgiumandClaude Sonnet 5 8d11bfe8e1 feat(claude_desktop): expose SUDO_PASSWORD option (LinuxServer.io convention)
Add SUDO_PASSWORD as a schema-only option, matching the existing
PASSWORD/TZ pattern. It passes through automatically via the add-on's
existing options-to-env-var mechanism (00-global_var.sh), which the
LinuxServer.io base image already reads to gate sudo access for the abc
user. Disabled by default when left unset; no rootfs changes needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 14:09:36 +02:00
Alexandre 587121cb1b Update config.yaml 2026-07-15 13:29:35 +02:00
Alexandre 29f2cfd198 Update 81-claude_update.sh 2026-07-15 13:29:07 +02:00
Alexandre 3329c4b40d nobuild 2026-07-15 13:17:03 +02:00
github-actions 9471dffcca GitHub bot: sanitize (spaces + LF endings) & chmod [nobuild] 2026-07-15 09:18:15 +00:00
Alexandre 215327e439 Merge pull request #2862 from alexbelgium/agent/claude-bypass-nonroot
Fix Claude bypass permissions under root PUID
2026-07-15 11:17:27 +02:00
Alexandre 8ecbfccdcd Document Claude bypass root fix 2026-07-15 11:05:49 +02:00
Alexandre 5a2efc4f9e Document non-root Claude bypass runtime 2026-07-15 11:04:27 +02:00
Alexandre ef0aecbde6 Bump Claude Desktop add-on version 2026-07-15 11:02:55 +02:00
Alexandre 7a6ad72617 Diagnose Claude bypass runtime identity 2026-07-15 11:02:23 +02:00
Alexandre b5985f230e Restore effective Claude runtime ownership 2026-07-15 11:01:22 +02:00
Alexandre d48d1f4d8d Drop root for Claude bypass launches 2026-07-15 11:01:06 +02:00
Alexandre 6bf379bffc Use effective Claude runtime ownership 2026-07-15 10:59:49 +02:00
Alexandre 5d577ad954 Run Claude bypass mode as non-root 2026-07-15 10:59:09 +02:00
9 changed files with 146 additions and 25 deletions

No files matched your search

+12
View File
@@ -1,3 +1,15 @@
## 1.23 (15-07-2026)
- Expose `SUDO_PASSWORD`, per LinuxServer.io's base-image convention: setting it grants the `abc` user sudo access gated by that password. Sudo access stays disabled by default when left unset. Passed straight through by the existing option-to-env-var mechanism; no rootfs changes needed.
## 1.21 (15-07-2026)
- Fix Claude Code bypass permissions being rejected when the add-on uses its default root `PUID`.
- In `permission_mode: bypass`, remap the shared `abc` Desktop runtime to an unused non-root UID before storage ownership and Selkies startup, while retaining its configured primary group for mounted-path access.
- Make folder setup and final Claude configuration ownership follow the effective `abc` identity instead of the configured root UID.
- Drop root console invocations of the add-on's `/usr/local/bin/claude` wrapper to the non-root `abc` runtime before passing `--dangerously-skip-permissions`.
- Extend `claude-tools-doctor.sh` with configured/effective UID and GID checks for bypass mode.
## 1.20 (15-07-2026)
- Complete the TokenSave Claude Code integration at startup: install its MCP server, permissions, PreToolUse/UserPromptSubmit/Stop hooks, global guidance, and Git synchronization hooks instead of registering only `tokensave serve`.
+28 -7
View File
@@ -35,6 +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
under an effective root UID.
- **gnome-keyring** provides the Secret Service backend Electron needs to
persist sign-in and dispatch permission grants across restarts.
@@ -63,6 +67,8 @@ Git synchronization hooks. A repository is indexed only when it is listed in
- Persistent sign-in through a bundled, auto-unlocked gnome-keyring.
- Configurable Claude Code permissions: strict prompts, automatic safe-action
approval, or explicit full bypass for trusted installations.
- Automatic non-root runtime enforcement for bypass mode, including root-console
wrapper launches.
- Optional runtime Claude Desktop updates from Anthropic's apt repository.
- Optional extra apt and pip package installation (pip installs use `uv`).
- Baked-in `git`, GitHub CLI (`gh`), `ripgrep`, `jq`, `shellcheck`, `yamllint`,
@@ -74,17 +80,19 @@ Git synchronization hooks. A repository is indexed only when it is listed in
Assistant.
- Independent hourly savings reports for Headroom, RTK, and TokenSave.
- `claude-tools-doctor.sh` diagnostics for binaries, routing, hooks, MCP
registrations, project indexes, proxy health, permissions, and gains.
registrations, project indexes, proxy health, permissions, runtime identity,
and gains.
- Low-power defaults for GPU mapping, Selkies frame rate, and volatile caches.
## Options
| Option | Default | Description |
| ------ | ------- | ----------- |
| `PUID` / `PGID` | `0` / `0` | Numeric user and group applied by the LinuxServer initialization. |
| `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. |
| `TZ` | | Optional timezone, for example `Europe/Brussels`. |
| `KEYBOARD` | | Optional Selkies keyboard layout. |
| `PASSWORD` | | Optional password for direct Selkies ports. |
| `SUDO_PASSWORD` | | LinuxServer.io convention: grants the `abc` user sudo access gated by this password. Sudo access is disabled by default when left unset. |
| `DRINODE` | | Optional GPU device override for Selkies. |
| `DNS_server` | `8.8.8.8` | DNS server used by the standard DNS module. |
| `auto_update` | `true` | Upgrade `claude-desktop` from Anthropic's apt repository at startup. |
@@ -122,9 +130,21 @@ permission_mode: auto
`bypassPermissions` in the shared settings and
`--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.
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
bypasses the add-on wrapper and will be rejected by Claude Code.
`bypass` gives Claude broad authority over all mounted writable data and every
command or credential available inside the add-on. Enable it only in a trusted
installation with trusted repositories and mounts.
installation with trusted repositories and mounts. Mounted paths must remain
accessible to the effective non-root UID or its retained group.
### TokenSave project example
@@ -171,10 +191,11 @@ Run the following inside the add-on through a custom script or container console
claude-tools-doctor.sh
```
The report checks the tool binaries, configuration switches, redacted MCP
registrations, Claude hooks, permission mode, Headroom health, TokenSave indexes,
routing, and recorded savings. It never prints MCP environment values because
the Home Assistant MCP entry can contain a long-lived token.
The report checks the tool binaries, configuration switches, configured and
effective runtime identities, redacted MCP registrations, Claude hooks,
permission mode, Headroom health, TokenSave indexes, routing, and recorded
savings. It never prints MCP environment values because the Home Assistant MCP
entry can contain a long-lived token.
The hourly report can also be invoked manually:
+9 -8
View File
@@ -13,8 +13,8 @@ environment:
AUTO_GPU: "1"
FM_HOME: /data/data
HOME: /data/data
PGID: "0"
PUID: "0"
PGID: "1000"
PUID: "1000"
SELKIES_FRAMERATE: "30"
START_DOCKER: "false"
TITLE: Claude Desktop
@@ -35,8 +35,6 @@ options:
env_vars: []
DNS_server: 8.8.8.8
data_location: /data/data
PGID: 0
PUID: 0
additional_apps: ""
additional_pip: ""
auto_update: true
@@ -76,12 +74,15 @@ 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
SUDO_PASSWORD: password?
TZ: match([A-Z][a-z]*./[A-Z][a-z]*.)?
additional_apps: str?
additional_pip: str?
auto_update: bool?
cifsdomain: str?
cifspassword: str?
cifsusername: str?
localdisks: str?
networkdisks: str?
github_email: str?
enable_ha_mcp: bool?
ha_mcp_url: str?
@@ -103,5 +104,5 @@ slug: claude_desktop
tmpfs: true
udev: true
url: https://github.com/alexbelgium/hassio-addons
version: "1.20"
version: "1.23"
video: true
@@ -0,0 +1,48 @@
#!/usr/bin/with-contenv bashio
# shellcheck shell=bash
set -e
set -o pipefail
# Claude Code deliberately refuses bypass-permissions mode when its effective UID is 0.
# The add-on historically defaults PUID to 0, so switch the shared `abc` desktop user to
# an unused non-root UID before storage ownership and Selkies runtime directories are set up.
# Keep abc's configured primary group (commonly group 0) so existing group-based access to
# Home Assistant mounts is preserved. Strict and auto permission modes are unchanged.
if [ "$(bashio::config 'permission_mode')" != "bypass" ]; then
exit 0
fi
CURRENT_UID="$(id -u abc)"
if [ "$CURRENT_UID" -ne 0 ]; then
bashio::log.info "Claude bypass runtime already uses non-root UID ${CURRENT_UID}"
exit 0
fi
find_available_uid() {
local candidate owner
for candidate in 1000 911 $(seq 1001 1099); do
owner="$(getent passwd "$candidate" | cut -d: -f1 || true)"
if [ -z "$owner" ] || [ "$owner" = "abc" ]; then
printf '%s' "$candidate"
return 0
fi
done
return 1
}
TARGET_UID="$(find_available_uid || true)"
if [ -z "$TARGET_UID" ]; then
bashio::exit.nok "Claude bypass mode requires a non-root runtime user, but no free fallback UID was found"
fi
usermod --uid "$TARGET_UID" abc
if [ "$(id -u abc)" -eq 0 ]; then
bashio::exit.nok "Unable to switch the Claude Desktop runtime away from root for bypass mode"
fi
mkdir -p /run/s6/container_environment
printf '%s' "$TARGET_UID" > /run/s6/container_environment/CLAUDE_RUNTIME_UID
printf '%s' "$(id -g abc)" > /run/s6/container_environment/CLAUDE_RUNTIME_GID
bashio::log.warning "Claude bypass mode cannot run as root; remapped abc from UID 0 to UID ${TARGET_UID} (GID $(id -g abc))"
@@ -3,9 +3,10 @@
# shellcheck disable=SC2046
set -e
# Define user
PUID=$(bashio::config "PUID")
PGID=$(bashio::config "PGID")
# 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.
PUID="$(id -u abc)"
PGID="$(id -g abc)"
# Check data location
LOCATION="$(bashio::config 'data_location')"
@@ -2,11 +2,9 @@
# shellcheck shell=bash
set -e
if bashio::config.true 'auto_update'; then
bashio::log.info "Checking for Claude Desktop updates..."
if apt-get update -o Acquire::http::Timeout=10 -o Acquire::https::Timeout=10 &> /dev/null && apt-get install -y --only-upgrade claude-desktop &> /dev/null; then
bashio::log.info "Claude Desktop version: $(dpkg-query -W -f='${Version}' claude-desktop)"
else
bashio::log.warning "Update check failed (offline?), keeping current version"
fi
bashio::log.info "Checking for Claude Desktop updates..."
if apt-get update -o Acquire::http::Timeout=10 -o Acquire::https::Timeout=10 &> /dev/null && apt-get install -y --only-upgrade claude-desktop &> /dev/null; then
bashio::log.info "Claude Desktop version: $(dpkg-query -W -f='${Version}' claude-desktop)"
else
bashio::log.warning "Update check failed (offline?), keeping current version"
fi
@@ -0,0 +1,17 @@
#!/usr/bin/with-contenv bashio
# 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.
RUNTIME_UID="$(id -u abc)"
RUNTIME_GID="$(id -g abc)"
for managed_path in "$HOME/.claude" "$HOME/.claude.json" "$HOME/.config/Claude"; do
if [ -e "$managed_path" ]; then
chown -R -- "${RUNTIME_UID}:${RUNTIME_GID}" "$managed_path" \
|| bashio::log.warning "Unable to set effective runtime ownership on $managed_path"
fi
done
@@ -27,6 +27,17 @@ if [ ! -x "$REAL_CLAUDE" ]; then
exit 127
fi
# Claude Code rejects bypass mode when the effective UID is 0. Normal Desktop sessions run
# as abc, which startup remaps to a non-root UID when bypass is selected. Also handle a user
# invoking this wrapper directly from a root container console by dropping to abc here.
if [ "$PERMISSION_MODE" = "bypass" ] && [ "$(id -u)" -eq 0 ]; then
if command -v s6-setuidgid > /dev/null 2>&1 && [ "$(id -u abc)" -ne 0 ]; then
exec s6-setuidgid abc "$0" "$@"
fi
echo "claude wrapper: bypass mode requires a non-root runtime user, but abc is still UID 0" >&2
exit 1
fi
if bashio::config.true 'install_headroom' && bashio::config.true 'headroom_wrap_claude_code'; then
if [ -x "$HEADROOM_BIN" ] && curl -fsS --max-time 2 "${HEADROOM_URL}/health" > /dev/null 2>&1; then
# Put /usr/bin before /usr/local/bin while Headroom resolves its upstream `claude`
@@ -26,6 +26,18 @@ for option in permission_mode install_headroom headroom_wrap_claude_code expose_
printf '%-30s %s\n' "$option" "$(bashio::config "$option")"
done
section "Runtime identity"
printf '%-30s %s\n' "configured PUID:PGID" "$(bashio::config 'PUID'):$(bashio::config 'PGID')"
printf '%-30s %s\n' "effective abc UID:GID" "$(id -u abc):$(id -g abc)"
printf '%-30s %s\n' "current process UID:GID" "$(id -u):$(id -g)"
if [ "$(bashio::config 'permission_mode')" = "bypass" ]; then
if [ "$(id -u abc)" -eq 0 ]; then
echo "bypass runtime: ERROR - Claude Code will reject bypass permissions while abc is root"
else
echo "bypass runtime: OK - Claude Desktop and Cowork run as a non-root UID"
fi
fi
section "Claude Code permission state"
python3 - <<'PY'
import json