From 92d3db46a7f0d30ff1e0c82d4f5b2e6ed3d73fad Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sat, 6 Apr 2024 13:02:11 +0200 Subject: [PATCH] netbrowse: convert to gunicorn and factor out mdns-alias app Converting to gunicorn, which is the recommended production server for Flask apps, means app main() function is no longer called at startup, so we have to factor out the mDNS CNAME functionality for infix.local and network.local to a separate app. We take this opportunity to collapse the structure, move non-class methods to __init.py__, and rename the AvahiAlias class. A prototype for replacing the overhead of mdns-alias with a C daemon is in its early stages. Signed-off-by: Joachim Wiberg --- board/common/netbrowse.conf | 5 +++ board/common/nginx.conf | 10 ++--- board/common/post-build.sh | 1 + configs/aarch64_defconfig | 2 + configs/r2s_defconfig | 2 + configs/x86_64_defconfig | 2 + package/Config.in | 1 + package/mdns-alias/Config.in | 7 ++++ package/mdns-alias/mdns-alias.mk | 25 +++++++++++++ package/mdns-alias/mdns-alias.svc | 2 + package/netbrowse/Config.in | 6 +-- package/netbrowse/netbrowse.svc | 5 +-- src/mdns-alias/LICENSE | 21 +++++++++++ src/mdns-alias/README.md | 11 ++++++ src/mdns-alias/mdns_alias/__init__.py | 24 ++++++++++++ .../mdns_alias/mdns_alias.py} | 4 +- src/mdns-alias/pyproject.toml | 20 ++++++++++ src/netbrowse/README.md | 8 +--- src/netbrowse/netbrowse/__init__.py | 32 ++++++++++++++++ .../netbrowse/avahi_alias/__init__.py | 6 --- .../netbrowse/{mdns_hosts => }/mdns_hosts.py | 0 .../netbrowse/mdns_hosts/__init__.py | 6 --- src/netbrowse/netbrowse/netbrowse.py | 37 ------------------- src/netbrowse/pyproject.toml | 14 +++---- 24 files changed, 173 insertions(+), 78 deletions(-) create mode 100644 board/common/netbrowse.conf create mode 100644 package/mdns-alias/Config.in create mode 100644 package/mdns-alias/mdns-alias.mk create mode 100644 package/mdns-alias/mdns-alias.svc create mode 100644 src/mdns-alias/LICENSE create mode 100644 src/mdns-alias/README.md create mode 100644 src/mdns-alias/mdns_alias/__init__.py rename src/{netbrowse/netbrowse/avahi_alias/avahi_alias.py => mdns-alias/mdns_alias/mdns_alias.py} (97%) create mode 100644 src/mdns-alias/pyproject.toml delete mode 100644 src/netbrowse/netbrowse/avahi_alias/__init__.py rename src/netbrowse/netbrowse/{mdns_hosts => }/mdns_hosts.py (100%) delete mode 100644 src/netbrowse/netbrowse/mdns_hosts/__init__.py delete mode 100755 src/netbrowse/netbrowse/netbrowse.py diff --git a/board/common/netbrowse.conf b/board/common/netbrowse.conf new file mode 100644 index 00000000..f6db335a --- /dev/null +++ b/board/common/netbrowse.conf @@ -0,0 +1,5 @@ +proxy_pass http://127.0.0.1:8000; +proxy_set_header Host $host; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; diff --git a/board/common/nginx.conf b/board/common/nginx.conf index 06f1eddd..9946f78e 100644 --- a/board/common/nginx.conf +++ b/board/common/nginx.conf @@ -36,8 +36,7 @@ http { } location /browse { - include fastcgi_params; - fastcgi_pass unix:/tmp/netbrowse.sock; + include netbrowse.conf; } } @@ -46,10 +45,9 @@ http { listen [::]:80; server_name network.local; - location / { - include fastcgi_params; - fastcgi_pass unix:/tmp/netbrowse.sock; - } + location / { + include netbrowse.conf; + } } # diff --git a/board/common/post-build.sh b/board/common/post-build.sh index f8e3be73..31900cf6 100755 --- a/board/common/post-build.sh +++ b/board/common/post-build.sh @@ -86,6 +86,7 @@ grep -qsE '^/bin/clish$$' "$TARGET_DIR/etc/shells" \ || echo "/bin/clish" >> "$TARGET_DIR/etc/shells" if [ -n "$BR2_PACKAGE_NGINX" ]; then + cp "$common/netbrowse.conf" "$TARGET_DIR/etc/nginx/" cp "$common/nginx.conf" "$TARGET_DIR/etc/nginx/" ln -sf ../available/nginx.conf "$TARGET_DIR/etc/finit.d/enabled/nginx.conf" diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig index 87603cf8..b60ca2d8 100644 --- a/configs/aarch64_defconfig +++ b/configs/aarch64_defconfig @@ -54,6 +54,7 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y BR2_PACKAGE_PYTHON3=y BR2_PACKAGE_PYTHON_CRYPTOGRAPHY=y +BR2_PACKAGE_PYTHON_GUNICORN=y BR2_PACKAGE_LIBSSH_OPENSSL=y BR2_PACKAGE_LIBSSH2=y BR2_PACKAGE_LIBSSH2_OPENSSL=y @@ -141,6 +142,7 @@ BR2_PACKAGE_KEYACK=y BR2_PACKAGE_KLISH_PLUGIN_INFIX=y BR2_PACKAGE_LOWDOWN=y BR2_PACKAGE_MCD=y +BR2_PACKAGE_MDNS_ALIAS=y BR2_PACKAGE_NETBROWSE=y BR2_PACKAGE_PODMAN=y BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y diff --git a/configs/r2s_defconfig b/configs/r2s_defconfig index 9b238ffc..f5ebf9f9 100644 --- a/configs/r2s_defconfig +++ b/configs/r2s_defconfig @@ -64,6 +64,7 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y BR2_PACKAGE_PYTHON3=y BR2_PACKAGE_PYTHON_CRYPTOGRAPHY=y +BR2_PACKAGE_PYTHON_GUNICORN=y BR2_PACKAGE_CA_CERTIFICATES=y BR2_PACKAGE_LIBSSH_OPENSSL=y BR2_PACKAGE_LIBSSH2=y @@ -170,6 +171,7 @@ BR2_PACKAGE_KEYACK=y BR2_PACKAGE_KLISH_PLUGIN_INFIX=y BR2_PACKAGE_LOWDOWN=y BR2_PACKAGE_MCD=y +BR2_PACKAGE_MDNS_ALIAS=y BR2_PACKAGE_NETBROWSE=y BR2_PACKAGE_PODMAN=y BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y diff --git a/configs/x86_64_defconfig b/configs/x86_64_defconfig index abec241b..a944b94d 100644 --- a/configs/x86_64_defconfig +++ b/configs/x86_64_defconfig @@ -49,6 +49,7 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y BR2_PACKAGE_PYTHON3=y BR2_PACKAGE_PYTHON_CRYPTOGRAPHY=y +BR2_PACKAGE_PYTHON_GUNICORN=y BR2_PACKAGE_LIBSSH_OPENSSL=y BR2_PACKAGE_LIBSSH2=y BR2_PACKAGE_LIBSSH2_OPENSSL=y @@ -144,6 +145,7 @@ BR2_PACKAGE_KEYACK=y BR2_PACKAGE_KLISH_PLUGIN_INFIX=y BR2_PACKAGE_LOWDOWN=y BR2_PACKAGE_MCD=y +BR2_PACKAGE_MDNS_ALIAS=y BR2_PACKAGE_NETBROWSE=y BR2_PACKAGE_PODMAN=y BR2_PACKAGE_PODMAN_DRIVER_BTRFS=y diff --git a/package/Config.in b/package/Config.in index 6b1db6ba..78417372 100644 --- a/package/Config.in +++ b/package/Config.in @@ -15,6 +15,7 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/klish-plugin-sysrepo/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/libsrx/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/lowdown/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/mcd/Config.in" +source "$BR2_EXTERNAL_INFIX_PATH/package/mdns-alias/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/netbrowse/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/podman/Config.in" source "$BR2_EXTERNAL_INFIX_PATH/package/skeleton-init-finit/Config.in" diff --git a/package/mdns-alias/Config.in b/package/mdns-alias/Config.in new file mode 100644 index 00000000..25a0f5bf --- /dev/null +++ b/package/mdns-alias/Config.in @@ -0,0 +1,7 @@ +config BR2_PACKAGE_MDNS_ALIAS + bool mdns-alias + select BR2_PACKAGE_DBUS + select BR2_PACKAGE_DBUS_PYTHON + help + Advertises the initial $(HOSTNAME).local and network.local to + Avahi over D-Bus. diff --git a/package/mdns-alias/mdns-alias.mk b/package/mdns-alias/mdns-alias.mk new file mode 100644 index 00000000..3aa0117b --- /dev/null +++ b/package/mdns-alias/mdns-alias.mk @@ -0,0 +1,25 @@ +################################################################################ +# +# mdns-alias +# +################################################################################ + +MDNS_ALIAS_VERSION = 1.0 +MDNS_ALIAS_SITE_METHOD = local +MDNS_ALIAS_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/mdns-alias +MDNS_ALIAS_SETUP_TYPE = setuptools +MDNS_ALIAS_LICENSE = MIT +MDNS_ALIAS_LICENSE_FILES = LICENSE +MDNS_ALIAS_REDISTRIBUTE = NO + +define MDNS_ALIAS_INSTALL_EXTRA + $(INSTALL) -D -m 0644 $(MDNS_ALIAS_PKGDIR)/mdns-alias.svc \ + $(FINIT_D)/available/mdns-alias.conf + ln -sf ../available/mdns-alias.conf $(FINIT_D)/enabled/mdns-alias.conf + $(INSTALL) -d -m 755 $(TARGET_DIR)/etc/default + echo "MDNS_ALIAS_ARGS=\"network.local $(BR2_TARGET_GENERIC_HOSTNAME).local\"" \ + > $(TARGET_DIR)/etc/default/mdns-alias +endef +MDNS_ALIAS_POST_INSTALL_TARGET_HOOKS += MDNS_ALIAS_INSTALL_EXTRA + +$(eval $(python-package)) diff --git a/package/mdns-alias/mdns-alias.svc b/package/mdns-alias/mdns-alias.svc new file mode 100644 index 00000000..dfe65d8f --- /dev/null +++ b/package/mdns-alias/mdns-alias.svc @@ -0,0 +1,2 @@ +service env:-/etc/default/mdns-alias log:prio:daemon.debug,tag:mdns \ + [2345] mdns-alias $MDNS_ALIAS_ARGS -- mDNS alias advertiser diff --git a/package/netbrowse/Config.in b/package/netbrowse/Config.in index 4c5125db..8781fa37 100644 --- a/package/netbrowse/Config.in +++ b/package/netbrowse/Config.in @@ -1,10 +1,6 @@ config BR2_PACKAGE_NETBROWSE bool netbrowse - select BR2_PACKAGE_DBUS - select BR2_PACKAGE_DBUS_PYTHON select BR2_PACKAGE_PYTHON_FLASK help - Advertises the initial $(HOSTNAME).local and network.local to - give your device a http://network.local mDNS/SD browser. + Browse mDNS hosts on your network from http://network.local - UNIX socket available for fastcgi at /tmp/netbrowse.sock diff --git a/package/netbrowse/netbrowse.svc b/package/netbrowse/netbrowse.svc index 002b7f84..789d0ffa 100644 --- a/package/netbrowse/netbrowse.svc +++ b/package/netbrowse/netbrowse.svc @@ -1,3 +1,2 @@ -service env:-/etc/default/netbrowse log:prio:daemon.debug,tag:mdns \ - @www-data:www-data \ - [2345] netbrowse $NETBROWSE_ARGS -- Network browser +service name:netbrowse @www-data:www-data log:prio:daemon.debug,tag:mdns \ + [2345] gunicorn -w 1 -b 127.0.0.1:8000 netbrowse:app -- Network browser diff --git a/src/mdns-alias/LICENSE b/src/mdns-alias/LICENSE new file mode 100644 index 00000000..b67cb375 --- /dev/null +++ b/src/mdns-alias/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Joachim Wiberg + +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/src/mdns-alias/README.md b/src/mdns-alias/README.md new file mode 100644 index 00000000..1e576b9d --- /dev/null +++ b/src/mdns-alias/README.md @@ -0,0 +1,11 @@ +mDNS Alias +========== + +This Python program both advertises CNAMEs (using D-Bus to Avahi). + +To start the program: + + mdns-alias $hostname.local network.local + +The latter CNAME can be used with nginx and netbrowse to provide +a basic mDNS service browser. diff --git a/src/mdns-alias/mdns_alias/__init__.py b/src/mdns-alias/mdns_alias/__init__.py new file mode 100644 index 00000000..17e78b8d --- /dev/null +++ b/src/mdns-alias/mdns_alias/__init__.py @@ -0,0 +1,24 @@ +""" + Avahi CNAME service over D-Bus advertiser +""" +import sys +import time + +from .mdns_alias import MdnsAlias +__all__ = ['MdnsAlias'] + +def main(): + """Advertises aliases from command line.""" + mdns_aliases = MdnsAlias() + + for arg in sys.argv[1:]: + mdns_aliases.publish_cname(str(arg).encode("utf-8", "strict")) + + while True: + time.sleep(3600) + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print("Exiting") diff --git a/src/netbrowse/netbrowse/avahi_alias/avahi_alias.py b/src/mdns-alias/mdns_alias/mdns_alias.py similarity index 97% rename from src/netbrowse/netbrowse/avahi_alias/avahi_alias.py rename to src/mdns-alias/mdns_alias/mdns_alias.py index c86ec061..f19d9a7c 100644 --- a/src/netbrowse/netbrowse/avahi_alias/avahi_alias.py +++ b/src/mdns-alias/mdns_alias/mdns_alias.py @@ -1,9 +1,9 @@ -"""Avahi CNAME alias class""" +"""Avahi CNAME publisher class""" from encodings.idna import ToASCII import dbus -class AvahiAlias: +class MdnsAlias: """Import to your project and use publish_cname() for each name.""" DBUS_NAME = 'org.freedesktop.Avahi' CLASS_IN = 0x01 diff --git a/src/mdns-alias/pyproject.toml b/src/mdns-alias/pyproject.toml new file mode 100644 index 00000000..4db8cdf5 --- /dev/null +++ b/src/mdns-alias/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +#build-backend = "setuptools.build_meta" + +[project] +name = "mdns-alias" +version = "1.0" +description = "Request mDNS-SD CNAME over D-Bus to Avahi." +authors = [{name = "Joachim Wiberg", email = "troglobit@gmail.com"}] +license = {file = "LICENSE"} +readme = "README.md" +dependencies = [ + "dbus", +] + +#[tool.setuptools.packages.find] +#where = ["mdns_alias"] + +[project.scripts] +mdns-alias = "mdns_alias:main" diff --git a/src/netbrowse/README.md b/src/netbrowse/README.md index c15a5233..79b0eecd 100644 --- a/src/netbrowse/README.md +++ b/src/netbrowse/README.md @@ -1,12 +1,8 @@ mDNS Network Browser ==================== -This Python program both advertises CNAMEs (using D-Bus to Avahi) and -provides an mDNS browser over fastcgi for, e.g., Nginx. - -To start the program: - - netbrowse $hostname.local network.local +This program is a Python Flask app that provides an mDNS browser for, +e.g., Nginx. It is intended to answer calls to https://network.local A UNIX socket, for fastcgi, is created in `/tmp/netbrowse.sock` with permissions 0660 as the user and group the program is started as. diff --git a/src/netbrowse/netbrowse/__init__.py b/src/netbrowse/netbrowse/__init__.py index e69de29b..ee498b8e 100644 --- a/src/netbrowse/netbrowse/__init__.py +++ b/src/netbrowse/netbrowse/__init__.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" + Very basic mDNS scanner with HTML table renderer +""" +from flask import Flask, render_template +from .mdns_hosts import MdnsHosts + +__all__ = ['MdnsHosts'] + +app = Flask(__name__) + +@app.route('/') +def index(): + """The /browse or network.local application""" + mdns_hosts = MdnsHosts() + hosts_services = mdns_hosts.scan() + + order = { 'HTTPS': 1, 'HTTP': 2, 'SSH': 3, 'SFTP': 4 } + for _, details in hosts_services.items(): + details['services'].sort(key=lambda x: order.get(x['type'], 999)) + + return render_template('browse.html', hosts_services=hosts_services) + +def main(): + """Stand-alone running with Flask development server.""" + try: + app.run(debug=True) + except KeyboardInterrupt: + print("Exiting") + +if __name__ == '__main__': + main() diff --git a/src/netbrowse/netbrowse/avahi_alias/__init__.py b/src/netbrowse/netbrowse/avahi_alias/__init__.py deleted file mode 100644 index 8ba9476b..00000000 --- a/src/netbrowse/netbrowse/avahi_alias/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" - Avahi CNAME service over D-Bus advertiser -""" - -from .avahi_alias import AvahiAlias -__all__ = ['AvahiAlias'] diff --git a/src/netbrowse/netbrowse/mdns_hosts/mdns_hosts.py b/src/netbrowse/netbrowse/mdns_hosts.py similarity index 100% rename from src/netbrowse/netbrowse/mdns_hosts/mdns_hosts.py rename to src/netbrowse/netbrowse/mdns_hosts.py diff --git a/src/netbrowse/netbrowse/mdns_hosts/__init__.py b/src/netbrowse/netbrowse/mdns_hosts/__init__.py deleted file mode 100644 index d9b98056..00000000 --- a/src/netbrowse/netbrowse/mdns_hosts/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" - Very basic mDNS scanner with HTML table renderer -""" - -from .mdns_hosts import MdnsHosts -__all__ = ['MdnsHosts'] diff --git a/src/netbrowse/netbrowse/netbrowse.py b/src/netbrowse/netbrowse/netbrowse.py deleted file mode 100755 index a0cc8788..00000000 --- a/src/netbrowse/netbrowse/netbrowse.py +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env python3 -""" - Main server loop. -""" -import sys - -from flask import Flask, render_template -from avahi_alias import AvahiAlias -from mdns_hosts import MdnsHosts - -app = Flask(__name__) - -@app.route('/browse') -def browse(): - """The /browse network.local application""" - mdns_hosts = MdnsHosts() - hosts_services = mdns_hosts.scan() - - order = { 'HTTPS': 1, 'HTTP': 2, 'SSH': 3, 'SFTP': 4 } - for _, details in hosts_services.items(): - details['services'].sort(key=lambda x: order.get(x['type'], 999)) - - return render_template('browse.html', hosts_services=hosts_services) - -def main(): - """Main entrypoint, advertises aliases from command line and starts browser.""" - avahi_aliases = AvahiAlias() - for each in sys.argv[1:]: - avahi_aliases.publish_cname(str(each).encode("utf-8", "strict")) - - try: - app.run(debug=True) - except KeyboardInterrupt: - print("Exiting") - -if __name__ == '__main__': - main() diff --git a/src/netbrowse/pyproject.toml b/src/netbrowse/pyproject.toml index fc502082..7b8f39da 100644 --- a/src/netbrowse/pyproject.toml +++ b/src/netbrowse/pyproject.toml @@ -1,11 +1,11 @@ [build-system] requires = ["setuptools>=42", "wheel"] -build-backend = "setuptools.build_meta" +# build-backend = "setuptools.build_meta" [project] name = "netbrowse" version = "1.0" -description = "mDNS-SD browser" +description = "mDNS Service Browser" authors = [{name = "Joachim Wiberg", email = "troglobit@gmail.com"}] license = {file = "LICENSE"} readme = "README.md" @@ -19,11 +19,11 @@ dev = [ "flake8>=3.8", ] -[tool.setuptools.packages.find] -where = ["netbrowse", "netbrowse.avahi_alias", "netbrowse.mdns_hosts"] +# [tool.setuptools.packages.find] +# where = ["netbrowse"] -[tool.setuptools.package-data] -netbrowse = ["templates/*.html", "static/*"] +# [tool.setuptools.package-data] +# netbrowse = ["templates/*.html", "static/*"] [project.scripts] -netbrowse = "netbrowse.netbrowse:main" +netbrowse = "netbrowse:main"