diff --git a/nginx_webserver_proxy/CHANGELOG.md b/nginx_webserver_proxy/CHANGELOG.md new file mode 100644 index 0000000000..4600fd7248 --- /dev/null +++ b/nginx_webserver_proxy/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +## Unreleased + +- Fix Codacy style warnings: add blank lines around lists in README + +## 2.14.0 + +- Initial release wrapping jc21/nginx-proxy-manager:latest +- NPM Admin UI on port 81; HTTP on port 80; HTTPS on port 443 +- Configurable static file server via NPM's default_host nginx config +- Supports /share, /media, /config paths; warns for /mnt; blocks dangerous system paths +- NPM state persisted via Docker volume (managed by HA Supervisor) +- Supports amd64 and aarch64 diff --git a/nginx_webserver_proxy/Dockerfile b/nginx_webserver_proxy/Dockerfile new file mode 100644 index 0000000000..e7833cec95 --- /dev/null +++ b/nginx_webserver_proxy/Dockerfile @@ -0,0 +1,23 @@ +ARG BUILD_FROM=jc21/nginx-proxy-manager:latest +FROM ${BUILD_FROM} + +# NPM is Debian-based; install jq for options parsing + stub with-contenv if absent +# hadolint ignore=DL3008 +RUN apt-get update \ + && apt-get install -y --no-install-recommends jq \ + && rm -rf /var/lib/apt/lists/* \ + && if ! command -v with-contenv >/dev/null 2>&1; then \ + printf '#!/usr/bin/env bash\nexec "$@"\n' > /usr/bin/with-contenv \ + && chmod +x /usr/bin/with-contenv; \ + fi + +COPY run.sh /npm-addon-init.sh +RUN chmod +x /npm-addon-init.sh + +ARG BUILD_VERSION +LABEL \ + io.hass.version="${BUILD_VERSION}" \ + io.hass.type="addon" \ + io.hass.arch="aarch64|amd64" + +ENTRYPOINT ["/npm-addon-init.sh"] diff --git a/nginx_webserver_proxy/README.md b/nginx_webserver_proxy/README.md new file mode 100644 index 0000000000..803637603c --- /dev/null +++ b/nginx_webserver_proxy/README.md @@ -0,0 +1,85 @@ +# Nginx Proxy Manager + Static Web Server + +[Nginx Proxy Manager](https://nginxproxymanager.com/) with a configurable static file server for Home Assistant. Manage reverse proxies and SSL certificates via the web UI (port 81) while serving static files from your HA storage (port 80). + +## Why This Add-on? + +Home Assistant's built-in folder server has limitations: + +- Can only serve from a single folder at a time +- No reverse proxy capabilities +- No SSL/HTTPS support +- Limited HTTP headers and caching control +- No support for URL rewriting or advanced routing + +This add-on combines a full reverse proxy with a proper static file server, allowing you to host multiple sites, manage SSL certificates, and proxy traffic to other services from a single interface. + +[![Open your Home Assistant instance and show the add add-on repository dialog with a specific repository URL pre-filled.](https://my.home-assistant.io/badges/supervisor_add_addon_repository.svg)](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Falexbelgium%2Fhassio-addons) + +## Features + +- Reverse proxy manager (web UI on port 81) +- Static file server (port 80) +- HTTPS support (port 443) +- Persistent configuration and SSL certificates +- Works on amd64 and aarch64 + +## Installation + +1. Add this repository to Home Assistant via Settings → Add-ons → Add-on Store → ⋮ → Manage repositories (or use the button above). +2. Install Nginx Proxy Manager + Static Web Server. +3. Configure options (defaults work for first run). +4. Start the add-on. +5. Open `http://:81` to access the admin UI. + +## Configuration + +| Option | Default | Description | +|--------|---------|-------------| +| `static_site_enabled` | `true` | Enable or disable the static file server on port 80 | +| `static_site_root` | `/share/www` | Path to serve static files from | +| `static_site_prefix` | `/` | URL prefix for the static site (e.g., `/www` for `http://host/www`) | +| `log_level` | `info` | Logging verbosity: `info`, `debug`, `warn`, or `error` | + +## Default Credentials + +First login (port 81): + +- Email: `admin@example.com` +- Password: `changeme` + +Change these on first login. + +## Path Validation + +Paths are validated at startup for safe access: + +- `/share`, `/media`, `/config` – fully supported (HA maps these automatically) +- `/mnt` – allowed but not mapped by HA. Create a symlink under `/share` or `/media` if files are inaccessible. +- `/`, `/etc`, `/bin`, `/lib`, `/proc`, `/sys` – blocked (will prevent startup) + +## Examples + +**Reverse proxy:** + +1. Open the admin UI at `http://:81` +2. Add a proxy host pointing to another service +3. Configure SSL via Let's Encrypt (optional) + +**Static website:** + +1. Place files in `/share/www` (or your configured `static_site_root`) +2. Access at `http://:80/` (or your configured `static_site_prefix`) + +You can run both simultaneously on the same ports. + +## Notes + +- Wraps `jc21/nginx-proxy-manager` upstream image +- State persists in `/data` (managed by HA Supervisor) +- Custom AppArmor profile restricts system access +- Edit NPM's database directly via SSH if needed + +## Issues + +For problems with this add-on (not the upstream NPM software), open an issue and tag @ToledoEM. diff --git a/nginx_webserver_proxy/apparmor.txt b/nginx_webserver_proxy/apparmor.txt new file mode 100644 index 0000000000..7633db0c1b --- /dev/null +++ b/nginx_webserver_proxy/apparmor.txt @@ -0,0 +1,104 @@ +#include + +profile nginx-proxy-manager flags=(attach_disconnected,mediate_deleted) { + #include + #include + #include + + # Core capabilities + capability chown, + capability dac_override, + capability net_bind_service, + capability setfcap, + capability setgid, + capability setuid, + capability sys_chroot, + capability kill, + + # Process and memory management + capability sys_resource, + + # Deny dangerous capabilities + deny capability dac_read_search, + deny capability linux_immutable, + deny capability mac_admin, + deny capability mac_override, + deny capability sys_admin, + deny capability sys_boot, + deny capability sys_module, + deny capability sys_rawio, + deny capability syslog, + + # Network + network inet stream, + network inet dgram, + network inet6 stream, + network inet6 dgram, + network unix stream, + network unix dgram, + + # /data - addon config (read/write) + /data/ r, + /data/** rwk, + + # /share - Home Assistant storage (read/write) + /share/ r, + /share/** rwk, + + # /media - Home Assistant media (read/write) + /media/ r, + /media/** rwk, + + # /config - Home Assistant config (read/write) + /config/ r, + /config/** rwk, + + # /addon_configs - addon instance config + /addon_configs/ r, + /addon_configs/** rwk, + + # /etc/letsencrypt - SSL certs + /etc/letsencrypt/ r, + /etc/letsencrypt/** rwk, + + # /proc and /sys + @{PROC}/ r, + @{PROC}/** rw, + @{sys}/ r, + @{sys}/** rw, + + # Temporary files + /tmp/ r, + /tmp/** rwk, + /var/tmp/ r, + /var/tmp/** rwk, + + # Basic system access + /bin/bash ix, + /bin/sh ix, + /bin/ls ix, + /bin/cat ix, + /bin/sed ix, + /usr/bin/jq ix, + + # Nginx binary and libraries + /usr/sbin/nginx ix, + /usr/local/sbin/nginx ix, + /usr/lib/** rm, + /lib/** rm, + /usr/local/lib/** rm, + + # Allow reading app-specific configs (read-only) + /etc/nginx/ r, + /etc/nginx/** r, + /var/log/ r, + /var/log/** w, + + # Deny sensitive system areas + deny /root/** rwkl, + deny /home/** rwkl, + deny /proc/sysrq-trigger rwkl, + deny /sys/firmware/** rwkl, + deny /sys/kernel/security/** rwkl, + deny /sys/kernel/debug/** rwkl, +} diff --git a/nginx_webserver_proxy/build.yaml b/nginx_webserver_proxy/build.yaml new file mode 100644 index 0000000000..001eebf302 --- /dev/null +++ b/nginx_webserver_proxy/build.yaml @@ -0,0 +1,4 @@ +--- +build_from: + aarch64: jc21/nginx-proxy-manager:latest + amd64: jc21/nginx-proxy-manager:latest diff --git a/nginx_webserver_proxy/config.yaml b/nginx_webserver_proxy/config.yaml new file mode 100644 index 0000000000..33d1495c2c --- /dev/null +++ b/nginx_webserver_proxy/config.yaml @@ -0,0 +1,40 @@ +name: "Nginx Proxy Manager + Static Web Server" +slug: nginx_webserver_proxy +description: "Nginx Proxy Manager with a built-in configurable static file server. Manage reverse proxies via NPM UI on port 81 while serving files from HA storage on port 80." +version: "2.14.0" +url: "https://github.com/alexbelgium/hassio-addons/tree/master/nginx_webserver_proxy" +arch: + - amd64 + - aarch64 +startup: services +init: false + +ports: + 80/tcp: 80 + 81/tcp: 81 + 443/tcp: 443 +ports_description: + 80/tcp: "HTTP (static site + NPM proxy hosts)" + 81/tcp: "NPM Admin Web UI" + 443/tcp: "HTTPS (NPM proxy hosts)" + +webui: "http://[HOST]:[PORT:81]" + +map: + - addon_config:rw + - share:rw + - media:rw + +options: + static_site_enabled: true + static_site_root: "/share/www" + static_site_prefix: "/" + log_level: "info" + +schema: + static_site_enabled: bool + static_site_root: str + static_site_prefix: str + log_level: list(info|debug|warn|error) + +image: "ghcr.io/alexbelgium/nginx_webserver_proxy-{arch}" diff --git a/nginx_webserver_proxy/icon.png b/nginx_webserver_proxy/icon.png new file mode 100644 index 0000000000..7db87bfb18 Binary files /dev/null and b/nginx_webserver_proxy/icon.png differ diff --git a/nginx_webserver_proxy/logo.png b/nginx_webserver_proxy/logo.png new file mode 100644 index 0000000000..7db87bfb18 Binary files /dev/null and b/nginx_webserver_proxy/logo.png differ diff --git a/nginx_webserver_proxy/run.sh b/nginx_webserver_proxy/run.sh new file mode 100755 index 0000000000..3742218214 --- /dev/null +++ b/nginx_webserver_proxy/run.sh @@ -0,0 +1,118 @@ +#!/usr/bin/with-contenv bash +# shellcheck shell=bash +set -Eeuo pipefail + +# HA Supervisor places options.json at /data/options.json. +# NPM also uses /data for its database and generated configs. +# The /data Docker VOLUME is persisted automatically by HA Supervisor between restarts. +OPTIONS_JSON="/data/options.json" + +log() { echo "[nginx-proxy-manager-addon] $*"; } +warn() { echo "[nginx-proxy-manager-addon] WARN: $*" >&2; } +die() { + echo "[nginx-proxy-manager-addon] ERROR: $*" >&2 + exit 1 +} + +read_opt() { + jq -er --arg k "$1" '.[$k]' "$OPTIONS_JSON" 2> /dev/null || true +} + +# --------------------------------------------------------------------------- +# Step 1: Read add-on options +# --------------------------------------------------------------------------- +[[ -f "$OPTIONS_JSON" ]] || die "Missing options file at ${OPTIONS_JSON}" + +STATIC_ENABLED="$(read_opt static_site_enabled)" +STATIC_ENABLED="${STATIC_ENABLED:-true}" +STATIC_ROOT_RAW="$(read_opt static_site_root)" +STATIC_ROOT_RAW="${STATIC_ROOT_RAW:-/share/www}" +STATIC_PREFIX="$(read_opt static_site_prefix)" +STATIC_PREFIX="${STATIC_PREFIX:-/}" +LOG_LEVEL="$(read_opt log_level)" +LOG_LEVEL="${LOG_LEVEL:-info}" + +# --------------------------------------------------------------------------- +# Step 2: Validate static_site_root +# --------------------------------------------------------------------------- +normalize_path() { + if command -v realpath > /dev/null 2>&1; then + realpath -m -- "$1" + else + local p="${1%/}" + [[ "$p" == /* ]] || p="/$p" + printf '%s\n' "$p" + fi +} + +STATIC_ROOT="$(normalize_path "$STATIC_ROOT_RAW")" + +case "$STATIC_ROOT" in + / | /etc | /etc/* | /bin | /bin/* | /sbin | /sbin/* | /lib | /lib/* | /proc | /proc/* | /sys | /sys/*) + die "static_site_root '${STATIC_ROOT}' is a dangerous system path. Use /share, /media, /config, or /mnt." + ;; +esac + +case "$STATIC_ROOT" in + /share | /share/* | /media | /media/* | /config | /config/*) + log "static_site_root: ${STATIC_ROOT}" + ;; + /mnt | /mnt/*) + warn "static_site_root '${STATIC_ROOT}' is under /mnt — HA cannot map /mnt." + warn "If files are inaccessible, create a symlink under /share or /media pointing to your /mnt path." + ;; + *) + warn "static_site_root '${STATIC_ROOT}' is outside standard HA-mapped paths — may not be accessible." + ;; +esac + +[[ "$STATIC_PREFIX" == /* ]] || die "static_site_prefix must start with '/'. Got: '${STATIC_PREFIX}'" + +# --------------------------------------------------------------------------- +# Step 3: Write static-site server block into NPM's default_host dir +# NPM includes /data/nginx/default_host/*.conf for the port 80 default server. +# Writing here replaces NPM's "Congratulations" page with our static file server. +# --------------------------------------------------------------------------- +DEFAULT_HOST_DIR="/data/nginx/default_host" +mkdir -p "$DEFAULT_HOST_DIR" +STATIC_CONF="${DEFAULT_HOST_DIR}/static_site.conf" + +if [[ "$STATIC_ENABLED" == "true" ]]; then + mkdir -p "$STATIC_ROOT" 2> /dev/null \ + || warn "Could not create '${STATIC_ROOT}' (may not be mounted yet)" + + cat > "$STATIC_CONF" << NGINX_EOF +# Managed by nginx_webserver_proxy add-on — regenerated on every container start. +# Edit options in the HA add-on configuration UI, not here. +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + + location ${STATIC_PREFIX} { + alias ${STATIC_ROOT}/; + autoindex on; + autoindex_exact_size off; + autoindex_localtime on; + try_files \$uri \$uri/ =404; + access_log /proc/1/fd/1; + error_log /proc/1/fd/1 warn; + } +} +NGINX_EOF + log "Static site config written → ${STATIC_CONF}" +else + printf '# Static site disabled by add-on options\n' > "$STATIC_CONF" + log "Static site disabled" +fi + +# --------------------------------------------------------------------------- +# Step 4: Hand off to NPM's own s6-overlay boot +# --------------------------------------------------------------------------- +log "static_site_root=${STATIC_ROOT} prefix=${STATIC_PREFIX} log_level=${LOG_LEVEL}" +# NPM's prepare service requires /etc/letsencrypt to exist. +# HA Supervisor maps the ssl volume there automatically; for other environments create it. +mkdir -p /etc/letsencrypt + +log "Handing off to NPM: exec /init" +exec /init diff --git a/nginx_webserver_proxy/translations/en.yaml b/nginx_webserver_proxy/translations/en.yaml new file mode 100644 index 0000000000..04da0af551 --- /dev/null +++ b/nginx_webserver_proxy/translations/en.yaml @@ -0,0 +1,25 @@ +configuration: + static_site_enabled: + name: Enable static file server + description: Serve files from static_site_root on port 80. NPM proxy functionality is always active regardless of this setting. + static_site_root: + name: Static site root path + description: > + Absolute path to the directory to serve as the static site. + /share, /media, and /config are fully mapped by HA. + /mnt is allowed with a warning — if files are inaccessible, create a symlink under /share or /media pointing to your /mnt path. + Dangerous system paths (/, /etc, /bin, /lib, /proc, /sys) are blocked and will prevent startup. + static_site_prefix: + name: URL prefix + description: > + The URL path at which static files are served on port 80. + Default "/" serves files at the root. Use a sub-path like "/files/" to restrict + static serving to that location, leaving other paths available for NPM proxy hosts. + log_level: + name: Log level + description: Controls the verbosity of add-on logging. + +network: + 80/tcp: HTTP — static file server and NPM proxy hosts + 81/tcp: NPM Admin Web UI + 443/tcp: HTTPS — NPM proxy hosts