mirror of
https://github.com/alexbelgium/hassio-addons.git
synced 2026-10-06 16:21:36 +02:00
Compare commits
10 Commits
278eeb931b
...
89d6952c46
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89d6952c46 | ||
|
|
58c3fd61b8 | ||
|
|
f10a566b4c | ||
|
|
0ead28a7bf | ||
|
|
5f9ecb7b05 | ||
|
|
99a55c2109 | ||
|
|
b2ada7e4a7 | ||
|
|
2e0db91c2b | ||
|
|
16931942b9 | ||
|
|
3b67bef373 |
6
.github/workflows/archived_lint-checks.yaml
vendored
6
.github/workflows/archived_lint-checks.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
container: ghcr.io/hadolint/hadolint:latest-alpine
|
||||
steps:
|
||||
- name: ↩️ Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed_files
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
container: koalaman/shellcheck-alpine:latest
|
||||
steps:
|
||||
- name: ↩️ Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Get changed files
|
||||
id: changed_files
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: ↩️ Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Full git history is needed to get a proper list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
|
||||
2
.github/workflows/daily_README.yaml
vendored
2
.github/workflows/daily_README.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Install jq + yq (v4)
|
||||
run: |
|
||||
|
||||
4
.github/workflows/daily_ai_fix.yaml
vendored
4
.github/workflows/daily_ai_fix.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
environment: CR_PAT
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.AI_PR_TOKEN }}
|
||||
@@ -125,7 +125,7 @@ jobs:
|
||||
|
||||
- name: Analyse and fix
|
||||
if: steps.batch.outputs.count != '0'
|
||||
uses: anthropics/claude-code-action@44423bdec74b97d67543eb16c110546762c110b2 # v1
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange. The scheduled path
|
||||
|
||||
2
.github/workflows/generate_stargazer_map.yml
vendored
2
.github/workflows/generate_stargazer_map.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v7
|
||||
|
||||
2
.github/workflows/helper_stats_graphs.yaml
vendored
2
.github/workflows/helper_stats_graphs.yaml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Install apps
|
||||
run: |
|
||||
git pull --rebase origin master
|
||||
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
4
.github/workflows/on_claude_mention.yml
vendored
4
.github/workflows/on_claude_mention.yml
vendored
@@ -59,12 +59,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
uses: anthropics/claude-code-action@44423bdec74b97d67543eb16c110546762c110b2 # v1
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# AI_PR_TOKEN, not GITHUB_TOKEN, so a PR Claude opens triggers CI.
|
||||
|
||||
4
.github/workflows/on_issue_approved.yaml
vendored
4
.github/workflows/on_issue_approved.yaml
vendored
@@ -75,7 +75,7 @@ jobs:
|
||||
environment: CR_PAT
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.AI_PR_TOKEN }}
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
|
||||
- name: Execute the plan
|
||||
if: steps.bundle.outputs.has_plan == 'true'
|
||||
uses: anthropics/claude-code-action@44423bdec74b97d67543eb16c110546762c110b2 # v1
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||
|
||||
2
.github/workflows/on_issues.yml
vendored
2
.github/workflows/on_issues.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Assign issues
|
||||
run: |
|
||||
# Init
|
||||
|
||||
4
.github/workflows/on_issues_ai_triage.yaml
vendored
4
.github/workflows/on_issues_ai_triage.yaml
vendored
@@ -123,7 +123,7 @@ jobs:
|
||||
# (issues.opened, dispatch) never set claim, so they always proceed.
|
||||
- name: Checkout tooling
|
||||
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
||||
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
id: classify
|
||||
if: github.event_name != 'issue_comment' || steps.claim.outputs.go == 'true'
|
||||
continue-on-error: true
|
||||
uses: anthropics/claude-code-action@44423bdec74b97d67543eb16c110546762c110b2 # v1
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Without this the action falls back to the OIDC -> Claude App token
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Ping mapped submitter when add-on is mentioned
|
||||
env:
|
||||
|
||||
4
.github/workflows/on_pr_coderabbit.yml
vendored
4
.github/workflows/on_pr_coderabbit.yml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
|
||||
- name: Checkout PR branch
|
||||
if: steps.claim.outputs.go == 'true'
|
||||
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
fetch-depth: 0
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
|
||||
- name: Address CodeRabbit comments
|
||||
if: steps.claim.outputs.go == 'true'
|
||||
uses: anthropics/claude-code-action@44423bdec74b97d67543eb16c110546762c110b2 # v1
|
||||
uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
# Skip the OIDC -> Claude App token exchange, which 401s whenever
|
||||
|
||||
6
.github/workflows/onpr_check-pr.yaml
vendored
6
.github/workflows/onpr_check-pr.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
changedChangelogFiles: ${{ steps.changed-files.outputs.changelogs_files }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
# Need the merge commit's parents resolvable (HEAD^1 below): a depth-1 shallow
|
||||
# checkout truncates parent refs entirely at the boundary commit.
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
addon: ${{ fromJSON(needs.check-addon-changes.outputs.changedAddons) }}
|
||||
steps:
|
||||
- name: ↩️ Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: 🔎 Run Home Assistant Add-on Lint
|
||||
uses: frenck/action-addon-linter@v2
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
addon: ${{ fromJSON(needs.check-addon-changes.outputs.changedAddons) }}
|
||||
steps:
|
||||
- name: ↩️ Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Copy templates into addon build context
|
||||
env:
|
||||
|
||||
12
.github/workflows/onpush_builder.yaml
vendored
12
.github/workflows/onpush_builder.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
changedAddons: ${{ steps.find_addons.outputs.changed_addons }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
sanitizeCommitted: ${{ steps.sanitize_commit.outputs.committed }}
|
||||
sanitizeCommitSha: ${{ steps.sanitize_commit.outputs.commit_long_sha }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -112,7 +112,7 @@ jobs:
|
||||
matrix:
|
||||
addon: ${{ fromJSON(needs.detect-changed-addons.outputs.changedAddons) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
- name: Run Home Assistant Add-on Lint
|
||||
uses: frenck/action-addon-linter@v2
|
||||
with:
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
- arch: aarch64
|
||||
runner: ubuntu-24.04-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -335,7 +335,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -406,7 +406,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
4
.github/workflows/weekly_crlftolf.yaml
vendored
4
.github/workflows/weekly_crlftolf.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository contents
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Use action to check for CRLF endings
|
||||
uses: erclu/check-crlf@v1
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
runs-on: ubuntu-latest # Use a Linux runner
|
||||
steps:
|
||||
- name: Checkout repository contents
|
||||
uses: actions/checkout@v7 # Use the checkout action
|
||||
uses: actions/checkout@v7.0.1 # Use the checkout action
|
||||
- name: Find files with CRLF endings
|
||||
uses: erclu/check-crlf@v1.2.0 # Use the check-crlf action
|
||||
id: check-crlf # Assign an id to this step
|
||||
|
||||
2
.github/workflows/weekly_reduceimagesize.yml
vendored
2
.github/workflows/weekly_reduceimagesize.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Compress Images
|
||||
id: calibre
|
||||
|
||||
2
.github/workflows/weekly_stats.yaml
vendored
2
.github/workflows/weekly_stats.yaml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v7.0.1
|
||||
- name: Create stats
|
||||
run: |
|
||||
echo "Starting"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
|
||||
## 1.6.0.2 (2026-07-27)
|
||||
|
||||
- Fix base_url sed patterns rewriting *every* `base_url` key in Bazarr's config.yaml (radarr.base_url, sonarr.base_url, and any other configured integration), instead of only Bazarr's own under `general:`. This silently broke the Radarr/Sonarr connections inside Bazarr on every addon restart when ingress was enabled
|
||||
|
||||
## 1.6.0.1 (2026-07-27)
|
||||
|
||||
- Fix ingress: nginx rewrote Bazarr's redirects into an absolute `http://<host>:8099/...` URL, which the browser blocked as mixed content when Home Assistant is served over HTTPS. Redirects now stay relative and point at the ingress path
|
||||
|
||||
@@ -112,4 +112,4 @@ schema:
|
||||
slug: bazarr_nas
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons/tree/master/bazarr
|
||||
version: "1.6.0.1"
|
||||
version: "1.6.0.2"
|
||||
|
||||
@@ -35,16 +35,20 @@ if [ -f "$CONFIG_LOCATION" ]; then
|
||||
ingress_noauth)
|
||||
bashio::log.green "Ingress is enabled, authentication is disabled"
|
||||
bashio::log.yellow "WARNING : Make sure that the port is not exposed externally by your router to avoid a security risk !"
|
||||
# Set base_url (must start with / for Flask blueprint registration)
|
||||
sed -i "s| base_url:.*| base_url: /$slug|" "$CONFIG_LOCATION"
|
||||
# Set base_url (must start with / for Flask blueprint registration).
|
||||
# Scoped to the general: block only -- config.yaml also carries a
|
||||
# base_url under each configured *arr integration (radarr.base_url,
|
||||
# sonarr.base_url, ...) and those must not be touched.
|
||||
sed -i "/^general:/,/^[^ ]/{ s| base_url:.*| base_url: /$slug|; }" "$CONFIG_LOCATION"
|
||||
# Disable auth
|
||||
sed -i '/^auth:/,/^[^ ]/{ s/ type:.*/ type: null/ }' "$CONFIG_LOCATION"
|
||||
;;
|
||||
# Ingress mode, with authentication
|
||||
ingress_auth)
|
||||
bashio::log.green "Ingress is enabled, and external authentication is enabled"
|
||||
# Set base_url (must start with / for Flask blueprint registration)
|
||||
sed -i "s| base_url:.*| base_url: /$slug|" "$CONFIG_LOCATION"
|
||||
# Set base_url (must start with / for Flask blueprint registration).
|
||||
# Scoped to the general: block only -- see note above.
|
||||
sed -i "/^general:/,/^[^ ]/{ s| base_url:.*| base_url: /$slug|; }" "$CONFIG_LOCATION"
|
||||
# Enable Bazarr auth when leaving ingress_noauth
|
||||
sed -i '/^auth:/,/^[^ ]/{ s/ type:.*/ type: form/ }' "$CONFIG_LOCATION"
|
||||
;;
|
||||
@@ -52,7 +56,8 @@ if [ -f "$CONFIG_LOCATION" ]; then
|
||||
noingress_auth)
|
||||
bashio::log.green "Disabling ingress and enabling authentication"
|
||||
bashio::log.yellow "WARNING : Ingress is disabled so the app won't be available from HA itself !"
|
||||
sed -i "s/ base_url:.*/ base_url: ''/" "$CONFIG_LOCATION"
|
||||
# Scoped to the general: block only -- see note above.
|
||||
sed -i "/^general:/,/^[^ ]/{ s/ base_url:.*/ base_url: ''/; }" "$CONFIG_LOCATION"
|
||||
# Enable Bazarr auth when leaving ingress_noauth
|
||||
sed -i '/^auth:/,/^[^ ]/{ s/ type:.*/ type: form/ }' "$CONFIG_LOCATION"
|
||||
;;
|
||||
|
||||
@@ -15,10 +15,13 @@ bashio::net.wait_for "$port" localhost 900
|
||||
if [ -f "$CONFIG_LOCATION" ]; then
|
||||
if ! bashio::config.true "ingress_disabled"; then
|
||||
if ! bashio::config.has_value "connection_mode" || [ "$(bashio::config 'connection_mode')" != "noingress_auth" ]; then
|
||||
if ! grep -q "base_url: /$slug" "$CONFIG_LOCATION"; then
|
||||
if ! sed -n "/^general:/,/^[^ ]/ { /^ base_url: \/$slug$/p; }" "$CONFIG_LOCATION" | grep -q .; then
|
||||
bashio::log.warning "BaseUrl not set properly, restarting"
|
||||
# Must start with / for Flask blueprint registration
|
||||
sed -i "s| base_url:.*| base_url: /$slug|" "$CONFIG_LOCATION"
|
||||
# Must start with / for Flask blueprint registration. Scoped to
|
||||
# the general: block only -- config.yaml also carries a base_url
|
||||
# under each configured *arr integration (radarr.base_url,
|
||||
# sonarr.base_url, ...) and those must not be touched.
|
||||
sed -i "/^general:/,/^[^ ]/{ s| base_url:.*| base_url: /$slug|; }" "$CONFIG_LOCATION"
|
||||
bashio::addon.restart
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,17 @@
|
||||
|
||||
## 1.36.1 (27-07-2026)
|
||||
|
||||
- Fix the Codex CLI install failing on every boot with `Verified Codex <version> installation failed; Codex is unavailable this boot`, leaving `install_codex_cli` permanently non-functional. The download, its SHA-256 verification, and the extraction all succeeded; the chain broke at the final step, which validates the candidate binary by running `--version` as the `abc` runtime user. `mktemp -d` creates its directory `0700 root:root`, and `abc` cannot traverse a root-only directory, so executing the staged binary failed with `unable to exec: Permission denied` (exit 126) before it could be moved into place. Reproduced and fixed by making the staging directory traversable (`chmod 0755`) immediately after `mktemp`; verified on a live add-on container, where the same probe goes from exit 126 to success once the mode is widened. Nothing secret is staged there — the public release archive and the extracted binary, both world-readable upstream artifacts — and the existing `cleanup()` trap still removes the directory on exit. The validation deliberately keeps running as `abc` rather than root, so the binary is exercised as the identity that will actually run it.
|
||||
|
||||
## 1.36 (27-07-2026)
|
||||
|
||||
- Add optional OpenAI Codex CLI support, so a Claude session in this add-on can delegate work to ChatGPT Codex. Three parts: an `install_codex_cli` switch, a browserless way to activate a ChatGPT subscription on it, and an MCP registration that makes Codex callable as a tool from Claude.
|
||||
- **Install (`install_codex_cli`, default off).** Codex is deliberately *not* baked into the image: the Linux release binary is ~310 MB extracted, which is not worth carrying in every installation for an off-by-default option, and updating it would then require an add-on rebuild. The new `81-codex_cli.sh` cont-init script downloads the pinned static-musl release (`ARG`/`ENV CODEX_VERSION`, currently `0.145.0`) into `/data/codex/bin` and symlinks it onto `PATH`. `/data` is the add-on's own persistent volume — independent of the configurable `data_location` — so the ~113 MB download happens once, not per boot and not again after an add-on update unless the pinned release changes. The install prefix is outside `$HOME` on purpose: the managed-MCP merge in `82-claude_tools.sh` treats any server `command` under `$HOME` as user-installed and refuses to manage it, so an add-on-owned binary living there could never be updated or removed by the add-on. The download fails open like the Claude Desktop update check — an offline boot, a GitHub outage or a bad pin logs a warning and leaves any existing binary in place instead of aborting startup, and the new binary is validated by actually running `--version` from the staging directory before it replaces the current one, so a truncated download can never overwrite a working install. Staging deliberately happens under `/data` rather than the default `/tmp`: `/tmp` in this image is a RAM-backed tmpfs, so holding the 110 MB archive plus the 310 MB binary there during boot is a real risk on a small Home Assistant host, and it is mounted `noexec`, which would make the validation step impossible anyway. Staging on the destination filesystem also turns the final move into an atomic rename instead of a second 310 MB copy. Disabling the option is non-destructive: the binary and the completed sign-in are kept, so a disable/re-enable cycle needs neither another download nor another login.
|
||||
- **Subscription activation (`codex-login`).** Codex's default sign-in serves an OAuth callback on `localhost:1455` and expects a local browser, which cannot work here — the image ships no browser, and the add-on is reached through Home Assistant ingress. The new `/usr/local/bin/codex-login` helper runs `codex login --device-auth` instead, the flow OpenAI documents for headless machines: it prints a verification URL and a short-lived one-time code to approve on any other device, then polls until done. It can be run from the desktop's xterm (openbox right-click menu), from a Claude Code session, or from the container console, and it drops to the `abc` runtime user first so `~/.codex/auth.json` is not created root-owned — the same failure mode `83-github_cli.sh` documents for `~/.gitconfig`. Output is line-buffered so the URL and code appear immediately even without a TTY. If a session is already authenticated the helper reports the account and exits rather than starting a second flow. Credentials live in the persistent home, so the sign-in survives restarts and updates.
|
||||
- **MCP bridge.** `codex mcp-server` exposes Codex itself as a stdio MCP server (tools `codex` and `codex-reply`, verified by driving the 0.145.0 binary over stdio), which is registered through the *existing* managed-MCP merge in `82-claude_tools.sh` rather than a second copy of that logic — so it inherits the merge's idempotence, its refusal to clobber a user-customized entry, its removal-when-disabled behaviour, and the `0600` re-tightening of the token-bearing config files. Registration covers both Claude Code (`~/.claude.json`) and Claude Desktop, so every session type gets the tool. A managed `CLAUDE.md` block tells Claude what Codex is and when a second agent is worth the round-trip, since registering a server without guidance rarely gets it used — the same gap the Headroom block exists to close.
|
||||
- **Sandbox (`codex_sandbox_mode`, default `danger-full-access`).** Codex normally confines itself with a Linux OS sandbox (Landlock, or its bundled bubblewrap), which is unreliable inside a Home Assistant add-on container — and the container is already the security boundary. The default therefore bypasses Codex's own sandbox; `read-only` and `workspace-write` remain selectable. The chosen mode is applied in two places that are kept in sync: as root-level `-c` config overrides in the MCP registration (Codex forwards those to the MCP server, and they must precede the subcommand), and as a managed block at the top of `~/.codex/config.toml` so plain terminal `codex`/`codex exec` runs behave the same. That block is written at position 0 rather than appended, because a bare TOML key placed after a `[table]` header would silently become a key *of that table*. `approval_policy` is always `never` — an MCP- or cron-driven run has nobody to answer a prompt. Claude Code's own permission prompts still gate every `mcp__codex__*` call unless `permission_mode` is `bypass`.
|
||||
- `84-claude_runtime_ownership.sh` now also reconciles `~/.codex`, and `claude-tools-doctor.sh` reports the Codex binary, the installed-versus-pinned version, the two new switches, and the login status (which prints the auth mode, never the token).
|
||||
|
||||
## 1.35 (23-07-2026)
|
||||
|
||||
- Fix recurring "For your security, sign in again to keep using Claude." and the Claude app's dispatch tab showing this desktop as offline when opened from mobile first. Root cause (confirmed from `~/.config/Claude/logs/main.log` on a live install): the app launches with `--password-store=gnome-libsecret`, forcing Electron's libsecret/Secret-Service backend, but `gnome-keyring` was removed from the image in a previous commit because it prompted for a keyring password on first boot and blocked the app from launching. With the flag still forcing libsecret and no keyring daemon running, `safeStorage.isEncryptionAvailable()` is `false` — logs showed `session will not persist; app secrets fall back to plaintext` and `cannot store allowlist cache`. The un-persisted session then goes stale, failing the elevated-access OAuth check (`session_stale_relogin`) that the cowork/dispatch bridge needs, so the bridge is "parked until re-login" — which is what the Claude app surfaces as the desktop being offline, until a fresh sign-in (only completable from a computer, see `SIGN_IN.md` Problem A) un-parks it. `rootfs/defaults/autostart` now launches with `--password-store=basic` instead: Electron's built-in fixed-key store needs no daemon and never prompts, and persists under `$HOME/.config/Claude` (`/data/data`, persistent), so the session survives restarts and dispatch stays online regardless of which device connects first. A passwordless keyring was considered and rejected — it would live in the same persistent volume as the ciphertext, adding no real protection in this single-user self-hosted setup. `Dockerfile`'s stale comment (still describing gnome-keyring as installed) is corrected; the package stays removed.
|
||||
|
||||
@@ -94,7 +94,7 @@ 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" -o -name "ha-cli" \) -print -exec chmod +x {} \; && \
|
||||
\( -name "*.sh" -o -name "run" -o -name "finish" -o -name "ha-cli" -o -name "codex-login" \) -print -exec chmod +x {} \; && \
|
||||
chmod +x /usr/local/bin/claude
|
||||
|
||||
# Uses /bin for compatibility purposes
|
||||
|
||||
@@ -78,6 +78,8 @@ Git synchronization hooks. A repository is indexed only when it is listed in
|
||||
- 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.
|
||||
- Optional OpenAI Codex CLI, authenticated exclusively with a ChatGPT
|
||||
subscription and reachable from Claude through the native Codex MCP server.
|
||||
- Optional Home Assistant MCP bridge so Claude can query and control Home
|
||||
Assistant.
|
||||
- Independent hourly savings reports for Headroom, RTK, and TokenSave.
|
||||
@@ -105,6 +107,8 @@ Git synchronization hooks. A repository is indexed only when it is listed in
|
||||
| `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. |
|
||||
| `install_codex_cli` | `false` | Install the latest stable OpenAI Codex CLI at startup and register its native MCP server so Claude can delegate work to ChatGPT Codex. |
|
||||
| `codex_sandbox_mode` | `workspace-write` | Filesystem scope Codex runs with: `read-only`, `workspace-write`, or `danger-full-access`. |
|
||||
| `enable_tools_health_report` | `true` | Write independent Headroom, RTK, and TokenSave gains to the add-on log hourly. |
|
||||
| `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. |
|
||||
@@ -195,6 +199,77 @@ The dashboard is disabled externally by default. To expose it:
|
||||
The dashboard is unauthenticated. Do not publish this port to the public
|
||||
internet.
|
||||
|
||||
## Codex CLI
|
||||
|
||||
Setting `install_codex_cli: true` adds OpenAI's Codex CLI alongside Claude and
|
||||
registers `codex mcp-server` in both Claude Code and Claude Desktop. A Claude
|
||||
session can therefore delegate a task to ChatGPT Codex and read its result back
|
||||
through MCP.
|
||||
|
||||
Codex is not baked into the image because its Linux binary is large and the
|
||||
feature is off by default. At each startup, the add-on resolves the latest
|
||||
stable upstream release. It downloads the architecture-specific binary into
|
||||
persistent `/data/codex/bin` only when the installed release is missing or
|
||||
outdated, verifies the GitHub-published SHA-256 digest before extraction or
|
||||
execution, validates the staged binary with `--version`, and replaces the
|
||||
existing binary atomically. If release metadata or the download is unavailable,
|
||||
startup continues and a previously working installation is retained.
|
||||
|
||||
### Signing in with a ChatGPT subscription
|
||||
|
||||
The add-on has no browser, so use the bundled device-code helper:
|
||||
|
||||
```bash
|
||||
codex-login
|
||||
```
|
||||
|
||||
Run it from the desktop's xterm, a Claude Code session, or the container
|
||||
console. It prints a verification URL and one-time code that you approve on
|
||||
another device. Credentials are stored in the runtime user's persistent
|
||||
`~/.codex/auth.json`, so the sign-in survives restarts and add-on updates.
|
||||
|
||||
This integration is deliberately **subscription-only**. The managed launcher
|
||||
removes any inherited `OPENAI_API_KEY` and starts every Codex command—including
|
||||
`codex mcp-server`—with:
|
||||
|
||||
```toml
|
||||
forced_login_method = "chatgpt"
|
||||
cli_auth_credentials_store = "file"
|
||||
```
|
||||
|
||||
The launcher also removes caller-provided overrides for those two keys before
|
||||
starting Codex. The same values are maintained in `~/.codex/config.toml`.
|
||||
Consequently, the MCP server uses the ChatGPT Codex entitlement and cannot
|
||||
silently fall back to usage-based OpenAI API-key billing.
|
||||
|
||||
### Using Codex from Claude
|
||||
|
||||
Claude receives two native MCP tools:
|
||||
|
||||
- `mcp__codex__codex` starts a task. Pass a self-contained `prompt` and set
|
||||
`cwd` to the repository Codex should inspect. The result includes a
|
||||
`threadId`.
|
||||
- `mcp__codex__codex-reply` continues the same Codex thread with its
|
||||
`threadId`.
|
||||
|
||||
The add-on also installs managed Claude guidance recommending Codex for
|
||||
independent review, a second diagnosis, or a competing implementation rather
|
||||
than routine lookups. Codex consumption counts against the signed-in ChatGPT
|
||||
plan's Codex allowance.
|
||||
|
||||
### Sandbox scope
|
||||
|
||||
`codex_sandbox_mode` defaults to `workspace-write`, allowing implementation
|
||||
inside the supplied repository without granting unrestricted access to every
|
||||
mounted path. Select `read-only` for review-only delegation. Use
|
||||
`danger-full-access` only as an explicit fallback when Codex's nested Linux
|
||||
sandbox is unavailable in the Home Assistant add-on container and the mounted
|
||||
paths are trusted.
|
||||
|
||||
`approval_policy` is always `never`, because an MCP-driven Codex process has no
|
||||
interactive operator to answer a prompt. Claude Code's own permissions still
|
||||
gate the `mcp__codex__*` call unless `permission_mode` is `bypass`.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
Run the following inside the add-on through a custom script or container console:
|
||||
@@ -206,8 +281,8 @@ claude-tools-doctor.sh
|
||||
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.
|
||||
savings. It never prints MCP environment values or raw Codex authentication
|
||||
status because either can contain credentials or masked credential fragments.
|
||||
|
||||
The hourly report can also be invoked manually:
|
||||
|
||||
@@ -288,6 +363,8 @@ Persistent state is stored in the configured `data_location` (default
|
||||
shared home
|
||||
- TokenSave repository indexes: `.tokensave/` inside each explicitly configured
|
||||
project
|
||||
- Codex authentication and configuration: `~/.codex`; the verified executable
|
||||
and subscription-only launcher live in persistent `/data/codex/bin`
|
||||
|
||||
Volatile cache data is redirected to `/tmp/cache` through `$XDG_CACHE_HOME` and
|
||||
`$HOME/.cache`.
|
||||
|
||||
@@ -59,6 +59,8 @@ options:
|
||||
headroom_auto_compress: true
|
||||
headroom_wrap_claude_code: true
|
||||
install_caveman: false
|
||||
install_codex_cli: false
|
||||
codex_sandbox_mode: workspace-write
|
||||
install_github_cli: true
|
||||
install_headroom: true
|
||||
install_rtk: true
|
||||
@@ -107,6 +109,8 @@ schema:
|
||||
headroom_auto_compress: bool?
|
||||
headroom_wrap_claude_code: bool
|
||||
install_caveman: bool
|
||||
install_codex_cli: bool
|
||||
codex_sandbox_mode: list(read-only|workspace-write|danger-full-access)
|
||||
install_github_cli: bool
|
||||
install_headroom: bool
|
||||
install_rtk: bool
|
||||
@@ -118,5 +122,5 @@ slug: claude_desktop
|
||||
tmpfs: true
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "1.35"
|
||||
version: "1.36.1"
|
||||
video: true
|
||||
|
||||
328
claude_desktop/rootfs/etc/cont-init.d/81-codex_cli.sh
Executable file
328
claude_desktop/rootfs/etc/cont-init.d/81-codex_cli.sh
Executable file
@@ -0,0 +1,328 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
set -o pipefail
|
||||
|
||||
# OpenAI Codex CLI, installed on demand rather than baked into the image: the Linux release
|
||||
# binary is large and the option is off by default. Runs before 82-claude_tools.sh so the binary
|
||||
# exists when that script registers the `codex` MCP server.
|
||||
#
|
||||
# The install prefix is /data/codex, NOT $HOME/.codex/bin: /data is persistent regardless of the
|
||||
# configurable data_location, and the managed MCP merge treats commands under $HOME as
|
||||
# user-installed. Codex state (auth.json, config.toml) remains in the runtime user's home.
|
||||
CODEX_ROOT="/data/codex"
|
||||
CODEX_PREFIX="${CODEX_ROOT}/bin"
|
||||
CODEX_BIN="${CODEX_PREFIX}/codex"
|
||||
CODEX_REAL="${CODEX_PREFIX}/codex-real"
|
||||
CODEX_STAMP="${CODEX_PREFIX}/.version"
|
||||
CODEX_LINK="/usr/local/bin/codex"
|
||||
CODEX_RELEASE_API="https://api.github.com/repos/openai/codex/releases/latest"
|
||||
|
||||
RUNTIME_HOME="$(getent passwd abc | cut -d: -f6)"
|
||||
if [ -z "$RUNTIME_HOME" ]; then
|
||||
bashio::log.warning "Unable to resolve the abc runtime home; using /data/data"
|
||||
RUNTIME_HOME="/data/data"
|
||||
fi
|
||||
|
||||
run_as_runtime_user() {
|
||||
s6-setuidgid abc env HOME="$RUNTIME_HOME" CODEX_HOME="$RUNTIME_HOME/.codex" "$@"
|
||||
}
|
||||
|
||||
if ! bashio::config.true 'install_codex_cli'; then
|
||||
# Non-destructive: preserve the binary and completed ChatGPT sign-in for a later re-enable.
|
||||
# 82-claude_tools.sh removes only the MCP registration and managed guidance.
|
||||
bashio::log.info "Codex CLI disabled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
case "$(uname -m)" in
|
||||
x86_64) CODEX_TARGET="x86_64-unknown-linux-musl" ;;
|
||||
aarch64 | arm64) CODEX_TARGET="aarch64-unknown-linux-musl" ;;
|
||||
*)
|
||||
bashio::log.warning "Codex CLI has no release binary for $(uname -m); skipping"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
CODEX_ASSET="codex-${CODEX_TARGET}.tar.gz"
|
||||
mkdir -p "$CODEX_PREFIX"
|
||||
|
||||
# Migrate the PR's earlier direct-binary layout to the enforced wrapper layout without another
|
||||
# download. The real binary is kept separately; `codex` becomes a small launcher that always
|
||||
# forces ChatGPT subscription authentication and removes any inherited API key.
|
||||
if [ ! -x "$CODEX_REAL" ] \
|
||||
&& [ -x "$CODEX_BIN" ] \
|
||||
&& run_as_runtime_user "$CODEX_BIN" --version > /dev/null 2>&1; then
|
||||
mv -f "$CODEX_BIN" "$CODEX_REAL"
|
||||
fi
|
||||
|
||||
# Resolve the latest stable release and its GitHub-published SHA-256 digest on every boot. This
|
||||
# follows upstream updates without pinning a version, while downloading the large asset only when
|
||||
# the installed version changes. A metadata outage never replaces or removes a working binary.
|
||||
codex_tmp="$(mktemp -d -p "$CODEX_ROOT")"
|
||||
# mktemp always creates 0700 root:root here, but the candidate binary is validated by running it
|
||||
# as the abc runtime user, which cannot traverse a root-only directory — that made every install
|
||||
# fail at the --version step with "unable to exec: Permission denied" (exit 126) and report
|
||||
# "Codex is unavailable this boot". Make the staging directory traversable. Nothing secret is
|
||||
# staged here: it holds the public release archive and the extracted binary, both of which are
|
||||
# world-readable upstream artifacts, and cleanup() removes the directory on exit.
|
||||
chmod 0755 "$codex_tmp"
|
||||
cleanup() {
|
||||
rm -rf "$codex_tmp"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
release_metadata="${codex_tmp}/release.json"
|
||||
release_info=""
|
||||
if curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 30 \
|
||||
-o "$release_metadata" "$CODEX_RELEASE_API"; then
|
||||
release_info="$(
|
||||
CODEX_ASSET="$CODEX_ASSET" python3 - "$release_metadata" <<'PY' 2> /dev/null || true
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
metadata = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
||||
tag = metadata.get("tag_name", "")
|
||||
if not isinstance(tag, str) or not tag.startswith("rust-v"):
|
||||
raise SystemExit("unexpected release tag")
|
||||
version = tag.removeprefix("rust-v")
|
||||
if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+(?:-(?:alpha|beta)(?:\.[0-9]+){0,2})?", version):
|
||||
raise SystemExit("unexpected release version")
|
||||
|
||||
asset_name = os.environ["CODEX_ASSET"]
|
||||
asset = next(
|
||||
(
|
||||
item
|
||||
for item in metadata.get("assets", [])
|
||||
if isinstance(item, dict) and item.get("name") == asset_name
|
||||
),
|
||||
None,
|
||||
)
|
||||
if asset is None:
|
||||
raise SystemExit("release asset missing")
|
||||
digest = asset.get("digest", "")
|
||||
if not isinstance(digest, str) or not re.fullmatch(r"sha256:[0-9a-fA-F]{64}", digest):
|
||||
raise SystemExit("release asset has no valid SHA-256 digest")
|
||||
url = asset.get("browser_download_url", "")
|
||||
if not isinstance(url, str) or not url.startswith("https://github.com/openai/codex/releases/download/"):
|
||||
raise SystemExit("unexpected release asset URL")
|
||||
|
||||
print(f"{version}\t{digest.removeprefix('sha256:').lower()}\t{url}")
|
||||
PY
|
||||
)"
|
||||
fi
|
||||
|
||||
if [ -z "$release_info" ]; then
|
||||
if [ -x "$CODEX_REAL" ] && run_as_runtime_user "$CODEX_REAL" --version > /dev/null 2>&1; then
|
||||
bashio::log.warning "Unable to resolve the latest verified Codex release; keeping the existing install"
|
||||
else
|
||||
bashio::log.warning "Unable to resolve the latest verified Codex release; Codex is unavailable this boot"
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
IFS=$'\t' read -r CODEX_WANTED CODEX_SHA256 CODEX_URL <<< "$release_info"
|
||||
|
||||
if [ -x "$CODEX_REAL" ] \
|
||||
&& [ "$(cat "$CODEX_STAMP" 2> /dev/null || true)" = "$CODEX_WANTED" ] \
|
||||
&& run_as_runtime_user "$CODEX_REAL" --version > /dev/null 2>&1; then
|
||||
bashio::log.info "Codex CLI ${CODEX_WANTED} already installed (latest stable)"
|
||||
else
|
||||
bashio::log.info "Installing latest stable Codex CLI ${CODEX_WANTED} (${CODEX_TARGET}); this is a large one-time download"
|
||||
archive="${codex_tmp}/${CODEX_ASSET}"
|
||||
extracted="${codex_tmp}/codex-${CODEX_TARGET}"
|
||||
|
||||
# Fail open for add-on startup but fail closed for the candidate binary: its official
|
||||
# release digest must match before extraction or execution, and replacement happens only
|
||||
# after the staged binary successfully runs.
|
||||
if curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 10 --max-time 600 \
|
||||
-o "$archive" "$CODEX_URL" \
|
||||
&& printf '%s %s\n' "$CODEX_SHA256" "$archive" | sha256sum -c - > /dev/null \
|
||||
&& tar -xzf "$archive" -C "$codex_tmp" \
|
||||
&& [ -f "$extracted" ] \
|
||||
&& chmod 0755 "$extracted" \
|
||||
&& run_as_runtime_user "$extracted" --version > /dev/null 2>&1 \
|
||||
&& mv -f "$extracted" "$CODEX_REAL"; then
|
||||
printf '%s' "$CODEX_WANTED" > "$CODEX_STAMP"
|
||||
bashio::log.info "Codex CLI installed: $("$CODEX_REAL" --version 2> /dev/null || echo unknown)"
|
||||
elif [ -x "$CODEX_REAL" ]; then
|
||||
bashio::log.warning "Verified Codex ${CODEX_WANTED} installation failed; keeping the existing install"
|
||||
else
|
||||
bashio::log.warning "Verified Codex ${CODEX_WANTED} installation failed; Codex is unavailable this boot"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$CODEX_REAL" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Every Codex entry point, including the MCP server launched by Claude, goes through this wrapper.
|
||||
# This is an execution-time guarantee in addition to the managed config below: API-key billing
|
||||
# cannot be selected even if an API key is present in the surrounding environment. Caller-provided
|
||||
# overrides for the two authentication guards are stripped before the forced root-level overrides
|
||||
# are inserted; root-level -c flags must precede Codex subcommands such as `mcp-server`.
|
||||
{
|
||||
printf '#!/usr/bin/env bash\n'
|
||||
printf 'CODEX_REAL=%q\n' "$CODEX_REAL"
|
||||
cat <<'SH'
|
||||
RUNTIME_HOME="$(getent passwd abc | cut -d: -f6)"
|
||||
if [ -z "$RUNTIME_HOME" ]; then
|
||||
echo "codex: unable to resolve the abc runtime home" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
is_managed_override() {
|
||||
local assignment="$1"
|
||||
local key="${assignment%%=*}"
|
||||
key="${key//[[:space:]]/}"
|
||||
case "$key" in
|
||||
forced_login_method | cli_auth_credentials_store) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
filtered_args=()
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-c | --config)
|
||||
if [ "$#" -lt 2 ]; then
|
||||
filtered_args+=("$1")
|
||||
shift
|
||||
continue
|
||||
fi
|
||||
if is_managed_override "$2"; then
|
||||
shift 2
|
||||
continue
|
||||
fi
|
||||
filtered_args+=("$1" "$2")
|
||||
shift 2
|
||||
;;
|
||||
--config=*)
|
||||
assignment="${1#--config=}"
|
||||
if ! is_managed_override "$assignment"; then
|
||||
filtered_args+=("$1")
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
-c*)
|
||||
assignment="${1#-c}"
|
||||
if ! is_managed_override "$assignment"; then
|
||||
filtered_args+=("$1")
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
filtered_args+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
forced_args=(
|
||||
-c 'forced_login_method="chatgpt"'
|
||||
-c 'cli_auth_credentials_store="file"'
|
||||
)
|
||||
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
exec s6-setuidgid abc env -u OPENAI_API_KEY \
|
||||
HOME="$RUNTIME_HOME" CODEX_HOME="$RUNTIME_HOME/.codex" \
|
||||
"$CODEX_REAL" "${forced_args[@]}" "${filtered_args[@]}"
|
||||
fi
|
||||
|
||||
unset OPENAI_API_KEY
|
||||
export HOME="$RUNTIME_HOME"
|
||||
export CODEX_HOME="$RUNTIME_HOME/.codex"
|
||||
exec "$CODEX_REAL" "${forced_args[@]}" "${filtered_args[@]}"
|
||||
SH
|
||||
} > "$CODEX_BIN"
|
||||
chmod 0755 "$CODEX_BIN"
|
||||
|
||||
chown -R -- "$(id -u abc):$(id -g abc)" "$CODEX_ROOT" \
|
||||
|| bashio::log.warning "Unable to set ownership on ${CODEX_ROOT}"
|
||||
ln -sfn "$CODEX_BIN" "$CODEX_LINK"
|
||||
|
||||
# Manage the root-level defaults used by terminal Codex and by `codex mcp-server`.
|
||||
#
|
||||
# `forced_login_method = "chatgpt"` makes subscription authentication the only permitted login
|
||||
# method, so an inherited OPENAI_API_KEY cannot silently switch this integration to API billing.
|
||||
# File storage is explicit because the container has no supported OS keyring.
|
||||
#
|
||||
# The managed block must be first: a bare TOML key after a [table] header belongs to that table.
|
||||
# Existing top-level definitions of the managed keys are removed before insertion; retaining them
|
||||
# would create duplicate keys and make the entire Codex configuration invalid.
|
||||
CODEX_SANDBOX_MODE="$(bashio::config 'codex_sandbox_mode' 'workspace-write')"
|
||||
run_as_runtime_user mkdir -p "$RUNTIME_HOME/.codex"
|
||||
CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" RUNTIME_HOME="$RUNTIME_HOME" \
|
||||
run_as_runtime_user python3 - <<'PY' \
|
||||
|| bashio::log.warning "Unable to update the managed Codex configuration block"
|
||||
import os
|
||||
import re
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
BEGIN = "# BEGIN managed by claude_desktop addon"
|
||||
END = "# END managed by claude_desktop addon"
|
||||
MANAGED_KEYS = {
|
||||
"sandbox_mode",
|
||||
"approval_policy",
|
||||
"forced_login_method",
|
||||
"cli_auth_credentials_store",
|
||||
}
|
||||
|
||||
block = "\n".join(
|
||||
[
|
||||
BEGIN,
|
||||
"# Managed defaults for terminal and MCP-driven Codex runs.",
|
||||
f'sandbox_mode = "{os.environ["CODEX_SANDBOX_MODE"]}"',
|
||||
'approval_policy = "never"',
|
||||
"# Require ChatGPT subscription OAuth; do not fall back to API-key billing.",
|
||||
'forced_login_method = "chatgpt"',
|
||||
"# This container has no supported OS keyring; keep OAuth credentials in auth.json.",
|
||||
'cli_auth_credentials_store = "file"',
|
||||
END,
|
||||
]
|
||||
)
|
||||
|
||||
path = Path(os.environ["RUNTIME_HOME"]) / ".codex" / "config.toml"
|
||||
original = path.read_text(encoding="utf-8") if path.exists() else ""
|
||||
rest = re.sub(
|
||||
rf"{re.escape(BEGIN)}.*?{re.escape(END)}\n?",
|
||||
"",
|
||||
original,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
|
||||
table_header = re.compile(r"^\s*\[\[?.+?\]\]?\s*(?:#.*)?$")
|
||||
assignment = re.compile(
|
||||
r'''^\s*(?P<key>[A-Za-z0-9_-]+|"[^"]+"|'[^']+')\s*='''
|
||||
)
|
||||
kept = []
|
||||
at_top_level = True
|
||||
for line in rest.splitlines(keepends=True):
|
||||
if at_top_level and table_header.match(line):
|
||||
at_top_level = False
|
||||
match = assignment.match(line) if at_top_level else None
|
||||
if match:
|
||||
key = match.group("key")
|
||||
if key[:1] in {'"', "'"}:
|
||||
key = key[1:-1]
|
||||
if key in MANAGED_KEYS:
|
||||
continue
|
||||
kept.append(line)
|
||||
|
||||
remainder = "".join(kept).lstrip("\n")
|
||||
new = block + "\n" + (("\n" + remainder) if remainder else "")
|
||||
tomllib.loads(new)
|
||||
if new != original:
|
||||
path.write_text(new, encoding="utf-8")
|
||||
path.chmod(0o600)
|
||||
PY
|
||||
|
||||
if [ -f "$RUNTIME_HOME/.codex/auth.json" ]; then
|
||||
bashio::log.info "Codex CLI is signed in with stored ChatGPT credentials"
|
||||
else
|
||||
bashio::log.info "Codex CLI is not signed in yet; run 'codex-login' to activate your ChatGPT subscription"
|
||||
fi
|
||||
@@ -293,6 +293,20 @@ if $TOKENSAVE_ENABLED; then
|
||||
done <<< "$TOKENSAVE_PROJECT_PATHS"
|
||||
fi
|
||||
|
||||
# Codex CLI is installed by 81-codex_cli.sh into /data/codex/bin — deliberately outside $HOME,
|
||||
# because is_managed() below treats any command under $HOME as user-installed.
|
||||
CODEX_BIN="/data/codex/bin/codex"
|
||||
CODEX_ENABLED=false
|
||||
CODEX_SANDBOX_MODE="$(bashio::config 'codex_sandbox_mode' 'workspace-write')"
|
||||
if bashio::config.true 'install_codex_cli'; then
|
||||
if [ -x "$CODEX_BIN" ]; then
|
||||
CODEX_ENABLED=true
|
||||
bashio::log.info "codex $("$CODEX_BIN" --version 2> /dev/null || true) available; registering the codex MCP server (sandbox: ${CODEX_SANDBOX_MODE})"
|
||||
else
|
||||
bashio::log.warning "codex is not available"
|
||||
fi
|
||||
fi
|
||||
|
||||
HA_MCP_ENABLED=false
|
||||
HA_MCP_URL=""
|
||||
HA_MCP_TOKEN=""
|
||||
@@ -314,6 +328,7 @@ fi
|
||||
HEADROOM_ENABLED="$HEADROOM_ENABLED" HEADROOM_BIN="$(command -v headroom || echo headroom)" \
|
||||
HEADROOM_HF_HOME="${HOME}/.headroom/hf" \
|
||||
TOKENSAVE_ENABLED="$TOKENSAVE_ENABLED" TOKENSAVE_BIN="$(command -v tokensave || echo tokensave)" \
|
||||
CODEX_ENABLED="$CODEX_ENABLED" CODEX_BIN="$CODEX_BIN" CODEX_SANDBOX_MODE="$CODEX_SANDBOX_MODE" \
|
||||
HA_MCP_ENABLED="$HA_MCP_ENABLED" HA_MCP_URL="$HA_MCP_URL" HA_MCP_TOKEN="$HA_MCP_TOKEN" \
|
||||
MCP_PROXY_BIN="$(command -v mcp-proxy || echo mcp-proxy)" \
|
||||
CLAUDE_DESKTOP_CONFIG="$CLAUDE_DESKTOP_CONFIG" CLAUDE_CODE_CONFIG="$CLAUDE_CODE_CONFIG" \
|
||||
@@ -326,6 +341,7 @@ MANAGED_BASENAMES = {
|
||||
"headroom": "headroom",
|
||||
"tokensave": "tokensave",
|
||||
"homeassistant": "mcp-proxy",
|
||||
"codex": "codex",
|
||||
}
|
||||
|
||||
desired = {}
|
||||
@@ -341,6 +357,25 @@ if os.environ["HEADROOM_ENABLED"] == "true":
|
||||
}
|
||||
if os.environ["TOKENSAVE_ENABLED"] == "true":
|
||||
desired["tokensave"] = {"command": os.environ["TOKENSAVE_BIN"], "args": ["serve"]}
|
||||
if os.environ["CODEX_ENABLED"] == "true":
|
||||
# `codex mcp-server` exposes Codex itself as an stdio MCP server (tools: codex, codex-reply),
|
||||
# which is what lets a Claude session hand a task to ChatGPT Codex. The sandbox/approval
|
||||
# policy is pinned with root-level `-c` overrides, which Codex forwards to the MCP server;
|
||||
# they must precede the subcommand. approval_policy is always "never" because an MCP-driven
|
||||
# run has nobody to answer a prompt. The sandbox defaults to workspace-write; users can opt
|
||||
# into danger-full-access explicitly if the nested sandbox is unavailable in their container.
|
||||
# 81-codex_cli.sh writes the same values into ~/.codex/config.toml so plain terminal `codex`
|
||||
# runs behave identically.
|
||||
desired["codex"] = {
|
||||
"command": os.environ["CODEX_BIN"],
|
||||
"args": [
|
||||
"-c",
|
||||
f'sandbox_mode="{os.environ["CODEX_SANDBOX_MODE"]}"',
|
||||
"-c",
|
||||
'approval_policy="never"',
|
||||
"mcp-server",
|
||||
],
|
||||
}
|
||||
if os.environ["HA_MCP_ENABLED"] == "true":
|
||||
# Home Assistant's MCP Server integration speaks stateless Streamable HTTP on /api/mcp;
|
||||
# mcp-proxy defaults to SSE, so the transport flags are required.
|
||||
@@ -531,6 +566,32 @@ else
|
||||
manage_claude_md_block ha-api-helper remove
|
||||
fi
|
||||
|
||||
# Registering the MCP server is not enough on its own: without guidance the model rarely reaches
|
||||
# for a second agent, the same gap the Headroom block above exists to close.
|
||||
if $CODEX_ENABLED; then
|
||||
manage_claude_md_block codex add <<'MD'
|
||||
## Delegating to ChatGPT Codex
|
||||
|
||||
The `codex` MCP server runs OpenAI's Codex agent locally, signed in with the user's ChatGPT
|
||||
subscription. It is a genuinely independent second agent — a different model family, reading the
|
||||
files itself — not a search tool. It is slow and costs the user's ChatGPT quota, so use it when a
|
||||
second opinion is worth minutes, not for routine lookups.
|
||||
|
||||
Good uses: an independent review of a design or a risky change before it lands; a second
|
||||
diagnosis of a bug you have a theory about but cannot confirm; a competing implementation of a
|
||||
self-contained piece you can then compare against your own.
|
||||
|
||||
Call `mcp__codex__codex` with `prompt` and always set `cwd` to the repository being discussed —
|
||||
Codex reads the files itself, so it needs the right working directory and enough context in the
|
||||
prompt to act without seeing this conversation. Continue an exchange with
|
||||
`mcp__codex__codex-reply` (note the hyphen) using the `threadId` it returned, rather than
|
||||
starting a fresh `codex` call. Treat its answers as a peer's opinion: verify claims about this
|
||||
codebase before acting on them.
|
||||
MD
|
||||
else
|
||||
manage_claude_md_block codex remove
|
||||
fi
|
||||
|
||||
if bashio::config.true 'install_rtk'; then
|
||||
if command -v rtk &> /dev/null; then
|
||||
bashio::log.info "Configuring rtk Claude Code integration"
|
||||
|
||||
@@ -2,14 +2,22 @@
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
# Earlier configuration scripts intentionally run as root. 20-folders.sh remapped abc to the
|
||||
# effective runtime identity (never root in bypass mode, where Claude Code refuses to run as
|
||||
# root). Reconcile ownership with that identity after all Claude configuration writes are
|
||||
# complete, as a safety net in case any intermediate step re-owned a managed path.
|
||||
# Earlier configuration scripts intentionally run as root. Reconcile the paths written by those
|
||||
# scripts with the final abc runtime identity and its configured persistent home.
|
||||
RUNTIME_UID="$(id -u abc)"
|
||||
RUNTIME_GID="$(id -g abc)"
|
||||
RUNTIME_HOME="$(getent passwd abc | cut -d: -f6)"
|
||||
|
||||
for managed_path in "$HOME/.claude" "$HOME/.claude.json" "$HOME/.config/Claude"; do
|
||||
if [ -z "$RUNTIME_HOME" ]; then
|
||||
bashio::log.warning "Unable to resolve the abc runtime home; skipping runtime ownership reconciliation"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
for managed_path in \
|
||||
"$RUNTIME_HOME/.claude" \
|
||||
"$RUNTIME_HOME/.claude.json" \
|
||||
"$RUNTIME_HOME/.config/Claude" \
|
||||
"$RUNTIME_HOME/.codex"; 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"
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
#!/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).
|
||||
# printing MCP environment values or authentication material.
|
||||
# shellcheck shell=bash
|
||||
set +e
|
||||
set -o pipefail
|
||||
export NO_COLOR=1
|
||||
export PATH="/lsiopy/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
|
||||
|
||||
RUNTIME_HOME="$(getent passwd abc | cut -d: -f6)"
|
||||
if [ -z "$RUNTIME_HOME" ]; then
|
||||
RUNTIME_HOME="/data/data"
|
||||
fi
|
||||
|
||||
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
|
||||
for tool in claude claude-desktop headroom rtk tokensave codex 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"
|
||||
@@ -22,13 +27,14 @@ for tool in claude claude-desktop headroom rtk tokensave git gh rg jq shellcheck
|
||||
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
|
||||
for option in permission_mode install_headroom headroom_wrap_claude_code expose_headroom_dashboard install_rtk install_tokensave install_codex_cli codex_sandbox_mode 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' "abc runtime home" "$RUNTIME_HOME"
|
||||
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
|
||||
@@ -39,11 +45,12 @@ if [ "$(bashio::config 'permission_mode')" = "bypass" ]; then
|
||||
fi
|
||||
|
||||
section "Claude Code permission state"
|
||||
python3 - <<'PY'
|
||||
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
path = Path.home() / ".claude/settings.json"
|
||||
path = Path(os.environ["RUNTIME_HOME"]) / ".claude/settings.json"
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
except FileNotFoundError:
|
||||
@@ -59,13 +66,15 @@ else:
|
||||
PY
|
||||
|
||||
section "MCP registrations (environment values redacted)"
|
||||
python3 - <<'PY'
|
||||
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
home = Path(os.environ["RUNTIME_HOME"])
|
||||
paths = [
|
||||
Path.home() / ".claude.json",
|
||||
Path.home() / ".config/Claude/claude_desktop_config.json",
|
||||
home / ".claude.json",
|
||||
home / ".config/Claude/claude_desktop_config.json",
|
||||
]
|
||||
for path in paths:
|
||||
print(path)
|
||||
@@ -95,11 +104,12 @@ for path in paths:
|
||||
PY
|
||||
|
||||
section "Claude Code hooks"
|
||||
python3 - <<'PY'
|
||||
RUNTIME_HOME="$RUNTIME_HOME" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
path = Path.home() / ".claude/settings.json"
|
||||
path = Path(os.environ["RUNTIME_HOME"]) / ".claude/settings.json"
|
||||
try:
|
||||
data = json.loads(path.read_text())
|
||||
except FileNotFoundError:
|
||||
@@ -148,18 +158,17 @@ section "TokenSave"
|
||||
if bashio::config.true 'install_tokensave'; then
|
||||
tokensave doctor --agent claude || true
|
||||
tokensave gain --all --range 30d || true
|
||||
# Capture before looping — see the matching comment in 82-claude_tools.sh: feeding the
|
||||
# loop straight from `< <(bashio::config ...)` yields an empty list under errexit.
|
||||
# Capture before looping — see the matching comment in 82-claude_tools.sh.
|
||||
TOKENSAVE_PROJECT_PATHS="$(bashio::config 'tokensave_project_paths')"
|
||||
while IFS= read -r configured_path || [ -n "$configured_path" ]; do
|
||||
if [ -z "$configured_path" ] || [ "$configured_path" = "null" ]; then
|
||||
continue
|
||||
fi
|
||||
repo_root="$(s6-setuidgid abc env HOME="$HOME" git -c safe.directory='*' -C "$configured_path" rev-parse --show-toplevel 2> /dev/null || true)"
|
||||
repo_root="$(s6-setuidgid abc env HOME="$RUNTIME_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
|
||||
s6-setuidgid abc env HOME="$RUNTIME_HOME" tokensave status "$repo_root" --short || true
|
||||
else
|
||||
echo "${repo_root}: NOT INITIALIZED"
|
||||
fi
|
||||
@@ -168,6 +177,45 @@ else
|
||||
echo "disabled"
|
||||
fi
|
||||
|
||||
section "Codex"
|
||||
if bashio::config.true 'install_codex_cli'; then
|
||||
codex_bin="/data/codex/bin/codex"
|
||||
if [ -x "$codex_bin" ]; then
|
||||
printf '%-30s %s\n' "installed" "$("$codex_bin" --version 2> /dev/null || echo 'FAILED TO RUN')"
|
||||
printf '%-30s %s\n' "installed version stamp" "$(cat /data/codex/bin/.version 2> /dev/null || echo 'MISSING')"
|
||||
printf '%-30s %s\n' "release policy" "latest stable, SHA-256 verified"
|
||||
printf '%-30s %s\n' "authentication policy" "ChatGPT subscription only"
|
||||
|
||||
# Never forward raw `login status` output: non-ChatGPT modes can include masked secret
|
||||
# fragments. Only print explicitly allow-listed states.
|
||||
codex_status="$(
|
||||
s6-setuidgid abc env -u OPENAI_API_KEY \
|
||||
HOME="$RUNTIME_HOME" CODEX_HOME="$RUNTIME_HOME/.codex" \
|
||||
"$codex_bin" login status 2>&1
|
||||
)"
|
||||
codex_status_rc=$?
|
||||
case "$codex_status" in
|
||||
*"Logged in using ChatGPT"*)
|
||||
echo "Logged in using ChatGPT"
|
||||
;;
|
||||
*"Not logged in"*)
|
||||
echo "Not logged in; run 'codex-login' to activate a ChatGPT subscription"
|
||||
;;
|
||||
*)
|
||||
if [ "$codex_status_rc" -eq 0 ]; then
|
||||
echo "Authenticated with a non-ChatGPT method; run 'codex-login' to enforce subscription authentication"
|
||||
else
|
||||
echo "Unable to determine Codex login status safely; run 'codex-login'"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "enabled but ${codex_bin} is MISSING (download failed or add-on not yet restarted)"
|
||||
fi
|
||||
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)"
|
||||
|
||||
48
claude_desktop/rootfs/usr/local/bin/codex-login
Executable file
48
claude_desktop/rootfs/usr/local/bin/codex-login
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# Activate a ChatGPT subscription on the Codex CLI from inside the add-on.
|
||||
#
|
||||
# The add-on ships no browser, so the normal `codex login` flow cannot complete here.
|
||||
# `codex login --device-auth` prints a verification URL and one-time code that can be
|
||||
# approved on another device. Credentials persist in the abc runtime user's home.
|
||||
# shellcheck shell=bash
|
||||
set -o pipefail
|
||||
|
||||
CODEX_BIN="/data/codex/bin/codex"
|
||||
RUNTIME_HOME="$(getent passwd abc | cut -d: -f6)"
|
||||
if [ -z "$RUNTIME_HOME" ]; then
|
||||
echo "codex-login: unable to resolve the abc runtime home" >&2
|
||||
exit 1
|
||||
fi
|
||||
export HOME="$RUNTIME_HOME"
|
||||
export CODEX_HOME="$RUNTIME_HOME/.codex"
|
||||
|
||||
if ! bashio::config.true 'install_codex_cli'; then
|
||||
echo "codex-login: the install_codex_cli add-on option is disabled" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -x "$CODEX_BIN" ]; then
|
||||
echo "codex-login: ${CODEX_BIN} is not installed; enable install_codex_cli and restart the add-on" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Everything runs as abc so auth.json is readable by the same account that launches the MCP
|
||||
# server. Explicit HOME handling also makes container-console calls safe when the caller is root.
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
exec s6-setuidgid abc env -u OPENAI_API_KEY HOME="$RUNTIME_HOME" CODEX_HOME="$RUNTIME_HOME/.codex" "$0" "$@"
|
||||
fi
|
||||
|
||||
# The managed Codex config forces `chatgpt` authentication and file credential storage. An
|
||||
# inherited API key is removed as defense in depth so this helper cannot activate API billing.
|
||||
unset OPENAI_API_KEY
|
||||
|
||||
# Branch on the exit code, not the text: "Not logged in" also contains "logged in".
|
||||
if "$CODEX_BIN" login status; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "codex-login: starting ChatGPT subscription device-code sign-in."
|
||||
echo "codex-login: open the URL below on any device and enter the displayed code."
|
||||
|
||||
# Line-buffered so the URL and code appear while Codex is still polling in non-TTY callers.
|
||||
exec stdbuf -oL -eL "$CODEX_BIN" login --device-auth "$@"
|
||||
3
portainer_be/CHANGELOG.md
Normal file
3
portainer_be/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 2.43.0 (2026-07-27)
|
||||
- Initial release: Portainer Business Edition add-on, based on the Portainer (CE) add-on (#873)
|
||||
- Ships the `portainer/portainer-ee` binary and web assets; enter a free Business Edition license key in the web UI to unlock BE features (changelog: https://github.com/portainer/portainer/releases)
|
||||
37
portainer_be/DOCS.md
Normal file
37
portainer_be/DOCS.md
Normal file
@@ -0,0 +1,37 @@
|
||||
Portainer can be used to execute custom commands in a docker container. It is a lightweight management UI which allows you to easily manage a Docker host(s) or Docker swarm clusters.
|
||||
|
||||
This is the **Business Edition** variant, shipping the `portainer/portainer-ee` build. Business Edition is free for up to 3 nodes with a license key obtained by registering at <https://www.portainer.io/take-3>. Enter that key in the web UI on first launch; without one it runs a time-limited trial. All add-on options and behaviour are otherwise identical to the Community Edition add-on.
|
||||
|
||||
# Quick start
|
||||
- Add my repository using this link
|
||||
[![Add repository on my Home Assistant][repository-badge]][repository-url]
|
||||
- Install the Portainer Business Edition addon from my repo
|
||||
- On first launch, open the web UI and enter your Business Edition license key
|
||||
- In the configuration panel of the addon, you can change the password
|
||||
- In the main page of the addon, disable "Protection mode", then start the addon
|
||||
- Login (default name is `admin`, default password is `homeassistant`)
|
||||
- Click on `Primary` in the environment (at the center of the page)
|
||||
- Click on `Containers` in the left menu bar
|
||||
- Increase the number of items per page to see all your addons
|
||||
- Click on the symbol `>_` next to the name of your selected addon to open the console page
|
||||
- Either change the username, or more usually just click connect
|
||||
- Type your commands, you have full access to the terminal of this specific container (this does not affect other parts of your HA system)
|
||||
|
||||
# Impact on your system
|
||||
- There is no impact of installing, or running portainer
|
||||
- Installing manually a custom container will modify your HA status to an unsupported/unhealthy state. You will be blocked from upgrading Home Assistant and upgrading any Add-ons you may have. Stopping this custom container will reset the normal status
|
||||
|
||||
# Tips and tricks
|
||||
|
||||
## Reset database
|
||||
Just change the password in your addon options and the database will be reset
|
||||
|
||||
## Timeout of 60s
|
||||
The addon includes a very long timeout. However, if you use another layer of proxy such as the addon nginx proxy manager, it will default to a timeout of 60s. You'll have to adapt the proxy layer to increase timeout. More details here : https://github.com/portainer/portainer/issues/2953#issuecomment-1235795256
|
||||
|
||||
## Further reference
|
||||
- Here is a full guide on using it : https://codeopolis.com/posts/beginners-guide-to-portainer/
|
||||
- Old page on the HA community forum about portainer : https://community.home-assistant.io/t/home-assistant-community-add-on-portainer
|
||||
|
||||
[repository-badge]: https://img.shields.io/badge/Add%20repository%20to%20my-Home%20Assistant-41BDF5?logo=home-assistant&style=for-the-badge
|
||||
[repository-url]: https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons
|
||||
156
portainer_be/Dockerfile
Normal file
156
portainer_be/Dockerfile
Normal file
@@ -0,0 +1,156 @@
|
||||
#============================#
|
||||
# ALEXBELGIUM'S DOCKERFILE #
|
||||
#============================#
|
||||
# _.------.
|
||||
# _.-` ('>.-`"""-.
|
||||
# '.--'` _'` _ .--.)
|
||||
# -' '-.-';` `
|
||||
# ' - _.' ``'--.
|
||||
# '---` .-'""`
|
||||
# /`
|
||||
#=== Home Assistant Addon ===#
|
||||
|
||||
#################
|
||||
# 1 Build Image #
|
||||
#################
|
||||
|
||||
ARG BUILD_FROM=ghcr.io/hassio-addons/base/amd64:11.0.0
|
||||
# Portainer Business Edition (Enterprise) upstream version.
|
||||
# Business Edition has no public release tarball like CE does; its binary and
|
||||
# web assets ship only inside the official portainer/portainer-ee image, so we
|
||||
# pull them from there. The image is multi-arch (amd64/arm64), so buildx picks
|
||||
# the variant matching the target architecture automatically.
|
||||
ARG BUILD_UPSTREAM="2.43.0"
|
||||
FROM portainer/portainer-ee:${BUILD_UPSTREAM} AS portainer_be
|
||||
|
||||
ARG BUILD_FROM
|
||||
FROM ${BUILD_FROM}
|
||||
|
||||
##################
|
||||
# 2 Modify Image #
|
||||
##################
|
||||
|
||||
# Set S6 wait time
|
||||
ENV S6_CMD_WAIT_FOR_SERVICES=1 \
|
||||
S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
||||
S6_SERVICES_GRACETIME=0
|
||||
|
||||
|
||||
# Set shell
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# Setup base
|
||||
ARG BUILD_UPSTREAM
|
||||
|
||||
# Install the Portainer BE binary and its web assets, mirroring the CE add-on
|
||||
# layout under /opt/portainer (the binary resolves ./public relative to itself,
|
||||
# so keeping them side by side needs no --assets flag at runtime).
|
||||
COPY --from=portainer_be /portainer /opt/portainer/portainer
|
||||
COPY --from=portainer_be /public /opt/portainer/public
|
||||
|
||||
##################
|
||||
# 3 Install apps #
|
||||
##################
|
||||
|
||||
# Add rootfs
|
||||
COPY rootfs/ /
|
||||
RUN find . -type f \( -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
|
||||
|
||||
# Modules
|
||||
ARG MODULES="00-banner.sh 01-custom_script.sh"
|
||||
|
||||
# Automatic modules download
|
||||
COPY ha_automodules.sh /ha_automodules.sh
|
||||
RUN chmod 744 /ha_automodules.sh && /ha_automodules.sh "$MODULES" && rm /ha_automodules.sh
|
||||
|
||||
# Manual apps
|
||||
ENV PACKAGES="nginx"
|
||||
|
||||
# Automatic apps & bashio
|
||||
COPY ha_autoapps.sh /ha_autoapps.sh
|
||||
RUN chmod 744 /ha_autoapps.sh && /ha_autoapps.sh "$PACKAGES" && rm /ha_autoapps.sh
|
||||
|
||||
################
|
||||
# 4 Entrypoint #
|
||||
################
|
||||
|
||||
# Add entrypoint
|
||||
ENV S6_STAGE2_HOOK=/ha_entrypoint.sh
|
||||
COPY ha_entrypoint.sh /ha_entrypoint.sh
|
||||
RUN chmod 777 /ha_entrypoint.sh
|
||||
|
||||
# Install bashio
|
||||
COPY bashio-standalone.sh /usr/local/lib/bashio-standalone.sh
|
||||
RUN chmod 0755 /usr/local/lib/bashio-standalone.sh
|
||||
|
||||
RUN chmod 777 /etc/services.d/*/*
|
||||
#
|
||||
#WORKDIR /
|
||||
#ENTRYPOINT [ "/usr/bin/env" ]
|
||||
#CMD [ "/ha_entrypoint.sh" ]
|
||||
#SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
############
|
||||
# 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/main/README.md" \
|
||||
org.opencontainers.image.created=${BUILD_DATE} \
|
||||
org.opencontainers.image.revision=${BUILD_REF} \
|
||||
org.opencontainers.image.version=${BUILD_VERSION}
|
||||
|
||||
#################
|
||||
# 6 Healthcheck #
|
||||
#################
|
||||
|
||||
# Avoid spamming logs
|
||||
# hadolint ignore=SC2016
|
||||
RUN \
|
||||
# Handle Apache configuration
|
||||
if [ -d /etc/apache2/sites-available ]; then \
|
||||
for file in /etc/apache2/sites-*/*.conf; do \
|
||||
sed -i '/<VirtualHost/a \ \n # Match requests with the custom User-Agent "HealthCheck" \n SetEnvIf User-Agent "HealthCheck" dontlog \n # Exclude matching requests from access logs \n CustomLog ${APACHE_LOG_DIR}/access.log combined env=!dontlog' "$file"; \
|
||||
done; \
|
||||
fi && \
|
||||
\
|
||||
# Handle Nginx configuration
|
||||
if [ -f /etc/nginx/nginx.conf ]; then \
|
||||
awk '/http \{/{print; print "map $http_user_agent $dontlog {\n default 0;\n \"~*HealthCheck\" 1;\n}\naccess_log /var/log/nginx/access.log combined if=$dontlog;"; next}1' /etc/nginx/nginx.conf > /etc/nginx/nginx.conf.new && \
|
||||
mv /etc/nginx/nginx.conf.new /etc/nginx/nginx.conf; \
|
||||
fi
|
||||
|
||||
ENV HEALTH_PORT="9000" \
|
||||
HEALTH_URL="/api/system/status"
|
||||
HEALTHCHECK \
|
||||
--interval=5s \
|
||||
--retries=5 \
|
||||
--start-period=30s \
|
||||
--timeout=25s \
|
||||
CMD curl -A "HealthCheck: Docker/1.0" -s -f "http://127.0.0.1:${HEALTH_PORT}${HEALTH_URL}" &>/dev/null || exit 1
|
||||
110
portainer_be/README.md
Normal file
110
portainer_be/README.md
Normal file
@@ -0,0 +1,110 @@
|
||||
|
||||
# Home assistant add-on: Portainer Business Edition
|
||||
|
||||
I maintain this and other Home Assistant add-ons in my free time: keeping up with upstream changes, HA changes, and testing on real hardware takes a lot of time (and some money). I use around 5-10 of my >110 addons so regularly I install test machines (and purchase some test services such as vpn) that I don't use myself to troubleshoot and improve the addons
|
||||
|
||||
If this add-on saves you time or makes your setup easier, I would be very grateful for your support!
|
||||
|
||||
[![Buy me a coffee][donation-badge]](https://www.buymeacoffee.com/alexbelgium)
|
||||
[![Donate via PayPal][paypal-badge]](https://www.paypal.com/donate/?hosted_button_id=DZFULJZTP3UQA)
|
||||
|
||||
## Addon informations
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
[](https://www.codacy.com/gh/alexbelgium/hassio-addons/dashboard?utm_source=github.com&utm_medium=referral&utm_content=alexbelgium/hassio-addons&utm_campaign=Badge_Grade)
|
||||
[](https://github.com/alexbelgium/hassio-addons/actions/workflows/weekly-supelinter.yaml)
|
||||
[](https://github.com/alexbelgium/hassio-addons/actions/workflows/onpush_builder.yaml)
|
||||
|
||||
[donation-badge]: https://img.shields.io/badge/Buy%20me%20a%20coffee-%23d32f2f?logo=buy-me-a-coffee&style=flat&logoColor=white
|
||||
[paypal-badge]: https://img.shields.io/badge/Donate%20via%20PayPal-0070BA?logo=paypal&style=flat&logoColor=white
|
||||
|
||||
This is the **Business Edition** variant of the [Portainer add-on](https://github.com/alexbelgium/hassio-addons/tree/master/portainer). It ships the `portainer/portainer-ee` build instead of the Community Edition. Business Edition is free for up to 3 nodes with a license key you obtain by registering at <https://www.portainer.io/take-3>; enter that key in the web UI on first launch. Without a key it runs in a time-limited trial.
|
||||
|
||||
Forked from : https://github.com/hassio-addons/addon-portainer
|
||||
Implemented changes : Business Edition image ; update to latest versions ; ingress ; ssl ; password setting through addon option ; allow manual override
|
||||
|
||||
_Thanks to everyone having starred my repo! To star it click on the image below, then it will be on top right. Thanks!_
|
||||
|
||||
[](https://github.com/alexbelgium/hassio-addons/stargazers)
|
||||
|
||||

|
||||
|
||||
## About
|
||||
|
||||
---
|
||||
|
||||
Portainer is an open-source lightweight management UI which allows you to
|
||||
easily manage your a Docker host(s) or Docker swarm clusters.
|
||||
|
||||
It has never been so easy to manage Docker. Portainer provides a detailed
|
||||
overview of Docker and allows you to manage containers, images, networks and
|
||||
volumes.
|
||||
|
||||
## RESTORE BACKUP
|
||||
|
||||
Open the addon options and set the password to "empty". Restart the addon, it will allow to restore portainer from a backup. You need to put your backup in an accessible folder such as /share to have it mounted in the addon
|
||||
|
||||
## WARNING
|
||||
|
||||
The Portainer add-on is really powerful and gives you virtually access to
|
||||
your whole system. While this add-on is created and maintained with care and
|
||||
with security in mind, in the wrong or inexperienced hands,
|
||||
it could damage your system.
|
||||
|
||||
## Installation
|
||||
|
||||
---
|
||||
|
||||
The installation of this add-on is pretty straightforward and not different in comparison to installing any other add-on.
|
||||
|
||||
1. Add my add-ons repository to your home assistant instance (in supervisor addons store at top right, or click button below if you have configured my HA)
|
||||
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons)
|
||||
1. Install this add-on.
|
||||
1. Click the `Save` button to store your configuration.
|
||||
1. Set the add-on options to your preferences
|
||||
1. Start the add-on.
|
||||
1. Check the logs of the add-on to see if everything went well.
|
||||
1. Open the webUI and adapt the software options
|
||||
|
||||
## Configuration
|
||||
|
||||
Webui can be found at <http://homeassistant:port> or in your sidebar using Ingress.
|
||||
The default username is "admin" and the password is the value you set in the add-on `password` option (default `homeassistant`).
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `ssl` | bool | `false` | Enable HTTPS for web interface |
|
||||
| `certfile` | str | `fullchain.pem` | SSL certificate file (in `/ssl/`) |
|
||||
| `keyfile` | str | `privkey.pem` | SSL private key file (in `/ssl/`) |
|
||||
| `password` | str | `homeassistant` | Admin password (min 12 characters, leave blank to restore backup) |
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```yaml
|
||||
ssl: true
|
||||
certfile: "fullchain.pem"
|
||||
keyfile: "privkey.pem"
|
||||
password: "your-secure-password-123"
|
||||
```
|
||||
|
||||
### Custom Scripts and Environment Variables
|
||||
|
||||
This addon supports custom scripts and environment variables through the `addon_config` mapping:
|
||||
|
||||
- **Custom scripts**: See [Running Custom Scripts in Addons](https://github.com/alexbelgium/hassio-addons/wiki/Running-custom-scripts-in-Addons)
|
||||
- **env_vars option**: Use the add-on `env_vars` option to pass extra environment variables (uppercase or lowercase names). See https://github.com/alexbelgium/hassio-addons/wiki/Add-Environment-variables-to-your-Addon-2 for details.
|
||||
|
||||
## Support
|
||||
|
||||
Create an issue on github
|
||||
|
||||
## Illustration
|
||||
|
||||
---
|
||||
|
||||

|
||||
62
portainer_be/apparmor.txt
Normal file
62
portainer_be/apparmor.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
#include <tunables/global>
|
||||
|
||||
profile portainer_be_addon flags=(attach_disconnected,mediate_deleted) {
|
||||
#include <abstractions/base>
|
||||
|
||||
capability chown,
|
||||
capability dac_override,
|
||||
capability fowner,
|
||||
capability setgid,
|
||||
capability setuid,
|
||||
capability sys_chroot,
|
||||
file,
|
||||
signal,
|
||||
mount,
|
||||
umount,
|
||||
remount,
|
||||
network udp,
|
||||
network tcp,
|
||||
network dgram,
|
||||
network stream,
|
||||
network inet,
|
||||
network inet6,
|
||||
network netlink raw,
|
||||
network unix dgram,
|
||||
|
||||
|
||||
# S6-Overlay
|
||||
/init ix,
|
||||
/run/{s6,s6-rc*,service}/** ix,
|
||||
/package/** ix,
|
||||
/command/** ix,
|
||||
/run/{,**} rwk,
|
||||
/dev/tty rw,
|
||||
/bin/** ix,
|
||||
/usr/bin/** ix,
|
||||
/usr/lib/bashio/** ix,
|
||||
/etc/s6/** rix,
|
||||
/run/s6/** rix,
|
||||
/etc/services.d/** rwix,
|
||||
/etc/cont-init.d/** rwix,
|
||||
/etc/cont-finish.d/** rwix,
|
||||
/init rix,
|
||||
/var/run/** mrwkl,
|
||||
/var/run/ mrwkl,
|
||||
/dev/i2c-1 mrwkl,
|
||||
/dev/fuse mrwkl,
|
||||
/dev/sda1 mrwkl,
|
||||
/dev/sdb1 mrwkl,
|
||||
/dev/nvme0 mrwkl,
|
||||
/dev/nvme1 mrwkl,
|
||||
/dev/mmcblk0p1 mrwkl,
|
||||
|
||||
# Data access
|
||||
/data/** rw,
|
||||
|
||||
# suppress ptrace denials when using 'docker ps' or using 'ps' inside a container
|
||||
ptrace (trace,read) peer=docker-default,
|
||||
|
||||
# docker daemon confinement requires explict allow rule for signal
|
||||
signal (receive) set=(kill,term) peer=/usr/bin/docker,
|
||||
|
||||
}
|
||||
6
portainer_be/build.json
Normal file
6
portainer_be/build.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"build_from": {
|
||||
"aarch64": "ghcr.io/hassio-addons/base/aarch64:16.0.0",
|
||||
"amd64": "ghcr.io/hassio-addons/base/amd64:16.0.0"
|
||||
}
|
||||
}
|
||||
45
portainer_be/config.yaml
Normal file
45
portainer_be/config.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
arch:
|
||||
- aarch64
|
||||
- amd64
|
||||
backup_exclude:
|
||||
- backups
|
||||
- docker_config/cli-plugins
|
||||
description: Manage your Docker environment with ease (Business Edition)
|
||||
docker_api: true
|
||||
hassio_api: true
|
||||
image: ghcr.io/alexbelgium/portainer_be-{arch}
|
||||
ingress: true
|
||||
ingress_port: 1337
|
||||
ingress_stream: true
|
||||
init: false
|
||||
map:
|
||||
- addon_config:rw
|
||||
- share:rw
|
||||
- ssl
|
||||
name: Portainer Business Edition
|
||||
options:
|
||||
env_vars: []
|
||||
certfile: fullchain.pem
|
||||
keyfile: privkey.pem
|
||||
password: homeassistant
|
||||
ssl: false
|
||||
panel_admin: false
|
||||
panel_icon: mdi:docker
|
||||
ports:
|
||||
8000/tcp: null
|
||||
9099/tcp: 9000
|
||||
ports_description:
|
||||
8000/tcp: Edge Agent Api (Enable when managing remote edge agents)
|
||||
9099/tcp: Web UI port
|
||||
schema:
|
||||
env_vars:
|
||||
- name: match(^[A-Za-z0-9_]+$)
|
||||
value: str?
|
||||
certfile: str
|
||||
keyfile: str
|
||||
password: str?
|
||||
ssl: bool
|
||||
slug: portainer_be
|
||||
udev: true
|
||||
url: https://github.com/alexbelgium/hassio-addons
|
||||
version: "2.43.0"
|
||||
BIN
portainer_be/icon.png
Normal file
BIN
portainer_be/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
portainer_be/logo.png
Normal file
BIN
portainer_be/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
35
portainer_be/rootfs/etc/cont-init.d/30-nginx.sh
Executable file
35
portainer_be/rootfs/etc/cont-init.d/30-nginx.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
#################
|
||||
# NGINX SETTING #
|
||||
#################
|
||||
|
||||
#declare admin_port
|
||||
declare portainer_protocol=http
|
||||
|
||||
# Generate Ingress configuration
|
||||
if bashio::config.true 'ssl'; then
|
||||
bashio::config.require.ssl
|
||||
portainer_protocol=https
|
||||
sed -i "s|9000|9443|g" /etc/nginx/includes/upstream.conf
|
||||
sed -i "s|9000|9443|g" /etc/services.d/nginx/run
|
||||
sed -i "s|9099 default_server|9099 ssl|g" /etc/nginx/templates/ingress.gtpl
|
||||
sed -i '8 i ssl_certificate /ssl/{{ .certfile }};' /etc/nginx/templates/ingress.gtpl
|
||||
sed -i '8 i ssl_certificate_key /ssl/{{ .keyfile }};' /etc/nginx/templates/ingress.gtpl
|
||||
bashio::log.info "Ssl enabled, please use https for connection"
|
||||
else
|
||||
sed -i '/connection_upgrade/a\proxy_set_header Origin "";' /etc/nginx/templates/ingress.gtpl
|
||||
fi
|
||||
|
||||
bashio::var.json \
|
||||
interface "$(bashio::addon.ip_address)" \
|
||||
port "^$(bashio::addon.ingress_port)" \
|
||||
protocol "${portainer_protocol}" \
|
||||
certfile "$(bashio::config 'certfile')" \
|
||||
keyfile "$(bashio::config 'keyfile')" \
|
||||
ssl "^$(bashio::config 'ssl')" \
|
||||
| tempio \
|
||||
-template /etc/nginx/templates/ingress.gtpl \
|
||||
-out /etc/nginx/servers/ingress.conf
|
||||
8
portainer_be/rootfs/etc/cont-init.d/31-portainer.sh
Executable file
8
portainer_be/rootfs/etc/cont-init.d/31-portainer.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
# ==============================================================================
|
||||
# Home Assistant Community Add-on: Portainer
|
||||
# Runs some initializations for Portainer
|
||||
# ==============================================================================
|
||||
bashio::require.unprotected
|
||||
96
portainer_be/rootfs/etc/nginx/includes/mime.types
Normal file
96
portainer_be/rootfs/etc/nginx/includes/mime.types
Normal file
@@ -0,0 +1,96 @@
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/javascript js;
|
||||
application/atom+xml atom;
|
||||
application/rss+xml rss;
|
||||
|
||||
text/mathml mml;
|
||||
text/plain txt;
|
||||
text/vnd.sun.j2me.app-descriptor jad;
|
||||
text/vnd.wap.wml wml;
|
||||
text/x-component htc;
|
||||
|
||||
image/png png;
|
||||
image/svg+xml svg svgz;
|
||||
image/tiff tif tiff;
|
||||
image/vnd.wap.wbmp wbmp;
|
||||
image/webp webp;
|
||||
image/x-icon ico;
|
||||
image/x-jng jng;
|
||||
image/x-ms-bmp bmp;
|
||||
|
||||
font/woff woff;
|
||||
font/woff2 woff2;
|
||||
|
||||
application/java-archive jar war ear;
|
||||
application/json json;
|
||||
application/mac-binhex40 hqx;
|
||||
application/msword doc;
|
||||
application/pdf pdf;
|
||||
application/postscript ps eps ai;
|
||||
application/rtf rtf;
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
application/vnd.google-earth.kml+xml kml;
|
||||
application/vnd.google-earth.kmz kmz;
|
||||
application/vnd.ms-excel xls;
|
||||
application/vnd.ms-fontobject eot;
|
||||
application/vnd.ms-powerpoint ppt;
|
||||
application/vnd.oasis.opendocument.graphics odg;
|
||||
application/vnd.oasis.opendocument.presentation odp;
|
||||
application/vnd.oasis.opendocument.spreadsheet ods;
|
||||
application/vnd.oasis.opendocument.text odt;
|
||||
application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptx;
|
||||
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsx;
|
||||
application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docx;
|
||||
application/vnd.wap.wmlc wmlc;
|
||||
application/x-7z-compressed 7z;
|
||||
application/x-cocoa cco;
|
||||
application/x-java-archive-diff jardiff;
|
||||
application/x-java-jnlp-file jnlp;
|
||||
application/x-makeself run;
|
||||
application/x-perl pl pm;
|
||||
application/x-pilot prc pdb;
|
||||
application/x-rar-compressed rar;
|
||||
application/x-redhat-package-manager rpm;
|
||||
application/x-sea sea;
|
||||
application/x-shockwave-flash swf;
|
||||
application/x-stuffit sit;
|
||||
application/x-tcl tcl tk;
|
||||
application/x-x509-ca-cert der pem crt;
|
||||
application/x-xpinstall xpi;
|
||||
application/xhtml+xml xhtml;
|
||||
application/xspf+xml xspf;
|
||||
application/zip zip;
|
||||
|
||||
application/octet-stream bin exe dll;
|
||||
application/octet-stream deb;
|
||||
application/octet-stream dmg;
|
||||
application/octet-stream iso img;
|
||||
application/octet-stream msi msp msm;
|
||||
|
||||
audio/midi mid midi kar;
|
||||
audio/mpeg mp3;
|
||||
audio/ogg ogg;
|
||||
audio/x-m4a m4a;
|
||||
audio/x-realaudio ra;
|
||||
|
||||
video/3gpp 3gpp 3gp;
|
||||
video/mp2t ts;
|
||||
video/mp4 mp4;
|
||||
video/mpeg mpeg mpg;
|
||||
video/quicktime mov;
|
||||
video/webm webm;
|
||||
video/x-flv flv;
|
||||
video/x-m4v m4v;
|
||||
video/x-mng mng;
|
||||
video/x-ms-asf asx asf;
|
||||
video/x-ms-wmv wmv;
|
||||
video/x-msvideo avi;
|
||||
}
|
||||
14
portainer_be/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
14
portainer_be/rootfs/etc/nginx/includes/proxy_params.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
proxy_connect_timeout 30m;
|
||||
proxy_http_version 1.1;
|
||||
proxy_ignore_client_abort off;
|
||||
proxy_read_timeout 30m;
|
||||
proxy_redirect off;
|
||||
proxy_send_timeout 30m;
|
||||
proxy_max_temp_file_size 0;
|
||||
|
||||
proxy_set_header Accept-Encoding "";
|
||||
proxy_set_header Origin $http_origin;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
1
portainer_be/rootfs/etc/nginx/includes/resolver.conf
Normal file
1
portainer_be/rootfs/etc/nginx/includes/resolver.conf
Normal file
@@ -0,0 +1 @@
|
||||
resolver 127.0.0.11 ipv6=off;
|
||||
@@ -0,0 +1,6 @@
|
||||
root /dev/null;
|
||||
server_name $hostname;
|
||||
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
9
portainer_be/rootfs/etc/nginx/includes/ssl_params.conf
Normal file
9
portainer_be/rootfs/etc/nginx/includes/ssl_params.conf
Normal file
@@ -0,0 +1,9 @@
|
||||
ssl_protocols TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA;
|
||||
ssl_ecdh_curve secp384r1;
|
||||
ssl_session_timeout 10m;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_tickets off;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
3
portainer_be/rootfs/etc/nginx/includes/upstream.conf
Normal file
3
portainer_be/rootfs/etc/nginx/includes/upstream.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
upstream backend {
|
||||
server 127.0.0.1:9000;
|
||||
}
|
||||
56
portainer_be/rootfs/etc/nginx/nginx.conf
Normal file
56
portainer_be/rootfs/etc/nginx/nginx.conf
Normal file
@@ -0,0 +1,56 @@
|
||||
# Run nginx in foreground.
|
||||
daemon off;
|
||||
|
||||
# This is run inside Docker.
|
||||
user root;
|
||||
|
||||
# Pid storage location.
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
# Set number of worker processes.
|
||||
worker_processes 1;
|
||||
|
||||
# Enables the use of JIT for regular expressions to speed-up their processing.
|
||||
pcre_jit on;
|
||||
|
||||
# Write error log to Hass.io add-on log.
|
||||
error_log /proc/1/fd/1 error;
|
||||
|
||||
# Load allowed environment vars
|
||||
env HASSIO_TOKEN;
|
||||
|
||||
# Load dynamic modules.
|
||||
include /etc/nginx/modules/*.conf;
|
||||
|
||||
# Max num of simultaneous connections by a worker process.
|
||||
events {
|
||||
worker_connections 512;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/includes/mime.types;
|
||||
|
||||
log_format hassio '[$time_local] $status '
|
||||
'$http_x_forwarded_for($remote_addr) '
|
||||
'$request ($http_user_agent)';
|
||||
|
||||
access_log /proc/1/fd/1 hassio;
|
||||
client_max_body_size 4G;
|
||||
default_type application/octet-stream;
|
||||
gzip on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
server_tokens off;
|
||||
tcp_nodelay on;
|
||||
tcp_nopush on;
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
include /etc/nginx/includes/resolver.conf;
|
||||
include /etc/nginx/includes/upstream.conf;
|
||||
|
||||
include /etc/nginx/servers/*.conf;
|
||||
}
|
||||
1
portainer_be/rootfs/etc/nginx/servers/.gitkeep
Normal file
1
portainer_be/rootfs/etc/nginx/servers/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
Without requirements or design, programming is the art of adding bugs to an empty text file. (Louis Srygley)
|
||||
24
portainer_be/rootfs/etc/nginx/templates/ingress.gtpl
Normal file
24
portainer_be/rootfs/etc/nginx/templates/ingress.gtpl
Normal file
@@ -0,0 +1,24 @@
|
||||
server {
|
||||
listen {{ .interface }}:{{ .port }} default_server;
|
||||
listen {{ .interface }}:9099 default_server;
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
client_max_body_size 0;
|
||||
gzip off;
|
||||
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
add_header Content-Security-Policy "frame-ancestors *";
|
||||
|
||||
location / {
|
||||
proxy_pass {{ .protocol }}://backend/;
|
||||
resolver 127.0.0.11 valid=180s;
|
||||
|
||||
# These headers must be under location section, if they moved into proxy_params.conf, even if this is valid, they won't work
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
}
|
||||
}
|
||||
8
portainer_be/rootfs/etc/services.d/nginx/finish
Normal file
8
portainer_be/rootfs/etc/services.d/nginx/finish
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/execlineb -S0
|
||||
# ==============================================================================
|
||||
# Take down the S6 supervision tree when Nginx fails
|
||||
# ==============================================================================
|
||||
if { s6-test ${1} -ne 0 }
|
||||
if { s6-test ${1} -ne 256 }
|
||||
|
||||
s6-svscanctl -t /var/run/s6/services
|
||||
13
portainer_be/rootfs/etc/services.d/nginx/run
Executable file
13
portainer_be/rootfs/etc/services.d/nginx/run
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/with-contenv bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
# ==============================================================================
|
||||
|
||||
bashio::log.info "Waiting for port 9000 to open..."
|
||||
|
||||
# Wait for transmission to become available
|
||||
bashio::net.wait_for 9000 localhost 900
|
||||
|
||||
bashio::log.info "Starting NGinx..."
|
||||
|
||||
exec nginx
|
||||
9
portainer_be/rootfs/etc/services.d/portainer/finish
Normal file
9
portainer_be/rootfs/etc/services.d/portainer/finish
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/execlineb -S0
|
||||
# ==============================================================================
|
||||
# Home Assistant Community Add-on: Portainer
|
||||
# Take down the S6 supervision tree when Portainer fails
|
||||
# ==============================================================================
|
||||
if { s6-test ${1} -ne 0 }
|
||||
if { s6-test ${1} -ne 256 }
|
||||
|
||||
s6-svscanctl -t /var/run/s6/services
|
||||
107
portainer_be/rootfs/etc/services.d/portainer/run
Executable file
107
portainer_be/rootfs/etc/services.d/portainer/run
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bashio
|
||||
# shellcheck shell=bash
|
||||
set -e
|
||||
|
||||
bashio::log.info "Starting Portainer..."
|
||||
|
||||
##################
|
||||
# DEFINE OPTIONS #
|
||||
##################
|
||||
declare -a options
|
||||
options+=(--data /data)
|
||||
options+=(--bind 0.0.0.0:9000)
|
||||
#options+=(--templates /opt/portainer/templates.json)
|
||||
|
||||
docker_socket="/var/run/docker.sock"
|
||||
if [[ ! -S "$docker_socket" ]]; then
|
||||
fallback_socket="/run/docker.sock"
|
||||
if [[ -S "$fallback_socket" ]]; then
|
||||
docker_socket="$fallback_socket"
|
||||
bashio::log.info "Docker socket not found at /var/run/docker.sock, using /run/docker.sock."
|
||||
else
|
||||
bashio::log.error "Docker socket not found at /var/run/docker.sock or /run/docker.sock."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
options+=(--host "unix://${docker_socket}")
|
||||
|
||||
##############
|
||||
# SSL CONFIG #
|
||||
##############
|
||||
|
||||
bashio::config.require.ssl
|
||||
if bashio::config.true 'ssl'; then
|
||||
bashio::log.info "SSL enabled. If web UI doesn't work, disable SSL or check your certificate paths."
|
||||
CERTFILE="$(bashio::config 'certfile')"
|
||||
KEYFILE="$(bashio::config 'keyfile')"
|
||||
options+=(--sslcert /ssl/"$CERTFILE")
|
||||
options+=(--sslkey /ssl/"$KEYFILE")
|
||||
bashio::log.info "... SSL activated."
|
||||
fi
|
||||
|
||||
################
|
||||
# SET PASSWORD #
|
||||
################
|
||||
|
||||
# Set up the initial password
|
||||
PASSWORD_FILE="/data/portainer_password"
|
||||
HIDDEN_FILE="/data/hidden"
|
||||
if ! bashio::config.has_value 'password'; then
|
||||
PASSWORD="empty"
|
||||
else
|
||||
PASSWORD="$(bashio::config 'password')"
|
||||
fi
|
||||
|
||||
# Check current password
|
||||
CURRENTPASSWORD=""
|
||||
touch "$PASSWORD_FILE"
|
||||
CURRENTPASSWORD="$(cat "$PASSWORD_FILE")"
|
||||
|
||||
# Reset password if not first run
|
||||
if bashio::fs.file_exists "$HIDDEN_FILE"; then
|
||||
if [[ "$CURRENTPASSWORD" != "$PASSWORD" ]]; then
|
||||
BACKUPLOCATION="/share/portainer_$(date +%m-%d-%Y)_$RANDOM.backup"
|
||||
mv -f /data/portainer.db "$BACKUPLOCATION" || true
|
||||
rm "$HIDDEN_FILE" || true
|
||||
bashio::log.warning "... password changed, database reset. Previous version stored in $BACKUPLOCATION"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Define option
|
||||
echo -n "$PASSWORD" > "$PASSWORD_FILE"
|
||||
if [[ "$PASSWORD" = "empty" ]]; then
|
||||
bashio::log.info "... starting without predefined password."
|
||||
bashio::log.warning "If this is your first boot, you have a 5 minutes time period to perform the initial set-up."
|
||||
bashio::log.warning "If you don't do it, you would be faced with a 404 error and will need to restart the add-on to access the set-up page."
|
||||
else
|
||||
options+=(--admin-password-file "$PASSWORD_FILE")
|
||||
bashio::log.info "... password set according to add-on options."
|
||||
fi
|
||||
|
||||
###################
|
||||
# HIDE CONTAINERS #
|
||||
###################
|
||||
|
||||
# Hide Hassio containers by default, but only enforce on first run
|
||||
if ! bashio::fs.file_exists "$HIDDEN_FILE"; then
|
||||
options+=(--hide-label io.hass.type=supervisor)
|
||||
options+=(--hide-label io.hass.type=homeassistant)
|
||||
options+=(--hide-label io.hass.type=base)
|
||||
options+=(--hide-label io.hass.type=core)
|
||||
# options+=(--hide-label io.hass.type=addon)
|
||||
options+=(--hide-label io.hass.type=audio)
|
||||
options+=(--hide-label io.hass.type=cli)
|
||||
options+=(--hide-label io.hass.type=dns)
|
||||
options+=(--hide-label io.hass.type=multicast)
|
||||
options+=(--hide-label io.hass.type=observer)
|
||||
bashio::log.info "... non-addon containers hidden."
|
||||
touch "$HIDDEN_FILE"
|
||||
fi
|
||||
|
||||
####################
|
||||
# LAUNCH PORTAINER #
|
||||
####################
|
||||
bashio::log.info "... launching Portainer."
|
||||
|
||||
exec /opt/portainer/portainer "${options[@]}"
|
||||
9
portainer_be/updater.json
Normal file
9
portainer_be/updater.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"github_exclude": "-",
|
||||
"last_update": "2026-07-27",
|
||||
"repository": "alexbelgium/hassio-addons",
|
||||
"slug": "portainer_be",
|
||||
"source": "dockerhub",
|
||||
"upstream_repo": "portainer/portainer-ee",
|
||||
"upstream_version": "2.43.0"
|
||||
}
|
||||
Reference in New Issue
Block a user