-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[openthread_border_router] Move web UI to Ingress #4346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| diff --git a/src/web/web-service/frontend/index.html b/src/web/web-service/frontend/index.html | ||
| index cb052f26..992f03d6 100644 | ||
| --- a/src/web/web-service/frontend/index.html | ||
| +++ b/src/web/web-service/frontend/index.html | ||
| @@ -89,7 +89,7 @@ | ||
| </header> | ||
| <nav class="demo-navigation mdl-navigation mdl-color--blue-grey-800"> | ||
| <md-list flex> | ||
| - <md-list-item md-ink-ripple class="mdl-navigation__link" ng-repeat="item in menu" ng-click="showPanels($index)" layout="row"> | ||
| + <md-list-item md-ink-ripple class="mdl-navigation__link" ng-repeat="item in menu" ng-click="showPanels($index)" ng-if="!item.hidden" layout="row"> | ||
| <div><i class="mdl-color-text--blue-grey-400 material-icons" role="presentation">{{item.icon}}</i>{{item.title}} | ||
| </div> | ||
| </md-list-item> | ||
| @@ -130,7 +130,6 @@ | ||
| <th>PAN ID</th> | ||
| <th>channel</th> | ||
| <th>Hardware Address</th> | ||
| - <th>Action</th> | ||
|
|
||
| </tr> | ||
| </thead> | ||
| @@ -142,9 +141,6 @@ | ||
| <td>{{item.pi}}</td> | ||
| <td>{{item.ch}}</td> | ||
| <td>{{item.ha}}</td> | ||
| - <td> | ||
| - <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored mdl-button show-modal" ng-click="showJoinDialog($event, $index, item)">Join</button> | ||
| - </td> | ||
| </tr> | ||
| </tbody> | ||
| </table> | ||
| diff --git a/src/web/web-service/frontend/res/js/app.js b/src/web/web-service/frontend/res/js/app.js | ||
| index e8453f6c..e0645f83 100644 | ||
| --- a/src/web/web-service/frontend/res/js/app.js | ||
| +++ b/src/web/web-service/frontend/res/js/app.js | ||
| @@ -57,7 +57,7 @@ | ||
| show: true, | ||
| }, | ||
| { | ||
| - title: 'Join', | ||
| + title: 'Scan', | ||
| icon: 'add_circle_outline', | ||
| show: false, | ||
| }, | ||
| @@ -65,6 +65,7 @@ | ||
| title: 'Form', | ||
| icon: 'open_in_new', | ||
| show: false, | ||
| + hidden: true, | ||
| }, | ||
| { | ||
| title: 'Status', | ||
| @@ -124,7 +125,7 @@ | ||
| }; | ||
| $scope.showPanels = async function(index) { | ||
| $scope.headerTitle = $scope.menu[index].title; | ||
| - for (var i = 0; i < 7; i++) { | ||
| + for (var i = 0; i < $scope.menu.length; i++) { | ||
| $scope.menu[i].show = false; | ||
| } | ||
| $scope.menu[index].show = true; | ||
| @@ -1226,3 +1227,15 @@ | ||
| } | ||
| }; | ||
| })(); | ||
| + | ||
| +// Use relative URLs for ingress compatibility | ||
| +angular.module('StarterApp').config(['$httpProvider', function($httpProvider) { | ||
| + $httpProvider.interceptors.push(function() { | ||
| + return { | ||
| + request: function(config) { | ||
| + config.url = config.url.replace(/^http:\/\/[^/]+\//, ''); | ||
| + return config; | ||
| + } | ||
| + }; | ||
| + }); | ||
| +}]); | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,16 @@ | ||||||||||||||||||
| diff --git a/src/rest/rest_web_server.cpp b/src/rest/rest_web_server.cpp | ||||||||||||||||||
| index 29ee6b506be..9b34948981a 100644 | ||||||||||||||||||
| --- a/src/rest/rest_web_server.cpp | ||||||||||||||||||
| +++ b/src/rest/rest_web_server.cpp | ||||||||||||||||||
| @@ -1839,6 +1839,11 @@ void RestWebServer::Init(const std::string &aRestListenAddress, int aRestListenP | ||||||||||||||||||
| { | ||||||||||||||||||
| otbrLogInfo("RestWebServer listening on %s:%u", aRestListenAddress.c_str(), aRestListenPort); | ||||||||||||||||||
| self->mServer.set_ipv6_v6only(false); | ||||||||||||||||||
| + self->mServer.set_socket_options([](int sock) { | ||||||||||||||||||
| + int opt = 1; | ||||||||||||||||||
| + // cpp-httplib defaults to SO_REUSEPORT instead of SO_REUSEADDR | ||||||||||||||||||
| + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | ||||||||||||||||||
|
Comment on lines
+11
to
+12
|
||||||||||||||||||
| + // cpp-httplib defaults to SO_REUSEPORT instead of SO_REUSEADDR | |
| + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); | |
| + // Override cpp-httplib's default SO_REUSEPORT behavior and use SO_REUSEADDR instead. | |
| +#ifdef SO_REUSEPORT | |
| + int disable = 0; | |
| + (void)setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &disable, sizeof(disable)); | |
| +#endif | |
| + (void)setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,8 @@ ENV DOCKER=1 | |
|
|
||
|
|
||
| COPY openthread-core-ha-config-posix.h /usr/src/ | ||
| COPY 0003-ha-web-ui-customizations.patch /usr/src/ | ||
| COPY 0004-socket-reuseport.patch /usr/src/ | ||
|
|
||
| WORKDIR /usr/src | ||
| RUN \ | ||
|
|
@@ -37,6 +39,8 @@ WORKDIR /usr/src/ot-br-posix | |
| RUN \ | ||
| set -x \ | ||
| && mv /usr/src/openthread-core-ha-config-posix.h /usr/src/ot-br-posix/third_party/openthread/repo/ \ | ||
| && patch -p1 < /usr/src/0003-ha-web-ui-customizations.patch \ | ||
| && patch -p1 < /usr/src/0004-socket-reuseport.patch \ | ||
|
Comment on lines
+42
to
+43
|
||
| && ./script/cmake-build \ | ||
| -DBUILD_TESTING=OFF \ | ||
| -DCMAKE_INSTALL_PREFIX=/opt/otbr-beta \ | ||
|
|
@@ -85,6 +89,7 @@ ENV DOCKER=1 | |
| COPY openthread-core-ha-config-posix.h /usr/src/ | ||
| COPY 0001-channel-monitor-disable-by-default.patch /usr/src/ | ||
| COPY 0002-spinel-Clear-source-match-tables-before-restoring.patch /usr/src/ | ||
| COPY 0003-ha-web-ui-customizations.patch /usr/src/ | ||
|
|
||
| WORKDIR /usr/src | ||
| RUN \ | ||
|
|
@@ -111,6 +116,7 @@ RUN \ | |
| && patch -p1 < /usr/src/0001-channel-monitor-disable-by-default.patch \ | ||
| && patch -p1 < /usr/src/0002-spinel-Clear-source-match-tables-before-restoring.patch \ | ||
| ) \ | ||
| && patch -p1 < /usr/src/0003-ha-web-ui-customizations.patch \ | ||
| && ./script/cmake-build \ | ||
| -DBUILD_TESTING=OFF \ | ||
| -DCMAKE_INSTALL_PREFIX=/opt/otbr-stable \ | ||
|
|
@@ -167,6 +173,7 @@ RUN \ | |
| libncurses6 \ | ||
| libprotobuf-lite32 \ | ||
| libjsoncpp26 \ | ||
| nginx \ | ||
| && pip install --break-system-packages --no-cache-dir \ | ||
| universal-silabs-flasher==${UNIVERSAL_SILABS_FLASHER_VERSION} \ | ||
| serialx==${SERIALX_VERSION} \ | ||
|
|
@@ -183,6 +190,7 @@ COPY --from=otbr-stable-builder /opt/otbr-stable /opt/otbr-stable | |
| COPY --from=otbr-stable-builder /usr/sbin/mdnsd /opt/otbr-stable/sbin/mdnsd | ||
| COPY --from=otbr-stable-builder /usr/lib/libdns_sd.so /usr/lib/libdns_sd.so | ||
| COPY --from=otbr-stable-builder /usr/lib/libdns_sd.so.1 /usr/lib/libdns_sd.so.1 | ||
|
|
||
| COPY rootfs / | ||
|
|
||
| ENV \ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |||||
| version: 2.16.4 | ||||||
| slug: openthread_border_router | ||||||
| name: OpenThread Border Router | ||||||
| description: OpenThread Border Router add-on | ||||||
| description: OpenThread Border Router | ||||||
|
||||||
| description: OpenThread Border Router | |
| description: OpenThread Border Router App |
Copilot
AI
Feb 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While ingress is correctly enabled, the ports 8080/tcp and 8081/tcp are still defined in the configuration (lines 37-39, not shown in this diff). With ingress enabled and services now bound to localhost/addon IP, these port definitions should be removed to prevent users from accidentally exposing the services on the host network, which would bypass the ingress proxy and its security benefits. This is especially important given the PR description mentions ensuring nothing is listening on the host network interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for now we want to keep them in case users want to access from extern.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||
| worker_processes 1; | ||||||
| pid /tmp/nginx.pid; | ||||||
| error_log /dev/stdout info; | ||||||
|
|
||||||
| events { | ||||||
| worker_connections 1024; | ||||||
| } | ||||||
|
|
||||||
| http { | ||||||
| include /etc/nginx/mime.types; | ||||||
| default_type application/octet-stream; | ||||||
| access_log /dev/stdout; | ||||||
|
|
||||||
| server { | ||||||
| listen 8080; | ||||||
|
||||||
| listen 8080; | |
| listen 127.0.0.1:8080; |
Copilot
AI
Feb 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The nginx proxy configuration is missing important security headers and proxy settings. Consider adding: 1) proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; to track original client IPs through the proxy chain, 2) proxy_set_header X-Forwarded-Proto $scheme; to preserve the original protocol, and 3) proxy_redirect off; to prevent nginx from rewriting Location headers. These are standard reverse proxy configurations that help maintain proper request context.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,13 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| #!/usr/bin/with-contenv bashio | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # vim: ft=bash | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # shellcheck shell=bash | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # ============================================================================== | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # Start nginx reverse proxy for OTBR web interface | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # ============================================================================== | ||||||||||||||||||||||||||||||||||||||||||||||||||
| bashio::log.info "Starting nginx..." | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # REST API listens on app IP, so we need to know it for proxying | ||||||||||||||||||||||||||||||||||||||||||||||||||
| addon_ip="$(bashio::addon.ip_address)" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| sed "s/ADDON_IP/${addon_ip}/g" /etc/nginx/nginx.conf > /tmp/nginx.conf | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Wait for otbr-agent API to be reachable before starting nginx | |
| otbr_agent_port=8081 | |
| otbr_agent_timeout=30 | |
| otbr_agent_waited=0 | |
| bashio::log.info "Waiting for otbr-agent at ${addon_ip}:${otbr_agent_port} (timeout: ${otbr_agent_timeout}s)..." | |
| while ! echo >"/dev/tcp/${addon_ip}/${otbr_agent_port}" 2>/dev/null; do | |
| sleep 1 | |
| otbr_agent_waited=$((otbr_agent_waited + 1)) | |
| if [ "${otbr_agent_waited}" -ge "${otbr_agent_timeout}" ]; then | |
| bashio::log.warning "otbr-agent not reachable after ${otbr_agent_timeout}s, starting nginx anyway." | |
| break | |
| fi | |
| done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is rootfs/etc/s6-overlay/s6-rc.d/otbr-web/dependencies.d/otbr-agent, which makes sure that the dependency is there. But I guess en explicit dependency from nginx -> otbr-agent wouldn't hurt 🤔 🤷
Copilot
AI
Feb 25, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sed command on line 11 should include error handling. If bashio::addon.ip_address returns an empty value or sed fails, nginx will start with an invalid configuration containing the literal string "ADDON_IP", causing runtime errors. Consider adding validation: addon_ip="$(bashio::addon.ip_address)" && [[ -n "$addon_ip" ]] || bashio::exit.nok "Failed to get addon IP address"
| addon_ip="$(bashio::addon.ip_address)" | |
| sed "s/ADDON_IP/${addon_ip}/g" /etc/nginx/nginx.conf > /tmp/nginx.conf | |
| addon_ip="$(bashio::addon.ip_address)" || bashio::exit.nok "Failed to get addon IP address" | |
| [[ -n "${addon_ip}" ]] || bashio::exit.nok "Addon IP address is empty" | |
| if ! sed "s/ADDON_IP/${addon_ip}/g" /etc/nginx/nginx.conf > /tmp/nginx.conf; then | |
| bashio::exit.nok "Failed to generate nginx configuration" | |
| fi |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| longrun |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,25 @@ | ||
| #!/usr/bin/with-contenv bashio | ||
| # shellcheck shell=bash | ||
| # ============================================================================== | ||
| # Home Assistant Community Add-on: Base Images | ||
| # Displays a simple add-on banner on startup | ||
| # Home Assistant Community App: Base Images | ||
| # Displays a simple app banner on startup | ||
| # ============================================================================== | ||
| if bashio::supervisor.ping; then | ||
| bashio::log.blue \ | ||
| '-----------------------------------------------------------' | ||
| bashio::log.blue " Add-on: $(bashio::addon.name)" | ||
| bashio::log.blue " App: $(bashio::addon.name)" | ||
| bashio::log.blue " $(bashio::addon.description)" | ||
| bashio::log.blue \ | ||
| '-----------------------------------------------------------' | ||
|
|
||
| bashio::log.blue " Add-on version: $(bashio::addon.version)" | ||
| bashio::log.blue " App version: $(bashio::addon.version)" | ||
| if bashio::var.true "$(bashio::addon.update_available)"; then | ||
| bashio::log.magenta ' There is an update available for this add-on!' | ||
| bashio::log.magenta ' There is an update available for this app!' | ||
| bashio::log.magenta \ | ||
| " Latest add-on version: $(bashio::addon.version_latest)" | ||
| " Latest app version: $(bashio::addon.version_latest)" | ||
| bashio::log.magenta ' Please consider upgrading as soon as possible.' | ||
| else | ||
| bashio::log.green ' You are running the latest version of this add-on.' | ||
| bashio::log.green ' You are running the latest version of this app.' | ||
|
Comment on lines
+4
to
+22
|
||
| fi | ||
|
|
||
| bashio::log.blue " System: $(bashio::info.operating_system)" \ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The URL rewriting logic strips absolute HTTP URLs to make them relative for ingress compatibility. However, this regex only handles HTTP URLs (
http://), not HTTPS URLs (https://). If the web UI ever generates HTTPS absolute URLs, they won't be rewritten. Consider using a protocol-agnostic pattern:config.url.replace(/^https?:\/\/[^/]+/, '')to handle both HTTP and HTTPS URLs.