Files
Alexandre 0ead28a7bf feat: add Portainer Business Edition add-on (#873) (#2916)
* feat(portainer_be): add Portainer Business Edition add-on

Adds a new `portainer_be` add-on based on the existing Portainer (CE)
add-on, requested in #873.

Business Edition has no public GitHub release tarball like CE, so the
binary and web assets are pulled from the official multi-arch
`portainer/portainer-ee` image via a multi-stage build and placed under
/opt/portainer, mirroring CE's layout exactly. All runtime scripts,
nginx/ingress config, options schema, SSL and password handling are
unchanged from CE, so behaviour is identical apart from the edition.

Users obtain a free (up to 3 nodes) Business Edition license key by
registering with Portainer and enter it in the web UI on first launch.

- config.yaml: slug portainer_be, BE image name, BE description/name
- Dockerfile: multi-stage COPY from portainer/portainer-ee (no CE tarball)
- updater.json: dockerhub source tracking portainer/portainer-ee
- apparmor.txt: unique profile name (portainer_be_addon)
- CHANGELOG/README/DOCS: BE-specific, documents the license-key step

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(portainer_be): address PR review feedback

- nginx finish: move shebang to byte 0 (leading blank line prevented S6 from
  recognising the interpreter, so the finish hook could fail to tear down the
  supervision tree) [Codex P2]
- ingress: tighten CSP to `frame-ancestors 'self'` to match the adjacent
  X-Frame-Options SAMEORIGIN; HA ingress embeds same-origin so the panel keeps
  working [CodeRabbit]
- README: correct login note (password is the configured option value, never
  printed to logs); drop MD012 consecutive blank lines [CodeRabbit]
- DOCS: fix "environement" -> "environment" typo [CodeRabbit]

Skipped: nginx SSL "idempotency" finding — /etc/nginx lives in the read-only
image layer and cont-init re-renders from the pristine template on every
container start, so in-place sed edits never accumulate or need restoring.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* revert(portainer_be): keep CSP frame-ancestors * for ingress compatibility

Reverts the frame-ancestors 'self' change from the previous commit. The
wildcard is required for the Home Assistant ingress iframe to embed the
Portainer UI; tightening it breaks the ingress panel. Matches the CE add-on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 22:08:17 +02:00

108 lines
3.4 KiB
Plaintext
Executable File

#!/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[@]}"