mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-09-21 01:04:00 +02:00
Compare commits
1 Commits
feat/claud
...
feat/claud
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13a4548372 |
3
.github/workflows/onpush_builder.yaml
vendored
3
.github/workflows/onpush_builder.yaml
vendored
@@ -300,7 +300,7 @@ jobs:
|
||||
|
||||
- name: Build ${{ matrix.addon }} add-on
|
||||
if: steps.info.outputs.build_arch == 'true' && steps.info.outputs.has_dockerfile == 'true'
|
||||
uses: home-assistant/builder/actions/build-image@2026.06.0
|
||||
uses: home-assistant/builder/actions/build-image@2026.03.2
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
cache-gha: "false"
|
||||
@@ -433,3 +433,4 @@ jobs:
|
||||
done
|
||||
|
||||
git push origin HEAD:master
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ If you want to do add the repository manually, please follow the procedure highl
|
||||
![amd64][amd64-badge]
|
||||
![ingress][ingress-badge]
|
||||
|
||||
✓  [Claude Desktop](claude_desktop/) : Claude Desktop with Headroom MCP context compression and RTK acceleration
|
||||
✓  [Claude Desktop](claude_desktop/) : Claude Desktop and a persistent Claude Code web terminal
|
||||
|
||||
  
|
||||

|
||||
@@ -313,7 +313,6 @@ If you want to do add the repository manually, please follow the procedure highl
|
||||
✓ [Elasticsearch server](elasticsearch/) : Free and Open, Distributed, RESTful Search Engine
|
||||
|
||||
  
|
||||

