From b7ec4fdc205b2756136130d8e9e5fc874fceceda Mon Sep 17 00:00:00 2001 From: Patrick Gniza Date: Fri, 14 Nov 2025 13:21:18 +0100 Subject: [PATCH] v0.1 test --- esphome_proxy/CHANGELOG.md | 3 + esphome_proxy/DOCS.md | 38 +++++++ esphome_proxy/Dockerfile | 38 +++++++ esphome_proxy/README.md | 13 +++ esphome_proxy/config.yaml | 42 ++++++++ esphome_proxy/icon.png | Bin 0 -> 1758 bytes esphome_proxy/logo.png | Bin 0 -> 22318 bytes .../rootfs/etc/nginx/includes/mime.types | 96 ++++++++++++++++++ .../etc/nginx/includes/proxy_params.conf | 15 +++ .../etc/nginx/includes/server_params.conf | 6 ++ .../rootfs/etc/nginx/includes/ssl_params.conf | 8 ++ esphome_proxy/rootfs/etc/nginx/nginx.conf | 43 ++++++++ .../rootfs/etc/nginx/servers/.gitkeep | 0 .../rootfs/etc/nginx/templates/ingress.gtpl | 23 +++++ .../s6-rc.d/init-nginx/dependencies.d/base | 0 .../etc/s6-overlay/s6-rc.d/init-nginx/run | 15 +++ .../etc/s6-overlay/s6-rc.d/init-nginx/type | 1 + .../etc/s6-overlay/s6-rc.d/init-nginx/up | 1 + .../s6-rc.d/nginx/dependencies.d/init-nginx | 0 .../etc/s6-overlay/s6-rc.d/nginx/finish | 28 +++++ .../rootfs/etc/s6-overlay/s6-rc.d/nginx/run | 8 ++ .../rootfs/etc/s6-overlay/s6-rc.d/nginx/type | 1 + .../s6-rc.d/user/contents.d/init-nginx | 0 .../s6-overlay/s6-rc.d/user/contents.d/nginx | 0 24 files changed, 379 insertions(+) create mode 100644 esphome_proxy/CHANGELOG.md create mode 100644 esphome_proxy/DOCS.md create mode 100644 esphome_proxy/Dockerfile create mode 100644 esphome_proxy/README.md create mode 100644 esphome_proxy/config.yaml create mode 100644 esphome_proxy/icon.png create mode 100644 esphome_proxy/logo.png create mode 100644 esphome_proxy/rootfs/etc/nginx/includes/mime.types create mode 100644 esphome_proxy/rootfs/etc/nginx/includes/proxy_params.conf create mode 100644 esphome_proxy/rootfs/etc/nginx/includes/server_params.conf create mode 100644 esphome_proxy/rootfs/etc/nginx/includes/ssl_params.conf create mode 100644 esphome_proxy/rootfs/etc/nginx/nginx.conf create mode 100644 esphome_proxy/rootfs/etc/nginx/servers/.gitkeep create mode 100644 esphome_proxy/rootfs/etc/nginx/templates/ingress.gtpl create mode 100644 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/dependencies.d/base create mode 100755 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run create mode 100644 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/type create mode 100755 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/up create mode 100644 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/init-nginx create mode 100755 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/finish create mode 100755 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/run create mode 100644 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/type create mode 100644 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-nginx create mode 100644 esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/nginx diff --git a/esphome_proxy/CHANGELOG.md b/esphome_proxy/CHANGELOG.md new file mode 100644 index 0000000..05bc96b --- /dev/null +++ b/esphome_proxy/CHANGELOG.md @@ -0,0 +1,3 @@ +### 0.1 + +- Initial release diff --git a/esphome_proxy/DOCS.md b/esphome_proxy/DOCS.md new file mode 100644 index 0000000..8e501f5 --- /dev/null +++ b/esphome_proxy/DOCS.md @@ -0,0 +1,38 @@ +This addon creates a proxy to a ESPHome server run separately from Home Assistant so that you can have the benefit of access in the sidebar without running ESPHome as an addon. + +Note that this addon does not run ESPHome itself. + +## Configuration + +### Option: `server` + +The `server` option sets the address of the ESPHome server. + +This must be in the format `http[s]://host:port`. The following are valid examples: + +- `http://ESPHome.local:6052` +- `http://192.168.0.101:6052` +- `https://192.168.0.101:443` + +### Option: `proxy_pass_host` + +Determines whether we should pass the host we're running on (for example, +`homeassistant.local`) to the server we're proxying to. In general, you probably +want this to be set to `true`. + +Set to `false` if the server needs to receive the host of the ESPHome instance +(not the host Home Assistant or this addon are running on). This might be the case +if your ESPHome instance is behind an SSL proxy (like Traefik or Caddy), which +needs to receive the ESPHome host in order to route the request correctly. + +### Option: `proxy_pass_real_ip` + +Determines whether we should pass the client's real IP address to the server we're proxying to. In general, you probably +want this to be set to `true`. + +Set to `false` if you need to know the request is coming from the HA IP. This might be the case if your ESPHome instance is behind a proxy which only allows specific IPs to connect. + +## Required Dependencies + +- Network access to running ESPHome server + diff --git a/esphome_proxy/Dockerfile b/esphome_proxy/Dockerfile new file mode 100644 index 0000000..2829fb1 --- /dev/null +++ b/esphome_proxy/Dockerfile @@ -0,0 +1,38 @@ +FROM ghcr.io/hassio-addons/debian-base:7.8.2 + +# Setup base +# hadolint ignore=DL3003 +RUN apt-get -qq update \ + && apt-get install -qq --no-install-recommends nginx \ + && rm -rf /var/lib/apt/lists/* + +# Copy root filesystem +COPY rootfs / + +# 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} \ + maintainer="Blake Blackshear " \ + org.opencontainers.image.title="${BUILD_NAME}" \ + org.opencontainers.image.description="${BUILD_DESCRIPTION}" \ + org.opencontainers.image.vendor="Buchhorster Add-ons" \ + org.opencontainers.image.authors="PAtrick Gniza " \ + 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/frigate_proxy/README.md" \ + org.opencontainers.image.created=${BUILD_DATE} \ + org.opencontainers.image.revision=${BUILD_REF} \ + org.opencontainers.image.version=${BUILD_VERSION} diff --git a/esphome_proxy/README.md b/esphome_proxy/README.md new file mode 100644 index 0000000..253d2cd --- /dev/null +++ b/esphome_proxy/README.md @@ -0,0 +1,13 @@ +# Home Assistant Add-on: ESPHome Proxy + +![Supports aarch64 Architecture][aarch64-shield] ![Supports amd64 Architecture][amd64-shield] ![Supports armhf Architecture][armhf-shield] ![Supports armv7 Architecture][armv7-shield] ![Supports i386 Architecture][i386-shield] + +This addon creates a proxy to a ESPHome server run separately from Home Assistant so that you can have the benefit of access in the sidebar without running ESPHome as an addon. + +Note that this addon does not run ESPHome itself. + +[aarch64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg +[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg +[armhf-shield]: https://img.shields.io/badge/armhf-yes-green.svg +[armv7-shield]: https://img.shields.io/badge/armv7-yes-green.svg +[i386-shield]: https://img.shields.io/badge/i386-yes-green.svg diff --git a/esphome_proxy/config.yaml b/esphome_proxy/config.yaml new file mode 100644 index 0000000..0efaf0e --- /dev/null +++ b/esphome_proxy/config.yaml @@ -0,0 +1,42 @@ +name: ESPHome Proxy +version: 0.1 +panel_icon: "mdi:cctv" +panel_title: ESPHome +slug: esphome-proxy +description: Proxy addon for ESPHome +url: "https://gitea.buchhorster.de/patrick/ha-Buchhorster-Addons" +startup: application +boot: auto +init: false +webui: "http://[HOST]:[PORT:6052]/" +watchdog: "http://[HOST]:[PORT:6252]/" +ingress: true +ingress_port: 6052 +ingress_entry: / +panel_admin: false +ports: + 6052/tcp: 6052 +ports_description: + 6052/tcp: Web interface (not required for Home Assistant Ingress) +host_network: false +devices: [] +usb: false +tmpfs: false +full_access: false +environment: {} +options: + server: "http://esphome.local:6052" + proxy_pass_host: true + proxy_pass_real_ip: true +schema: + server: "match(^https?://.+:\\d+$)" + proxy_pass_host: bool + proxy_pass_real_ip: bool +services: [] +arch: + - aarch64 + - amd64 + - armhf + - armv7 + - i386 +map: [] diff --git a/esphome_proxy/icon.png b/esphome_proxy/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..dcdeed9d1afb3b42c54a9f25375eb0dc48b257f2 GIT binary patch literal 1758 zcmc(g`#;lr9LEvbnMA}~ayg-P5+gY(x1$h`=p1$08FKknQpOxp&JUmW`}2N%UXS+=pC6vD!XFMF zG=y3~wY0PhLqh_OX!?`S2cn~?TlejKs->lWCp0kNNBoUH_}-Vz66_u?R;P!LuVcGH zfGW7)VPz0}_qQeofLrc;!+j{!BOq?|OG3_|g9)iYZ zTS?f|_hG(WT%+ra4F;^FY%5TyUFLBASlHO;0q>@xT-%YbW9l`4fT3KXwQO^Ks!WzP zG4D1>l*uXOLLEMtrb=un-!eI!r6Widzkh6isVWNrB$qUk_0=g4}pbx~1rc+qW zA`ghOBoVO&IpRrG&Y*Zz?wHc#*Cf@3GThST6p|RT_OXEYmM{;3;u>t#FMmFfD8)D>>LNSedi3CPr*lb+XH&dJU)~mDl$fXr;$RU{g!(M&M@nS zpKmMnku%velMw!#g8%0cgWwAId6C6xH`jt0(yh{5Ow9RQ}SoRDA?;3 z4l}?hJhrP5$qViNdP@3p~ zA-aN2IvwuS)XEXFxSqw$%_=w{CEIB>rgAo0F&%bnG!GZ44s^5O znI65vFYbKsVl_LnA4aCCjlYRpDQEQp#&PyKDS9ns?{CoICj%gDU-=DoX)n4S0lOm=`dl4-kKa;rrvYqb}Z6G21*eJNrSh9Q-?<2Q`+vfj+k8#vJw`{Sm zJ-ISLIg|~@Z$3TYFSpfY4?^D#v509TZimUuOs|q+Zr+54bfACP)2L5YjpN`q@Bc!Z z_>&*wl|!sZGBouDe;iD4{&O6AK+vS z<=AIXl#SBut0z7MTN=(+9SW9KdHP4LA#{U#qxL-W9_%KNCg(a5>j5I6g?_$}k%eNu zQoXAfVV&B2AGtjfG3cM?b_G`gQZ0xkR;1F8{nek&p+)`Gvcn$2pB|T-giT6qQ(fvj zR?-so-FFN%e@=%zr`sjpPB)e4Pk>w0@r~cCRGSTZU45WVvWr|8PkG;Tp7U0b7Pk(1 nv0=mi&*OhbKw*w1tJG>EfY6?MAyKHwEv?X?!-3TO@wtBkbF)vP literal 0 HcmV?d00001 diff --git a/esphome_proxy/logo.png b/esphome_proxy/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..eb2808c351cc13db254aa46ea320d03d1c7a87f6 GIT binary patch literal 22318 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 literal 0 HcmV?d00001 diff --git a/esphome_proxy/rootfs/etc/nginx/includes/mime.types b/esphome_proxy/rootfs/etc/nginx/includes/mime.types new file mode 100644 index 0000000..7c7cdef --- /dev/null +++ b/esphome_proxy/rootfs/etc/nginx/includes/mime.types @@ -0,0 +1,96 @@ +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/svg+xml svg svgz; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/webp webp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + + font/woff woff; + font/woff2 woff2; + + application/java-archive jar war ear; + application/json json; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.apple.mpegurl m3u8; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/vnd.ms-excel xls; + application/vnd.ms-fontobject eot; + application/vnd.ms-powerpoint ppt; + application/vnd.oasis.opendocument.graphics odg; + application/vnd.oasis.opendocument.presentation odp; + application/vnd.oasis.opendocument.spreadsheet ods; + application/vnd.oasis.opendocument.text odt; + application/vnd.openxmlformats-officedocument.presentationml.presentation + pptx; + application/vnd.openxmlformats-officedocument.spreadsheetml.sheet + xlsx; + application/vnd.openxmlformats-officedocument.wordprocessingml.document + docx; + application/vnd.wap.wmlc wmlc; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/xspf+xml xspf; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp2t ts; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/esphome_proxy/rootfs/etc/nginx/includes/proxy_params.conf b/esphome_proxy/rootfs/etc/nginx/includes/proxy_params.conf new file mode 100644 index 0000000..518c6ec --- /dev/null +++ b/esphome_proxy/rootfs/etc/nginx/includes/proxy_params.conf @@ -0,0 +1,15 @@ +proxy_http_version 1.1; +proxy_ignore_client_abort off; +proxy_read_timeout 86400s; +proxy_redirect off; +proxy_send_timeout 86400s; +proxy_max_temp_file_size 0; + +proxy_set_header Accept-Encoding ""; +proxy_set_header Connection $connection_upgrade; +proxy_set_header Upgrade $http_upgrade; +proxy_set_header X-Forwarded-Proto $scheme; +proxy_set_header X-NginX-Proxy true; + +proxy_ssl_server_name on; +proxy_ssl_session_reuse off; diff --git a/esphome_proxy/rootfs/etc/nginx/includes/server_params.conf b/esphome_proxy/rootfs/etc/nginx/includes/server_params.conf new file mode 100644 index 0000000..09c0654 --- /dev/null +++ b/esphome_proxy/rootfs/etc/nginx/includes/server_params.conf @@ -0,0 +1,6 @@ +root /dev/null; +server_name $hostname; + +add_header X-Content-Type-Options nosniff; +add_header X-XSS-Protection "1; mode=block"; +add_header X-Robots-Tag none; diff --git a/esphome_proxy/rootfs/etc/nginx/includes/ssl_params.conf b/esphome_proxy/rootfs/etc/nginx/includes/ssl_params.conf new file mode 100644 index 0000000..e6789cb --- /dev/null +++ b/esphome_proxy/rootfs/etc/nginx/includes/ssl_params.conf @@ -0,0 +1,8 @@ +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; +ssl_session_timeout 10m; +ssl_session_cache shared:SSL:10m; +ssl_session_tickets off; +ssl_stapling on; +ssl_stapling_verify on; diff --git a/esphome_proxy/rootfs/etc/nginx/nginx.conf b/esphome_proxy/rootfs/etc/nginx/nginx.conf new file mode 100644 index 0000000..3fae5ac --- /dev/null +++ b/esphome_proxy/rootfs/etc/nginx/nginx.conf @@ -0,0 +1,43 @@ +# Run nginx in foreground. +daemon off; + +# This is run inside Docker. +user root; + +# Pid storage location. +pid /var/run/nginx.pid; + +# Set number of worker processes. +worker_processes 1; + +# Enables the use of JIT for regular expressions to speed-up their processing. +pcre_jit on; + +# Write error log to the add-on log. +error_log /proc/1/fd/1 error; + +# Max num of simultaneous connections by a worker process. +events { + worker_connections 512; +} + +http { + include /etc/nginx/includes/mime.types; + + access_log off; + client_max_body_size 4G; + default_type application/octet-stream; + gzip on; + keepalive_timeout 65; + sendfile on; + server_tokens off; + tcp_nodelay on; + tcp_nopush on; + + map $http_upgrade $connection_upgrade { + default upgrade; + '' close; + } + + include /etc/nginx/servers/*.conf; +} diff --git a/esphome_proxy/rootfs/etc/nginx/servers/.gitkeep b/esphome_proxy/rootfs/etc/nginx/servers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/esphome_proxy/rootfs/etc/nginx/templates/ingress.gtpl b/esphome_proxy/rootfs/etc/nginx/templates/ingress.gtpl new file mode 100644 index 0000000..92773cb --- /dev/null +++ b/esphome_proxy/rootfs/etc/nginx/templates/ingress.gtpl @@ -0,0 +1,23 @@ +server { + listen 6052 default_server; + + include /etc/nginx/includes/server_params.conf; + + location / { + allow 172.30.32.2; + deny all; + + proxy_pass {{ .server }}; + proxy_set_header X-Ingress-Path {{ .entry }}; + + {{ if .proxy_pass_host }} + proxy_set_header Host $http_host; + {{ end }} + {{ if .proxy_pass_real_ip }} + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + {{ end }} + + include /etc/nginx/includes/proxy_params.conf; + } +} diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/dependencies.d/base b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/dependencies.d/base new file mode 100644 index 0000000..e69de29 diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run new file mode 100755 index 0000000..bfc549e --- /dev/null +++ b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/run @@ -0,0 +1,15 @@ +#!/command/with-contenv bashio +# shellcheck shell=bash +# ============================================================================== +# Configures NGINX for use with this add-on. +# ============================================================================== + +# Note the ^ at the beginning of the proxy_pass_host value +# This stops bashio:var.json from passing the value as a string +bashio::var.json \ + entry "$(bashio::addon.ingress_entry)" \ + server "$(bashio::config 'server')" \ + proxy_pass_host "^$(bashio::config 'proxy_pass_host')" \ + | tempio \ + -template /etc/nginx/templates/ingress.gtpl \ + -out /etc/nginx/servers/ingress.conf diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/type b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/type new file mode 100644 index 0000000..bdd22a1 --- /dev/null +++ b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/type @@ -0,0 +1 @@ +oneshot diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/up b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/up new file mode 100755 index 0000000..b3b5b49 --- /dev/null +++ b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/init-nginx/up @@ -0,0 +1 @@ +/etc/s6-overlay/s6-rc.d/init-nginx/run diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/init-nginx b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/init-nginx new file mode 100644 index 0000000..e69de29 diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/finish b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/finish new file mode 100755 index 0000000..1b0f0bc --- /dev/null +++ b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/finish @@ -0,0 +1,28 @@ +#!/command/with-contenv bashio +# shellcheck shell=bash +# ============================================================================== +# Take down the S6 supervision tree when the NGINX fails +# ============================================================================== +readonly exit_code_service="${1}" +readonly exit_code_signal="${2}" +exit_code_container=$(cat /run/s6-linux-init-container-results/exitcode) +readonly exit_code_container +readonly service="nginx" + +bashio::log.info \ + "Service ${service} exited with code ${exit_code_service}" \ + "(by signal ${exit_code_signal})" + +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 + if [[ "${exit_code_signal}" -eq 15 ]]; then + exec /run/s6/basedir/bin/halt + fi +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/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/run b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/run new file mode 100755 index 0000000..3acc0b2 --- /dev/null +++ b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/run @@ -0,0 +1,8 @@ +#!/command/with-contenv bashio +# shellcheck shell=bash +# ============================================================================== +# Runs the NGINX daemon +# ============================================================================== + +bashio::log.info "Starting NGINX..." +exec nginx diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/type b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/type new file mode 100644 index 0000000..5883cff --- /dev/null +++ b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/nginx/type @@ -0,0 +1 @@ +longrun diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-nginx b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/init-nginx new file mode 100644 index 0000000..e69de29 diff --git a/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/nginx b/esphome_proxy/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/nginx new file mode 100644 index 0000000..e69de29