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 <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2024-04-15 15:36:41 +02:00
parent 5d128d74f5
commit 92d3db46a7
24 changed files with 173 additions and 78 deletions
+5
View File
@@ -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;
+4 -6
View File
@@ -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;
}
}
#
+1
View File
@@ -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"
+2
View File
@@ -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
+2
View File
@@ -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
+2
View File
@@ -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
+1
View File
@@ -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"
+7
View File
@@ -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.
+25
View File
@@ -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))
+2
View File
@@ -0,0 +1,2 @@
service <!service/mdns/running> env:-/etc/default/mdns-alias log:prio:daemon.debug,tag:mdns \
[2345] mdns-alias $MDNS_ALIAS_ARGS -- mDNS alias advertiser
+1 -5
View File
@@ -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
+2 -3
View File
@@ -1,3 +1,2 @@
service env:-/etc/default/netbrowse log:prio:daemon.debug,tag:mdns \
<!service/mdns/running> @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
+21
View File
@@ -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.
+11
View File
@@ -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.
+24
View File
@@ -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")
@@ -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
+20
View File
@@ -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"
+2 -6
View File
@@ -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.
+32
View File
@@ -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()
@@ -1,6 +0,0 @@
"""
Avahi CNAME service over D-Bus advertiser
"""
from .avahi_alias import AvahiAlias
__all__ = ['AvahiAlias']
@@ -1,6 +0,0 @@
"""
Very basic mDNS scanner with HTML table renderer
"""
from .mdns_hosts import MdnsHosts
__all__ = ['MdnsHosts']
-37
View File
@@ -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()
+7 -7
View File
@@ -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"