|
||||
![aarch64][aarch64-badge]
|
||||
![amd64][amd64-badge]
|
||||
|
||||
|
||||
10
chatgpt_codex/CHANGELOG.md
Normal file
10
chatgpt_codex/CHANGELOG.md
Normal file
@@ -0,0 +1,10 @@
|
||||
## 0.144.3-2 (14-07-2026)
|
||||
|
||||
- Initial ChatGPT Codex add-on.
|
||||
- Added a persistent, administrator-only Home Assistant ingress terminal backed by tmux.
|
||||
- Made `headroom wrap codex` the default launch path.
|
||||
- Configured Docker builds to install the latest stable Codex, Headroom, RTK, ttyd, and Rust toolchain versions without hard-coded tool version pins.
|
||||
- Added RTK native Codex initialization and savings reporting.
|
||||
- Added device-code authentication and direct Codex fallback helpers.
|
||||
- Added persistent configuration, GitHub CLI integration, mount support, and safe defaults.
|
||||
- Made the default workspace follow a custom `data_location`.
|
||||
157
chatgpt_codex/Dockerfile
Normal file
157
chatgpt_codex/Dockerfile
Normal file
@@ -0,0 +1,157 @@
|
||||
#============================#
|
||||
# ALEXBELGIUM'S DOCKERFILE #
|
||||
#============================#
|
||||
#=== Home Assistant Addon ===#
|
||||
|
||||
#################
|
||||
# 1 Build Image #
|
||||
#################
|
||||
|
||||
ARG BUILD_FROM
|
||||
ARG BUILD_VERSION
|
||||
|
||||
FROM rust:bookworm AS rtk-builder
|
||||
RUN set -eux; \
|
||||
rtk_version="$(git ls-remote --tags --refs --sort=-v:refname \
|
||||
https://github.com/rtk-ai/rtk.git 'refs/tags/v*' \
|
||||
| awk -F/ '$3 ~ /^v[0-9]+\.[0-9]+\.[0-9]+$/ { print $3; exit }')"; \
|
||||
test -n "$rtk_version"; \
|
||||
git clone --depth 1 --branch "$rtk_version" https://github.com/rtk-ai/rtk.git /src/rtk; \
|
||||
cd /src/rtk; \
|
||||
cargo build --release --locked; \
|
||||
install -D -m 0755 target/release/rtk /out/rtk; \
|
||||
/out/rtk --version
|
||||
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
##################
|
||||
# 2 Modify Image #
|
||||
##################
|
||||
|
||||
ENV S6_CMD_WAIT_FOR_SERVICES=1 \
|
||||
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
||||
S6_SERVICES_GRACETIME=0 \
|
||||
HEADROOM_CONTEXT_TOOL=rtk
|
||||
|
||||
USER root
|
||||
VOLUME [ "/sys/fs/cgroup" ]
|
||||
|
||||
ARG TEMPLATE_BASE_URL="https://raw.githubusercontent.com/alexbelgium/hassio-addons/master/.templates"
|
||||
|
||||
##################
|
||||
# 3 Install apps #
|
||||
##################
|
||||
|
||||
COPY rootfs/ /
|
||||
RUN find /etc/cont-init.d /etc/s6-overlay /usr/local/bin \
|
||||
-type f \( -name "*.sh" -o -name "run" -o -name "finish" -o -path "/usr/local/bin/*" \) \
|
||||
-print -exec chmod +x {} \;
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
gh \
|
||||
jq \
|
||||
less \
|
||||
nano \
|
||||
openssh-client \
|
||||
python3-pip \
|
||||
ripgrep \
|
||||
tmux && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install the latest stable official Codex static binary for the target architecture.
|
||||
RUN set -eux; \
|
||||
case "$(dpkg --print-architecture)" in \
|
||||
amd64) codex_arch="x86_64" ;; \
|
||||
arm64) codex_arch="aarch64" ;; \
|
||||
*) echo "Unsupported architecture: $(dpkg --print-architecture)" >&2; exit 1 ;; \
|
||||
esac; \
|
||||
archive="/tmp/codex.tar.gz"; \
|
||||
curl -fsSL --retry 3 --retry-delay 2 \
|
||||
-o "$archive" \
|
||||
"https://github.com/openai/codex/releases/latest/download/codex-${codex_arch}-unknown-linux-musl.tar.gz"; \
|
||||
tar -xzf "$archive" -C /tmp; \
|
||||
install -m 0755 "/tmp/codex-${codex_arch}-unknown-linux-musl" /usr/local/bin/codex; \
|
||||
rm -f "$archive" "/tmp/codex-${codex_arch}-unknown-linux-musl"; \
|
||||
codex --version
|
||||
|
||||
# Install the latest stable ttyd binary for the Home Assistant ingress terminal.
|
||||
RUN set -eux; \
|
||||
case "$(dpkg --print-architecture)" in \
|
||||
amd64) ttyd_arch="x86_64" ;; \
|
||||
arm64) ttyd_arch="aarch64" ;; \
|
||||
*) echo "Unsupported architecture: $(dpkg --print-architecture)" >&2; exit 1 ;; \
|
||||
esac; \
|
||||
curl -fsSL --retry 3 --retry-delay 2 \
|
||||
-o /usr/local/bin/ttyd \
|
||||
"https://github.com/tsl0922/ttyd/releases/latest/download/ttyd.${ttyd_arch}"; \
|
||||
chmod 0755 /usr/local/bin/ttyd; \
|
||||
ttyd --version
|
||||
|
||||
COPY --from=rtk-builder /out/rtk /usr/local/bin/rtk
|
||||
RUN rtk --version && \
|
||||
pip3 install --upgrade --break-system-packages --no-cache-dir "headroom-ai[proxy,code,mcp]" && \
|
||||
headroom --version
|
||||
|
||||
ARG MODULES="00-banner.sh 00-global_var.sh 01-custom_script.sh 00-local_mounts.sh 00-smb_mounts.sh 90-dns_set.sh"
|
||||
RUN curl -fsSL --retry 3 --retry-delay 2 \
|
||||
-o /ha_automodules.sh "${TEMPLATE_BASE_URL}/ha_automodules.sh" && \
|
||||
chmod 744 /ha_automodules.sh && \
|
||||
/ha_automodules.sh "$MODULES" && \
|
||||
rm /ha_automodules.sh
|
||||
|
||||
################
|
||||
# 4 Entrypoint #
|
||||
################
|
||||
|
||||
RUN curl -fsSL --retry 3 --retry-delay 2 \
|
||||
-o /ha_entrypoint.sh "${TEMPLATE_BASE_URL}/ha_entrypoint.sh" && \
|
||||
curl -fsSL --retry 3 --retry-delay 2 \
|
||||
-o /usr/local/lib/bashio-standalone.sh "${TEMPLATE_BASE_URL}/bashio-standalone.sh" && \
|
||||
chmod 0777 /ha_entrypoint.sh && \
|
||||
chmod 0755 /usr/local/lib/bashio-standalone.sh
|
||||
|
||||
ENTRYPOINT [ "/usr/bin/env" ]
|
||||
CMD [ "/ha_entrypoint.sh" ]
|
||||
|
||||
############
|
||||
# 5 Labels #
|
||||
############
|
||||
|
||||
ARG BUILD_ARCH
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_DESCRIPTION
|
||||
ARG BUILD_NAME
|
||||
ARG BUILD_REF
|
||||
ARG BUILD_REPOSITORY
|
||||
ARG BUILD_VERSION
|
||||
ENV BUILD_VERSION="${BUILD_VERSION}"
|
||||
LABEL \
|
||||
io.hass.name="${BUILD_NAME}" \
|
||||
io.hass.description="${BUILD_DESCRIPTION}" \
|
||||
io.hass.arch="${BUILD_ARCH}" \
|
||||
io.hass.type="addon" \
|
||||
io.hass.version=${BUILD_VERSION} \
|
||||
maintainer="alexbelgium (https://github.com/alexbelgium)" \
|
||||
org.opencontainers.image.title="${BUILD_NAME}" \
|
||||
org.opencontainers.image.description="${BUILD_DESCRIPTION}" \
|
||||
org.opencontainers.image.vendor="Home Assistant Add-ons" \
|
||||
org.opencontainers.image.authors="alexbelgium (https://github.com/alexbelgium)" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.url="https://github.com/alexbelgium" \
|
||||
org.opencontainers.image.source="https://github.com/${BUILD_REPOSITORY}" \
|
||||
org.opencontainers.image.documentation="https://github.com/${BUILD_REPOSITORY}/blob/master/chatgpt_codex/README.md" \
|
||||
org.opencontainers.image.created=${BUILD_DATE} \
|
||||
org.opencontainers.image.revision=${BUILD_REF} \
|
||||
org.opencontainers.image.version=${BUILD_VERSION}
|
||||
|
||||
#################
|
||||
# 6 Healthcheck #
|
||||
#################
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s \
|
||||
CMD curl -fsS http://127.0.0.1:7681/ > /dev/null || exit 1
|
||||
112
chatgpt_codex/README.md
Normal file
112
chatgpt_codex/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# Home Assistant add-on: ChatGPT Codex
|
||||
|
||||
![Supports aarch64 Architecture][aarch64-shield]
|
||||
![Supports amd64 Architecture][amd64-shield]
|
||||
![Project Maintenance][maintenance-shield]
|
||||
|
||||
Run the official OpenAI Codex CLI in a persistent Home Assistant ingress terminal. The optimized path uses `headroom wrap codex`, with RTK handling command-output compression before results reach Codex.
|
||||
|
||||
> The repository already contains an unrelated add-on named **Codex** for comic archives. This coding-agent add-on therefore uses the slug `chatgpt_codex`.
|
||||
|
||||
## Features
|
||||
|
||||
- Latest stable Codex, Headroom, RTK, ttyd, and Rust toolchain versions are resolved during every Docker build; tool versions are not pinned in the Dockerfile.
|
||||
- Official Codex CLI static binary for `amd64` and `aarch64`.
|
||||
- Home Assistant authenticated, administrator-only ingress; no unauthenticated terminal port is exposed.
|
||||
- Persistent `$HOME`, Codex authentication, settings, sessions, Headroom state, and RTK statistics.
|
||||
- Persistent `tmux` session that survives browser disconnects.
|
||||
- `headroom wrap codex` as the default launch path.
|
||||
- Baked-in RTK with native Codex initialization.
|
||||
- Optional Headroom output shaping and code-aware compression.
|
||||
- Direct Codex fallback for troubleshooting.
|
||||
- Device-code login helper designed for a remote or headless container.
|
||||
- Baked-in Git, GitHub CLI, ripgrep, jq, SSH client, and common terminal tools.
|
||||
- Optional GitHub CLI authentication and Git author configuration.
|
||||
- Optional extra apt and pip packages.
|
||||
- Local and SMB mount support through the repository standard modules.
|
||||
|
||||
## Installation and first login
|
||||
|
||||
1. Install **ChatGPT Codex** from this add-on repository.
|
||||
2. Keep the default `data_location` and `workspace`, or select writable mounted paths.
|
||||
3. Start the add-on and open its web UI.
|
||||
4. Codex starts automatically through Headroom.
|
||||
5. When prompted to authenticate, follow the device-code instructions. You can also exit Codex and run:
|
||||
|
||||
```shell
|
||||
codex-login
|
||||
```
|
||||
|
||||
Codex supports ChatGPT sign-in and API-key authentication. The device-code flow is the recommended option for this headless add-on.
|
||||
|
||||
## Launch commands
|
||||
|
||||
Optimized default:
|
||||
|
||||
```shell
|
||||
codex-headroom
|
||||
```
|
||||
|
||||
This runs:
|
||||
|
||||
```shell
|
||||
headroom wrap codex
|
||||
```
|
||||
|
||||
Headroom starts its local proxy, configures Codex routing and MCP support, and uses RTK as the CLI context tool.
|
||||
|
||||
Direct troubleshooting path:
|
||||
|
||||
```shell
|
||||
codex-direct
|
||||
```
|
||||
|
||||
Check optimization status and measured savings:
|
||||
|
||||
```shell
|
||||
headroom doctor
|
||||
headroom perf
|
||||
rtk gain
|
||||
```
|
||||
|
||||
## Persistence
|
||||
|
||||
The terminal attaches every browser connection to the same `tmux` session. Closing the browser detaches the client but does not stop Codex or commands running in the session.
|
||||
|
||||
Persistent data is stored below `data_location`:
|
||||
|
||||
- Codex state: `~/.codex`
|
||||
- Headroom state and metrics: `~/.headroom`
|
||||
- RTK state: its normal paths below the persistent home
|
||||
- Default workspace: `~/workspace`
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `data_location` | `/data/data` | Persistent home. Must be below `/data`, `/share`, `/media`, `/config`, or `/mnt`. |
|
||||
| `workspace` | `<data_location>/workspace` | Initial project directory. Leave empty to follow `data_location`. |
|
||||
| `PUID` / `PGID` | `0` / `0` | Runtime user and group used by the LinuxServer `abc` account. |
|
||||
| `TZ` | | Optional timezone, for example `Europe/Brussels`. |
|
||||
| `auto_start_codex` | `true` | Start Codex automatically when the tmux session is first created. |
|
||||
| `use_headroom` | `true` | Use `headroom wrap codex`; disabling this starts Codex directly. |
|
||||
| `headroom_output_shaper` | `true` | Enable Headroom output-token shaping. |
|
||||
| `headroom_code_aware` | `true` | Enable Headroom AST-aware code compression. |
|
||||
| `github_token` | | Authenticate GitHub CLI and Git operations. |
|
||||
| `github_username` / `github_email` | | Configure the global Git author. |
|
||||
| `additional_apps` | | Comma-separated Debian packages installed at startup. |
|
||||
| `additional_pip` | | Comma-separated Python packages installed at startup. |
|
||||
| `localdisks` / `networkdisks` | | Optional local-disk and SMB mounts supported by the repository modules. |
|
||||
| `env_vars` | `[]` | Additional environment variables exported in the container. |
|
||||
|
||||
Configuration changes affecting the launch command apply to a newly created tmux session. To recreate it, exit Codex and run `tmux kill-session -t codex`, then reopen the add-on web UI.
|
||||
|
||||
## Security
|
||||
|
||||
The add-on deliberately does not enable Codex approval or sandbox bypass flags. Codex can execute commands and edit files available inside the configured workspace, so only mount locations you intend it to access.
|
||||
|
||||
The terminal is exposed only through Home Assistant administrator-only ingress. Do not add an unauthenticated direct port mapping. Treat `github_token`, Codex authentication data, and the persistent home as secrets and include them only in trusted backups.
|
||||
|
||||
[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg
|
||||
[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg
|
||||
[maintenance-shield]: https://img.shields.io/maintenance/yes/2026.svg
|
||||
6
chatgpt_codex/build.json
Normal file
6
chatgpt_codex/build.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"build_from": {
|
||||
"aarch64": "ghcr.io/linuxserver/baseimage-debian:arm64v8-bookworm",
|
||||
"amd64": "ghcr.io/linuxserver/baseimage-debian:amd64-bookworm"
|
||||
}
|
||||
}
|
||||
68
chatgpt_codex/config.yaml
Normal file
68
chatgpt_codex/config.yaml
Normal file
@@ -0,0 +1,68 @@
|
||||
arch:
|
||||
- aarch64
|
||||
- amd64
|
||||
description: "Persistent OpenAI Codex web terminal optimized with Headroom and RTK"
|
||||
devices:
|
||||
- /dev/fuse
|
||||
environment:
|
||||
HOME: /data/data
|
||||
PGID: "0"
|
||||
PUID: "0"
|
||||
TERM: xterm-256color
|
||||
image: ghcr.io/alexbelgium/chatgpt_codex-{arch}
|
||||
ingress: true
|
||||
ingress_port: 7681
|
||||
ingress_stream: true
|
||||
init: false
|
||||
map:
|
||||
- addon_config:rw
|
||||
- share:rw
|
||||
- media:rw
|
||||
- ssl
|
||||
name: ChatGPT Codex
|
||||
options:
|
||||
env_vars: []
|
||||
DNS_server: 8.8.8.8
|
||||
data_location: /data/data
|
||||
workspace: ""
|
||||
PUID: 0
|
||||
PGID: 0
|
||||
auto_start_codex: true
|
||||
use_headroom: true
|
||||
headroom_output_shaper: true
|
||||
headroom_code_aware: true
|
||||
github_token: ""
|
||||
github_username: ""
|
||||
github_email: ""
|
||||
additional_apps: ""
|
||||
additional_pip: ""
|
||||
panel_icon: mdi:code-braces-box
|
||||
privileged:
|
||||
- SYS_ADMIN
|
||||
- DAC_READ_SEARCH
|
||||
schema:
|
||||
env_vars:
|
||||
- name: match(^[A-Za-z0-9_]+$)
|
||||
value: str?
|
||||
DNS_server: str?
|
||||
data_location: str?
|
||||
workspace: str?
|
||||
PUID: int
|
||||
PGID: int
|
||||
TZ: match([A-Z][a-z]*./[A-Z][a-z]*.)?
|
||||
auto_start_codex: bool
|
||||
use_headroom: bool
|
||||
headroom_output_shaper: bool
|
||||
headroom_code_aware: bool
|
||||
github_token: password?
|
||||
github_username: str?
|
||||
github_email: str?
|
||||
additional_apps: str?
|
||||
additional_pip: str?
|
||||
localdisks: str?
|
||||
networkdisks: str?
|
||||
slug: chatgpt_codex
|
||||
tmpfs: true
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "0.144.3-3"
|
||||
46
chatgpt_codex/rootfs/etc/cont-init.d/20-folders.sh
Normal file
46
chatgpt_codex/rootfs/etc/cont-init.d/20-folders.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
PUID="$(bashio::config 'PUID')"
|
||||
PGID="$(bashio::config 'PGID')"
|
||||
LOCATION="$(bashio::config 'data_location')"
|
||||
|
||||
if [ -z "$LOCATION" ] || [ "$LOCATION" = "null" ]; then
|
||||
LOCATION="/data/data"
|
||||
fi
|
||||
|
||||
case "$LOCATION" in
|
||||
/data/* | /share/* | /media/* | /config/* | /mnt/*)
|
||||
;;
|
||||
*)
|
||||
bashio::log.fatal "data_location must be below /data, /share, /media, /config, or /mnt"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -L "$LOCATION" ]; then
|
||||
bashio::log.fatal "data_location must not be a symbolic link"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bashio::log.info "Using persistent home: $LOCATION"
|
||||
install -d -m 0750 -o "$PUID" -g "$PGID" "$LOCATION"
|
||||
install -d -m 0750 -o "$PUID" -g "$PGID" "$LOCATION/.codex" "$LOCATION/.headroom"
|
||||
install -d -m 0755 /tmp/cache /run/s6/container_environment
|
||||
|
||||
sed -i "s|^\(abc:[^:]*:[^:]*:[^:]*:[^:]*:\)[^:]*|\1$LOCATION|" /etc/passwd
|
||||
|
||||
for variable in HOME CODEX_HOME HEADROOM_WORKSPACE_DIR XDG_CACHE_HOME; do
|
||||
case "$variable" in
|
||||
HOME) value="$LOCATION" ;;
|
||||
CODEX_HOME) value="$LOCATION/.codex" ;;
|
||||
HEADROOM_WORKSPACE_DIR) value="$LOCATION/.headroom" ;;
|
||||
XDG_CACHE_HOME) value="/tmp/cache" ;;
|
||||
esac
|
||||
printf '%s' "$value" > "/run/s6/container_environment/$variable"
|
||||
done
|
||||
|
||||
chown -R "$PUID:$PGID" "$LOCATION/.codex" "$LOCATION/.headroom"
|
||||
chown "$PUID:$PGID" "$LOCATION"
|
||||
33
chatgpt_codex/rootfs/etc/cont-init.d/80-configuration.sh
Normal file
33
chatgpt_codex/rootfs/etc/cont-init.d/80-configuration.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
if bashio::config.has_value 'additional_apps'; then
|
||||
packages="$(bashio::config 'additional_apps')"
|
||||
apt-get update -o Acquire::http::Timeout=10 -o Acquire::https::Timeout=10
|
||||
for package in ${packages//,/ }; do
|
||||
bashio::log.info "Installing apt package: $package"
|
||||
apt-get install -y --no-install-recommends "$package"
|
||||
done
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'additional_pip'; then
|
||||
packages="$(bashio::config 'additional_pip')"
|
||||
for package in ${packages//,/ }; do
|
||||
bashio::log.info "Installing pip package: $package"
|
||||
pip3 install --break-system-packages "$package"
|
||||
done
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'TZ'; then
|
||||
timezone="$(bashio::config 'TZ')"
|
||||
if [ ! -e "/usr/share/zoneinfo/$timezone" ]; then
|
||||
bashio::log.fatal "Invalid timezone: $timezone"
|
||||
exit 1
|
||||
fi
|
||||
ln -snf "/usr/share/zoneinfo/$timezone" /etc/localtime
|
||||
printf '%s\n' "$timezone" > /etc/timezone
|
||||
fi
|
||||
55
chatgpt_codex/rootfs/etc/cont-init.d/82-codex-tools.sh
Normal file
55
chatgpt_codex/rootfs/etc/cont-init.d/82-codex-tools.sh
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
PUID="$(bashio::config 'PUID')"
|
||||
PGID="$(bashio::config 'PGID')"
|
||||
|
||||
if ! command -v codex > /dev/null 2>&1; then
|
||||
bashio::log.fatal "Codex CLI is not available"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v headroom > /dev/null 2>&1; then
|
||||
bashio::log.fatal "Headroom is not available"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v rtk > /dev/null 2>&1; then
|
||||
bashio::log.fatal "RTK is not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bashio::log.info "Codex: $(codex --version 2>&1 | head -n 1)"
|
||||
bashio::log.info "Headroom: $(headroom --version 2>&1 | head -n 1)"
|
||||
bashio::log.info "RTK: $(rtk --version 2>&1 | head -n 1)"
|
||||
|
||||
# Configure RTK's native Codex integration ahead of the first wrapped session.
|
||||
if ! s6-setuidgid abc env \
|
||||
HOME="$HOME" \
|
||||
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" \
|
||||
PATH="$HOME/.local/bin:/usr/local/bin:/usr/bin:/bin" \
|
||||
RTK_NONINTERACTIVE=1 \
|
||||
rtk init -g --codex; then
|
||||
bashio::log.warning "RTK Codex initialization failed; Headroom will retry when wrapping Codex"
|
||||
fi
|
||||
|
||||
for key in CODEX_AUTO_START CODEX_USE_HEADROOM HEADROOM_OUTPUT_SHAPER HEADROOM_CODE_AWARE_ENABLED; do
|
||||
case "$key" in
|
||||
CODEX_AUTO_START)
|
||||
bashio::config.true 'auto_start_codex' && value="1" || value="0"
|
||||
;;
|
||||
CODEX_USE_HEADROOM)
|
||||
bashio::config.true 'use_headroom' && value="1" || value="0"
|
||||
;;
|
||||
HEADROOM_OUTPUT_SHAPER)
|
||||
bashio::config.true 'headroom_output_shaper' && value="1" || value="0"
|
||||
;;
|
||||
HEADROOM_CODE_AWARE_ENABLED)
|
||||
bashio::config.true 'headroom_code_aware' && value="1" || value="0"
|
||||
;;
|
||||
esac
|
||||
printf '%s' "$value" > "/run/s6/container_environment/$key"
|
||||
done
|
||||
printf '%s' 'rtk' > /run/s6/container_environment/HEADROOM_CONTEXT_TOOL
|
||||
|
||||
chown -R "$PUID:$PGID" "$HOME/.codex" "$HOME/.headroom"
|
||||
29
chatgpt_codex/rootfs/etc/cont-init.d/83-github_cli.sh
Normal file
29
chatgpt_codex/rootfs/etc/cont-init.d/83-github_cli.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
if bashio::config.has_value 'github_username'; then
|
||||
s6-setuidgid abc git config --global user.name "$(bashio::config 'github_username')"
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'github_email'; then
|
||||
s6-setuidgid abc git config --global user.email "$(bashio::config 'github_email')"
|
||||
fi
|
||||
|
||||
if bashio::config.has_value 'github_token'; then
|
||||
token="$(bashio::config 'github_token')"
|
||||
if s6-setuidgid abc env -u GH_TOKEN -u GITHUB_TOKEN gh auth status --hostname github.com > /dev/null 2>&1; then
|
||||
bashio::log.info "GitHub CLI is already authenticated"
|
||||
else
|
||||
bashio::log.info "Authenticating GitHub CLI"
|
||||
printf '%s\n' "$token" | s6-setuidgid abc env -u GH_TOKEN -u GITHUB_TOKEN \
|
||||
gh auth login --hostname github.com --with-token || \
|
||||
bashio::log.warning "GitHub CLI authentication failed"
|
||||
fi
|
||||
s6-setuidgid abc env -u GH_TOKEN -u GITHUB_TOKEN \
|
||||
gh auth setup-git --hostname github.com || \
|
||||
bashio::log.warning "GitHub CLI git credential setup failed"
|
||||
else
|
||||
bashio::log.info "Set github_token to authenticate gh and Git operations"
|
||||
fi
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
port=7681
|
||||
workspace="$(bashio::config 'workspace')"
|
||||
|
||||
if [ -z "$workspace" ] || [ "$workspace" = "null" ]; then
|
||||
workspace="$HOME/workspace"
|
||||
fi
|
||||
|
||||
if [[ "$workspace" != /* ]]; then
|
||||
bashio::log.fatal "workspace must be an absolute path"
|
||||
exec sleep infinity
|
||||
fi
|
||||
if [ -L "$workspace" ]; then
|
||||
bashio::log.fatal "workspace must not be a symbolic link"
|
||||
exec sleep infinity
|
||||
fi
|
||||
|
||||
workspace="$(realpath -m -- "$workspace")"
|
||||
case "$workspace" in
|
||||
"$HOME" | "$HOME"/* | /share/* | /media/* | /mnt/* | /data/* | /config/*)
|
||||
;;
|
||||
*)
|
||||
bashio::log.fatal "workspace must be below the persistent home, /share, /media, /mnt, /data, or /config"
|
||||
exec sleep infinity
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -e "$workspace" ]; then
|
||||
install -d -m 0750 -o abc -g abc "$workspace"
|
||||
elif [ ! -d "$workspace" ]; then
|
||||
bashio::log.fatal "workspace is not a directory: $workspace"
|
||||
exec sleep infinity
|
||||
fi
|
||||
|
||||
if ! s6-setuidgid abc test -r "$workspace" || \
|
||||
! s6-setuidgid abc test -w "$workspace" || \
|
||||
! s6-setuidgid abc test -x "$workspace"; then
|
||||
bashio::log.fatal "workspace must be readable, writable, and searchable by user abc: $workspace"
|
||||
exec sleep infinity
|
||||
fi
|
||||
|
||||
export CODEX_TERMINAL_WORKSPACE="$workspace"
|
||||
bashio::log.info "Starting persistent Codex terminal on Home Assistant ingress port $port"
|
||||
exec s6-setuidgid abc ttyd \
|
||||
-p "$port" \
|
||||
-W \
|
||||
-O \
|
||||
-t disableLeaveAlert=true \
|
||||
-t fontSize=14 \
|
||||
/usr/local/bin/codex-terminal-shell
|
||||
@@ -0,0 +1 @@
|
||||
longrun
|
||||
4
chatgpt_codex/rootfs/usr/local/bin/codex-direct
Normal file
4
chatgpt_codex/rootfs/usr/local/bin/codex-direct
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||
exec codex "$@"
|
||||
15
chatgpt_codex/rootfs/usr/local/bin/codex-headroom
Normal file
15
chatgpt_codex/rootfs/usr/local/bin/codex-headroom
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||
export HEADROOM_CONTEXT_TOOL="rtk"
|
||||
|
||||
if ! command -v headroom > /dev/null 2>&1; then
|
||||
echo "Headroom is unavailable; launching Codex directly." >&2
|
||||
exec codex "$@"
|
||||
fi
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
exec headroom wrap codex
|
||||
fi
|
||||
exec headroom wrap codex -- "$@"
|
||||
4
chatgpt_codex/rootfs/usr/local/bin/codex-login
Normal file
4
chatgpt_codex/rootfs/usr/local/bin/codex-login
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||
exec codex login --device-auth "$@"
|
||||
33
chatgpt_codex/rootfs/usr/local/bin/codex-terminal-shell
Normal file
33
chatgpt_codex/rootfs/usr/local/bin/codex-terminal-shell
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
export SHELL="/bin/bash"
|
||||
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}"
|
||||
|
||||
workspace="${CODEX_TERMINAL_WORKSPACE:-${HOME}/workspace}"
|
||||
session_name="${CODEX_TMUX_SESSION:-codex}"
|
||||
|
||||
if [ ! -d "$workspace" ]; then
|
||||
echo "Codex workspace does not exist: $workspace" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
new_session=0
|
||||
if ! tmux has-session -t "$session_name" 2> /dev/null; then
|
||||
tmux new-session -d -s "$session_name" -c "$workspace" /bin/bash -l
|
||||
new_session=1
|
||||
fi
|
||||
|
||||
if [ "$new_session" -eq 1 ]; then
|
||||
tmux send-keys -t "$session_name" \
|
||||
"printf '\\nChatGPT Codex add-on\\n login: codex-login\\n optimized: codex-headroom\\n direct: codex-direct\\n savings: rtk gain && headroom perf\\n\\n'" C-m
|
||||
if [ "${CODEX_AUTO_START:-1}" = "1" ]; then
|
||||
if [ "${CODEX_USE_HEADROOM:-1}" = "1" ]; then
|
||||
tmux send-keys -t "$session_name" "codex-headroom" C-m
|
||||
else
|
||||
tmux send-keys -t "$session_name" "codex-direct" C-m
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
exec tmux attach-session -t "$session_name"
|
||||
12
chatgpt_codex/updater.json
Normal file
12
chatgpt_codex/updater.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"github_beta": false,
|
||||
"github_fulltag": false,
|
||||
"github_havingasset": true,
|
||||
"github_tagfilter": "rust-v",
|
||||
"last_update": "2026-07-14",
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "chatgpt_codex",
|
||||
"source": "github",
|
||||
"upstream_repo": "openai/codex",
|
||||
"upstream_version": "0.144.3"
|
||||
}
|
||||
@@ -1,30 +1,4 @@
|
||||
## 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`.
|
||||
- Add `tokensave_project_paths` for explicit per-repository initialization and incremental synchronization; no repositories are scanned or indexed unless listed.
|
||||
- Route PATH-based Claude Code launches through the already-supervised Headroom proxy by default with a recursion-safe `/usr/local/bin/claude` wrapper; fall back to the official binary when the proxy is unavailable.
|
||||
- Pass the local proxy URL explicitly to the Headroom MCP server, while retaining MCP-only integration for the Desktop Electron application.
|
||||
- Keep the unauthenticated Headroom dashboard container-local by default; add `expose_headroom_dashboard` and leave port `8787/tcp` unmapped until explicitly enabled.
|
||||
- Fix the hourly gains report so Headroom no longer suppresses RTK output, add TokenSave gains, and gate each tool on its actual add-on option.
|
||||
- Add `claude-tools-doctor.sh` to inspect binaries, redacted MCP registrations, hooks, proxy health, routing, project indexes, and gains.
|
||||
- Install local validation tools (`jq`, `shellcheck`, `yamllint`, current `hadolint`, and current `actionlint`) to reduce avoidable CI round-trips.
|
||||
- Disable the unpinned third-party Caveman startup installer by default; it remains opt-in.
|
||||
|
||||
## 1.19 (14-07-2026)
|
||||
- Minor bugs fixed
|
||||
## 1.18 (14-07-2026)
|
||||
## 1.17 (14-07-2026)
|
||||
|
||||
- **Breaking:** remove the standalone Claude Code web terminal (ttyd/tmux service, port `7681`, and the `enable_terminal`, `terminal_username`, `terminal_password`, `terminal_workspace` options). The add-on is now built purely around Claude Desktop; Claude Code remains installed and powers Desktop cowork/dispatch sessions with the RTK hook, Caveman, and MCP servers intact. If the add-on refuses to start after the update, open its Configuration tab and re-save to drop the removed options.
|
||||
- Remove the `claude-direct` and `claude-headroom` terminal wrapper scripts and the unused `ha_smart_context` and `dangerously_skip_permissions` options.
|
||||
@@ -41,7 +15,7 @@
|
||||
- Minor bugs fixed
|
||||
## 1.15 (13-07-2026)
|
||||
- Minor bugs fixed
|
||||
|
||||
|
||||
## ubunturesolute-version-6dc44b0e (2026-07-13)
|
||||
- Update to latest version from linuxserver/docker-baseimage-selkies (changelog : https://github.com/linuxserver/docker-baseimage-selkies/releases)
|
||||
## 1.14 (10-07-2026)
|
||||
|
||||
@@ -34,7 +34,6 @@ RUN cargo install tokensave --version "${TOKENSAVE_VERSION}" --locked --root /ou
|
||||
/out/bin/tokensave --version
|
||||
|
||||
FROM ${BUILD_FROM}
|
||||
ARG BUILD_ARCH
|
||||
|
||||
##################
|
||||
# 2 Modify Image #
|
||||
@@ -74,17 +73,15 @@ RUN curl -fsSL --retry 3 --retry-delay 2 \
|
||||
# cannot alter executables elsewhere in the image.
|
||||
COPY rootfs/ /
|
||||
RUN find /etc/cont-init.d /etc/s6-overlay /defaults /usr/local/bin -type f \
|
||||
\( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \; && \
|
||||
chmod +x /usr/local/bin/claude
|
||||
\( -name "*.sh" -o -name "run" -o -name "finish" \) -print -exec chmod +x {} \;
|
||||
|
||||
# Uses /bin for compatibility purposes
|
||||
# hadolint ignore=DL4005
|
||||
RUN if [ ! -f /bin/sh ] && [ -f /usr/bin/sh ]; then ln -s /usr/bin/sh /bin/sh; fi && \
|
||||
if [ ! -f /bin/bash ] && [ -f /usr/bin/bash ]; then ln -s /usr/bin/bash /bin/bash; fi
|
||||
|
||||
# Install Claude Desktop, Claude Code, Python tooling, and lightweight local validators.
|
||||
# gnome-keyring provides the Secret Service backend Electron safeStorage needs to persist
|
||||
# sign-in and dispatch grants.
|
||||
# Install Claude Desktop, Claude Code, and Python tooling. gnome-keyring provides the
|
||||
# Secret Service backend Electron safeStorage needs to persist sign-in and dispatch grants.
|
||||
RUN install -d -m 0755 /etc/apt/keyrings && \
|
||||
curl -fsSLo /usr/share/keyrings/claude-desktop-archive-keyring.asc https://downloads.claude.ai/claude-desktop/key.asc && \
|
||||
curl -fsSLo /etc/apt/keyrings/claude-code.asc https://downloads.claude.ai/keys/claude-code.asc && \
|
||||
@@ -95,46 +92,15 @@ RUN install -d -m 0755 /etc/apt/keyrings && \
|
||||
claude-desktop \
|
||||
claude-code \
|
||||
python3-pip \
|
||||
gnome-keyring \
|
||||
libsecret-1-0 \
|
||||
dbus-x11 \
|
||||
git \
|
||||
gh \
|
||||
ripgrep \
|
||||
jq \
|
||||
shellcheck \
|
||||
yamllint && \
|
||||
test -x /usr/bin/claude && \
|
||||
ripgrep && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install the current upstream hadolint and actionlint releases for both supported
|
||||
# architectures. The GitHub release API resolves the latest asset at build time, so these
|
||||
# developer tools are intentionally not version-pinned.
|
||||
RUN set -eux; \
|
||||
case "${BUILD_ARCH}" in \
|
||||
amd64) hadolint_arch="x86_64"; actionlint_arch="amd64" ;; \
|
||||
aarch64) hadolint_arch="arm64"; actionlint_arch="arm64" ;; \
|
||||
*) echo "Unsupported validation-tools architecture: ${BUILD_ARCH}" >&2; exit 1 ;; \
|
||||
esac; \
|
||||
hadolint_name="hadolint-linux-${hadolint_arch}"; \
|
||||
hadolint_url="$(curl -fsSL https://api.github.com/repos/hadolint/hadolint/releases/latest \
|
||||
| jq -r --arg name "${hadolint_name}" '.assets[] | select(.name == $name) | .browser_download_url' \
|
||||
| head -n 1)"; \
|
||||
test -n "${hadolint_url}"; \
|
||||
curl -fsSL --retry 3 --retry-delay 2 -o /usr/local/bin/hadolint "${hadolint_url}"; \
|
||||
chmod 0755 /usr/local/bin/hadolint; \
|
||||
actionlint_suffix="_linux_${actionlint_arch}.tar.gz"; \
|
||||
actionlint_url="$(curl -fsSL https://api.github.com/repos/rhysd/actionlint/releases/latest \
|
||||
| jq -r --arg suffix "${actionlint_suffix}" '.assets[] | select(.name | endswith($suffix)) | .browser_download_url' \
|
||||
| head -n 1)"; \
|
||||
test -n "${actionlint_url}"; \
|
||||
curl -fsSL --retry 3 --retry-delay 2 -o /tmp/actionlint.tar.gz "${actionlint_url}"; \
|
||||
tar -xzf /tmp/actionlint.tar.gz -C /tmp actionlint; \
|
||||
install -m 0755 /tmp/actionlint /usr/local/bin/actionlint; \
|
||||
rm -f /tmp/actionlint /tmp/actionlint.tar.gz; \
|
||||
hadolint --version; \
|
||||
actionlint -version
|
||||
|
||||
# Copy the pinned Bookworm-built RTK and tokensave binaries and execute them in the final
|
||||
# image. This makes an ABI mismatch fail the image build instead of surfacing at runtime.
|
||||
COPY --from=rtk-builder /out/rtk /usr/local/bin/rtk
|
||||
@@ -142,7 +108,7 @@ COPY --from=tokensave-builder /out/bin/tokensave /usr/local/bin/tokensave
|
||||
RUN /usr/local/bin/rtk --version && /usr/local/bin/tokensave --version
|
||||
|
||||
# Install only the Headroom proxy, code-compression, and MCP features used by this add-on,
|
||||
# plus mcp-proxy (stdio->HTTP bridge for the Home Assistant MCP server) and uv (fast
|
||||
# plus mcp-proxy (stdio->SSE bridge for the Home Assistant MCP server) and uv (fast
|
||||
# installer used for the additional_pip option).
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends nodejs && \
|
||||
@@ -194,6 +160,7 @@ CMD [ "/ha_entrypoint.sh" ]
|
||||
# 5 Labels #
|
||||
############
|
||||
|
||||
ARG BUILD_ARCH
|
||||
ARG BUILD_DATE
|
||||
ARG BUILD_DESCRIPTION
|
||||
ARG BUILD_NAME
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
![Supports amd64 Architecture][amd64-shield]
|
||||
![Project Maintenance][maintenance-shield]
|
||||
|
||||
Run Claude Desktop in a LinuxServer.io Selkies add-on, with Headroom context
|
||||
compression, RTK Bash-output acceleration, and TokenSave semantic code
|
||||
intelligence wired in by default.
|
||||
Run Claude Desktop in a LinuxServer.io Selkies add-on, with Headroom MCP
|
||||
context compression, RTK Bash-output acceleration, and code-intelligence
|
||||
tooling wired in by default.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -24,39 +24,15 @@ currently does not include Computer Use or dictation.
|
||||
Everything is built around the Claude Desktop app. Claude Code is installed in
|
||||
the same image but is not exposed as a standalone service: Claude Desktop's
|
||||
cowork and dispatch sessions run it internally, and they pick up the shared
|
||||
Claude Code configuration (`~/.claude`), hooks, MCP servers, permissions, and
|
||||
PATH tools.
|
||||
Claude Code configuration (`~/.claude`), hooks, and MCP servers automatically.
|
||||
|
||||
- **Claude Desktop** uses Headroom through its MCP tools.
|
||||
- **Claude Code sessions inside Desktop** get the same MCP servers, permission
|
||||
mode, and RTK/TokenSave hooks through the shared Claude Code configuration.
|
||||
- PATH-based Claude Code launches are routed through the supervised Headroom
|
||||
proxy when `headroom_wrap_claude_code` is enabled. If a Desktop release calls
|
||||
`/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.
|
||||
- **Claude Code sessions inside Desktop** get the same MCP servers via
|
||||
`~/.claude.json` and RTK's `PreToolUse` Bash hook via
|
||||
`~/.claude/settings.json`.
|
||||
- **gnome-keyring** provides the Secret Service backend Electron needs to
|
||||
persist sign-in and dispatch permission grants across restarts.
|
||||
|
||||
## Optimization layers
|
||||
|
||||
The three bundled optimization tools are complementary:
|
||||
|
||||
- **RTK** rewrites supported Bash commands so Claude receives compact output.
|
||||
- **TokenSave** builds a local semantic graph for explicitly selected code
|
||||
repositories and steers Claude away from repeated Explore/Grep/Read fan-out.
|
||||
- **Headroom** transparently compresses proxied Claude Code traffic and also
|
||||
exposes on-demand compress/retrieve/statistics MCP tools to Claude Desktop.
|
||||
|
||||
TokenSave's complete Claude integration is installed at startup: MCP server,
|
||||
permissions, PreToolUse/UserPromptSubmit/Stop hooks, global prompt rules, and
|
||||
Git synchronization hooks. A repository is indexed only when it is listed in
|
||||
`tokensave_project_paths`; no automatic filesystem scan is performed.
|
||||
|
||||
## Features
|
||||
|
||||
- Claude Desktop in single-app Selkies mode with Home Assistant ingress.
|
||||
@@ -65,143 +41,65 @@ Git synchronization hooks. A repository is indexed only when it is listed in
|
||||
- Persistent `$HOME` at the configured `data_location` (default `/data/data`),
|
||||
preserving Desktop and Claude Code state across restarts.
|
||||
- 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`,
|
||||
`hadolint`, and `actionlint`.
|
||||
- Optional extra apt and pip package installation (pip installs use `uv` for
|
||||
speed).
|
||||
- Baked-in `git`, GitHub CLI (`gh`), and `ripgrep`.
|
||||
- Custom script support through the repository standard `claude_desktop.sh`.
|
||||
- Bundled optimization tools: Headroom, RTK, and TokenSave; Caveman remains
|
||||
available as an opt-in plugin.
|
||||
- Bundled optimization tools: Headroom (MCP + local proxy), RTK, tokensave,
|
||||
and Caveman — each individually switchable.
|
||||
- Optional Home Assistant MCP bridge so Claude can query and control Home
|
||||
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, runtime identity,
|
||||
and gains.
|
||||
- Headroom dashboard exposed on mapped port `8787`.
|
||||
- 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 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` | `0` / `0` | Numeric user and group applied by the LinuxServer initialization. |
|
||||
| `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. |
|
||||
| `permission_mode` | `auto` | Claude Code permission policy: `strict`, `auto`, or `bypass`. |
|
||||
| `install_headroom` | `true` | Register Headroom MCP and run the supervised local proxy. |
|
||||
| `headroom_wrap_claude_code` | `true` | Route PATH-based Claude Code launches through the already-running Headroom proxy. |
|
||||
| `expose_headroom_dashboard` | `false` | Bind Headroom to all interfaces. Port `8787/tcp` must also be mapped manually. |
|
||||
| `install_rtk` | `true` | Configure RTK's Claude Code `PreToolUse` Bash hook. |
|
||||
| `install_tokensave` | `true` | Install TokenSave's complete global Claude integration. |
|
||||
| `tokensave_project_paths` | `[]` | Explicit absolute Git repository paths to initialize or sync at startup. |
|
||||
| `install_caveman` | `false` | Install the third-party Caveman Claude Code plugin at startup. |
|
||||
| `enable_tools_health_report` | `true` | Write independent Headroom, RTK, and TokenSave gains to the add-on log hourly. |
|
||||
| `install_headroom` | `true` | Register the Headroom MCP server and run the supervised local proxy/dashboard. |
|
||||
| `install_rtk` | `true` | Configure RTK's Claude Code `PreToolUse` hook. |
|
||||
| `install_tokensave` | `true` | Register the tokensave code-intelligence MCP server for Desktop and Claude Code. |
|
||||
| `install_caveman` | `true` | Install the Caveman Claude Code plugin in the persistent Claude home. |
|
||||
| `install_github_cli` | `true` | Enable setup checks for the baked-in `git` and `gh` commands. |
|
||||
| `github_token` | | Optional GitHub token used to authenticate `gh` and Git operations. |
|
||||
| `github_username` | | Optional global Git author name. |
|
||||
| `github_email` | | Optional global Git author email. |
|
||||
| `enable_ha_mcp` | `false` | Register Home Assistant's MCP server in Claude (requires `ha_mcp_token`). |
|
||||
| `ha_mcp_url` | `http://homeassistant:8123/api/mcp` | Streamable HTTP endpoint of Home Assistant's MCP Server integration. |
|
||||
| `ha_mcp_url` | `http://homeassistant:8123/mcp_server/sse` | SSE endpoint of Home Assistant's MCP Server integration. |
|
||||
| `ha_mcp_token` | | Home Assistant long-lived access token used by the MCP bridge. |
|
||||
| `additional_apps` | | Comma-separated Debian apt packages to install at startup. |
|
||||
| `additional_pip` | | Comma-separated pip packages installed at startup (via `uv`). |
|
||||
| `data_location` | `/data/data` | Persistent home directory for Claude and tooling. |
|
||||
| `env_vars` | `[]` | Additional environment variables exported inside the container. |
|
||||
|
||||
### Permission modes
|
||||
|
||||
```yaml
|
||||
permission_mode: auto
|
||||
```
|
||||
|
||||
- `strict` keeps Claude Code's normal interactive permission prompts.
|
||||
- `auto` asks Claude Code's automatic permission classifier to approve safe
|
||||
operations while retaining prompts for risky actions. This is the default.
|
||||
- `bypass` disables Claude Code permission checks by using
|
||||
`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. Mounted paths must remain
|
||||
accessible to the effective non-root UID or its retained group.
|
||||
|
||||
### TokenSave project example
|
||||
|
||||
Only repositories listed here are indexed. Paths must be absolute, mounted in
|
||||
the add-on, and resolve to a Git working tree:
|
||||
|
||||
```yaml
|
||||
tokensave_project_paths:
|
||||
- /share/projects/hassio-addons
|
||||
- /share/projects/birdnet-go
|
||||
```
|
||||
|
||||
At startup, an uninitialized repository receives `tokensave init`; an existing
|
||||
index receives an incremental `tokensave sync`. Removing a path from the option
|
||||
stops automatic synchronization but does not delete its `.tokensave` database.
|
||||
Configured repositories are added to Git's `safe.directory` list for the shared
|
||||
runtime user before TokenSave performs repository discovery.
|
||||
|
||||
## Headroom behavior
|
||||
|
||||
When `install_headroom` is enabled, the add-on registers `headroom mcp serve`
|
||||
with the explicit local proxy URL in Claude Desktop and Claude Code, then starts
|
||||
a supervised Headroom backend on `127.0.0.1:8787`.
|
||||
in Claude Desktop and Claude Code, and starts a supervised local Headroom
|
||||
backend. Claude can use `headroom_compress`, `headroom_retrieve`, and
|
||||
`headroom_stats` through MCP.
|
||||
|
||||
Claude Desktop overrides `ANTHROPIC_BASE_URL`, so Desktop chat deliberately uses
|
||||
the MCP integration. The `/usr/local/bin/claude` wrapper routes PATH-based Claude
|
||||
Code sessions through `headroom wrap claude --no-proxy`, reusing the supervised
|
||||
backend without starting a second proxy.
|
||||
Claude Desktop overrides `ANTHROPIC_BASE_URL`, so it is deliberately launched
|
||||
without proxy injection; the MCP integration is the supported path.
|
||||
|
||||
The dashboard is disabled externally by default. To expose it:
|
||||
The Headroom dashboard is available at:
|
||||
|
||||
1. Set `expose_headroom_dashboard: true`.
|
||||
2. Map `8787/tcp` in the add-on **Network** section.
|
||||
3. Open `http://<home-assistant-host>:8787/dashboard`.
|
||||
|
||||
The dashboard is unauthenticated. Do not publish this port to the public
|
||||
internet.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
Run the following inside the add-on through a custom script or container console:
|
||||
|
||||
```bash
|
||||
claude-tools-doctor.sh
|
||||
```text
|
||||
http://<home-assistant-host>:8787/dashboard
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
claude-gains-report.sh
|
||||
```
|
||||
through the default `8787/tcp` port mapping. The dashboard is unauthenticated
|
||||
and is reachable wherever Home Assistant publishes that port, so treat it as
|
||||
sensitive: do not expose it directly to the public internet, and unmap the port
|
||||
in the add-on **Network** section if you do not want it reachable at all.
|
||||
|
||||
## Home Assistant MCP bridge
|
||||
|
||||
@@ -231,12 +129,9 @@ Persistent state is stored in the configured `data_location` (default
|
||||
|
||||
- Claude Desktop sign-in: `~/.config/Claude` (token encrypted via
|
||||
gnome-keyring; keyring DB in `~/.local/share/keyrings`)
|
||||
- Claude Code settings, hooks, sessions, plugins, and permission mode:
|
||||
`~/.claude`
|
||||
- Headroom, RTK, and TokenSave user state: their standard paths below the
|
||||
- Claude Code settings, hooks, sessions, and plugins: `~/.claude`
|
||||
- Headroom, RTK, and tokensave user state: their standard paths below the
|
||||
shared home
|
||||
- TokenSave repository indexes: `.tokensave/` inside each explicitly configured
|
||||
project
|
||||
|
||||
Volatile cache data is redirected to `/tmp/cache` through `$XDG_CACHE_HOME` and
|
||||
`$HOME/.cache`.
|
||||
|
||||
@@ -2,7 +2,7 @@ arch:
|
||||
- aarch64
|
||||
- amd64
|
||||
audio: true
|
||||
description: "Claude Desktop with Headroom, RTK, and TokenSave optimization"
|
||||
description: "Claude Desktop with Headroom MCP context compression and RTK acceleration"
|
||||
devices:
|
||||
- /dev/dri
|
||||
- /dev/dri/card0
|
||||
@@ -13,8 +13,8 @@ environment:
|
||||
AUTO_GPU: "1"
|
||||
FM_HOME: /data/data
|
||||
HOME: /data/data
|
||||
PGID: "1000"
|
||||
PUID: "1000"
|
||||
PGID: "0"
|
||||
PUID: "0"
|
||||
SELKIES_FRAMERATE: "30"
|
||||
START_DOCKER: "false"
|
||||
TITLE: Claude Desktop
|
||||
@@ -35,6 +35,8 @@ options:
|
||||
env_vars: []
|
||||
DNS_server: 8.8.8.8
|
||||
data_location: /data/data
|
||||
PGID: 0
|
||||
PUID: 0
|
||||
additional_apps: ""
|
||||
additional_pip: ""
|
||||
auto_update: true
|
||||
@@ -44,24 +46,19 @@ options:
|
||||
ha_mcp_token: ""
|
||||
github_token: ""
|
||||
github_username: ""
|
||||
enable_tools_health_report: true
|
||||
expose_headroom_dashboard: false
|
||||
headroom_wrap_claude_code: true
|
||||
install_caveman: false
|
||||
install_caveman: true
|
||||
install_github_cli: true
|
||||
install_headroom: true
|
||||
install_rtk: true
|
||||
install_tokensave: true
|
||||
permission_mode: auto
|
||||
tokensave_project_paths: []
|
||||
panel_admin: false
|
||||
panel_icon: mdi:robot-happy
|
||||
ports:
|
||||
3001/tcp: null
|
||||
8787/tcp: null
|
||||
8787/tcp: 8787
|
||||
ports_description:
|
||||
3001/tcp: Claude Desktop web interface
|
||||
8787/tcp: Optional Headroom dashboard and proxy
|
||||
8787/tcp: Headroom dashboard and proxy
|
||||
privileged:
|
||||
- SYS_ADMIN
|
||||
- DAC_READ_SEARCH
|
||||
@@ -74,35 +71,26 @@ 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?
|
||||
SUDO_PASSWORD: password?
|
||||
PGID: int
|
||||
PUID: int
|
||||
TZ: match([A-Z][a-z]*./[A-Z][a-z]*.)?
|
||||
additional_apps: str?
|
||||
additional_pip: str?
|
||||
cifsdomain: str?
|
||||
cifspassword: str?
|
||||
cifsusername: str?
|
||||
localdisks: str?
|
||||
networkdisks: str?
|
||||
auto_update: bool?
|
||||
github_email: str?
|
||||
enable_ha_mcp: bool?
|
||||
ha_mcp_url: str?
|
||||
ha_mcp_token: password?
|
||||
github_token: password?
|
||||
github_username: str?
|
||||
enable_tools_health_report: bool
|
||||
expose_headroom_dashboard: bool
|
||||
headroom_wrap_claude_code: bool
|
||||
install_caveman: bool
|
||||
install_github_cli: bool
|
||||
install_headroom: bool
|
||||
install_rtk: bool
|
||||
install_tokensave: bool
|
||||
permission_mode: list(strict|auto|bypass)
|
||||
tokensave_project_paths:
|
||||
- str
|
||||
slug: claude_desktop
|
||||
tmpfs: true
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "1.23"
|
||||
version: "1.17"
|
||||
video: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Hourly RTK + Headroom + TokenSave savings report to the add-on log.
|
||||
# Hourly rtk + headroom token-savings report to the add-on log (heartbeat + gains).
|
||||
# Seeded to /data/data/crontabs/root by init-crontab-config and run by svc-cron; edit the
|
||||
# persistent copy to customize. Output goes to /proc/1/fd/1 so it shows in the add-on log.
|
||||
0 * * * * /usr/local/bin/claude-gains-report.sh > /proc/1/fd/1 2>&1
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/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,10 +3,9 @@
|
||||
# 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.
|
||||
PUID="$(id -u abc)"
|
||||
PGID="$(id -g abc)"
|
||||
# Define user
|
||||
PUID=$(bashio::config "PUID")
|
||||
PGID=$(bashio::config "PGID")
|
||||
|
||||
# Check data location
|
||||
LOCATION="$(bashio::config 'data_location')"
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
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"
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
if ! bashio::config.true 'install_tokensave' || ! command -v git > /dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
declare -A REPOS_SEEN=()
|
||||
while IFS= read -r configured_path; do
|
||||
configured_path="${configured_path#"${configured_path%%[![:space:]]*}"}"
|
||||
configured_path="${configured_path%"${configured_path##*[![:space:]]}"}"
|
||||
[ -n "$configured_path" ] || continue
|
||||
|
||||
case "$configured_path" in
|
||||
/*) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
[ -d "$configured_path" ] || continue
|
||||
|
||||
# The one-shot safe.directory override is used only to discover the repository root.
|
||||
# Persist the resolved root in the shared runtime user's Git config before 82-claude_tools.sh
|
||||
# performs normal repository detection, avoiding Git's dubious-ownership rejection.
|
||||
repo_root="$(s6-setuidgid abc env HOME="$HOME" \
|
||||
git -c safe.directory='*' -C "$configured_path" rev-parse --show-toplevel 2> /dev/null || true)"
|
||||
[ -n "$repo_root" ] && [ "$repo_root" != "/" ] || continue
|
||||
[[ -z "${REPOS_SEEN[$repo_root]:-}" ]] || continue
|
||||
REPOS_SEEN[$repo_root]=1
|
||||
|
||||
if ! s6-setuidgid abc env HOME="$HOME" git config --global --get-all safe.directory \
|
||||
| grep -Fxq -- "$repo_root"; then
|
||||
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')
|
||||
@@ -7,18 +7,15 @@ PUID="$(if bashio::config.has_value 'PUID'; then bashio::config 'PUID'; else ech
|
||||
PGID="$(if bashio::config.has_value 'PGID'; then bashio::config 'PGID'; else echo '0'; fi)"
|
||||
mkdir -p "$HOME/.claude"
|
||||
|
||||
run_as_runtime_user() {
|
||||
s6-setuidgid abc env HOME="$HOME" "$@"
|
||||
}
|
||||
|
||||
CLAUDE_DESKTOP_COMMAND_FILE="/tmp/claude-desktop-command"
|
||||
DEFAULT_CLAUDE_DESKTOP_COMMAND='claude-desktop --no-sandbox --disable-dev-shm-usage --password-store=gnome-libsecret'
|
||||
printf '%s\n' "$DEFAULT_CLAUDE_DESKTOP_COMMAND" > "$CLAUDE_DESKTOP_COMMAND_FILE"
|
||||
|
||||
# Headroom's proxy routing works by setting ANTHROPIC_BASE_URL, which the Claude Desktop
|
||||
# Electron app force-overrides to the production endpoint (headroom #869). Desktop therefore
|
||||
# uses Headroom's MCP tools. Claude Code launches that resolve `claude` through PATH use the
|
||||
# add-on's /usr/local/bin/claude wrapper and can be transparently proxied when enabled.
|
||||
# headroom's "wrap"/proxy routing works by setting ANTHROPIC_BASE_URL, which the Claude Desktop
|
||||
# Electron app force-overrides to the production endpoint (headroom #869), so transparent
|
||||
# compression cannot be applied to the desktop launch. The integration that does work with
|
||||
# Claude Desktop is headroom's MCP server, which exposes the headroom_compress/headroom_retrieve/
|
||||
# headroom_stats tools inside the app.
|
||||
#
|
||||
# Register the add-on-managed MCP servers (headroom, tokensave, homeassistant) in both Claude
|
||||
# Desktop's config and Claude Code's user config (used by Desktop cowork/dispatch sessions).
|
||||
@@ -41,18 +38,10 @@ TOKENSAVE_ENABLED=false
|
||||
if bashio::config.true 'install_tokensave'; then
|
||||
if command -v tokensave &> /dev/null; then
|
||||
TOKENSAVE_ENABLED=true
|
||||
bashio::log.info "tokensave $(tokensave --version 2> /dev/null || true) available; configuring the complete Claude Code integration"
|
||||
# The upstream installer adds the MCP entry, PreToolUse/UserPromptSubmit/Stop hooks,
|
||||
# MCP permissions, global CLAUDE.md rules, and the global post-commit/checkout sync hook.
|
||||
run_as_runtime_user tokensave install --agent claude --git-hook yes \
|
||||
|| bashio::log.warning "tokensave Claude Code integration setup failed"
|
||||
bashio::log.info "tokensave $(tokensave --version 2> /dev/null || true) available; registering the tokensave MCP server"
|
||||
else
|
||||
bashio::log.warning "tokensave is not available"
|
||||
fi
|
||||
elif command -v tokensave &> /dev/null; then
|
||||
bashio::log.info "Removing the tokensave Claude Code integration"
|
||||
run_as_runtime_user tokensave uninstall --agent claude \
|
||||
|| bashio::log.warning "tokensave Claude Code integration removal failed"
|
||||
fi
|
||||
|
||||
HA_MCP_ENABLED=false
|
||||
@@ -91,10 +80,7 @@ MANAGED_BASENAMES = {
|
||||
|
||||
desired = {}
|
||||
if os.environ["HEADROOM_ENABLED"] == "true":
|
||||
desired["headroom"] = {
|
||||
"command": os.environ["HEADROOM_BIN"],
|
||||
"args": ["mcp", "serve", "--proxy-url", "http://127.0.0.1:8787"],
|
||||
}
|
||||
desired["headroom"] = {"command": os.environ["HEADROOM_BIN"], "args": ["mcp", "serve"]}
|
||||
if os.environ["TOKENSAVE_ENABLED"] == "true":
|
||||
desired["tokensave"] = {"command": os.environ["TOKENSAVE_BIN"], "args": ["serve"]}
|
||||
if os.environ["HA_MCP_ENABLED"] == "true":
|
||||
@@ -112,7 +98,6 @@ if os.environ["HA_MCP_ENABLED"] == "true":
|
||||
# under $HOME stay untouched because those are user-installed.
|
||||
HOME_PREFIX = os.path.expanduser("~") + os.sep
|
||||
|
||||
|
||||
def is_managed(name, entry):
|
||||
if not isinstance(entry, dict):
|
||||
return False
|
||||
@@ -121,7 +106,6 @@ def is_managed(name, entry):
|
||||
return False
|
||||
return os.path.basename(command) == MANAGED_BASENAMES[name]
|
||||
|
||||
|
||||
for config_var, stdio_type in (("CLAUDE_DESKTOP_CONFIG", False), ("CLAUDE_CODE_CONFIG", True)):
|
||||
path = Path(os.environ[config_var])
|
||||
try:
|
||||
@@ -161,56 +145,12 @@ for config_var, stdio_type in (("CLAUDE_DESKTOP_CONFIG", False), ("CLAUDE_CODE_C
|
||||
path.chmod(0o600)
|
||||
PY
|
||||
|
||||
# Initialize or incrementally sync only explicitly configured repositories. TokenSave deliberately
|
||||
# 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
|
||||
# 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
|
||||
|
||||
case "$configured_path" in
|
||||
/*) ;;
|
||||
*)
|
||||
bashio::log.warning "Skipping non-absolute tokensave_project_paths entry: ${configured_path}"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
if [ ! -d "$configured_path" ]; then
|
||||
bashio::log.warning "Skipping missing TokenSave project path: ${configured_path}"
|
||||
continue
|
||||
fi
|
||||
|
||||
repo_root="$(git -C "$configured_path" rev-parse --show-toplevel 2> /dev/null || true)"
|
||||
if [ -z "$repo_root" ] || [ "$repo_root" = "/" ]; then
|
||||
bashio::log.warning "Skipping TokenSave path that is not a supported Git repository: ${configured_path}"
|
||||
continue
|
||||
fi
|
||||
if [[ -n "${TOKENSAVE_REPOS_SEEN[$repo_root]:-}" ]]; then
|
||||
continue
|
||||
fi
|
||||
TOKENSAVE_REPOS_SEEN[$repo_root]=1
|
||||
|
||||
if [ -f "$repo_root/.tokensave/tokensave.db" ]; then
|
||||
bashio::log.info "Synchronizing TokenSave index: ${repo_root}"
|
||||
run_as_runtime_user tokensave sync "$repo_root" \
|
||||
|| bashio::log.warning "TokenSave sync failed for ${repo_root}"
|
||||
else
|
||||
bashio::log.info "Initializing TokenSave index: ${repo_root}"
|
||||
run_as_runtime_user tokensave init "$repo_root" \
|
||||
|| bashio::log.warning "TokenSave initialization failed for ${repo_root}"
|
||||
fi
|
||||
done < <(bashio::config.array 'tokensave_project_paths')
|
||||
fi
|
||||
|
||||
# Guide Claude to actually use the Headroom compression tools so the MCP integration produces
|
||||
# real savings when transparent proxying is unavailable. Managed, idempotent block appended to
|
||||
# the user's global CLAUDE.md; removed when Headroom is disabled.
|
||||
# Guide Claude to actually use the headroom compression tools so the MCP integration produces
|
||||
# real savings (otherwise the tools sit unused and `headroom savings` stays empty). Managed,
|
||||
# idempotent block appended to the user's global CLAUDE.md; removed when headroom is disabled.
|
||||
CLAUDE_MD="$HOME/.claude/CLAUDE.md"
|
||||
HEADROOM_GUIDE_BEGIN="<!-- BEGIN headroom (managed by claude_desktop addon) -->"
|
||||
if $HEADROOM_ENABLED; then
|
||||
if bashio::config.true 'install_headroom'; then
|
||||
mkdir -p "$(dirname "$CLAUDE_MD")"
|
||||
if ! { [ -f "$CLAUDE_MD" ] && grep -qF "$HEADROOM_GUIDE_BEGIN" "$CLAUDE_MD"; }; then
|
||||
bashio::log.info "Adding headroom usage guidance to CLAUDE.md"
|
||||
@@ -253,13 +193,14 @@ fi
|
||||
|
||||
if bashio::config.true 'install_rtk'; then
|
||||
if command -v rtk &> /dev/null; then
|
||||
bashio::log.info "Configuring rtk Claude Code integration"
|
||||
run_as_runtime_user env RTK_NONINTERACTIVE=1 rtk init -g \
|
||||
|| bashio::log.warning "rtk global files configuration failed"
|
||||
python3 - <<'PY' || bashio::log.warning "Unable to configure rtk hook automatically"
|
||||
if [ -f "$HOME/.claude/settings.json" ] && grep -q 'rtk hook claude' "$HOME/.claude/settings.json"; then
|
||||
bashio::log.info "rtk Claude Code hook already configured"
|
||||
else
|
||||
bashio::log.info "Configuring rtk Claude Code hook"
|
||||
RTK_NONINTERACTIVE=1 rtk init -g || bashio::log.warning "rtk global files configuration failed"
|
||||
python3 - <<'PY' || bashio::log.warning "Unable to configure rtk hook automatically"
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
path = Path.home() / ".claude" / "settings.json"
|
||||
try:
|
||||
data = json.loads(path.read_text()) if path.exists() else {}
|
||||
@@ -277,6 +218,7 @@ if not any("rtk hook claude" in json.dumps(entry) for entry in pre if isinstance
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(data, indent=2) + "\n")
|
||||
PY
|
||||
fi
|
||||
else
|
||||
bashio::log.warning "rtk is not available"
|
||||
fi
|
||||
@@ -348,8 +290,7 @@ if bashio::config.true 'install_caveman'; then
|
||||
bashio::log.info "caveman Claude Code plugin already configured"
|
||||
else
|
||||
bashio::log.info "Installing caveman Claude Code plugin"
|
||||
curl --connect-timeout 10 --max-time 60 -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash > /dev/null \
|
||||
|| bashio::log.warning "caveman install failed (offline?)"
|
||||
curl --connect-timeout 10 --max-time 60 -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash >/dev/null || bashio::log.warning "caveman install failed (offline?)"
|
||||
fi
|
||||
else
|
||||
bashio::log.info "Disabling caveman Claude Code plugin"
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
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)"
|
||||
PERMISSION_MODE="$(bashio::config 'permission_mode')"
|
||||
SETTINGS_PATH="$HOME/.claude/settings.json"
|
||||
STATE_PATH="$HOME/.claude/.addon-permission-mode.json"
|
||||
|
||||
case "$PERMISSION_MODE" in
|
||||
strict|auto|bypass) ;;
|
||||
*)
|
||||
bashio::log.warning "Unknown permission_mode '${PERMISSION_MODE}'; falling back to strict"
|
||||
PERMISSION_MODE="strict"
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "$(dirname "$SETTINGS_PATH")"
|
||||
PERMISSION_MODE="$PERMISSION_MODE" SETTINGS_PATH="$SETTINGS_PATH" STATE_PATH="$STATE_PATH" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
mode = os.environ["PERMISSION_MODE"]
|
||||
settings_path = Path(os.environ["SETTINGS_PATH"])
|
||||
state_path = Path(os.environ["STATE_PATH"])
|
||||
|
||||
try:
|
||||
settings = json.loads(settings_path.read_text()) if settings_path.exists() else {}
|
||||
except (OSError, json.JSONDecodeError):
|
||||
if settings_path.exists():
|
||||
settings_path.rename(settings_path.with_suffix(settings_path.suffix + ".bak"))
|
||||
settings = {}
|
||||
if not isinstance(settings, dict):
|
||||
settings = {}
|
||||
|
||||
try:
|
||||
state = json.loads(state_path.read_text()) if state_path.exists() else None
|
||||
except (OSError, json.JSONDecodeError):
|
||||
state = None
|
||||
if not isinstance(state, dict):
|
||||
state = None
|
||||
|
||||
permissions = settings.get("permissions")
|
||||
if not isinstance(permissions, dict):
|
||||
permissions = {}
|
||||
|
||||
if mode == "strict":
|
||||
# Restore the value that existed before the add-on first managed this setting.
|
||||
if state is not None:
|
||||
if state.get("previous_exists"):
|
||||
permissions["defaultMode"] = state.get("previous_value")
|
||||
else:
|
||||
permissions.pop("defaultMode", None)
|
||||
state_path.unlink(missing_ok=True)
|
||||
else:
|
||||
if state is None:
|
||||
state = {
|
||||
"previous_exists": "defaultMode" in permissions,
|
||||
"previous_value": permissions.get("defaultMode"),
|
||||
}
|
||||
state_path.write_text(json.dumps(state, indent=2) + "\n")
|
||||
state_path.chmod(0o600)
|
||||
permissions["defaultMode"] = "auto" if mode == "auto" else "bypassPermissions"
|
||||
|
||||
if permissions:
|
||||
settings["permissions"] = permissions
|
||||
else:
|
||||
settings.pop("permissions", None)
|
||||
|
||||
settings_path.write_text(json.dumps(settings, indent=2) + "\n")
|
||||
settings_path.chmod(0o600)
|
||||
PY
|
||||
|
||||
case "$PERMISSION_MODE" in
|
||||
strict)
|
||||
bashio::log.info "Claude Code permission mode: strict (normal prompts)"
|
||||
;;
|
||||
auto)
|
||||
bashio::log.info "Claude Code permission mode: auto (safe actions approved automatically)"
|
||||
;;
|
||||
bypass)
|
||||
bashio::log.warning "Claude Code permission mode: bypass (permission checks disabled for mounted data and available tools)"
|
||||
;;
|
||||
esac
|
||||
|
||||
chown -- "${PUID}:${PGID}" "$SETTINGS_PATH" 2> /dev/null || true
|
||||
if [ -e "$STATE_PATH" ]; then
|
||||
chown -- "${PUID}:${PGID}" "$STATE_PATH" 2> /dev/null || true
|
||||
fi
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,15 +1,11 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# Headroom optimization proxy — local backend for Claude Desktop MCP and Claude Code.
|
||||
declare port=8787
|
||||
declare host=127.0.0.1
|
||||
# Bind all interfaces so the dashboard is reachable on the mapped host port
|
||||
# (http://<ha-ip>:8787/dashboard). Local consumers keep using 127.0.0.1.
|
||||
declare host=0.0.0.0
|
||||
|
||||
# The dashboard is unauthenticated. Keep it container-local by default and bind all
|
||||
# interfaces only when the user explicitly opts in and maps port 8787.
|
||||
if bashio::config.true 'expose_headroom_dashboard'; then
|
||||
host=0.0.0.0
|
||||
fi
|
||||
|
||||
if bashio::config.true 'install_headroom' && command -v headroom > /dev/null 2>&1; then
|
||||
if bashio::config.true 'install_headroom' && command -v headroom >/dev/null 2>&1; then
|
||||
bashio::log.info "svc-headroom: starting local Headroom proxy on ${host}:${port}"
|
||||
exec s6-setuidgid abc headroom proxy --host "${host}" --port "${port}" --code-aware
|
||||
fi
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -o pipefail
|
||||
|
||||
REAL_CLAUDE="/usr/bin/claude"
|
||||
HEADROOM_BIN="/usr/local/bin/headroom"
|
||||
HEADROOM_URL="http://127.0.0.1:8787"
|
||||
PERMISSION_MODE="$(bashio::config 'permission_mode')"
|
||||
declare -a CLAUDE_PERMISSION_ARGS=()
|
||||
|
||||
case "$PERMISSION_MODE" in
|
||||
bypass)
|
||||
CLAUDE_PERMISSION_ARGS+=("--dangerously-skip-permissions")
|
||||
;;
|
||||
auto)
|
||||
CLAUDE_PERMISSION_ARGS+=("--permission-mode" "auto")
|
||||
;;
|
||||
strict|"")
|
||||
;;
|
||||
*)
|
||||
echo "claude wrapper: unknown permission_mode '${PERMISSION_MODE}', using strict mode" >&2
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ ! -x "$REAL_CLAUDE" ]; then
|
||||
echo "claude wrapper: ${REAL_CLAUDE} is unavailable" >&2
|
||||
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`
|
||||
# executable; otherwise it would resolve this wrapper recursively.
|
||||
export HEADROOM_CONTEXT_TOOL="rtk"
|
||||
exec env PATH="/usr/bin:/bin:/usr/local/bin" \
|
||||
"$HEADROOM_BIN" wrap claude --no-proxy -- \
|
||||
"${CLAUDE_PERMISSION_ARGS[@]}" "$@"
|
||||
fi
|
||||
echo "claude wrapper: Headroom proxy is unavailable; launching Claude Code directly" >&2
|
||||
fi
|
||||
|
||||
exec "$REAL_CLAUDE" "${CLAUDE_PERMISSION_ARGS[@]}" "$@"
|
||||
@@ -1,44 +1,34 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# Hourly RTK + Headroom + TokenSave token-savings snapshot for the add-on log.
|
||||
# Invoked by cron (see /defaults/crontabs/root); stdout is redirected to /proc/1/fd/1.
|
||||
# Each tool is reported independently so enabling Headroom cannot hide RTK or TokenSave data.
|
||||
# with-contenv supplies the configured persistent HOME.
|
||||
export NO_COLOR=1
|
||||
# Hourly rtk + headroom token-savings snapshot for the add-on log.
|
||||
# Invoked by cron (see /defaults/crontabs/root); its stdout is redirected to /proc/1/fd/1,
|
||||
# so the report appears in the add-on log. Doubles as a heartbeat: if the numbers stop
|
||||
# growing, the corresponding tool has stopped working.
|
||||
# with-contenv supplies HOME from the s6 envdir, so this honors a custom `data_location`
|
||||
# (see 20-folders.sh) instead of hardcoding /data/data; it also makes bashio::config
|
||||
# available for the install_headroom gate below.
|
||||
export NO_COLOR=1 # keep the add-on log free of ANSI color codes
|
||||
export PATH="/lsiopy/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
|
||||
|
||||
if ! bashio::config.true 'enable_tools_health_report'; then
|
||||
exit 0
|
||||
fi
|
||||
have_rtk=false; command -v rtk >/dev/null 2>&1 && have_rtk=true
|
||||
have_headroom=false; command -v headroom >/dev/null 2>&1 && have_headroom=true
|
||||
|
||||
rtk_enabled=false
|
||||
# headroom is pip-installed unconditionally at build time, so its binary is on PATH even
|
||||
# when install_headroom is off — gate on the same config svc-headroom checks, and only
|
||||
# fall back to have_headroom as a secondary availability guard.
|
||||
headroom_enabled=false
|
||||
tokensave_enabled=false
|
||||
|
||||
if bashio::config.true 'install_rtk' && command -v rtk > /dev/null 2>&1; then
|
||||
rtk_enabled=true
|
||||
fi
|
||||
if bashio::config.true 'install_headroom' && command -v headroom > /dev/null 2>&1; then
|
||||
if bashio::config.true 'install_headroom' && $have_headroom; then
|
||||
headroom_enabled=true
|
||||
fi
|
||||
if bashio::config.true 'install_tokensave' && command -v tokensave > /dev/null 2>&1; then
|
||||
tokensave_enabled=true
|
||||
fi
|
||||
|
||||
if ! $rtk_enabled && ! $headroom_enabled && ! $tokensave_enabled; then
|
||||
exit 0
|
||||
fi
|
||||
# Nothing to report if neither tool is active — stay quiet.
|
||||
if ! $have_rtk && ! $headroom_enabled; then exit 0; fi
|
||||
|
||||
echo "===== claude tools report $(date '+%Y-%m-%d %H:%M:%S') ====="
|
||||
echo "===== claude gains report $(date '+%Y-%m-%d %H:%M:%S') ====="
|
||||
if $headroom_enabled; then
|
||||
echo "--- headroom savings ---"
|
||||
headroom savings 2>&1 || echo "[warn] headroom savings failed"
|
||||
fi
|
||||
if $rtk_enabled; then
|
||||
elif $have_rtk; then
|
||||
echo "--- rtk gain ---"
|
||||
rtk gain 2>&1 || echo "[warn] rtk gain failed"
|
||||
fi
|
||||
if $tokensave_enabled; then
|
||||
echo "--- tokensave gain ---"
|
||||
tokensave gain --all --range 30d 2>&1 || echo "[warn] tokensave gain failed"
|
||||
fi
|
||||
echo "===== end claude tools report ====="
|
||||
echo "===== end gains report ====="
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# Diagnose installation, registration, routing, indexing, permissions, and recorded savings without
|
||||
# printing MCP environment values (which may contain the Home Assistant access token).
|
||||
# shellcheck shell=bash
|
||||
set +e
|
||||
set -o pipefail
|
||||
export NO_COLOR=1
|
||||
export PATH="/lsiopy/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
|
||||
|
||||
section() {
|
||||
printf '\n=== %s ===\n' "$1"
|
||||
}
|
||||
|
||||
section "Installed binaries"
|
||||
for tool in claude claude-desktop headroom rtk tokensave git gh rg jq shellcheck yamllint hadolint actionlint; do
|
||||
resolved="$(command -v "$tool" 2> /dev/null || true)"
|
||||
if [ -n "$resolved" ]; then
|
||||
printf '%-16s %s\n' "$tool" "$resolved"
|
||||
else
|
||||
printf '%-16s %s\n' "$tool" "MISSING"
|
||||
fi
|
||||
done
|
||||
|
||||
section "Configured switches"
|
||||
for option in permission_mode install_headroom headroom_wrap_claude_code expose_headroom_dashboard install_rtk install_tokensave install_caveman enable_tools_health_report; do
|
||||
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
|
||||
from pathlib import Path
|
||||
|
||||
path = Path.home() / ".claude/settings.json"
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
except FileNotFoundError:
|
||||
print("settings: MISSING")
|
||||
except Exception as exc:
|
||||
print(f"settings: INVALID: {exc}")
|
||||
else:
|
||||
permissions = data.get("permissions", {})
|
||||
if isinstance(permissions, dict):
|
||||
print(f"permissions.defaultMode: {permissions.get('defaultMode', '<upstream default>')}")
|
||||
else:
|
||||
print("permissions: INVALID")
|
||||
print(f"managed-state marker: {(Path.home() / '.claude/.addon-permission-mode.json').exists()}")
|
||||
PY
|
||||
|
||||
section "MCP registrations (environment values redacted)"
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
paths = [
|
||||
Path.home() / ".claude.json",
|
||||
Path.home() / ".config/Claude/claude_desktop_config.json",
|
||||
]
|
||||
for path in paths:
|
||||
print(path)
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
except FileNotFoundError:
|
||||
print(" MISSING")
|
||||
continue
|
||||
except Exception as exc:
|
||||
print(f" INVALID: {exc}")
|
||||
continue
|
||||
servers = data.get("mcpServers", {})
|
||||
if not isinstance(servers, dict) or not servers:
|
||||
print(" no MCP servers")
|
||||
continue
|
||||
for name, spec in sorted(servers.items()):
|
||||
if not isinstance(spec, dict):
|
||||
print(f" {name}: invalid entry")
|
||||
continue
|
||||
command = spec.get("command", "?")
|
||||
args = spec.get("args", [])
|
||||
server_type = spec.get("type", "")
|
||||
suffix = f" type={server_type}" if server_type else ""
|
||||
print(f" {name}: {command} {args}{suffix}")
|
||||
if spec.get("env"):
|
||||
print(" env: <redacted>")
|
||||
PY
|
||||
|
||||
section "Claude Code hooks"
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
path = Path.home() / ".claude/settings.json"
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
except FileNotFoundError:
|
||||
print("MISSING")
|
||||
raise SystemExit(0)
|
||||
except Exception as exc:
|
||||
print(f"INVALID: {exc}")
|
||||
raise SystemExit(0)
|
||||
|
||||
hooks = data.get("hooks", {})
|
||||
if not isinstance(hooks, dict) or not hooks:
|
||||
print("no hooks")
|
||||
raise SystemExit(0)
|
||||
for event, entries in hooks.items():
|
||||
print(event)
|
||||
if not isinstance(entries, list):
|
||||
print(" invalid entries")
|
||||
continue
|
||||
for entry in entries:
|
||||
matcher = entry.get("matcher", "*") if isinstance(entry, dict) else "?"
|
||||
commands = entry.get("hooks", []) if isinstance(entry, dict) else []
|
||||
rendered = []
|
||||
for command in commands if isinstance(commands, list) else []:
|
||||
if isinstance(command, dict):
|
||||
rendered.append(" ".join([str(command.get("command", "?")), *map(str, command.get("args", []))]))
|
||||
print(f" matcher={matcher}: {', '.join(rendered) or 'no command'}")
|
||||
PY
|
||||
|
||||
section "Headroom"
|
||||
if bashio::config.true 'install_headroom'; then
|
||||
curl -fsS --max-time 2 http://127.0.0.1:8787/health && echo || echo "proxy health: FAILED"
|
||||
headroom mcp status || true
|
||||
headroom savings || true
|
||||
else
|
||||
echo "disabled"
|
||||
fi
|
||||
|
||||
section "RTK"
|
||||
if bashio::config.true 'install_rtk'; then
|
||||
rtk gain || true
|
||||
else
|
||||
echo "disabled"
|
||||
fi
|
||||
|
||||
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
|
||||
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"
|
||||
elif [ -f "$repo_root/.tokensave/tokensave.db" ]; then
|
||||
s6-setuidgid abc env HOME="$HOME" tokensave status "$repo_root" --short || true
|
||||
else
|
||||
echo "${repo_root}: NOT INITIALIZED"
|
||||
fi
|
||||
done < <(bashio::config.array 'tokensave_project_paths')
|
||||
else
|
||||
echo "disabled"
|
||||
fi
|
||||
|
||||
section "Claude routing"
|
||||
printf 'PATH claude: %s\n' "$(command -v claude 2> /dev/null || true)"
|
||||
printf 'real claude: %s\n' "$([ -x /usr/bin/claude ] && echo /usr/bin/claude || echo MISSING)"
|
||||
if bashio::config.true 'headroom_wrap_claude_code'; then
|
||||
echo "PATH-based Claude Code launches are configured for Headroom wrapping."
|
||||
else
|
||||
echo "Claude Code Headroom wrapping is disabled; Headroom remains available through MCP."
|
||||
fi
|
||||
Reference in New Issue
Block a user