Netmaker add-on: pull prebuilt image from Gitea registry

Home Assistant add-on that joins the instance to a Netmaker network via
netclient. Distributed as a repository add-on using a prebuilt arm64 image
(gitea.savant.io/homeassistant/netmaker) to avoid local buildx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Savant Coder
2026-07-21 14:05:20 -04:00
commit 2803cf6792
27 changed files with 638 additions and 0 deletions

View File

@ -0,0 +1,29 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
export LOG_FD
# ==============================================================================
# Wait for the local network (default HA interface) to be ready
# ==============================================================================
readonly WAIT_DELAY=5 # 5s
readonly WAIT_COUNT=60 # 60*5s = 300s = 5m
declare wait_counter=0
# Some services need a working local network to function properly.
# They can mark this service as dependency, and wait for the successful startup.
# Until HA has no default interface, we wait a little
while ! bashio::api.supervisor GET "/network/interface/default/info" false &> /dev/null; do
if (( wait_counter++ == WAIT_COUNT )); then
# We emit only a warning to let the app start, maybe this is the only connection to access the device, better to start than not.
# Let netclient figure out a way to connect to the network if the local network is temporarily down.
bashio::log.warning "Local network (default Home Assistant interface) is unreachable"
break
fi
bashio::log.info "Waiting for the local network (default Home Assistant interface) to be ready..."
sleep $WAIT_DELAY
done
if (( wait_counter != 0 && wait_counter <= WAIT_COUNT )); then
bashio::log.info "Local network is ready"
fi

View File

@ -0,0 +1 @@
oneshot

View File

@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/local-network/run

View File

@ -0,0 +1,40 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
export LOG_FD
# ==============================================================================
# Home Assistant Community App: Netmaker
# Cleans up on daemon exit and takes down the S6 supervision tree on failure
# ==============================================================================
readonly exit_code_container=$(</run/s6-linux-init-container-results/exitcode)
readonly exit_code_service="${1}"
readonly exit_code_signal="${2}"
readonly service="netclient-daemon"
declare iface
bashio::log.info \
"Service ${service} exited with code ${exit_code_service}" \
"(by signal ${exit_code_signal})"
# Replicate the cleanup netclient's container entrypoint performs on shutdown.
# The daemon is exec'd, so its own shell trap does not survive; do it here.
iface=$(bashio::config 'interface')
if bashio::var.is_empty "${iface}"; then
iface="netmaker"
fi
ip rule delete pref 3000 2> /dev/null || true
ip rule delete pref 2500 2> /dev/null || true
ip rule delete pref 2000 2> /dev/null || true
ip link del "${iface}" 2> /dev/null || true
if [[ "${exit_code_service}" -eq 256 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo $((128 + $exit_code_signal)) > /run/s6-linux-init-container-results/exitcode
fi
[[ "${exit_code_signal}" -eq 15 ]] && exec /run/s6/basedir/bin/halt
elif [[ "${exit_code_service}" -ne 0 ]]; then
if [[ "${exit_code_container}" -eq 0 ]]; then
echo "${exit_code_service}" > /run/s6-linux-init-container-results/exitcode
fi
exec /run/s6/basedir/bin/halt
fi

View File

@ -0,0 +1,39 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
export LOG_FD
# ==============================================================================
# Home Assistant Community App: Netmaker
# Runs the netclient daemon
# ==============================================================================
declare log_level
declare verbosity
bashio::log.info 'Starting Netmaker netclient...'
# Netclient stores its host identity, keys and node/server config under the
# hard-coded path /etc/netclient/. Persist it across restarts by pointing that
# path at the add-on's /data volume so the node is not re-registered each boot.
mkdir -p /data/netclient
if [ ! -L /etc/netclient ]; then
# Migrate anything an earlier image may have written to the real directory
if [ -d /etc/netclient ] && [ ! -d /data/netclient/.migrated ]; then
cp -a /etc/netclient/. /data/netclient/ 2>/dev/null || true
rm -rf /etc/netclient
fi
ln -sf /data/netclient /etc/netclient
fi
# Map the app's log_level to netclient's numeric verbosity (0..4)
log_level=$(bashio::config 'log_level')
case "${log_level}" in
trace) verbosity=4 ;;
debug) verbosity=3 ;;
info|notice) verbosity=2 ;;
warning) verbosity=1 ;;
error|fatal) verbosity=0 ;;
*) verbosity=2 ;;
esac
# Run the daemon in the foreground so s6 supervises it directly.
# functions.Daemon() blocks and handles SIGTERM (clean shutdown) / SIGHUP (reload).
exec /opt/netclient daemon -v "${verbosity}"

View File

@ -0,0 +1 @@
longrun

View File

@ -0,0 +1,68 @@
#!/command/with-contenv bashio
# shellcheck shell=bash
export LOG_FD
# ==============================================================================
# Home Assistant Community App: Netmaker
# Joins the netmaker network once, using an enrollment token
# ==============================================================================
declare token
declare -a options=()
declare value
declare wait_counter=0
readonly NODES_FILE="/etc/netclient/nodes.json"
readonly WAIT_DELAY=2
readonly WAIT_COUNT=30 # 30*2s = 60s
# An enrollment token is required for a headless join. The token is a base64
# blob that already embeds the netmaker server address, so nothing else is
# needed to reach the server.
token=$(bashio::config 'enrollment_token')
if bashio::var.is_empty "${token}"; then
bashio::exit.nok \
"No 'enrollment_token' configured. Generate one in your Netmaker dashboard" \
"(Enrollment Keys) and set it in this app's configuration."
fi
# If this host is already registered to a network, don't join again. The daemon
# keeps nodes.json populated; a non-empty object means we have joined before.
if bashio::fs.file_exists "${NODES_FILE}" \
&& jq --exit-status 'length > 0' "${NODES_FILE}" > /dev/null 2>&1; then
bashio::log.info "netclient is already joined to a network, skipping join"
exit 0
fi
# Build the optional join flags from configuration.
if value=$(bashio::config 'host_name') && bashio::var.has_value "${value}"; then
options+=(-o "${value}")
fi
if bashio::config.has_value 'port' && [[ "$(bashio::config 'port')" -ne 0 ]]; then
options+=(-p "$(bashio::config 'port')")
fi
if value=$(bashio::config 'interface') && bashio::var.has_value "${value}"; then
options+=(-I "${value}")
fi
if value=$(bashio::config 'endpoint') && bashio::var.has_value "${value}"; then
options+=(-e "${value}")
fi
if value=$(bashio::config 'endpoint6') && bashio::var.has_value "${value}"; then
options+=(-E "${value}")
fi
if value=$(bashio::config 'firewall') && bashio::var.has_value "${value}"; then
options+=(-f "${value}")
fi
# Give the daemon a moment to initialize its config directory before joining.
while [ ! -d /etc/netclient ]; do
if (( wait_counter++ == WAIT_COUNT )); then
break
fi
sleep "${WAIT_DELAY}"
done
bashio::log.info "Joining netmaker network..."
if ! /opt/netclient join -t "${token}" "${options[@]}"; then
bashio::exit.nok "Failed to join the netmaker network"
fi
bashio::log.info "Successfully joined the netmaker network"

View File

@ -0,0 +1 @@
oneshot

View File

@ -0,0 +1 @@
/etc/s6-overlay/s6-rc.d/netclient-join/run