Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 179 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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/')
Expand Down Expand Up @@ -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 }}
7 changes: 7 additions & 0 deletions homeassistant/Dockerfile.HA
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ARG BUILD_FROM
FROM ${BUILD_FROM}

COPY rootfs /
RUN chmod +x /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
52 changes: 52 additions & 0 deletions homeassistant/ci/addon-meta.sh
Original file line number Diff line number Diff line change
@@ -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"
51 changes: 51 additions & 0 deletions homeassistant/ci/bump-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Stamps the just-published version into homeassistant/<addon>/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
25 changes: 25 additions & 0 deletions homeassistant/octobot-latest/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!-- https://developers.home-assistant.io/docs/apps/presentation#keeping-a-changelog -->
<!-- Versions are now CI-generated per push to `dev` (see homeassistant/ci/addon-meta.sh) —
one entry per notable change, not per build. -->

## 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`.
Loading
Loading