support SSL ws session
This commit is contained in:
+2
-1
@@ -7,4 +7,5 @@ __pycache__/
|
|||||||
.venv/
|
.venv/
|
||||||
test/
|
test/
|
||||||
build/
|
build/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
ssl/
|
||||||
@@ -10,7 +10,9 @@ watchhosts:
|
|||||||
# "localhost":
|
# "localhost":
|
||||||
# "haschloss" :
|
# "haschloss" :
|
||||||
# "cotgate":
|
# "cotgate":
|
||||||
# "wentworth":
|
"wentworth":
|
||||||
|
notify: +4915123456789
|
||||||
|
src: "signal"
|
||||||
"y":
|
"y":
|
||||||
notify: +4915123456789
|
notify: +4915123456789
|
||||||
src: "signal"
|
src: "signal"
|
||||||
@@ -25,7 +27,18 @@ pushover_user: "uDhH33UjQQDYtNzJb1ThRiWb9ingGK"
|
|||||||
pushsrv: "pushover"
|
pushsrv: "pushover"
|
||||||
|
|
||||||
dyndomains: {"wrede.org"}
|
dyndomains: {"wrede.org"}
|
||||||
|
toemail: ["aew.hbd.notify@wrede.ca"]
|
||||||
|
fromemail: "aew.hbd@wrede.ca"
|
||||||
smtpserver: "smtp.fastmail.com"
|
smtpserver: "smtp.fastmail.com"
|
||||||
smtpuser: "andreas@wrede.ca"
|
smtpuser: "andreas@wrede.ca"
|
||||||
smtppassword: "pvtvefyp5gbhnch2"
|
smtppassword: "r8psra6wj6gcakkp"
|
||||||
smtpport: 587
|
smtpport: 587
|
||||||
|
|
||||||
|
ws_port: 50005
|
||||||
|
wss_port: 50006
|
||||||
|
cert_path: "/usr/local/etc/letsencrypt/live/hbd.wrede.ca/"
|
||||||
|
cert_path: "ssl/"
|
||||||
|
# CERT_PATH = "./test/"
|
||||||
|
wss_pem: "fullchain.pem"
|
||||||
|
wss_key: "privkey.pem"
|
||||||
|
|
||||||
|
|||||||
+8
-2
@@ -30,8 +30,14 @@ DEFAULTS = {
|
|||||||
"smtpserver": "smtp.fastmail.com",
|
"smtpserver": "smtp.fastmail.com",
|
||||||
"smtpuser": "andreas@wrede.ca",
|
"smtpuser": "andreas@wrede.ca",
|
||||||
"smtppassword": "pvtvefyp5gbhnch2",
|
"smtppassword": "pvtvefyp5gbhnch2",
|
||||||
"smtpport": 587
|
"smtpport": 587,
|
||||||
|
"toemail": ["aew.hbd.notify@wrede.ca"],
|
||||||
|
"fromemail": "aew.hbd@wrede.ca",
|
||||||
|
"ws_port": 50005,
|
||||||
|
"wss_port": None,
|
||||||
|
"cert_path": "/usr/local/etc/ssl/",
|
||||||
|
"wss_pem": "fullchain.pem",
|
||||||
|
"wss_key": "privkey.pem"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -54,7 +54,7 @@ def nsupdate(hostname: str, newip: str, dyndomain: str, nsupdate_bin: str = "/us
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
async def dns_update_worker(hbdclass, cfg: dict, async_queue=None, log: Optional[callable] = None, email: Optional[callable] = None, loop: Optional[asyncio.AbstractEventLoop] = None):
|
async def dns_update_worker(hbdclass, cfg: dict, async_queue=None, log: Optional[callable] = None, pushmsg: Optional[callable] = None, loop: Optional[asyncio.AbstractEventLoop] = None):
|
||||||
"""Pure async DNS worker that processes updates from asyncio.Queue.
|
"""Pure async DNS worker that processes updates from asyncio.Queue.
|
||||||
|
|
||||||
Exits when it receives a None sentinel.
|
Exits when it receives a None sentinel.
|
||||||
@@ -99,9 +99,9 @@ async def dns_update_worker(hbdclass, cfg: dict, async_queue=None, log: Optional
|
|||||||
err = await loop.run_in_executor(None, nsupdate, name, addr, dyndomain, cfg.get("nsupdate_bin", "/usr/local/bin/nsupdate"), cfg.get("rndc_key", "/etc/dhcpc/rndc-key"))
|
err = await loop.run_in_executor(None, nsupdate, name, addr, dyndomain, cfg.get("nsupdate_bin", "/usr/local/bin/nsupdate"), cfg.get("rndc_key", "/etc/dhcpc/rndc-key"))
|
||||||
if err:
|
if err:
|
||||||
m += f", DNS update failed: {err}"
|
m += f", DNS update failed: {err}"
|
||||||
if email:
|
if pushmsg:
|
||||||
try:
|
try:
|
||||||
await loop.run_in_executor(None, email, "error: nsupdate failed", f"{name}.dy.{dyndomain}: {m}")
|
await loop.run_in_executor(None, pushmsg, "error: nsupdate failed", f"{name}.dy.{dyndomain}: {m}")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -125,7 +125,7 @@ async def dns_update_worker(hbdclass, cfg: dict, async_queue=None, log: Optional
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def start_dns_worker(hbdclass, cfg: dict, log: Optional[callable] = None, email: Optional[callable] = None, loop: Optional[asyncio.AbstractEventLoop] = None):
|
def start_dns_worker(hbdclass, cfg: dict, log: Optional[callable] = None, pushmsg: Optional[callable] = None, loop: Optional[asyncio.AbstractEventLoop] = None):
|
||||||
"""Start the async DNS worker and return the Task.
|
"""Start the async DNS worker and return the Task.
|
||||||
|
|
||||||
Replaces Host.dnsQ with an asyncio.Queue wrapped in a thread-safe bridge
|
Replaces Host.dnsQ with an asyncio.Queue wrapped in a thread-safe bridge
|
||||||
@@ -167,5 +167,5 @@ def start_dns_worker(hbdclass, cfg: dict, log: Optional[callable] = None, email:
|
|||||||
bridge = _QueueBridge(loop, async_q)
|
bridge = _QueueBridge(loop, async_q)
|
||||||
hbdclass.Host.dnsQ = bridge
|
hbdclass.Host.dnsQ = bridge
|
||||||
|
|
||||||
task = loop.create_task(dns_update_worker(hbdclass, cfg, async_queue=async_q, log=log, email=email, loop=loop))
|
task = loop.create_task(dns_update_worker(hbdclass, cfg, async_queue=async_q, log=log, pushmsg=pushmsg, loop=loop))
|
||||||
return task
|
return task
|
||||||
|
|||||||
+14
-1
@@ -139,7 +139,10 @@ async def start(
|
|||||||
host = config.get("hb_host", "localhost")
|
host = config.get("hb_host", "localhost")
|
||||||
extra_scripts = config.get("http_extra_scripts", "")
|
extra_scripts = config.get("http_extra_scripts", "")
|
||||||
host = request.host.split(":")[0]
|
host = request.host.split(":")[0]
|
||||||
heartbeat_ws_url = f"ws://{host}:{config.get('ws_port', 50005)}/hbd"
|
if config.get("wss_port"):
|
||||||
|
heartbeat_ws_url = f"wss://{host}:{config['wss_port']}/hbd"
|
||||||
|
else:
|
||||||
|
heartbeat_ws_url = f"ws://{host}:{config.get('ws_port', 50005)}/hbd"
|
||||||
tmpl = env.get_template("live.html")
|
tmpl = env.get_template("live.html")
|
||||||
body = tmpl.render(
|
body = tmpl.render(
|
||||||
title="Heartbeat",
|
title="Heartbeat",
|
||||||
@@ -158,6 +161,7 @@ async def start(
|
|||||||
URL form: /static/<path>
|
URL form: /static/<path>
|
||||||
"""
|
"""
|
||||||
p = request.match_info.get("path", "")
|
p = request.match_info.get("path", "")
|
||||||
|
logger.debug("static file requested: %s", p)
|
||||||
base = os.path.abspath(os.path.join(os.path.dirname(__file__), "static"))
|
base = os.path.abspath(os.path.join(os.path.dirname(__file__), "static"))
|
||||||
# normalize and prevent directory traversal
|
# normalize and prevent directory traversal
|
||||||
target = os.path.abspath(os.path.normpath(os.path.join(base, p)))
|
target = os.path.abspath(os.path.normpath(os.path.join(base, p)))
|
||||||
@@ -168,6 +172,14 @@ async def start(
|
|||||||
logger.info("serving static file: %s", target)
|
logger.info("serving static file: %s", target)
|
||||||
return web.FileResponse(path=target)
|
return web.FileResponse(path=target)
|
||||||
|
|
||||||
|
async def favicon(request):
|
||||||
|
"""Serve favicon.ico from the package static directory."""
|
||||||
|
base = os.path.abspath(os.path.join(os.path.dirname(__file__), "static/images"))
|
||||||
|
target = os.path.join(base, "favicon.ico")
|
||||||
|
if not os.path.exists(target) or not os.path.isfile(target):
|
||||||
|
return web.Response(status=404, text="Not Found")
|
||||||
|
return web.FileResponse(path=target)
|
||||||
|
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
app.add_routes(
|
app.add_routes(
|
||||||
[
|
[
|
||||||
@@ -181,6 +193,7 @@ async def start(
|
|||||||
web.get("/r", restart),
|
web.get("/r", restart),
|
||||||
web.get("/live", live),
|
web.get("/live", live),
|
||||||
web.get("/static/{path:.*}", static),
|
web.get("/static/{path:.*}", static),
|
||||||
|
web.get("/favicon.ico", favicon),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -9,7 +9,7 @@ from typing import Optional
|
|||||||
from . import hbdclass
|
from . import hbdclass
|
||||||
DROPOVERDUE = 7 * 24 * 3600
|
DROPOVERDUE = 7 * 24 * 3600
|
||||||
|
|
||||||
def checkoverdue(config: dict, hbdclass, log: callable, email: callable, pushmsg: callable, msg_to_websockets: callable):
|
def checkoverdue(config: dict, hbdclass, log: callable, pushmsg: callable, msg_to_websockets: callable):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
for h in list(hbdclass.Host.hosts.keys()):
|
for h in list(hbdclass.Host.hosts.keys()):
|
||||||
pmsg = []
|
pmsg = []
|
||||||
@@ -27,7 +27,6 @@ def checkoverdue(config: dict, hbdclass, log: callable, email: callable, pushmsg
|
|||||||
conn.newstate(hbdclass.Connection.UNKNOWN, conn.lastbeat)
|
conn.newstate(hbdclass.Connection.UNKNOWN, conn.lastbeat)
|
||||||
if pmsg != []:
|
if pmsg != []:
|
||||||
if h in config.get("watchhosts", []):
|
if h in config.get("watchhosts", []):
|
||||||
email("overdue", "%s overdue" % " and ".join(pmsg))
|
|
||||||
pushmsg("%s %s overdue" % (h, " and ".join(pmsg)))
|
pushmsg("%s %s overdue" % (h, " and ".join(pmsg)))
|
||||||
log(h, "%s overdue" % " and ".join(pmsg))
|
log(h, "%s overdue" % " and ".join(pmsg))
|
||||||
msg_to_websockets("host", hbdclass.Host.hosts[h].stateinfo())
|
msg_to_websockets("host", hbdclass.Host.hosts[h].stateinfo())
|
||||||
@@ -36,11 +35,10 @@ async def start(
|
|||||||
config: dict,
|
config: dict,
|
||||||
hbdclass: callable,
|
hbdclass: callable,
|
||||||
log=None,
|
log=None,
|
||||||
email=None,
|
|
||||||
pushmsg=None,
|
pushmsg=None,
|
||||||
msg_to_websockets=None,
|
msg_to_websockets=None,
|
||||||
):
|
):
|
||||||
""" start a monitor loop that checks for overdue hosts every minute """
|
""" start a monitor loop that checks for overdue hosts every minute """
|
||||||
while True:
|
while True:
|
||||||
await asyncio.sleep(15) # 15 seconds between checks
|
await asyncio.sleep(15) # 15 seconds between checks
|
||||||
checkoverdue(config, hbdclass, log, email, pushmsg, msg_to_websockets)
|
checkoverdue(config, hbdclass, log, pushmsg, msg_to_websockets)
|
||||||
|
|||||||
+13
-7
@@ -21,21 +21,21 @@ def setup(cfg: dict):
|
|||||||
_config = dict(cfg)
|
_config = dict(cfg)
|
||||||
|
|
||||||
|
|
||||||
def send_email(aemail, smtpserver, sender, subject, body, debug=0):
|
def send_email(toaddrs, smtpserver, sender, subject, body, debug=0):
|
||||||
"""Send a plain email via SMTP. Returns True on success."""
|
"""Send a plain email via SMTP. Returns True on success."""
|
||||||
try:
|
try:
|
||||||
smtpport = _config.get("smtpport", 587)
|
smtpport = _config.get("smtpport", 587)
|
||||||
server = smtplib.SMTP(smtpserver, smtpport))
|
server = smtplib.SMTP(smtpserver, smtpport)
|
||||||
if debug > 0:
|
if debug > 0:
|
||||||
server.set_debuglevel(1)
|
server.set_debuglevel(1)
|
||||||
if smtpport == 587:
|
if smtpport == 587:
|
||||||
server.starttls()
|
server.starttls()
|
||||||
server.ehlo()
|
server.ehlo()
|
||||||
smtpuser = _config.get("smtpuser", None)
|
smtpuser = _config.get("smtpuser", None)
|
||||||
smtppassword = _config.get("smtp_password", None)
|
smtppassword = _config.get("smtppassword", None)
|
||||||
if smtpuser and smtppassword:
|
if smtpuser and smtppassword:
|
||||||
server.login(smtpuser, smtppassword)
|
server.login(smtpuser, smtppassword)
|
||||||
server.sendmail(sender, aemail, body)
|
server.sendmail(sender, toaddrs, body)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("email send failed: %s", e)
|
logger.warning("email send failed: %s", e)
|
||||||
try:
|
try:
|
||||||
@@ -56,9 +56,12 @@ def email(subject: str, msg: str, debug: int = 0) -> bool:
|
|||||||
Uses module-level configuration to supply recipient list, smtp server
|
Uses module-level configuration to supply recipient list, smtp server
|
||||||
and sender address.
|
and sender address.
|
||||||
"""
|
"""
|
||||||
toaddrs = _config.get("AEMAIL") or _config.get("aemail") or _config.get("email_to") or []
|
toaddrs = _config.get("toemail")
|
||||||
fromemail = _config.get("fromemail") or _config.get("sender") or f"aew.heartbeat@{_config.get('domain','local') }"
|
fromemail = _config.get("fromemail")
|
||||||
smtpserver = _config.get("smtpserver") or _config.get("SMTPSERVER", "localhost")
|
smtpserver = _config.get("smtpserver")
|
||||||
|
if not toaddrs or not fromemail or not smtpserver:
|
||||||
|
logger.warning("email config incomplete: toemail=%s, fromemail=%s, smtpserver=%s", toaddrs, fromemail, smtpserver)
|
||||||
|
return False
|
||||||
date = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.localtime())
|
date = time.strftime("%a, %d %b %Y %H:%M:%S %z", time.localtime())
|
||||||
body = "To: %s\nFrom: %s\nSubject: %s\nDate: %s\n\n%s" % (
|
body = "To: %s\nFrom: %s\nSubject: %s\nDate: %s\n\n%s" % (
|
||||||
toaddrs[0] if toaddrs else "",
|
toaddrs[0] if toaddrs else "",
|
||||||
@@ -153,6 +156,9 @@ def pushmsg(cfg: dict, msg: str, debug: int = 0):
|
|||||||
if p in ("all", "signal"):
|
if p in ("all", "signal"):
|
||||||
ok = pushsignal(cfg.get("signal_cli", "/usr/local/bin/signal-cli"), cfg.get("signal_user", ""), cfg.get("signal_recipient", ""), msg, debug=debug)
|
ok = pushsignal(cfg.get("signal_cli", "/usr/local/bin/signal-cli"), cfg.get("signal_user", ""), cfg.get("signal_recipient", ""), msg, debug=debug)
|
||||||
results["signal"] = ok
|
results["signal"] = ok
|
||||||
|
if p in ("all", "email"):
|
||||||
|
ok = email("Heartbeat notification", msg, debug=debug)
|
||||||
|
results["email"] = ok
|
||||||
logger.debug("push results: %s", results)
|
logger.debug("push results: %s", results)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
+19
-7
@@ -5,6 +5,8 @@ import socket
|
|||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
import ssl
|
||||||
|
import pathlib
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
from . import udp
|
from . import udp
|
||||||
@@ -82,7 +84,6 @@ async def _run_async(config):
|
|||||||
|
|
||||||
notify_mod.setup(config)
|
notify_mod.setup(config)
|
||||||
|
|
||||||
email = notify_mod.email
|
|
||||||
pushmsg = notify_mod.pushmsg_from_config
|
pushmsg = notify_mod.pushmsg_from_config
|
||||||
|
|
||||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||||
@@ -106,7 +107,6 @@ async def _run_async(config):
|
|||||||
config=config,
|
config=config,
|
||||||
hbdclass=hbdclass,
|
hbdclass=hbdclass,
|
||||||
log=log,
|
log=log,
|
||||||
email=email,
|
|
||||||
pushmsg=pushmsg,
|
pushmsg=pushmsg,
|
||||||
msg_to_websockets=msg_to_websockets,
|
msg_to_websockets=msg_to_websockets,
|
||||||
DEBUG=config.get("debug", 0),
|
DEBUG=config.get("debug", 0),
|
||||||
@@ -129,7 +129,6 @@ async def _run_async(config):
|
|||||||
hbdclass=hbdclass,
|
hbdclass=hbdclass,
|
||||||
msgs_getter=lambda: msgs,
|
msgs_getter=lambda: msgs,
|
||||||
log=log,
|
log=log,
|
||||||
email=email,
|
|
||||||
pushmsg=pushmsg,
|
pushmsg=pushmsg,
|
||||||
msg_to_websockets=msg_to_websockets,
|
msg_to_websockets=msg_to_websockets,
|
||||||
tcss=None,
|
tcss=None,
|
||||||
@@ -146,19 +145,33 @@ async def _run_async(config):
|
|||||||
# start dns update worker (async)
|
# start dns update worker (async)
|
||||||
dns_task = None
|
dns_task = None
|
||||||
try:
|
try:
|
||||||
dns_task = dns_mod.start_dns_worker(hbdclass, config, log=log, email=email, loop=loop)
|
dns_task = dns_mod.start_dns_worker(hbdclass, config, log=log, pushmsg=pushmsg, loop=loop)
|
||||||
logger.info("dns update worker started")
|
logger.info("dns update worker started")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception("dns worker failed to start: %s", e)
|
logger.exception("dns worker failed to start: %s", e)
|
||||||
|
|
||||||
# Start the websocket servers as a background task
|
# Start the websocket servers as a background task
|
||||||
|
if config.get("wss_port", None):
|
||||||
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||||
|
ssl_path = config.get("cert_path", "")
|
||||||
|
wss_pem = ssl_path + config.get("wss_pem", "")
|
||||||
|
wss_key = ssl_path + config.get("wss_key", "")
|
||||||
|
try:
|
||||||
|
ssl_context.load_cert_chain(wss_pem, keyfile=wss_key)
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error("error: missing SSL keys %s or %s", wss_pem, wss_key)
|
||||||
|
sys.exit(1)
|
||||||
|
logger.info("Starting secure WebSocket server on port %s with cert %s", config.get("wss_port", None), wss_pem)
|
||||||
|
else:
|
||||||
|
ssl_context = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ws_task = asyncio.create_task(
|
ws_task = asyncio.create_task(
|
||||||
ws_mod.start(
|
ws_mod.start(
|
||||||
host=config.get("hbd_host", ""),
|
host=config.get("hbd_host", ""),
|
||||||
ws_port=config.get("ws_port", 50005),
|
ws_port=config.get("ws_port", None),
|
||||||
wss_port=config.get("wss_port", None),
|
wss_port=config.get("wss_port", None),
|
||||||
ssl_context=None,
|
ssl_context=ssl_context,
|
||||||
get_hosts=lambda: [hbdclass.Host.hosts[h].stateinfo() for h in sorted(hbdclass.Host.hosts)],
|
get_hosts=lambda: [hbdclass.Host.hosts[h].stateinfo() for h in sorted(hbdclass.Host.hosts)],
|
||||||
get_msgs=lambda: msgs,
|
get_msgs=lambda: msgs,
|
||||||
verbose=config.get("verbose", False),
|
verbose=config.get("verbose", False),
|
||||||
@@ -175,7 +188,6 @@ async def _run_async(config):
|
|||||||
config=config,
|
config=config,
|
||||||
hbdclass=hbdclass,
|
hbdclass=hbdclass,
|
||||||
log=log,
|
log=log,
|
||||||
email=email,
|
|
||||||
pushmsg=pushmsg,
|
pushmsg=pushmsg,
|
||||||
msg_to_websockets=msg_to_websockets,
|
msg_to_websockets=msg_to_websockets,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -185,11 +185,11 @@
|
|||||||
function WS_Connect() {
|
function WS_Connect() {
|
||||||
if ("WebSocket" in window) {
|
if ("WebSocket" in window) {
|
||||||
//N.B: subprotocol field causes chrome to error 1006
|
//N.B: subprotocol field causes chrome to error 1006
|
||||||
var ws_hbd = new WebSocket("{{heartbeat_ws_url}}" /*, "hdb" */);
|
var ws_hbd = new WebSocket("{{heartbeat_ws_url}}", /* "hdb" */ );
|
||||||
|
|
||||||
ws_hbd.onopen = function () {
|
ws_hbd.onopen = function () {
|
||||||
// Web Socket is connected, send data using send()
|
// Web Socket is connected, send data using send()
|
||||||
console.log("ws connect");
|
console.log("ws connect {{heartbeat_ws_url}}");
|
||||||
// Hide modal window if visible
|
// Hide modal window if visible
|
||||||
var modal = document.getElementById("connectionModal");
|
var modal = document.getElementById("connectionModal");
|
||||||
if (modal) {
|
if (modal) {
|
||||||
|
|||||||
-14
@@ -68,7 +68,6 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
- config: dict of configuration
|
- config: dict of configuration
|
||||||
- hbdclass: module providing Host/Connection classes
|
- hbdclass: module providing Host/Connection classes
|
||||||
- log: callable(loghost, message)
|
- log: callable(loghost, message)
|
||||||
- email: callable(subject, message)
|
|
||||||
- pushmsg: callable(message)
|
- pushmsg: callable(message)
|
||||||
- msg_to_websockets: callable(typ, data)
|
- msg_to_websockets: callable(typ, data)
|
||||||
- DEBUG, verbose
|
- DEBUG, verbose
|
||||||
@@ -79,7 +78,6 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
cfg = ctx.get("config", {})
|
cfg = ctx.get("config", {})
|
||||||
hbdcls = ctx.get("hbdclass")
|
hbdcls = ctx.get("hbdclass")
|
||||||
log = ctx.get("log")
|
log = ctx.get("log")
|
||||||
email = ctx.get("email")
|
|
||||||
pushmsg = ctx.get("pushmsg")
|
pushmsg = ctx.get("pushmsg")
|
||||||
msg_to_websockets = ctx.get("msg_to_websockets")
|
msg_to_websockets = ctx.get("msg_to_websockets")
|
||||||
DEBUG = ctx.get("DEBUG", 0)
|
DEBUG = ctx.get("DEBUG", 0)
|
||||||
@@ -122,8 +120,6 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
if log:
|
if log:
|
||||||
log(uname, res)
|
log(uname, res)
|
||||||
if uname in cfg.get("watchhosts", []):
|
if uname in cfg.get("watchhosts", []):
|
||||||
if email:
|
|
||||||
email("address change", "%s %s" % (host.name, res))
|
|
||||||
if pushmsg:
|
if pushmsg:
|
||||||
pushmsg("%s %s" % (host.name, res))
|
pushmsg("%s %s" % (host.name, res))
|
||||||
|
|
||||||
@@ -138,16 +134,12 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
log(uname, "booted")
|
log(uname, "booted")
|
||||||
if uname in cfg.get("watchhosts", []):
|
if uname in cfg.get("watchhosts", []):
|
||||||
m = "%s booted" % (host.name)
|
m = "%s booted" % (host.name)
|
||||||
if email:
|
|
||||||
email("booted", m)
|
|
||||||
if pushmsg:
|
if pushmsg:
|
||||||
pushmsg(m)
|
pushmsg(m)
|
||||||
if message:
|
if message:
|
||||||
if log:
|
if log:
|
||||||
log(uname, "msg: %s" % message, service=service)
|
log(uname, "msg: %s" % message, service=service)
|
||||||
if uname in cfg.get("watchhosts", []):
|
if uname in cfg.get("watchhosts", []):
|
||||||
if email:
|
|
||||||
email("msg", message)
|
|
||||||
if pushmsg:
|
if pushmsg:
|
||||||
pushmsg(message)
|
pushmsg(message)
|
||||||
|
|
||||||
@@ -158,8 +150,6 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
if log:
|
if log:
|
||||||
log(uname, m)
|
log(uname, m)
|
||||||
if uname in cfg.get("watchhosts", []):
|
if uname in cfg.get("watchhosts", []):
|
||||||
if email:
|
|
||||||
email("%s back" % conn.afam, uname)
|
|
||||||
if pushmsg:
|
if pushmsg:
|
||||||
pushmsg("%s %s is back" % (uname, conn.afam))
|
pushmsg("%s %s is back" % (uname, conn.afam))
|
||||||
|
|
||||||
@@ -172,8 +162,6 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
if log:
|
if log:
|
||||||
log(uname, "%s shutdown" % conn.afam)
|
log(uname, "%s shutdown" % conn.afam)
|
||||||
if uname in cfg.get("watchhosts", []):
|
if uname in cfg.get("watchhosts", []):
|
||||||
if email:
|
|
||||||
email("shutdown", "%s %s shutdown" % (uname, conn.afam))
|
|
||||||
if pushmsg:
|
if pushmsg:
|
||||||
pushmsg("%s %s shutdown" % (uname, conn.afam))
|
pushmsg("%s %s shutdown" % (uname, conn.afam))
|
||||||
conn.newstate(hbdcls.Connection.DOWN, now)
|
conn.newstate(hbdcls.Connection.DOWN, now)
|
||||||
@@ -197,8 +185,6 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
while len(host.cmds):
|
while len(host.cmds):
|
||||||
op, rmsg = host.cmds[0]
|
op, rmsg = host.cmds[0]
|
||||||
if op == "CMD":
|
if op == "CMD":
|
||||||
if email:
|
|
||||||
email("%s cmd exec" % uname, "command '%s' sent" % rmsg)
|
|
||||||
del host.cmds[0]
|
del host.cmds[0]
|
||||||
if log:
|
if log:
|
||||||
log(uname, "command sent")
|
log(uname, "command sent")
|
||||||
|
|||||||
@@ -20,11 +20,9 @@ _verbose = False
|
|||||||
|
|
||||||
|
|
||||||
async def _handler(websocket, path=None):
|
async def _handler(websocket, path=None):
|
||||||
# Some versions of the websockets library call handler(connection) only;
|
|
||||||
# accept optional path and fall back to websocket.path when missing.
|
|
||||||
global _connections
|
global _connections
|
||||||
_connections.add(websocket)
|
_connections.add(websocket)
|
||||||
remote_address = getattr(websocket, "remote_address", None)
|
remote_address = websocket.remote_address
|
||||||
if path is None:
|
if path is None:
|
||||||
path = getattr(websocket, "path", None)
|
path = getattr(websocket, "path", None)
|
||||||
if _verbose:
|
if _verbose:
|
||||||
@@ -76,36 +74,25 @@ async def start(host: str, ws_port: int, wss_port: Optional[int] = None, ssl_con
|
|||||||
|
|
||||||
servers = []
|
servers = []
|
||||||
# plain WebSocket
|
# plain WebSocket
|
||||||
ws_server = websockets.serve(_handler, host, ws_port) #, subprotocols=["hbd"])
|
|
||||||
websockets_logger = logging.getLogger("websockets.server")
|
websockets_logger = logging.getLogger("websockets.server")
|
||||||
websockets_logger.setLevel(logging.INFO)
|
websockets_logger.setLevel(logging.DEBUG if verbose else logging.INFO)
|
||||||
|
# regular WebSocket
|
||||||
|
ws_server = websockets.serve(_handler, host, ws_port) #, subprotocols=["hbd"])
|
||||||
servers.append(ws_server)
|
servers.append(ws_server)
|
||||||
|
|
||||||
# secure WebSocket (optional)
|
# secure WebSocket (optional)
|
||||||
if wss_port and ssl_context:
|
if wss_port and ssl_context:
|
||||||
wss_server = websockets.serve(_handler, host, wss_port, ssl=ssl_context) #, subprotocols=["hbd"])
|
wss_server = websockets.serve(_handler, host, wss_port, ssl=ssl_context ) #, subprotocols=["hbd"])
|
||||||
servers.append(wss_server)
|
servers.append(wss_server)
|
||||||
|
|
||||||
# await starting of all servers
|
# await starting of all servers
|
||||||
try:
|
for srv in servers:
|
||||||
for srv in servers:
|
await srv
|
||||||
await srv
|
|
||||||
|
|
||||||
if _verbose:
|
if _verbose:
|
||||||
logger.info("WebSocket server started on port %s (wss %s)", ws_port, wss_port)
|
logger.info("WebSocket server(s) started on port %s (wss %s)", ws_port, wss_port)
|
||||||
|
|
||||||
# block forever (until loop is stopped or cancelled)
|
# block forever (until loop is stopped or cancelled)
|
||||||
await asyncio.Future()
|
await asyncio.Future()
|
||||||
except asyncio.CancelledError:
|
|
||||||
logger.info("WebSocket server shutting down...")
|
|
||||||
# Close all active connections
|
|
||||||
for conn in list(_connections):
|
|
||||||
try:
|
|
||||||
await conn.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
_connections.clear()
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def broadcast(typ: str, data) -> bool:
|
def broadcast(typ: str, data) -> bool:
|
||||||
@@ -115,7 +102,7 @@ def broadcast(typ: str, data) -> bool:
|
|||||||
connected websockets. Returns False if server was not running.
|
connected websockets. Returns False if server was not running.
|
||||||
"""
|
"""
|
||||||
global _loop
|
global _loop
|
||||||
|
|
||||||
if not _loop:
|
if not _loop:
|
||||||
return False
|
return False
|
||||||
jmsg = json.dumps({"type": typ, "data": data})
|
jmsg = json.dumps({"type": typ, "data": data})
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ version = "5.0.1"
|
|||||||
description = "Heartbeat daemon (hbd) — receive heartbeats and act on them"
|
description = "Heartbeat daemon (hbd) — receive heartbeats and act on them"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
license = { text = "MIT" }
|
license = "MIT"
|
||||||
keywords = ["heartbeat", "monitoring", "dns", "websocket"]
|
keywords = ["heartbeat", "monitoring", "dns", "websocket"]
|
||||||
authors = [
|
authors = [
|
||||||
{ name = "heartbeat contributors" }
|
{ name = "heartbeat contributors" }
|
||||||
|
|||||||
Reference in New Issue
Block a user