From 2803cf6792e02e4f2b36b85927ee867a78ddbc68 Mon Sep 17 00:00:00 2001 From: Savant Coder Date: Tue, 21 Jul 2026 14:05:20 -0400 Subject: [PATCH] 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 --- LICENSE.md | 21 ++++ README.md | 46 +++++++++ netmaker/.README.j2 | 39 ++++++++ netmaker/DOCS.md | 95 +++++++++++++++++++ netmaker/Dockerfile | 63 ++++++++++++ netmaker/apparmor.txt | 45 +++++++++ netmaker/build.yaml | 4 + netmaker/config.yaml | 46 +++++++++ netmaker/rootfs/command/with-contenv-merge | 14 +++ .../s6-rc.d/local-network/dependencies.d/base | 0 .../etc/s6-overlay/s6-rc.d/local-network/run | 29 ++++++ .../etc/s6-overlay/s6-rc.d/local-network/type | 1 + .../etc/s6-overlay/s6-rc.d/local-network/up | 1 + .../netclient-daemon/dependencies.d/base | 0 .../s6-rc.d/netclient-daemon/finish | 40 ++++++++ .../s6-overlay/s6-rc.d/netclient-daemon/run | 39 ++++++++ .../s6-overlay/s6-rc.d/netclient-daemon/type | 1 + .../dependencies.d/local-network | 0 .../dependencies.d/netclient-daemon | 0 .../etc/s6-overlay/s6-rc.d/netclient-join/run | 68 +++++++++++++ .../s6-overlay/s6-rc.d/netclient-join/type | 1 + .../etc/s6-overlay/s6-rc.d/netclient-join/up | 1 + .../s6-rc.d/user/contents.d/netclient-daemon | 0 .../s6-rc.d/user/contents.d/netclient-join | 0 netmaker/rootfs/usr/bin/healthcheck | 35 +++++++ netmaker/translations/en.yaml | 45 +++++++++ repository.yaml | 4 + 27 files changed, 638 insertions(+) create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 netmaker/.README.j2 create mode 100644 netmaker/DOCS.md create mode 100644 netmaker/Dockerfile create mode 100644 netmaker/apparmor.txt create mode 100644 netmaker/build.yaml create mode 100644 netmaker/config.yaml create mode 100644 netmaker/rootfs/command/with-contenv-merge create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/dependencies.d/base create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/run create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/type create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/up create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/dependencies.d/base create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/finish create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/run create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/type create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/dependencies.d/local-network create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/dependencies.d/netclient-daemon create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/run create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/type create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/up create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/netclient-daemon create mode 100644 netmaker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/netclient-join create mode 100644 netmaker/rootfs/usr/bin/healthcheck create mode 100644 netmaker/translations/en.yaml create mode 100644 repository.yaml diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..c5304ad --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2026 Savant Coder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..a7f5738 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Home Assistant Community App: Netmaker + +![Project Stage][project-stage-shield] + +![Supports aarch64 Architecture][aarch64-shield] +![Supports amd64 Architecture][amd64-shield] + +Join your Home Assistant instance to a Netmaker WireGuard network. + +## About + +[Netmaker][netmaker] is a platform for creating and managing fast, secure +WireGuard networks. This app joins your Home Assistant instance to a Netmaker +network using the Netmaker client, `netclient`. + +You provide an enrollment token generated in your Netmaker dashboard, and the +app registers your device and keeps the connection running in the background. +The host's WireGuard identity is persisted to the app's `/data` volume, so your +device remains the same node across restarts and updates. + +## Installation + +The installation of this app is straightforward and not different in comparison +to installing any other Home Assistant app. + +1. Install this app. +1. Generate an enrollment token in your Netmaker dashboard (**Enrollment Keys**). +1. Set the token in the app's `enrollment_token` configuration option. +1. Start the "Netmaker" app. +1. Check the logs of the app to confirm it joined the network. + +See the [documentation][docs] for full configuration details. + +## Support + +This add-on is a community port and is not affiliated with Netmaker. + +## License + +MIT License — see [LICENSE.md](LICENSE.md). + +[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg +[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg +[docs]: netmaker/DOCS.md +[netmaker]: https://www.netmaker.io/ +[project-stage-shield]: https://img.shields.io/badge/project%20stage-experimental-yellow.svg diff --git a/netmaker/.README.j2 b/netmaker/.README.j2 new file mode 100644 index 0000000..a178d42 --- /dev/null +++ b/netmaker/.README.j2 @@ -0,0 +1,39 @@ +# Home Assistant Community App: Netmaker + +![Project Stage][project-stage-shield] ![Project Maintenance][maintenance-shield] + +Join your Home Assistant instance to a Netmaker WireGuard network. + +## About + +Netmaker is a platform for creating and managing fast, secure WireGuard +networks. This app joins your Home Assistant instance to a Netmaker network +using the Netmaker client, `netclient`. + +You provide an enrollment token from your Netmaker dashboard, and the app +registers your device and keeps the connection running in the background. + +{% if channel == "edge" %} +## WARNING! THIS IS AN EDGE VERSION! + +This Home Assistant Apps repository contains edge builds of apps. +Edge builds apps are based upon the latest development version. + +- They may not work at all. +- They might stop working at any time. +- They could have a negative impact on your system. + +{% endif %} +{% if channel == "beta" %} +## WARNING! THIS IS A BETA VERSION! + +This Home Assistant Apps repository contains beta releases of apps. + +- They might stop working at any time. +- They could have a negative impact on your system. + +{% endif %} +[maintenance-shield]: https://img.shields.io/maintenance/yes/2026.svg +[project-stage-shield]: https://img.shields.io/badge/project%20stage-experimental-yellow.svg +[release-shield]: https://img.shields.io/badge/version-{{ version }}-blue.svg +[release]: {{ repo }}/tree/{{ version }} diff --git a/netmaker/DOCS.md b/netmaker/DOCS.md new file mode 100644 index 0000000..194352d --- /dev/null +++ b/netmaker/DOCS.md @@ -0,0 +1,95 @@ +# Home Assistant Community App: Netmaker + +Netmaker is a platform for creating and managing fast, secure WireGuard +networks. This app joins your Home Assistant instance to a Netmaker network +using the Netmaker client, `netclient`. + +## Prerequisites + +In order to use this app, you'll need access to a Netmaker server and an +**enrollment token** (also called an enrollment key) for the network you want +to join. + +You can run your own Netmaker server or use a hosted one. See the Netmaker +documentation for details: + + + +## Installation + +1. Install this app. +1. Generate an enrollment token in your Netmaker dashboard, under + **Enrollment Keys**. +1. Open the app's **Configuration** tab and paste the token into the + `enrollment_token` option. +1. Start the "Netmaker" app. +1. Check the logs of the "Netmaker" app to confirm it joined the network. +1. Done! + +The host's WireGuard identity and keys are stored on the app's persistent +`/data` volume, so your device stays the same node across restarts and updates. + +## Configuration + +Example configuration: + +```yaml +enrollment_token: "eyJ...your-token..." +log_level: info +host_name: "" +port: 0 +interface: "" +endpoint: "" +endpoint6: "" +firewall: "" +``` + +### Option: `enrollment_token` (required) + +The enrollment token used to join your Netmaker network. Generate one in your +Netmaker dashboard under **Enrollment Keys**. The token already contains the +address of your Netmaker server, so no separate server URL is needed. + +### Option: `log_level` + +Controls the verbosity of the netclient logs. One of `trace`, `debug`, `info`, +`notice`, `warning`, `error`, `fatal`. Use `debug` or `trace` when +troubleshooting connection problems. + +### Option: `host_name` + +Optional name to register this device as within your Netmaker network. Leave +empty to use the default host name. + +### Option: `port` + +Optional UDP port for WireGuard to listen on. Leave at `0` to let netclient +choose a port. If you set a specific port, expose the same port in the +**Network** section of the app. + +### Option: `interface` + +Optional name for the WireGuard network interface created on the host. On Linux +the name must start with `netmaker`. Leave empty for the default. + +### Option: `endpoint` / `endpoint6` + +Optional static public IPv4/IPv6 endpoint for this host. Leave empty to let +Netmaker detect the endpoint automatically. + +### Option: `firewall` + +Optional firewall backend to use for ACL enforcement: `iptables` or `nftables`. +Leave empty to let netclient auto-detect. If neither tool is available, +netclient runs without firewall/ACL enforcement (the connection still works). + +## Network + +This app runs on the host network. netclient uses WireGuard over UDP; by +default port `51821/udp` is used. If you configure a specific `port`, expose it +in the **Network** section. + +## Leaving a network + +To leave the Netmaker network, uninstall the app. To rotate the node's +identity, clear the app's data (uninstall and reinstall) before joining again. diff --git a/netmaker/Dockerfile b/netmaker/Dockerfile new file mode 100644 index 0000000..a80b265 --- /dev/null +++ b/netmaker/Dockerfile @@ -0,0 +1,63 @@ +ARG BUILD_FROM=ghcr.io/hassio-addons/base/amd64:10.1.1 +# hadolint ignore=DL3006 +FROM ${BUILD_FROM} + +# Set shell +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Setup base +ARG BUILD_ARCH=amd64 +ARG NETCLIENT_VERSION="v1.6.0" +# hadolint ignore=DL3018 +RUN \ + apk add --no-cache \ + iproute2 \ + iptables \ + ip6tables \ + nftables \ + wireguard-tools \ + openresolv \ + coreutils \ + \ + && ln -sf /sbin/xtables-nft-multi /sbin/ip6tables \ + && ln -sf /sbin/xtables-nft-multi /sbin/iptables \ + \ + && if [ "${BUILD_ARCH}" = "aarch64" ]; then ARCH="arm64"; fi \ + && if [ "${BUILD_ARCH}" = "amd64" ]; then ARCH="amd64"; fi \ + \ + && curl -L -s -o /opt/netclient \ + "https://github.com/gravitl/netclient/releases/download/${NETCLIENT_VERSION}/netclient-linux-${ARCH}" \ + && chmod 0755 /opt/netclient \ + && ln -sf /opt/netclient /usr/bin/netclient + +# Copy root filesystem +COPY rootfs / + +HEALTHCHECK \ + CMD healthcheck + +# Build arguments +ARG BUILD_ARCH +ARG BUILD_DATE +ARG BUILD_DESCRIPTION +ARG BUILD_NAME +ARG BUILD_REF +ARG BUILD_REPOSITORY +ARG BUILD_VERSION + +# Labels +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} \ + org.opencontainers.image.title="${BUILD_NAME}" \ + org.opencontainers.image.description="${BUILD_DESCRIPTION}" \ + org.opencontainers.image.vendor="Home Assistant Community Apps" \ + org.opencontainers.image.licenses="MIT" \ + 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} diff --git a/netmaker/apparmor.txt b/netmaker/apparmor.txt new file mode 100644 index 0000000..04df7d1 --- /dev/null +++ b/netmaker/apparmor.txt @@ -0,0 +1,45 @@ +#include + +profile netmaker flags=(attach_disconnected,mediate_deleted) { + #include + + # Capabilities + file, + signal (send) set=(kill,term,int,hup,cont), + + # S6-Overlay + /init ix, + /bin/** ix, + /usr/bin/** ix, + /run/{s6,s6-rc*,service}/** ix, + /package/** ix, + /command/** ix, + /etc/services.d/** rwix, + /etc/cont-init.d/** rwix, + /etc/cont-finish.d/** rwix, + /run/{,**} rwk, + /dev/tty rw, + + # Bashio + /usr/lib/bashio/** ix, + /tmp/** rwk, + + # Access to options.json and other files within your app + /data/** rw, + + # Netclient configuration/state (symlinked to /data/netclient) + /etc/netclient/{,**} rw, + + # General - capabilities derived from complain mode analysis + capability net_bind_service, + capability dac_override, + capability fsetid, + capability setgid, + capability setuid, + capability chown, + capability kill, + + # General - based on Config.yaml + capability net_admin, + capability net_raw, +} diff --git a/netmaker/build.yaml b/netmaker/build.yaml new file mode 100644 index 0000000..3fe413f --- /dev/null +++ b/netmaker/build.yaml @@ -0,0 +1,4 @@ +--- +build_from: + aarch64: ghcr.io/hassio-addons/base/aarch64:10.1.1 + amd64: ghcr.io/hassio-addons/base/amd64:10.1.1 diff --git a/netmaker/config.yaml b/netmaker/config.yaml new file mode 100644 index 0000000..78ae942 --- /dev/null +++ b/netmaker/config.yaml @@ -0,0 +1,46 @@ +--- +name: Netmaker +version: dev +slug: netmaker +description: Join your Home Assistant instance to a Netmaker WireGuard network +url: https://github.com/hassio-addons/app-netmaker +image: gitea.savant.io/homeassistant/netmaker +startup: services +panel_icon: mdi:lan-connect +arch: + - aarch64 + - amd64 +init: false +hassio_api: true +host_network: true +privileged: + - NET_ADMIN + - NET_RAW +devices: + - /dev/net/tun +map: + - type: homeassistant_config + read_only: false + path: /config + - type: share + read_only: false +ports: + 51821/udp: null +options: + enrollment_token: "" + log_level: info + host_name: "" + port: 0 + interface: "" + endpoint: "" + endpoint6: "" + firewall: "" +schema: + enrollment_token: str + log_level: list(trace|debug|info|notice|warning|error|fatal) + host_name: str? + port: int(0,65535)? + interface: "match(^netmaker[a-zA-Z0-9_-]*$)?" + endpoint: str? + endpoint6: str? + firewall: list(iptables|nftables)? diff --git a/netmaker/rootfs/command/with-contenv-merge b/netmaker/rootfs/command/with-contenv-merge new file mode 100644 index 0000000..596bfc6 --- /dev/null +++ b/netmaker/rootfs/command/with-contenv-merge @@ -0,0 +1,14 @@ +#!/command/execlineb -S0 + +ifelse +{ + importas -D 0 S6_KEEP_ENV S6_KEEP_ENV + eltest 0${S6_KEEP_ENV} -eq 0 +} +{ + s6-envdir -Lf -- /run/s6/container_environment + exec + $@ +} + +$@ diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/dependencies.d/base b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/dependencies.d/base new file mode 100644 index 0000000..e69de29 diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/run b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/run new file mode 100644 index 0000000..31c7e06 --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/run @@ -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 diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/type b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/type new file mode 100644 index 0000000..bdd22a1 --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/type @@ -0,0 +1 @@ +oneshot diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/up b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/up new file mode 100644 index 0000000..0831d37 --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/local-network/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/local-network/run diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/dependencies.d/base b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/dependencies.d/base new file mode 100644 index 0000000..e69de29 diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/finish b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/finish new file mode 100644 index 0000000..326fbe8 --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/finish @@ -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=$( /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 diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/run b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/run new file mode 100644 index 0000000..fbdbe8e --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/run @@ -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}" diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/type b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/type new file mode 100644 index 0000000..5883cff --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-daemon/type @@ -0,0 +1 @@ +longrun diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/dependencies.d/local-network b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/dependencies.d/local-network new file mode 100644 index 0000000..e69de29 diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/dependencies.d/netclient-daemon b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/dependencies.d/netclient-daemon new file mode 100644 index 0000000..e69de29 diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/run b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/run new file mode 100644 index 0000000..a5388f1 --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/run @@ -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" diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/type b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/type new file mode 100644 index 0000000..bdd22a1 --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/type @@ -0,0 +1 @@ +oneshot diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/up b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/up new file mode 100644 index 0000000..ab1a418 --- /dev/null +++ b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/netclient-join/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/netclient-join/run diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/netclient-daemon b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/netclient-daemon new file mode 100644 index 0000000..e69de29 diff --git a/netmaker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/netclient-join b/netmaker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/netclient-join new file mode 100644 index 0000000..e69de29 diff --git a/netmaker/rootfs/usr/bin/healthcheck b/netmaker/rootfs/usr/bin/healthcheck new file mode 100644 index 0000000..9a720b8 --- /dev/null +++ b/netmaker/rootfs/usr/bin/healthcheck @@ -0,0 +1,35 @@ +#!/command/with-contenv bashio +# shellcheck shell=bash +export LOG_FD + +# Netclient has no queryable status socket, so this treats the app as: +# - healthy while it is still trying to join (within HEALTHCHECK_RESTART_TIMEOUT) +# - healthy once it has joined a network and the daemon process is running +# - unhealthy if it never manages to join within HEALTHCHECK_RESTART_TIMEOUT + +# Redirect outputs to the log (&1 and &2 are not initialized by Docker) +exec &> /proc/1/fd/1 +if [[ "${LOG_FD:-}" =~ ^[0-9]+$ ]]; then + eval "exec ${LOG_FD}>&1" || true +fi + +readonly HEALTHCHECK_RESTART_TIMEOUT=3600 # 1 hour +readonly NODES_FILE="/etc/netclient/nodes.json" + +# STARTED_TIMESTAMP is stored in contenv at /var/run/s6/container_environment +if ! bashio::var.has_value "${STARTED_TIMESTAMP-}"; then + STARTED_TIMESTAMP=$(date +"%s") + printf "%s" "${STARTED_TIMESTAMP}" > /var/run/s6/container_environment/STARTED_TIMESTAMP +fi + +# Healthy as soon as we are joined to at least one network. +if bashio::fs.file_exists "${NODES_FILE}" \ + && jq --exit-status 'length > 0' "${NODES_FILE}" > /dev/null 2>&1; then + exit 0 +fi + +# Not joined yet: stay healthy during the initial window, then fail so the +# container is restarted. +if (( $(date +"%s") - STARTED_TIMESTAMP > HEALTHCHECK_RESTART_TIMEOUT )); then + bashio::exit.nok "netclient has not joined a network within the expected time" +fi diff --git a/netmaker/translations/en.yaml b/netmaker/translations/en.yaml new file mode 100644 index 0000000..5230fb1 --- /dev/null +++ b/netmaker/translations/en.yaml @@ -0,0 +1,45 @@ +--- +configuration: + enrollment_token: + name: Enrollment token + description: >- + The enrollment token (enrollment key) used to join your Netmaker network. + Generate one in your Netmaker dashboard under "Enrollment Keys" and paste + it here. The token already contains the address of your Netmaker server. + log_level: + name: Log level + description: >- + Controls the verbosity of the netclient logs. Set to "debug" or "trace" + when troubleshooting connection problems. + host_name: + name: Host name + description: >- + Optional name to register this device as within your Netmaker network. + Leave empty to use the default host name. + port: + name: WireGuard port + description: >- + Optional UDP port for WireGuard to listen on. Leave at 0 to let netclient + choose a port. If you set this, expose the same port in the Network section. + interface: + name: Interface name + description: >- + Optional name for the WireGuard network interface created on the host. + On Linux the name must start with "netmaker". Leave empty for the default. + endpoint: + name: Static endpoint (IPv4) + description: >- + Optional static public IPv4 endpoint for this host. Leave empty to let + Netmaker detect the endpoint automatically. + endpoint6: + name: Static endpoint (IPv6) + description: >- + Optional static public IPv6 endpoint for this host. Leave empty to let + Netmaker detect the endpoint automatically. + firewall: + name: Firewall + description: >- + Optional firewall backend to use for ACL enforcement (iptables or + nftables). Leave empty to let netclient auto-detect. +network: + 51821/udp: WireGuard port used by netclient diff --git a/repository.yaml b/repository.yaml new file mode 100644 index 0000000..df10e53 --- /dev/null +++ b/repository.yaml @@ -0,0 +1,4 @@ +--- +name: Savant Home Assistant Add-ons +url: https://gitea.savant.io/homeassistant/hassio-addons +maintainer: Savant Coder