From 78d1274d351312586b95231545fd0a79618719b3 Mon Sep 17 00:00:00 2001 From: Herklos Date: Fri, 31 Jul 2026 15:45:19 +0200 Subject: [PATCH] feat: integrate Home Assistant add-on into OctoBot repo, chained to Docker publish Moves the HA add-on (previously Drakkar-Software/octobot-home-assistant) into homeassistant/, building it right after the core Docker publish job so it's pinned to the exact multi-arch digest just pushed - fixes base-image skew from rebuilding against a floating :stable/:latest tag. octobot (stable, tag builds) auto-bumps config.yaml and pushes to master; octobot-latest (DEV) is a manual version bump, since Supervisor's need_update is a plain version-string inequality against the store's committed config.yaml with no digest/rolling-tag mechanism (verified against supervisor source and the hassio-addons/ repository-updater precedent) - the DEV image itself still rebuilds and stays digest-pinned on every dev push regardless. Also renames 5 test fixtures literally named config.json to bot_config.json, since Supervisor's recursive **/config.* store scan would otherwise log a schema-validation warning for each, forever, now that this repo is what Supervisor clones directly. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/main.yml | 181 +++++++++++++++++- homeassistant/Dockerfile.HA | 7 + homeassistant/ci/addon-meta.sh | 52 +++++ homeassistant/ci/bump-version.sh | 51 +++++ homeassistant/octobot-latest/CHANGELOG.md | 25 +++ homeassistant/octobot-latest/DOCS.md | 45 +++++ homeassistant/octobot-latest/README.md | 10 + homeassistant/octobot-latest/config.yaml | 33 ++++ homeassistant/octobot-latest/icon.png | Bin 0 -> 11805 bytes homeassistant/octobot-latest/logo.png | Bin 0 -> 24963 bytes .../octobot-latest/translations/en.yaml | 7 + homeassistant/octobot/CHANGELOG.md | 25 +++ homeassistant/octobot/DOCS.md | 45 +++++ homeassistant/octobot/README.md | 10 + homeassistant/octobot/config.yaml | 30 +++ homeassistant/octobot/icon.png | Bin 0 -> 11805 bytes homeassistant/octobot/logo.png | Bin 0 -> 24963 bytes homeassistant/octobot/translations/en.yaml | 7 + homeassistant/rootfs/entrypoint.sh | 29 +++ homeassistant/tests/options.json | 3 + homeassistant/tests/smoke.sh | 87 +++++++++ .../octobot_commons/tests/test_config.py | 2 +- .../static/{config.json => bot_config.json} | 0 .../static/{config.json => bot_config.json} | 0 .../static/{config.json => bot_config.json} | 0 .../static/{config.json => bot_config.json} | 0 repository.yaml | 3 + tests/static/{config.json => bot_config.json} | 0 .../unit_tests/test_configuration_manager.py | 4 +- 29 files changed, 651 insertions(+), 5 deletions(-) create mode 100644 homeassistant/Dockerfile.HA create mode 100755 homeassistant/ci/addon-meta.sh create mode 100755 homeassistant/ci/bump-version.sh create mode 100644 homeassistant/octobot-latest/CHANGELOG.md create mode 100644 homeassistant/octobot-latest/DOCS.md create mode 100644 homeassistant/octobot-latest/README.md create mode 100644 homeassistant/octobot-latest/config.yaml create mode 100644 homeassistant/octobot-latest/icon.png create mode 100644 homeassistant/octobot-latest/logo.png create mode 100644 homeassistant/octobot-latest/translations/en.yaml create mode 100644 homeassistant/octobot/CHANGELOG.md create mode 100644 homeassistant/octobot/DOCS.md create mode 100644 homeassistant/octobot/README.md create mode 100644 homeassistant/octobot/config.yaml create mode 100644 homeassistant/octobot/icon.png create mode 100644 homeassistant/octobot/logo.png create mode 100644 homeassistant/octobot/translations/en.yaml create mode 100755 homeassistant/rootfs/entrypoint.sh create mode 100644 homeassistant/tests/options.json create mode 100755 homeassistant/tests/smoke.sh rename packages/commons/tests/static/{config.json => bot_config.json} (100%) rename packages/evaluators/tests/static/{config.json => bot_config.json} (100%) rename packages/tentacles/Meta/Keywords/scripting_library/tests/static/{config.json => bot_config.json} (100%) rename packages/trading/tests/static/{config.json => bot_config.json} (100%) create mode 100644 repository.yaml rename tests/static/{config.json => bot_config.json} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b13de45e6..bac484e185 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -212,6 +212,9 @@ jobs: strategy: matrix: os: [ ubuntu-latest ] + outputs: + image: ${{ steps.vars.outputs.image }} + digest: ${{ steps.push_dev.outputs.digest || steps.push_tag.outputs.digest }} steps: - uses: actions/checkout@v6 @@ -220,9 +223,12 @@ jobs: uses: reviewdog/action-hadolint@v1 with: github_token: ${{ secrets.github_token }} - hadolint_ignore: DL3013 DL3008 + # DL3006: homeassistant/Dockerfile.HA's ARG BUILD_FROM is intentionally + # unpinned — CI always supplies an exact digest, see the ha-build job. + hadolint_ignore: DL3013 DL3008 DL3006 - name: Set Environment Variables + id: vars run: | OWNER="$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]' | tr -d '-')" IMG=octobot @@ -236,6 +242,7 @@ jobs: echo "CONTAINER_NAME=octobot" >> $GITHUB_ENV echo "CHECK_TENTACLE_CONTAINER_TIME=10" >> $GITHUB_ENV echo "WAIT_CONTAINER_TIME=80" >> $GITHUB_ENV + echo "image=${OWNER}/${IMG}" >> $GITHUB_OUTPUT - name: Set up Python ${{ matrix.version }} uses: actions/setup-python@v6 @@ -327,7 +334,12 @@ jobs: docker logs ${{ env.CONTAINER_NAME }} docker inspect ${{ env.CONTAINER_NAME }} | jq '.[].State.Health.Status' | grep "healthy" + - name: Smoke test HA add-on + if: github.event_name != 'push' + run: homeassistant/tests/smoke.sh "${{ env.IMAGE }}:${{ env.SHA }}" + - name: Build and push latest + id: push_dev if: github.event_name == 'push' && !startsWith(github.ref, 'refs/tags') && github.ref == 'refs/heads/dev' uses: docker/build-push-action@v6 with: @@ -356,6 +368,7 @@ jobs: cache-to: type=local,dest=/tmp/.buildx-cache - name: Build and push on tag + id: push_tag if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') uses: docker/build-push-action@v6 with: @@ -370,7 +383,167 @@ jobs: ${{ env.IMAGE }}:${{ env.VERSION }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache - + + ha-lint: + name: HA add-on lint - ${{ matrix.addon }} + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + addon: [ octobot, octobot-latest ] + steps: + - uses: actions/checkout@v6 + + - name: Lint add-on + uses: frenck/action-addon-linter@v2.21 + with: + path: "./homeassistant/${{ matrix.addon }}" + + - name: Shellcheck entrypoint + run: shellcheck homeassistant/rootfs/entrypoint.sh + + ha-prepare: + name: HA add-on - prepare + needs: [ docker ] + if: >- + github.event_name == 'push' && + (github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/')) + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + addon: ${{ steps.meta.outputs.addon }} + version: ${{ steps.meta.outputs.version }} + image_name: ${{ steps.meta.outputs.image_name }} + registry_prefix: ${{ steps.meta.outputs.registry_prefix }} + architectures: ${{ steps.meta.outputs.architectures }} + name: ${{ steps.meta.outputs.name }} + description: ${{ steps.meta.outputs.description }} + build_matrix: ${{ steps.matrix.outputs.matrix }} + build_from: ${{ needs.docker.outputs.image }}@${{ needs.docker.outputs.digest }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Guard - core digest must exist + env: + DIGEST: ${{ needs.docker.outputs.digest }} + run: | + [[ "$DIGEST" == sha256:* ]] || { echo "::error::docker job produced no digest"; exit 1; } + + - name: Compute add-on metadata + id: meta + env: + REF: ${{ github.ref }} + run: homeassistant/ci/addon-meta.sh + + - name: Prepare multi-arch matrix + id: matrix + uses: home-assistant/builder/actions/prepare-multi-arch-matrix@2026.06.0 + with: + architectures: ${{ steps.meta.outputs.architectures }} + image-name: ${{ steps.meta.outputs.image_name }} + registry-prefix: ${{ steps.meta.outputs.registry_prefix }} + + ha-build: + name: HA add-on - build ${{ matrix.arch }} + needs: [ ha-prepare ] + runs-on: ${{ matrix.os }} + permissions: + contents: read + packages: write + id-token: write + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.ha-prepare.outputs.build_matrix) }} + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + + # BUILD_FROM is on Docker Hub and build-image sets pull:true, so every arch of + # every run pulls it anonymously. build-image's own ghcr.io login merges into + # ~/.docker/config.json, it does not replace this one. + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build add-on image + uses: home-assistant/builder/actions/build-image@2026.06.0 + with: + arch: ${{ matrix.arch }} + image: ${{ matrix.image }} + version: ${{ needs.ha-prepare.outputs.version }} + container-registry-password: ${{ secrets.GITHUB_TOKEN }} + context: ./homeassistant + file: ./homeassistant/Dockerfile.HA + build-args: | + BUILD_FROM=${{ needs.ha-prepare.outputs.build_from }} + cache-gha-scope: ${{ needs.ha-prepare.outputs.addon }} + image-tags: | + ${{ needs.ha-prepare.outputs.version }} + latest + labels: | + io.hass.type=app + io.hass.name=${{ needs.ha-prepare.outputs.name }} + io.hass.description=${{ needs.ha-prepare.outputs.description }} + org.opencontainers.image.title=Home Assistant Add-on: ${{ needs.ha-prepare.outputs.name }} + push: true + + ha-publish: + name: HA add-on - publish manifest & bump version + needs: [ ha-prepare, ha-build ] + runs-on: ubuntu-latest + # Only octobot (tag builds) auto-bumps config.yaml - see the step below. This group + # still matters: it serializes those (infrequent) tag-triggered bumps against each + # other so bump-version.sh's fetch/reset/push can't race itself; the script's own + # retry loop still guards against an unrelated human push landing on master in between. + concurrency: + group: ha-addon-publish + cancel-in-progress: false + permissions: + contents: write + packages: write + id-token: write + steps: + # Runs for both channels: the image itself always stays fresh and digest-pinned + # to the exact base image, regardless of whether Supervisor's version bumps. + - name: Publish multi-arch manifest + uses: home-assistant/builder/actions/publish-multi-arch-manifest@2026.06.0 + with: + architectures: ${{ needs.ha-prepare.outputs.architectures }} + image-name: ${{ needs.ha-prepare.outputs.image_name }} + registry-prefix: ${{ needs.ha-prepare.outputs.registry_prefix }} + container-registry-password: ${{ secrets.GITHUB_TOKEN }} + image-tags: | + ${{ needs.ha-prepare.outputs.version }} + latest + + # octobot-latest (DEV) is a deliberate manual bump, not automated: see + # homeassistant/octobot-latest/config.yaml. Only octobot (a tag push, i.e. a real + # OctoBot release) auto-advances the version Supervisor offers as an update. + # + # Supervisor clones the default branch (master) regardless of which branch or tag + # triggered this run - see supervisor/store/git.py::_clone - so the bump always + # targets master explicitly, not github.ref (which is detached for a tag build). + - uses: actions/checkout@v6 + if: needs.ha-prepare.outputs.addon == 'octobot' + with: + ref: master + fetch-depth: 0 + + - name: Bump add-on version on master + if: needs.ha-prepare.outputs.addon == 'octobot' + env: + ADDON: ${{ needs.ha-prepare.outputs.addon }} + NEW_VERSION: ${{ needs.ha-prepare.outputs.version }} + run: homeassistant/ci/bump-version.sh + tentacles: needs: [ build, tests ] if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' || startsWith(github.ref, 'refs/tags/') @@ -790,6 +963,10 @@ jobs: - version - docs - publish-npm + - ha-lint + - ha-prepare + - ha-build + - ha-publish uses: Drakkar-Software/.github/.github/workflows/failure_notify_workflow.yml@master secrets: DISCORD_GITHUB_WEBHOOK: ${{ secrets.DISCORD_GITHUB_WEBHOOK }} diff --git a/homeassistant/Dockerfile.HA b/homeassistant/Dockerfile.HA new file mode 100644 index 0000000000..9bc175e7b9 --- /dev/null +++ b/homeassistant/Dockerfile.HA @@ -0,0 +1,7 @@ +ARG BUILD_FROM +FROM ${BUILD_FROM} + +COPY rootfs / +RUN chmod +x /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/homeassistant/ci/addon-meta.sh b/homeassistant/ci/addon-meta.sh new file mode 100755 index 0000000000..ebeb242202 --- /dev/null +++ b/homeassistant/ci/addon-meta.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Computes which add-on channel to build and what version to publish it under. +# +# Deliberately NOT using home-assistant/actions/helpers/info@master: that action +# reads `version:` from the committed config.yaml. For octobot that field only ever +# holds a "0.0.0" placeholder here — bump-version.sh stamps the real value afterwards, +# from this script's `version` output, on every tag push. For octobot-latest `version` +# here is only the Docker image tag (traceability) — config.yaml's version is a +# deliberate manual bump instead, see homeassistant/octobot-latest/config.yaml. +# +# Inputs (env): REF (github.ref) +# Output: appends addon/version/image_name/registry_prefix/architectures/name/description +# to $GITHUB_OUTPUT (or stdout, when run locally). +set -euo pipefail + +out="${GITHUB_OUTPUT:-/dev/stdout}" +here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # homeassistant/ + +if [[ "$REF" == refs/tags/* ]]; then + addon="octobot" + version="${REF##refs/tags/}" +else + addon="octobot-latest" + base="$(grep -Po '^VERSION\s*=\s*"\K[^"]+' "$here/../octobot/__init__.py")" + base="${base%%-*}"; base="${base%%+*}" # 3.0.0-beta1 -> 3.0.0 + # Date-based, not a run counter: monotonic by construction, no dependency on + # github.run_number (which is per-workflow-*file* and silently resets if + # main.yml is ever renamed/recreated, which would make a rebuild look older + # than the last real one). + version="${base}-dev.$(date -u +%Y%m%d%H%M)" +fi + +# Docker tag charset guard — catch an illegal tag (e.g. '+') before spending a +# 20-minute build. AwesomeVersion also ignores SemVer build metadata, so a +# scheme using '+' would silently never be offered as an update either way. +if [[ ! "$version" =~ ^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$ ]]; then + echo "::error::'$version' is not a valid Docker tag" >&2 + exit 1 +fi + +cfg="$here/${addon}/config.yaml" +image="$(yq -r '.image' "$cfg")" + +{ + echo "addon=${addon}" + echo "version=${version}" + echo "image_name=${image##*/}" + echo "registry_prefix=${image%/*}" + echo "architectures=$(yq -o=json -I=0 '.arch' "$cfg")" # matrix action needs a JSON array + echo "name=$(yq -r '.name' "$cfg")" + echo "description=$(yq -r '.description' "$cfg")" +} >> "$out" diff --git a/homeassistant/ci/bump-version.sh b/homeassistant/ci/bump-version.sh new file mode 100755 index 0000000000..b8e656fde6 --- /dev/null +++ b/homeassistant/ci/bump-version.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Stamps the just-published version into homeassistant//config.yaml and +# pushes it directly to master — the branch Supervisor actually clones (see +# supervisor/store/git.py::_clone, which omits the branch arg entirely unless +# the user's repository URL has a '#branch' suffix, so it defaults to the repo's +# default branch). +# +# Only ever invoked for ADDON=octobot (a tag push, i.e. a real OctoBot release) — +# see the `if:` guard on this step in ha-publish. octobot-latest's version is a +# deliberate manual bump instead; see homeassistant/octobot-latest/config.yaml. +# +# Runs from a checkout of THIS repo (core OctoBot repo doubles as the add-on +# distribution repo per the user's decision to accept the larger Supervisor +# clone rather than maintain a separate thin repo). +# +# Inputs (env): ADDON, NEW_VERSION +set -euo pipefail + +# Assumes CWD is the repo root (true for a plain `actions/checkout@v6` step with +# no working-directory override), same as the git commands below. +cfg_rel="homeassistant/${ADDON}/config.yaml" + +git config user.name "github-actions[bot]" +git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + +# Retry loop: a concurrency group (see ha-publish) already serializes concurrent +# runs of this step against each other, but an unrelated human push to master in +# between fetch and push is still possible - retry rather than fail the run. +for attempt in 1 2 3 4 5; do + git fetch origin master + git reset --hard origin/master + + # Explicit double-quote style: a bare 1.10 would otherwise parse as a float. + V="$NEW_VERSION" yq -i '.version = strenv(V) | .version style="double"' "$cfg_rel" + + if git diff --quiet -- "$cfg_rel"; then + echo "already at ${NEW_VERSION}, nothing to commit" + exit 0 + fi + + git add "$cfg_rel" + git commit -m "ci(ha): bump ${ADDON} add-on to ${NEW_VERSION} [skip ci]" + if git push origin HEAD:master; then + exit 0 + fi + echo "push race on attempt ${attempt}, retrying" + sleep $((attempt * 5)) +done + +echo "::error::could not push ${ADDON} version bump to master" +exit 1 diff --git a/homeassistant/octobot-latest/CHANGELOG.md b/homeassistant/octobot-latest/CHANGELOG.md new file mode 100644 index 0000000000..34df224a47 --- /dev/null +++ b/homeassistant/octobot-latest/CHANGELOG.md @@ -0,0 +1,25 @@ + + + +## 1.1.2 + +- Add optional `node_external_host` configuration option: sets the external host + (and port, if non-default) the Node sync/mobile features advertise, for + reverse-proxy / public-hostname setups. Leave empty to keep OctoBot's default. + +## 1.1.1 + +- Rebuild against the current OctoBot base image. + +## 1.1.0 + +- Tentacles are no longer persisted: they are wiped on every start so OctoBot + reinstalls a fresh default tentacles set each boot. User config, logs and + backtesting data still persist under `/data`. + +## 1.0.0 + +- Initial release: wraps `drakkarsoftware/octobot:latest`, exposes the Node + web interface on port 8000 with a web UI button, persists OctoBot data + under `/data`. diff --git a/homeassistant/octobot-latest/DOCS.md b/homeassistant/octobot-latest/DOCS.md new file mode 100644 index 0000000000..f70164839f --- /dev/null +++ b/homeassistant/octobot-latest/DOCS.md @@ -0,0 +1,45 @@ +# Home Assistant Add-on: OctoBot DEV + +## How to use + +This add-on runs [OctoBot](https://github.com/Drakkar-Software/OctoBot), an +open-source cryptocurrency trading bot, from the official +`drakkarsoftware/octobot:latest` Docker image. + +Click **OPEN WEB UI** (or go to `http://:8000/app`) to reach the OctoBot +Node web interface. The web interface is served without a login by default; +configure exchanges, strategies and password protection from within OctoBot +itself once it's running. + +> The Node web interface is being rolled out as OctoBot's default UI. If +> `/app` returns a 404 on your installed OctoBot version, the node web bundle +> isn't baked into that build yet — uncomment the `5001/tcp` port in this +> add-on's config and point the web UI button at +> `http://:5001` (the classic dashboard) as a fallback. + +## Configuration + +### Option: `enable_node_api` + +Enables OctoBot's Node API/web interface service on port 8000. Leave this on +unless you only need the classic dashboard on 5001. + +### Option: `node_external_host` (optional) + +External host (and port, if non-default) the Node sync/mobile features advertise +— set this when OctoBot is reached through a reverse proxy or a public hostname +different from the add-on's own address. Leave empty to keep OctoBot's default. + +## Data persistence + +OctoBot's user config, logs and backtesting data are stored under this add-on's +persistent `/data` folder, so they survive add-on updates and rebuilds. + +Installed **tentacles are intentionally not persisted**: they are wiped on every +start, so OctoBot reinstalls a fresh default tentacles set each time the add-on +boots. + +## Want a stable release instead? + +Install the **OctoBot** add-on instead, which tracks +`drakkarsoftware/octobot:stable`. diff --git a/homeassistant/octobot-latest/README.md b/homeassistant/octobot-latest/README.md new file mode 100644 index 0000000000..d120c8bef5 --- /dev/null +++ b/homeassistant/octobot-latest/README.md @@ -0,0 +1,10 @@ +# Home Assistant Add-on: OctoBot DEV + +_Run [OctoBot](https://github.com/Drakkar-Software/OctoBot), an open-source +cryptocurrency trading bot, as a Home Assistant add-on (DEV/latest channel)._ + +![Supports aarch64 Architecture][aarch64-shield] +![Supports amd64 Architecture][amd64-shield] + +[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg +[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg diff --git a/homeassistant/octobot-latest/config.yaml b/homeassistant/octobot-latest/config.yaml new file mode 100644 index 0000000000..e31ceacc55 --- /dev/null +++ b/homeassistant/octobot-latest/config.yaml @@ -0,0 +1,33 @@ +# https://developers.home-assistant.io/docs/apps/configuration#app-configuration +name: OctoBot DEV +# Manually maintained, unlike homeassistant/octobot/config.yaml's version (which +# ha-publish auto-bumps on every tag push). The DEV image itself is still rebuilt and +# republished automatically on every push to dev (see the ha-build/ha-publish jobs) and +# always stays digest-pinned to the exact core :latest image that triggered it — only +# Supervisor's "update available" signal for already-installed users requires this field +# to change. Bump it by hand when you want existing DEV installs to see an update. +version: "1.1.5" +slug: octobot-latest +description: OctoBot crypto trading bot (DEV/latest) with Node web interface +url: "https://github.com/drakkar-software/OctoBot/tree/master/homeassistant/octobot-latest" +arch: + - aarch64 + - amd64 +init: false +ports: + 8000/tcp: 8000 + # 5001/tcp: 5001 # fallback: classic web dashboard, uncomment if node /app 404s +ports_description: + 8000/tcp: "OctoBot Node web interface" +webui: "http://[HOST]:[PORT:8000]/app" +options: + enable_node_api: true +schema: + enable_node_api: bool + node_external_host: str? +image: "ghcr.io/drakkar-software/addon-octobot-latest" +# ^ No {arch} placeholder: both the CI builder and the Supervisor prepend the +# arch prefix themselves (e.g. amd64-addon-octobot-latest); a literal {arch} +# token in this name breaks the CI build (invalid tag: double arch prefix). +# Required for CI to know where to publish; comment out only for local +# Supervisor builds from the Dockerfile — keep it set on main. diff --git a/homeassistant/octobot-latest/icon.png b/homeassistant/octobot-latest/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4b35088f76e5893bab148565ab99d78b23e03f4e GIT binary patch literal 11805 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 + + +## 1.1.2 + +- Add optional `node_external_host` configuration option: sets the external host + (and port, if non-default) the Node sync/mobile features advertise, for + reverse-proxy / public-hostname setups. Leave empty to keep OctoBot's default. + +## 1.1.1 + +- Rebuild against the current OctoBot base image. + +## 1.1.0 + +- Tentacles are no longer persisted: they are wiped on every start so OctoBot + reinstalls a fresh default tentacles set each boot. User config, logs and + backtesting data still persist under `/data`. + +## 1.0.0 + +- Initial release: wraps `drakkarsoftware/octobot:stable`, exposes the Node + web interface on port 8000 with a web UI button, persists OctoBot data + under `/data`. diff --git a/homeassistant/octobot/DOCS.md b/homeassistant/octobot/DOCS.md new file mode 100644 index 0000000000..7b485d23de --- /dev/null +++ b/homeassistant/octobot/DOCS.md @@ -0,0 +1,45 @@ +# Home Assistant Add-on: OctoBot + +## How to use + +This add-on runs [OctoBot](https://github.com/Drakkar-Software/OctoBot), an +open-source cryptocurrency trading bot, from the official +`drakkarsoftware/octobot:stable` Docker image. + +Click **OPEN WEB UI** (or go to `http://:8000/app`) to reach the OctoBot +Node web interface. The web interface is served without a login by default; +configure exchanges, strategies and password protection from within OctoBot +itself once it's running. + +> The Node web interface is being rolled out as OctoBot's default UI. If +> `/app` returns a 404 on your installed OctoBot version, the node web bundle +> isn't baked into that build yet — uncomment the `5001/tcp` port in this +> add-on's config and point the web UI button at +> `http://:5001` (the classic dashboard) as a fallback. + +## Configuration + +### Option: `enable_node_api` + +Enables OctoBot's Node API/web interface service on port 8000. Leave this on +unless you only need the classic dashboard on 5001. + +### Option: `node_external_host` (optional) + +External host (and port, if non-default) the Node sync/mobile features advertise +— set this when OctoBot is reached through a reverse proxy or a public hostname +different from the add-on's own address. Leave empty to keep OctoBot's default. + +## Data persistence + +OctoBot's user config, logs and backtesting data are stored under this add-on's +persistent `/data` folder, so they survive add-on updates and rebuilds. + +Installed **tentacles are intentionally not persisted**: they are wiped on every +start, so OctoBot reinstalls a fresh default tentacles set each time the add-on +boots. + +## Want the bleeding edge instead? + +Install the **OctoBot DEV** add-on instead, which tracks +`drakkarsoftware/octobot:latest`. diff --git a/homeassistant/octobot/README.md b/homeassistant/octobot/README.md new file mode 100644 index 0000000000..fa59c097c6 --- /dev/null +++ b/homeassistant/octobot/README.md @@ -0,0 +1,10 @@ +# Home Assistant Add-on: OctoBot + +_Run [OctoBot](https://github.com/Drakkar-Software/OctoBot), an open-source +cryptocurrency trading bot, as a Home Assistant add-on (stable channel)._ + +![Supports aarch64 Architecture][aarch64-shield] +![Supports amd64 Architecture][amd64-shield] + +[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg +[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg diff --git a/homeassistant/octobot/config.yaml b/homeassistant/octobot/config.yaml new file mode 100644 index 0000000000..dd4bf7c027 --- /dev/null +++ b/homeassistant/octobot/config.yaml @@ -0,0 +1,30 @@ +# https://developers.home-assistant.io/docs/apps/configuration#app-configuration +name: OctoBot +# Placeholder only — never edit by hand. ha-publish overwrites this field on every +# tag push (see homeassistant/ci/bump-version.sh), so it always tracks the OctoBot +# git tag that triggered the release. +version: "0.0.0" +slug: octobot +description: OctoBot crypto trading bot (stable) with Node web interface +url: "https://github.com/drakkar-software/OctoBot/tree/master/homeassistant/octobot" +arch: + - aarch64 + - amd64 +init: false +ports: + 8000/tcp: 8000 + # 5001/tcp: 5001 # fallback: classic web dashboard, uncomment if node /app 404s +ports_description: + 8000/tcp: "OctoBot Node web interface" +webui: "http://[HOST]:[PORT:8000]/app" +options: + enable_node_api: true +schema: + enable_node_api: bool + node_external_host: str? +image: "ghcr.io/drakkar-software/addon-octobot" +# ^ No {arch} placeholder: both the CI builder and the Supervisor prepend the +# arch prefix themselves (e.g. amd64-addon-octobot); a literal {arch} token +# in this name breaks the CI build (invalid tag: double arch prefix). +# Required for CI to know where to publish; comment out only for local +# Supervisor builds from the Dockerfile — keep it set on main. diff --git a/homeassistant/octobot/icon.png b/homeassistant/octobot/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4b35088f76e5893bab148565ab99d78b23e03f4e GIT binary patch literal 11805 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/dev/null || echo true; } +node_external_host() { python3 -c "import json;print(json.load(open('$OPTS')).get('node_external_host') or '')" 2>/dev/null || echo ""; } + +export ENABLE_NODE_API="$(enable_node_api)" +export AUTO_OPEN_IN_WEB_BROWSER=false + +# Optional external host (and port, if non-default) the Node sync/mobile features +# advertise — set it when OctoBot sits behind a reverse proxy or a public +# hostname. Only exported when non-empty so OctoBot keeps its own default. +NODE_EXTERNAL_HOST_VALUE="$(node_external_host)" +[ -n "$NODE_EXTERNAL_HOST_VALUE" ] && export NODE_EXTERNAL_HOST="$NODE_EXTERNAL_HOST_VALUE" + +# OctoBot resolves its user/tentacles/backtesting/logs folders as plain relative +# paths against the process's current working directory (no env override exists +# for user/tentacles/backtesting). Relocating CWD to HA's persistent /data keeps +# them off the base image's inherited /octobot/{user,tentacles,logs,backtesting} +# VOLUMEs, so data survives add-on rebuilds/updates. +cd /data + +# Tentacles are intentionally NOT persisted: wipe them on every start so OctoBot +# reinstalls a fresh default tentacles set each boot. user/logs/backtesting stay +# under /data and keep persisting. +rm -rf /data/tentacles + +exec OctoBot "$@" diff --git a/homeassistant/tests/options.json b/homeassistant/tests/options.json new file mode 100644 index 0000000000..b058d06a3d --- /dev/null +++ b/homeassistant/tests/options.json @@ -0,0 +1,3 @@ +{ + "enable_node_api": true +} diff --git a/homeassistant/tests/smoke.sh b/homeassistant/tests/smoke.sh new file mode 100755 index 0000000000..6d1e1af980 --- /dev/null +++ b/homeassistant/tests/smoke.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Builds homeassistant/Dockerfile.HA on top of a given core OctoBot image and +# asserts the behaviours rootfs/entrypoint.sh exists to provide — the entire +# reason the HA wrapper exists over running the base image directly: +# +# 1. the container answers on :8000/app (Node web interface) +# 2. /data/tentacles is wiped on every boot (tentacles are never persisted) +# 3. the running process's cwd is /data (so user/logs/backtesting persist +# under HA's /data volume, not the base image's /octobot/* VOLUMEs) +# 4. ENABLE_NODE_API reflects options.json, and NODE_EXTERNAL_HOST stays +# unset when the option is absent/empty +# +# Usage: homeassistant/tests/smoke.sh +# No registry access required if is already loaded locally +# (docker/build-push-action's `load: true` in the docker job's PR path does this). +set -euo pipefail + +BASE_FROM="${1:?usage: smoke.sh }" +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # homeassistant/ +IMG="octobot-ha-smoketest" +CONTAINER="octobot-ha-smoketest" +DATA_DIR="$(mktemp -d)" +FAIL=0 + +cleanup() { + docker rm -f "$CONTAINER" >/dev/null 2>&1 || true + rm -rf "$DATA_DIR" +} +trap cleanup EXIT + +check() { + local desc="$1" got="$2" want="$3" + if [[ "$got" == "$want" ]]; then + echo "OK - $desc" + else + echo "FAIL - $desc (got: '$got', want: '$want')" + FAIL=1 + fi +} + +echo "Building $IMG from BUILD_FROM=$BASE_FROM ..." +docker build -t "$IMG" --build-arg "BUILD_FROM=$BASE_FROM" -f "$HERE/Dockerfile.HA" "$HERE" + +mkdir -p "$DATA_DIR/tentacles" +touch "$DATA_DIR/tentacles/marker" # must be gone after boot: tentacles are wiped every start +cp "$HERE/tests/options.json" "$DATA_DIR/options.json" + +echo "Starting $CONTAINER ..." +docker run -d --name "$CONTAINER" -p 18000:8000 -v "$DATA_DIR:/data" "$IMG" >/dev/null + +# The wrapper contract (cwd, tentacle wipe, env translation) is set up by +# rootfs/entrypoint.sh before it execs OctoBot, so these don't need to wait for +# the app to finish booting — just for the container to be up. entrypoint execs +# OctoBot as the final step, so OctoBot is PID 1 inside the container. +sleep 5 + +CWD="$(docker exec "$CONTAINER" readlink /proc/1/cwd)" +check "process cwd is /data" "$CWD" "/data" + +check "/data/tentacles wiped on boot" "$( [[ -e "$DATA_DIR/tentacles/marker" ]] && echo present || echo absent )" "absent" + +ENVIRON="$(docker exec "$CONTAINER" sh -c "tr '\0' '\n' < /proc/1/environ")" +check "ENABLE_NODE_API reflects options.json" "$(grep -c '^ENABLE_NODE_API=true$' <<<"$ENVIRON")" "1" +check "NODE_EXTERNAL_HOST unset when absent from options.json" "$(grep -c '^NODE_EXTERNAL_HOST=' <<<"$ENVIRON")" "0" + +# Non-fatal: OctoBot must download a full tentacles set from tentacles.octobot.online +# before /app serves anything (tentacles are wiped above, unlike the core docker job's +# health check which mounts tentacles from the host). Also documented in DOCS.md as +# occasionally 404-ing on builds that predate the node web bundle, with :5001 as the +# fallback. A slow or missing response here is not evidence the wrapper is broken. +echo "Waiting for :8000/app (best-effort, not a hard gate) ..." +up=false +for _ in $(seq 1 60); do + if curl -sf -o /dev/null "http://127.0.0.1:18000/app"; then + up=true + break + fi + sleep 3 +done +if [[ "$up" == "true" ]]; then + echo "OK - answers on :8000/app" +else + echo "WARN - never answered on :8000/app within 180s (non-fatal, see comment above)" + docker logs "$CONTAINER" || true +fi + +exit "$FAIL" diff --git a/packages/commons/octobot_commons/tests/test_config.py b/packages/commons/octobot_commons/tests/test_config.py index 2bd7c70289..1eae936711 100644 --- a/packages/commons/octobot_commons/tests/test_config.py +++ b/packages/commons/octobot_commons/tests/test_config.py @@ -29,7 +29,7 @@ def get_test_config(test_folder=TEST_FOLDER): Return test default config :return: test default config """ - return os.path.join(test_folder, STATIC_FOLDER, constants.CONFIG_FILE) + return os.path.join(test_folder, STATIC_FOLDER, "bot_config.json") def get_test_profile(test_folder=TEST_FOLDER): diff --git a/packages/commons/tests/static/config.json b/packages/commons/tests/static/bot_config.json similarity index 100% rename from packages/commons/tests/static/config.json rename to packages/commons/tests/static/bot_config.json diff --git a/packages/evaluators/tests/static/config.json b/packages/evaluators/tests/static/bot_config.json similarity index 100% rename from packages/evaluators/tests/static/config.json rename to packages/evaluators/tests/static/bot_config.json diff --git a/packages/tentacles/Meta/Keywords/scripting_library/tests/static/config.json b/packages/tentacles/Meta/Keywords/scripting_library/tests/static/bot_config.json similarity index 100% rename from packages/tentacles/Meta/Keywords/scripting_library/tests/static/config.json rename to packages/tentacles/Meta/Keywords/scripting_library/tests/static/bot_config.json diff --git a/packages/trading/tests/static/config.json b/packages/trading/tests/static/bot_config.json similarity index 100% rename from packages/trading/tests/static/config.json rename to packages/trading/tests/static/bot_config.json diff --git a/repository.yaml b/repository.yaml new file mode 100644 index 0000000000..f2f8da648b --- /dev/null +++ b/repository.yaml @@ -0,0 +1,3 @@ +name: OctoBot Home Assistant app repository +url: 'https://github.com/Drakkar-Software/OctoBot' +maintainer: 'Drakkar-Software ' diff --git a/tests/static/config.json b/tests/static/bot_config.json similarity index 100% rename from tests/static/config.json rename to tests/static/bot_config.json diff --git a/tests/unit_tests/test_configuration_manager.py b/tests/unit_tests/test_configuration_manager.py index 6edad45f00..748b311bbc 100644 --- a/tests/unit_tests/test_configuration_manager.py +++ b/tests/unit_tests/test_configuration_manager.py @@ -34,7 +34,7 @@ def test_init_config(): configuration_manager.init_config( config_file=config_path, - from_config_file=os.path.join(test_config.TEST_CONFIG_FOLDER, commons_constants.CONFIG_FILE), + from_config_file=os.path.join(test_config.TEST_CONFIG_FOLDER, "bot_config.json"), ) assert os.path.isfile(config_path) os.remove(config_path) @@ -50,7 +50,7 @@ def test_init_config_uses_runtime_user_root_not_import_time_default(tmp_path): configuration_manager.init_config( from_config_file=os.path.join( test_config.TEST_CONFIG_FOLDER, - commons_constants.CONFIG_FILE, + "bot_config.json", ), ) assert automation_config_path.is_file()