mirror of
https://github.com/craigerl/aprsd.git
synced 2024-11-25 17:38:44 -05:00
Refactor utils usage
This patch separates out the config from the utils.py utils.py has grown into a catchall for everything and this patch is the start of that cleanup.
This commit is contained in:
parent
65ea33290a
commit
23e3876e7b
@ -38,11 +38,6 @@ class Client:
|
|||||||
if config:
|
if config:
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def new(self):
|
|
||||||
obj = super().__new__(Client)
|
|
||||||
obj.config = self.config
|
|
||||||
return obj
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def client(self):
|
def client(self):
|
||||||
if not self.aprs_client:
|
if not self.aprs_client:
|
||||||
|
367
aprsd/config.py
Normal file
367
aprsd/config.py
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import click
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from aprsd import utils
|
||||||
|
|
||||||
|
|
||||||
|
LOG_LEVELS = {
|
||||||
|
"CRITICAL": logging.CRITICAL,
|
||||||
|
"ERROR": logging.ERROR,
|
||||||
|
"WARNING": logging.WARNING,
|
||||||
|
"INFO": logging.INFO,
|
||||||
|
"DEBUG": logging.DEBUG,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_DATE_FORMAT = "%m/%d/%Y %I:%M:%S %p"
|
||||||
|
DEFAULT_LOG_FORMAT = (
|
||||||
|
"[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s]"
|
||||||
|
" %(message)s - [%(pathname)s:%(lineno)d]"
|
||||||
|
)
|
||||||
|
|
||||||
|
QUEUE_DATE_FORMAT = "[%m/%d/%Y] [%I:%M:%S %p]"
|
||||||
|
QUEUE_LOG_FORMAT = (
|
||||||
|
"%(asctime)s [%(threadName)-20.20s] [%(levelname)-5.5s]"
|
||||||
|
" %(message)s - [%(pathname)s:%(lineno)d]"
|
||||||
|
)
|
||||||
|
|
||||||
|
CORE_MESSAGE_PLUGINS = [
|
||||||
|
"aprsd.plugins.email.EmailPlugin",
|
||||||
|
"aprsd.plugins.fortune.FortunePlugin",
|
||||||
|
"aprsd.plugins.location.LocationPlugin",
|
||||||
|
"aprsd.plugins.ping.PingPlugin",
|
||||||
|
"aprsd.plugins.query.QueryPlugin",
|
||||||
|
"aprsd.plugins.stock.StockPlugin",
|
||||||
|
"aprsd.plugins.time.TimePlugin",
|
||||||
|
"aprsd.plugins.weather.USWeatherPlugin",
|
||||||
|
"aprsd.plugins.version.VersionPlugin",
|
||||||
|
]
|
||||||
|
|
||||||
|
CORE_NOTIFY_PLUGINS = [
|
||||||
|
"aprsd.plugins.notify.NotifySeenPlugin",
|
||||||
|
]
|
||||||
|
|
||||||
|
# an example of what should be in the ~/.aprsd/config.yml
|
||||||
|
DEFAULT_CONFIG_DICT = {
|
||||||
|
"ham": {"callsign": "NOCALL"},
|
||||||
|
"aprs": {
|
||||||
|
"enabled": True,
|
||||||
|
"login": "CALLSIGN",
|
||||||
|
"password": "00000",
|
||||||
|
"host": "rotate.aprs2.net",
|
||||||
|
"port": 14580,
|
||||||
|
},
|
||||||
|
"kiss": {
|
||||||
|
"tcp": {
|
||||||
|
"enabled": False,
|
||||||
|
"host": "direwolf.ip.address",
|
||||||
|
"port": "8001",
|
||||||
|
},
|
||||||
|
"serial": {
|
||||||
|
"enabled": False,
|
||||||
|
"device": "/dev/ttyS0",
|
||||||
|
"baudrate": 9600,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"aprsd": {
|
||||||
|
"logfile": "/tmp/aprsd.log",
|
||||||
|
"logformat": DEFAULT_LOG_FORMAT,
|
||||||
|
"dateformat": DEFAULT_DATE_FORMAT,
|
||||||
|
"trace": False,
|
||||||
|
"enabled_plugins": CORE_MESSAGE_PLUGINS,
|
||||||
|
"units": "imperial",
|
||||||
|
"watch_list": {
|
||||||
|
"enabled": False,
|
||||||
|
# Who gets the alert?
|
||||||
|
"alert_callsign": "NOCALL",
|
||||||
|
# 43200 is 12 hours
|
||||||
|
"alert_time_seconds": 43200,
|
||||||
|
# How many packets to save in a ring Buffer
|
||||||
|
# for a particular callsign
|
||||||
|
"packet_keep_count": 10,
|
||||||
|
"callsigns": [],
|
||||||
|
"enabled_plugins": CORE_NOTIFY_PLUGINS,
|
||||||
|
},
|
||||||
|
"web": {
|
||||||
|
"enabled": True,
|
||||||
|
"logging_enabled": True,
|
||||||
|
"host": "0.0.0.0",
|
||||||
|
"port": 8001,
|
||||||
|
"users": {
|
||||||
|
"admin": "password-here",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"enabled": True,
|
||||||
|
"shortcuts": {
|
||||||
|
"aa": "5551239999@vtext.com",
|
||||||
|
"cl": "craiglamparter@somedomain.org",
|
||||||
|
"wb": "555309@vtext.com",
|
||||||
|
},
|
||||||
|
"smtp": {
|
||||||
|
"login": "SMTP_USERNAME",
|
||||||
|
"password": "SMTP_PASSWORD",
|
||||||
|
"host": "smtp.gmail.com",
|
||||||
|
"port": 465,
|
||||||
|
"use_ssl": False,
|
||||||
|
"debug": False,
|
||||||
|
},
|
||||||
|
"imap": {
|
||||||
|
"login": "IMAP_USERNAME",
|
||||||
|
"password": "IMAP_PASSWORD",
|
||||||
|
"host": "imap.gmail.com",
|
||||||
|
"port": 993,
|
||||||
|
"use_ssl": True,
|
||||||
|
"debug": False,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"aprs.fi": {"apiKey": "APIKEYVALUE"},
|
||||||
|
"openweathermap": {"apiKey": "APIKEYVALUE"},
|
||||||
|
"opencagedata": {"apiKey": "APIKEYVALUE"},
|
||||||
|
"avwx": {"base_url": "http://host:port", "apiKey": "APIKEYVALUE"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
home = str(Path.home())
|
||||||
|
DEFAULT_CONFIG_DIR = f"{home}/.config/aprsd/"
|
||||||
|
DEFAULT_SAVE_FILE = f"{home}/.config/aprsd/aprsd.p"
|
||||||
|
DEFAULT_CONFIG_FILE = f"{home}/.config/aprsd/aprsd.yml"
|
||||||
|
|
||||||
|
|
||||||
|
def add_config_comments(raw_yaml):
|
||||||
|
end_idx = utils.end_substr(raw_yaml, "aprs:")
|
||||||
|
if end_idx != -1:
|
||||||
|
# lets insert a comment
|
||||||
|
raw_yaml = utils.insert_str(
|
||||||
|
raw_yaml,
|
||||||
|
"\n # Set enabled to False if there is no internet connectivity."
|
||||||
|
"\n # This is useful for a direwolf KISS aprs connection only. "
|
||||||
|
"\n"
|
||||||
|
"\n # Get the passcode for your callsign here: "
|
||||||
|
"\n # https://apps.magicbug.co.uk/passcode",
|
||||||
|
end_idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
end_idx = utils.end_substr(raw_yaml, "aprs.fi:")
|
||||||
|
if end_idx != -1:
|
||||||
|
# lets insert a comment
|
||||||
|
raw_yaml = utils.insert_str(
|
||||||
|
raw_yaml,
|
||||||
|
"\n # Get the apiKey from your aprs.fi account here: "
|
||||||
|
"\n # http://aprs.fi/account",
|
||||||
|
end_idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
end_idx = utils.end_substr(raw_yaml, "opencagedata:")
|
||||||
|
if end_idx != -1:
|
||||||
|
# lets insert a comment
|
||||||
|
raw_yaml = utils.insert_str(
|
||||||
|
raw_yaml,
|
||||||
|
"\n # (Optional for TimeOpenCageDataPlugin) "
|
||||||
|
"\n # Get the apiKey from your opencagedata account here: "
|
||||||
|
"\n # https://opencagedata.com/dashboard#api-keys",
|
||||||
|
end_idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
end_idx = utils.end_substr(raw_yaml, "openweathermap:")
|
||||||
|
if end_idx != -1:
|
||||||
|
# lets insert a comment
|
||||||
|
raw_yaml = utils.insert_str(
|
||||||
|
raw_yaml,
|
||||||
|
"\n # (Optional for OWMWeatherPlugin) "
|
||||||
|
"\n # Get the apiKey from your "
|
||||||
|
"\n # openweathermap account here: "
|
||||||
|
"\n # https://home.openweathermap.org/api_keys",
|
||||||
|
end_idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
end_idx = utils.end_substr(raw_yaml, "avwx:")
|
||||||
|
if end_idx != -1:
|
||||||
|
# lets insert a comment
|
||||||
|
raw_yaml = utils.insert_str(
|
||||||
|
raw_yaml,
|
||||||
|
"\n # (Optional for AVWXWeatherPlugin) "
|
||||||
|
"\n # Use hosted avwx-api here: https://avwx.rest "
|
||||||
|
"\n # or deploy your own from here: "
|
||||||
|
"\n # https://github.com/avwx-rest/avwx-api",
|
||||||
|
end_idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
return raw_yaml
|
||||||
|
|
||||||
|
|
||||||
|
def dump_default_cfg():
|
||||||
|
return add_config_comments(
|
||||||
|
yaml.dump(
|
||||||
|
DEFAULT_CONFIG_DICT,
|
||||||
|
indent=4,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_default_config():
|
||||||
|
"""Create a default config file."""
|
||||||
|
# make sure the directory location exists
|
||||||
|
config_file_expanded = os.path.expanduser(DEFAULT_CONFIG_FILE)
|
||||||
|
config_dir = os.path.dirname(config_file_expanded)
|
||||||
|
if not os.path.exists(config_dir):
|
||||||
|
click.echo(f"Config dir '{config_dir}' doesn't exist, creating.")
|
||||||
|
utils.mkdir_p(config_dir)
|
||||||
|
with open(config_file_expanded, "w+") as cf:
|
||||||
|
cf.write(dump_default_cfg())
|
||||||
|
|
||||||
|
|
||||||
|
def get_config(config_file):
|
||||||
|
"""This tries to read the yaml config from <config_file>."""
|
||||||
|
config_file_expanded = os.path.expanduser(config_file)
|
||||||
|
if os.path.exists(config_file_expanded):
|
||||||
|
with open(config_file_expanded) as stream:
|
||||||
|
config = yaml.load(stream, Loader=yaml.FullLoader)
|
||||||
|
return config
|
||||||
|
else:
|
||||||
|
if config_file == DEFAULT_CONFIG_FILE:
|
||||||
|
click.echo(
|
||||||
|
f"{config_file_expanded} is missing, creating config file",
|
||||||
|
)
|
||||||
|
create_default_config()
|
||||||
|
msg = (
|
||||||
|
"Default config file created at {}. Please edit with your "
|
||||||
|
"settings.".format(config_file)
|
||||||
|
)
|
||||||
|
click.echo(msg)
|
||||||
|
else:
|
||||||
|
# The user provided a config file path different from the
|
||||||
|
# Default, so we won't try and create it, just bitch and bail.
|
||||||
|
msg = f"Custom config file '{config_file}' is missing."
|
||||||
|
click.echo(msg)
|
||||||
|
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
# This method tries to parse the config yaml file
|
||||||
|
# and consume the settings.
|
||||||
|
# If the required params don't exist,
|
||||||
|
# it will look in the environment
|
||||||
|
def parse_config(config_file):
|
||||||
|
# for now we still use globals....ugh
|
||||||
|
global CONFIG
|
||||||
|
|
||||||
|
def fail(msg):
|
||||||
|
click.echo(msg)
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
def check_option(config, chain, default_fail=None):
|
||||||
|
try:
|
||||||
|
config = check_config_option(config, chain, default_fail=default_fail)
|
||||||
|
except Exception as ex:
|
||||||
|
fail(repr(ex))
|
||||||
|
else:
|
||||||
|
return config
|
||||||
|
|
||||||
|
config = get_config(config_file)
|
||||||
|
|
||||||
|
# special check here to make sure user has edited the config file
|
||||||
|
# and changed the ham callsign
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
[
|
||||||
|
"ham",
|
||||||
|
"callsign",
|
||||||
|
],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["ham"]["callsign"],
|
||||||
|
)
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["services", "aprs.fi", "apiKey"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["services"]["aprs.fi"]["apiKey"],
|
||||||
|
)
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["aprs", "login"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["aprs"]["login"],
|
||||||
|
)
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["aprs", "password"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["aprs"]["password"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure they change the admin password
|
||||||
|
if config["aprsd"]["web"]["enabled"] is True:
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["aprsd", "web", "users", "admin"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["web"]["users"]["admin"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if config["aprsd"]["watch_list"]["enabled"] is True:
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["aprsd", "watch_list", "alert_callsign"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["watch_list"]["alert_callsign"],
|
||||||
|
)
|
||||||
|
|
||||||
|
if config["aprsd"]["email"]["enabled"] is True:
|
||||||
|
# Check IMAP server settings
|
||||||
|
check_option(config, ["aprsd", "email", "imap", "host"])
|
||||||
|
check_option(config, ["aprsd", "email", "imap", "port"])
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["aprsd", "email", "imap", "login"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["imap"]["login"],
|
||||||
|
)
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["aprsd", "email", "imap", "password"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["imap"]["password"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check SMTP server settings
|
||||||
|
check_option(config, ["aprsd", "email", "smtp", "host"])
|
||||||
|
check_option(config, ["aprsd", "email", "smtp", "port"])
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["aprsd", "email", "smtp", "login"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["smtp"]["login"],
|
||||||
|
)
|
||||||
|
check_option(
|
||||||
|
config,
|
||||||
|
["aprsd", "email", "smtp", "password"],
|
||||||
|
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["smtp"]["password"],
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def conf_option_exists(conf, chain):
|
||||||
|
_key = chain.pop(0)
|
||||||
|
if _key in conf:
|
||||||
|
return conf_option_exists(conf[_key], chain) if chain else conf[_key]
|
||||||
|
|
||||||
|
|
||||||
|
def check_config_option(config, chain, default_fail=None):
|
||||||
|
result = conf_option_exists(config, chain.copy())
|
||||||
|
if result is None:
|
||||||
|
raise Exception(
|
||||||
|
"'{}' was not in config file".format(
|
||||||
|
chain,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if default_fail:
|
||||||
|
if result == default_fail:
|
||||||
|
# We have to fail and bail if the user hasn't edited
|
||||||
|
# this config option.
|
||||||
|
raise Exception(
|
||||||
|
"Config file needs to be edited from provided defaults for {}.".format(
|
||||||
|
chain,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return config
|
@ -14,7 +14,9 @@ import click_completion
|
|||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import client, plugin, utils
|
from aprsd import client
|
||||||
|
from aprsd import config as aprsd_config
|
||||||
|
from aprsd import plugin
|
||||||
|
|
||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
@ -156,7 +158,7 @@ def setup_logging(config, loglevel, quiet):
|
|||||||
"--config",
|
"--config",
|
||||||
"config_file",
|
"config_file",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=utils.DEFAULT_CONFIG_FILE,
|
default=aprsd_config.DEFAULT_CONFIG_FILE,
|
||||||
help="The aprsd config file to use for options.",
|
help="The aprsd config file to use for options.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
@ -178,7 +180,7 @@ def test_plugin(
|
|||||||
):
|
):
|
||||||
"""APRSD Plugin test app."""
|
"""APRSD Plugin test app."""
|
||||||
|
|
||||||
config = utils.parse_config(config_file)
|
config = aprsd_config.parse_config(config_file)
|
||||||
|
|
||||||
setup_logging(config, loglevel, False)
|
setup_logging(config, loglevel, False)
|
||||||
LOG.info(f"Test APRSD PLugin version: {aprsd.__version__}")
|
LOG.info(f"Test APRSD PLugin version: {aprsd.__version__}")
|
||||||
|
@ -17,9 +17,9 @@ from flask_socketio import Namespace, SocketIO
|
|||||||
from werkzeug.security import check_password_hash, generate_password_hash
|
from werkzeug.security import check_password_hash, generate_password_hash
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import (
|
from aprsd import client
|
||||||
client, kissclient, messaging, packets, plugin, stats, threads, utils,
|
from aprsd import config as aprsd_config
|
||||||
)
|
from aprsd import kissclient, messaging, packets, plugin, stats, threads, utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
@ -553,10 +553,10 @@ def setup_logging(config, flask_app, loglevel, quiet):
|
|||||||
flask_app.logger.disabled = True
|
flask_app.logger.disabled = True
|
||||||
return
|
return
|
||||||
|
|
||||||
log_level = utils.LOG_LEVELS[loglevel]
|
log_level = aprsd_config.LOG_LEVELS[loglevel]
|
||||||
LOG.setLevel(log_level)
|
LOG.setLevel(log_level)
|
||||||
log_format = config["aprsd"].get("logformat", utils.DEFAULT_LOG_FORMAT)
|
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
|
||||||
date_format = config["aprsd"].get("dateformat", utils.DEFAULT_DATE_FORMAT)
|
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT)
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
log_file = config["aprsd"].get("logfile", None)
|
log_file = config["aprsd"].get("logfile", None)
|
||||||
if log_file:
|
if log_file:
|
||||||
|
@ -19,7 +19,7 @@ import requests
|
|||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import utils
|
from aprsd import config as aprsd_config
|
||||||
|
|
||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
@ -172,7 +172,7 @@ def parse_delta_str(s):
|
|||||||
"--config",
|
"--config",
|
||||||
"config_file",
|
"config_file",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=utils.DEFAULT_CONFIG_FILE,
|
default=aprsd_config.DEFAULT_CONFIG_FILE,
|
||||||
help="The aprsd config file to use for options.",
|
help="The aprsd config file to use for options.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
@ -191,7 +191,7 @@ def parse_delta_str(s):
|
|||||||
def check(loglevel, config_file, health_url, timeout):
|
def check(loglevel, config_file, health_url, timeout):
|
||||||
"""APRSD Plugin test app."""
|
"""APRSD Plugin test app."""
|
||||||
|
|
||||||
config = utils.parse_config(config_file)
|
config = aprsd_config.parse_config(config_file)
|
||||||
|
|
||||||
setup_logging(config, loglevel, False)
|
setup_logging(config, loglevel, False)
|
||||||
LOG.debug(f"APRSD HealthCheck version: {aprsd.__version__}")
|
LOG.debug(f"APRSD HealthCheck version: {aprsd.__version__}")
|
||||||
|
@ -36,7 +36,9 @@ import click_completion
|
|||||||
|
|
||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import client, messaging, stats, threads, trace, utils
|
from aprsd import client
|
||||||
|
from aprsd import config as aprsd_config
|
||||||
|
from aprsd import messaging, stats, threads, trace, utils
|
||||||
|
|
||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
@ -169,10 +171,10 @@ def signal_handler(sig, frame):
|
|||||||
# to disable logging to stdout, but still log to file
|
# to disable logging to stdout, but still log to file
|
||||||
# use the --quiet option on the cmdln
|
# use the --quiet option on the cmdln
|
||||||
def setup_logging(config, loglevel, quiet):
|
def setup_logging(config, loglevel, quiet):
|
||||||
log_level = utils.LOG_LEVELS[loglevel]
|
log_level = aprsd_config.LOG_LEVELS[loglevel]
|
||||||
LOG.setLevel(log_level)
|
LOG.setLevel(log_level)
|
||||||
log_format = config["aprsd"].get("logformat", utils.DEFAULT_LOG_FORMAT)
|
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
|
||||||
date_format = config["aprsd"].get("dateformat", utils.DEFAULT_DATE_FORMAT)
|
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT)
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
log_file = config["aprsd"].get("logfile", None)
|
log_file = config["aprsd"].get("logfile", None)
|
||||||
if log_file:
|
if log_file:
|
||||||
@ -218,7 +220,7 @@ def setup_logging(config, loglevel, quiet):
|
|||||||
"--config",
|
"--config",
|
||||||
"config_file",
|
"config_file",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=utils.DEFAULT_CONFIG_FILE,
|
default=aprsd_config.DEFAULT_CONFIG_FILE,
|
||||||
help="The aprsd config file to use for options.",
|
help="The aprsd config file to use for options.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
@ -258,7 +260,7 @@ def listen(
|
|||||||
"""Send a message to a callsign via APRS_IS."""
|
"""Send a message to a callsign via APRS_IS."""
|
||||||
global got_ack, got_response
|
global got_ack, got_response
|
||||||
|
|
||||||
config = utils.parse_config(config_file)
|
config = aprsd_config.parse_config(config_file)
|
||||||
if not aprs_login:
|
if not aprs_login:
|
||||||
click.echo("Must set --aprs_login or APRS_LOGIN")
|
click.echo("Must set --aprs_login or APRS_LOGIN")
|
||||||
return
|
return
|
||||||
|
@ -37,9 +37,10 @@ import click_completion
|
|||||||
# local imports here
|
# local imports here
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import (
|
from aprsd import (
|
||||||
client, flask, kissclient, messaging, packets, plugin, stats, threads,
|
flask, kissclient, messaging, packets, plugin, stats, threads, trace, utils,
|
||||||
trace, utils,
|
|
||||||
)
|
)
|
||||||
|
from aprsd import client
|
||||||
|
from aprsd import config as aprsd_config
|
||||||
|
|
||||||
|
|
||||||
# setup the global logger
|
# setup the global logger
|
||||||
@ -48,22 +49,8 @@ LOG = logging.getLogger("APRSD")
|
|||||||
|
|
||||||
|
|
||||||
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
||||||
|
|
||||||
flask_enabled = False
|
flask_enabled = False
|
||||||
|
|
||||||
# server_event = threading.Event()
|
|
||||||
|
|
||||||
# localization, please edit:
|
|
||||||
# HOST = "noam.aprs2.net" # north america tier2 servers round robin
|
|
||||||
# USER = "KM6XXX-9" # callsign of this aprs client with SSID
|
|
||||||
# PASS = "99999" # google how to generate this
|
|
||||||
# BASECALLSIGN = "KM6XXX" # callsign of radio in the field to send email
|
|
||||||
# shortcuts = {
|
|
||||||
# "aa" : "5551239999@vtext.com",
|
|
||||||
# "cl" : "craiglamparter@somedomain.org",
|
|
||||||
# "wb" : "5553909472@vtext.com"
|
|
||||||
# }
|
|
||||||
|
|
||||||
|
|
||||||
def custom_startswith(string, incomplete):
|
def custom_startswith(string, incomplete):
|
||||||
"""A custom completion match that supports case insensitive matching."""
|
"""A custom completion match that supports case insensitive matching."""
|
||||||
@ -172,10 +159,10 @@ def signal_handler(sig, frame):
|
|||||||
# to disable logging to stdout, but still log to file
|
# to disable logging to stdout, but still log to file
|
||||||
# use the --quiet option on the cmdln
|
# use the --quiet option on the cmdln
|
||||||
def setup_logging(config, loglevel, quiet):
|
def setup_logging(config, loglevel, quiet):
|
||||||
log_level = utils.LOG_LEVELS[loglevel]
|
log_level = aprsd_config.LOG_LEVELS[loglevel]
|
||||||
LOG.setLevel(log_level)
|
LOG.setLevel(log_level)
|
||||||
log_format = config["aprsd"].get("logformat", utils.DEFAULT_LOG_FORMAT)
|
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
|
||||||
date_format = config["aprsd"].get("dateformat", utils.DEFAULT_DATE_FORMAT)
|
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT)
|
||||||
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
|
||||||
log_file = config["aprsd"].get("logfile", None)
|
log_file = config["aprsd"].get("logfile", None)
|
||||||
if log_file:
|
if log_file:
|
||||||
@ -196,15 +183,15 @@ def setup_logging(config, loglevel, quiet):
|
|||||||
imap_logger.addHandler(fh)
|
imap_logger.addHandler(fh)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
utils.check_config_option(
|
aprsd_config.check_config_option(
|
||||||
config, ["aprsd", "web", "enabled"],
|
config, ["aprsd", "web", "enabled"],
|
||||||
default_fail=False,
|
default_fail=False,
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
qh = logging.handlers.QueueHandler(threads.logging_queue)
|
qh = logging.handlers.QueueHandler(threads.logging_queue)
|
||||||
q_log_formatter = logging.Formatter(
|
q_log_formatter = logging.Formatter(
|
||||||
fmt=utils.QUEUE_LOG_FORMAT,
|
fmt=aprsd_config.QUEUE_LOG_FORMAT,
|
||||||
datefmt=utils.QUEUE_DATE_FORMAT,
|
datefmt=aprsd_config.QUEUE_DATE_FORMAT,
|
||||||
)
|
)
|
||||||
qh.setFormatter(q_log_formatter)
|
qh.setFormatter(q_log_formatter)
|
||||||
LOG.addHandler(qh)
|
LOG.addHandler(qh)
|
||||||
@ -234,11 +221,11 @@ def setup_logging(config, loglevel, quiet):
|
|||||||
"--config",
|
"--config",
|
||||||
"config_file",
|
"config_file",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=utils.DEFAULT_CONFIG_FILE,
|
default=aprsd_config.DEFAULT_CONFIG_FILE,
|
||||||
help="The aprsd config file to use for options.",
|
help="The aprsd config file to use for options.",
|
||||||
)
|
)
|
||||||
def check_version(loglevel, config_file):
|
def check_version(loglevel, config_file):
|
||||||
config = utils.parse_config(config_file)
|
config = aprsd_config.parse_config(config_file)
|
||||||
|
|
||||||
setup_logging(config, loglevel, False)
|
setup_logging(config, loglevel, False)
|
||||||
level, msg = utils._check_version()
|
level, msg = utils._check_version()
|
||||||
@ -251,7 +238,7 @@ def check_version(loglevel, config_file):
|
|||||||
@main.command()
|
@main.command()
|
||||||
def sample_config():
|
def sample_config():
|
||||||
"""This dumps the config to stdout."""
|
"""This dumps the config to stdout."""
|
||||||
click.echo(utils.dump_default_cfg())
|
click.echo(aprsd_config.dump_default_cfg())
|
||||||
|
|
||||||
|
|
||||||
@main.command()
|
@main.command()
|
||||||
@ -272,7 +259,7 @@ def sample_config():
|
|||||||
"--config",
|
"--config",
|
||||||
"config_file",
|
"config_file",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=utils.DEFAULT_CONFIG_FILE,
|
default=aprsd_config.DEFAULT_CONFIG_FILE,
|
||||||
help="The aprsd config file to use for options.",
|
help="The aprsd config file to use for options.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
@ -312,7 +299,7 @@ def send_message(
|
|||||||
"""Send a message to a callsign via APRS_IS."""
|
"""Send a message to a callsign via APRS_IS."""
|
||||||
global got_ack, got_response
|
global got_ack, got_response
|
||||||
|
|
||||||
config = utils.parse_config(config_file)
|
config = aprsd_config.parse_config(config_file)
|
||||||
if not aprs_login:
|
if not aprs_login:
|
||||||
click.echo("Must set --aprs_login or APRS_LOGIN")
|
click.echo("Must set --aprs_login or APRS_LOGIN")
|
||||||
return
|
return
|
||||||
@ -429,7 +416,7 @@ def send_message(
|
|||||||
"--config",
|
"--config",
|
||||||
"config_file",
|
"config_file",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
default=utils.DEFAULT_CONFIG_FILE,
|
default=aprsd_config.DEFAULT_CONFIG_FILE,
|
||||||
help="The aprsd config file to use for options.",
|
help="The aprsd config file to use for options.",
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
@ -454,7 +441,7 @@ def server(
|
|||||||
if not quiet:
|
if not quiet:
|
||||||
click.echo("Load config")
|
click.echo("Load config")
|
||||||
|
|
||||||
config = utils.parse_config(config_file)
|
config = aprsd_config.parse_config(config_file)
|
||||||
|
|
||||||
setup_logging(config, loglevel, quiet)
|
setup_logging(config, loglevel, quiet)
|
||||||
level, msg = utils._check_version()
|
level, msg = utils._check_version()
|
||||||
@ -523,7 +510,7 @@ def server(
|
|||||||
keepalive = threads.KeepAliveThread(config=config)
|
keepalive = threads.KeepAliveThread(config=config)
|
||||||
keepalive.start()
|
keepalive.start()
|
||||||
|
|
||||||
web_enabled = utils.check_config_option(config, ["aprsd", "web", "enabled"], default_fail=False)
|
web_enabled = aprsd_config.check_config_option(config, ["aprsd", "web", "enabled"], default_fail=False)
|
||||||
|
|
||||||
if web_enabled:
|
if web_enabled:
|
||||||
flask_enabled = True
|
flask_enabled = True
|
||||||
|
@ -9,7 +9,9 @@ import re
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from aprsd import client, kissclient, packets, stats, threads, trace, utils
|
from aprsd import client
|
||||||
|
from aprsd import config as aprsd_config
|
||||||
|
from aprsd import kissclient, packets, stats, threads, trace
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
@ -113,11 +115,11 @@ class MsgTrack:
|
|||||||
LOG.debug(f"Save tracker to disk? {len(self)}")
|
LOG.debug(f"Save tracker to disk? {len(self)}")
|
||||||
if len(self) > 0:
|
if len(self) > 0:
|
||||||
LOG.info(f"Saving {len(self)} tracking messages to disk")
|
LOG.info(f"Saving {len(self)} tracking messages to disk")
|
||||||
pickle.dump(self.dump(), open(utils.DEFAULT_SAVE_FILE, "wb+"))
|
pickle.dump(self.dump(), open(aprsd_config.DEFAULT_SAVE_FILE, "wb+"))
|
||||||
else:
|
else:
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
"Nothing to save, flushing old save file '{}'".format(
|
"Nothing to save, flushing old save file '{}'".format(
|
||||||
utils.DEFAULT_SAVE_FILE,
|
aprsd_config.DEFAULT_SAVE_FILE,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.flush()
|
self.flush()
|
||||||
@ -131,8 +133,8 @@ class MsgTrack:
|
|||||||
return dump
|
return dump
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
if os.path.exists(utils.DEFAULT_SAVE_FILE):
|
if os.path.exists(aprsd_config.DEFAULT_SAVE_FILE):
|
||||||
raw = pickle.load(open(utils.DEFAULT_SAVE_FILE, "rb"))
|
raw = pickle.load(open(aprsd_config.DEFAULT_SAVE_FILE, "rb"))
|
||||||
if raw:
|
if raw:
|
||||||
self.track = raw
|
self.track = raw
|
||||||
LOG.debug("Loaded MsgTrack dict from disk.")
|
LOG.debug("Loaded MsgTrack dict from disk.")
|
||||||
@ -171,8 +173,8 @@ class MsgTrack:
|
|||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
"""Nuke the old pickle file that stored the old results from last aprsd run."""
|
"""Nuke the old pickle file that stored the old results from last aprsd run."""
|
||||||
if os.path.exists(utils.DEFAULT_SAVE_FILE):
|
if os.path.exists(aprsd_config.DEFAULT_SAVE_FILE):
|
||||||
pathlib.Path(utils.DEFAULT_SAVE_FILE).unlink()
|
pathlib.Path(aprsd_config.DEFAULT_SAVE_FILE).unlink()
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.track = {}
|
self.track = {}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from aprsd import plugin, plugin_utils, trace, utils
|
from aprsd import config, plugin, plugin_utils, trace
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
@ -24,7 +24,7 @@ class LocationPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
|
|
||||||
# get last location of a callsign, get descriptive name from weather service
|
# get last location of a callsign, get descriptive name from weather service
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
config.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
||||||
return "No aprs.fi apikey found"
|
return "No aprs.fi apikey found"
|
||||||
|
@ -5,7 +5,7 @@ import time
|
|||||||
from opencage.geocoder import OpenCageGeocode
|
from opencage.geocoder import OpenCageGeocode
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from aprsd import fuzzyclock, plugin, plugin_utils, trace, utils
|
from aprsd import config, fuzzyclock, plugin, plugin_utils, trace
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
@ -64,7 +64,7 @@ class TimeOpenCageDataPlugin(TimePlugin):
|
|||||||
|
|
||||||
# get last location of a callsign, get descriptive name from weather service
|
# get last location of a callsign, get descriptive name from weather service
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
config.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
||||||
return "No aprs.fi apikey found"
|
return "No aprs.fi apikey found"
|
||||||
@ -95,7 +95,7 @@ class TimeOpenCageDataPlugin(TimePlugin):
|
|||||||
lon = aprs_data["entries"][0]["lng"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, "opencagedata", "apiKey")
|
config.check_config_option(self.config, "opencagedata", "apiKey")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to find config opencage:apiKey {ex}")
|
LOG.error(f"Failed to find config opencage:apiKey {ex}")
|
||||||
return "No opencage apiKey found"
|
return "No opencage apiKey found"
|
||||||
@ -130,7 +130,7 @@ class TimeOWMPlugin(TimePlugin):
|
|||||||
|
|
||||||
# get last location of a callsign, get descriptive name from weather service
|
# get last location of a callsign, get descriptive name from weather service
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
config.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
||||||
return "No aprs.fi apikey found"
|
return "No aprs.fi apikey found"
|
||||||
@ -160,7 +160,7 @@ class TimeOWMPlugin(TimePlugin):
|
|||||||
lon = aprs_data["entries"][0]["lng"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(
|
config.check_config_option(
|
||||||
self.config,
|
self.config,
|
||||||
["services", "openweathermap", "apiKey"],
|
["services", "openweathermap", "apiKey"],
|
||||||
)
|
)
|
||||||
|
@ -4,7 +4,7 @@ import re
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from aprsd import plugin, plugin_utils, trace, utils
|
from aprsd import config, plugin, plugin_utils, trace
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
LOG = logging.getLogger("APRSD")
|
||||||
@ -34,7 +34,7 @@ class USWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
# message = packet.get("message_text", None)
|
# message = packet.get("message_text", None)
|
||||||
# ack = packet.get("msgNo", "0")
|
# ack = packet.get("msgNo", "0")
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
config.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
||||||
return "No aprs.fi apikey found"
|
return "No aprs.fi apikey found"
|
||||||
@ -115,7 +115,7 @@ class USMetarPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
fromcall = fromcall
|
fromcall = fromcall
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(
|
config.check_config_option(
|
||||||
self.config,
|
self.config,
|
||||||
["services", "aprs.fi", "apiKey"],
|
["services", "aprs.fi", "apiKey"],
|
||||||
)
|
)
|
||||||
@ -199,7 +199,7 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
searchcall = fromcall
|
searchcall = fromcall
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
config.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
||||||
return "No aprs.fi apikey found"
|
return "No aprs.fi apikey found"
|
||||||
@ -220,7 +220,7 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
lon = aprs_data["entries"][0]["lng"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(
|
config.check_config_option(
|
||||||
self.config,
|
self.config,
|
||||||
["services", "openweathermap", "apiKey"],
|
["services", "openweathermap", "apiKey"],
|
||||||
)
|
)
|
||||||
@ -229,7 +229,7 @@ class OWMWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
return "No openweathermap apiKey found"
|
return "No openweathermap apiKey found"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["aprsd", "units"])
|
config.check_config_option(self.config, ["aprsd", "units"])
|
||||||
except Exception:
|
except Exception:
|
||||||
LOG.debug("Couldn't find untis in aprsd:services:units")
|
LOG.debug("Couldn't find untis in aprsd:services:units")
|
||||||
units = "metric"
|
units = "metric"
|
||||||
@ -323,7 +323,7 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
searchcall = fromcall
|
searchcall = fromcall
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
config.check_config_option(self.config, ["services", "aprs.fi", "apiKey"])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
LOG.error(f"Failed to find config aprs.fi:apikey {ex}")
|
||||||
return "No aprs.fi apikey found"
|
return "No aprs.fi apikey found"
|
||||||
@ -344,13 +344,13 @@ class AVWXWeatherPlugin(plugin.APRSDRegexCommandPluginBase):
|
|||||||
lon = aprs_data["entries"][0]["lng"]
|
lon = aprs_data["entries"][0]["lng"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["services", "avwx", "apiKey"])
|
config.check_config_option(self.config, ["services", "avwx", "apiKey"])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error(f"Failed to find config avwx:apiKey {ex}")
|
LOG.error(f"Failed to find config avwx:apiKey {ex}")
|
||||||
return "No avwx apiKey found"
|
return "No avwx apiKey found"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
utils.check_config_option(self.config, ["services", "avwx", "base_url"])
|
config.check_config_option(self.config, ["services", "avwx", "base_url"])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.debug(f"Didn't find avwx:base_url {ex}")
|
LOG.debug(f"Didn't find avwx:base_url {ex}")
|
||||||
base_url = "https://avwx.rest"
|
base_url = "https://avwx.rest"
|
||||||
|
348
aprsd/utils.py
348
aprsd/utils.py
@ -3,128 +3,13 @@
|
|||||||
import collections
|
import collections
|
||||||
import errno
|
import errno
|
||||||
import functools
|
import functools
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import click
|
|
||||||
import update_checker
|
import update_checker
|
||||||
import yaml
|
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import plugin
|
|
||||||
|
|
||||||
|
|
||||||
LOG_LEVELS = {
|
|
||||||
"CRITICAL": logging.CRITICAL,
|
|
||||||
"ERROR": logging.ERROR,
|
|
||||||
"WARNING": logging.WARNING,
|
|
||||||
"INFO": logging.INFO,
|
|
||||||
"DEBUG": logging.DEBUG,
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFAULT_DATE_FORMAT = "%m/%d/%Y %I:%M:%S %p"
|
|
||||||
DEFAULT_LOG_FORMAT = (
|
|
||||||
"[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s]"
|
|
||||||
" %(message)s - [%(pathname)s:%(lineno)d]"
|
|
||||||
)
|
|
||||||
|
|
||||||
QUEUE_DATE_FORMAT = "[%m/%d/%Y] [%I:%M:%S %p]"
|
|
||||||
QUEUE_LOG_FORMAT = (
|
|
||||||
"%(asctime)s [%(threadName)-20.20s] [%(levelname)-5.5s]"
|
|
||||||
" %(message)s - [%(pathname)s:%(lineno)d]"
|
|
||||||
)
|
|
||||||
|
|
||||||
# an example of what should be in the ~/.aprsd/config.yml
|
|
||||||
DEFAULT_CONFIG_DICT = {
|
|
||||||
"ham": {"callsign": "NOCALL"},
|
|
||||||
"aprs": {
|
|
||||||
"enabled": True,
|
|
||||||
"login": "CALLSIGN",
|
|
||||||
"password": "00000",
|
|
||||||
"host": "rotate.aprs2.net",
|
|
||||||
"port": 14580,
|
|
||||||
},
|
|
||||||
"kiss": {
|
|
||||||
"tcp": {
|
|
||||||
"enabled": False,
|
|
||||||
"host": "direwolf.ip.address",
|
|
||||||
"port": "8001",
|
|
||||||
},
|
|
||||||
"serial": {
|
|
||||||
"enabled": False,
|
|
||||||
"device": "/dev/ttyS0",
|
|
||||||
"baudrate": 9600,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"aprsd": {
|
|
||||||
"logfile": "/tmp/aprsd.log",
|
|
||||||
"logformat": DEFAULT_LOG_FORMAT,
|
|
||||||
"dateformat": DEFAULT_DATE_FORMAT,
|
|
||||||
"trace": False,
|
|
||||||
"enabled_plugins": plugin.CORE_MESSAGE_PLUGINS,
|
|
||||||
"units": "imperial",
|
|
||||||
"watch_list": {
|
|
||||||
"enabled": False,
|
|
||||||
# Who gets the alert?
|
|
||||||
"alert_callsign": "NOCALL",
|
|
||||||
# 43200 is 12 hours
|
|
||||||
"alert_time_seconds": 43200,
|
|
||||||
# How many packets to save in a ring Buffer
|
|
||||||
# for a particular callsign
|
|
||||||
"packet_keep_count": 10,
|
|
||||||
"callsigns": [],
|
|
||||||
"enabled_plugins": plugin.CORE_NOTIFY_PLUGINS,
|
|
||||||
},
|
|
||||||
"web": {
|
|
||||||
"enabled": True,
|
|
||||||
"logging_enabled": True,
|
|
||||||
"host": "0.0.0.0",
|
|
||||||
"port": 8001,
|
|
||||||
"users": {
|
|
||||||
"admin": "password-here",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"enabled": True,
|
|
||||||
"shortcuts": {
|
|
||||||
"aa": "5551239999@vtext.com",
|
|
||||||
"cl": "craiglamparter@somedomain.org",
|
|
||||||
"wb": "555309@vtext.com",
|
|
||||||
},
|
|
||||||
"smtp": {
|
|
||||||
"login": "SMTP_USERNAME",
|
|
||||||
"password": "SMTP_PASSWORD",
|
|
||||||
"host": "smtp.gmail.com",
|
|
||||||
"port": 465,
|
|
||||||
"use_ssl": False,
|
|
||||||
"debug": False,
|
|
||||||
},
|
|
||||||
"imap": {
|
|
||||||
"login": "IMAP_USERNAME",
|
|
||||||
"password": "IMAP_PASSWORD",
|
|
||||||
"host": "imap.gmail.com",
|
|
||||||
"port": 993,
|
|
||||||
"use_ssl": True,
|
|
||||||
"debug": False,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"services": {
|
|
||||||
"aprs.fi": {"apiKey": "APIKEYVALUE"},
|
|
||||||
"openweathermap": {"apiKey": "APIKEYVALUE"},
|
|
||||||
"opencagedata": {"apiKey": "APIKEYVALUE"},
|
|
||||||
"avwx": {"base_url": "http://host:port", "apiKey": "APIKEYVALUE"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
home = str(Path.home())
|
|
||||||
DEFAULT_CONFIG_DIR = f"{home}/.config/aprsd/"
|
|
||||||
DEFAULT_SAVE_FILE = f"{home}/.config/aprsd/aprsd.p"
|
|
||||||
DEFAULT_CONFIG_FILE = f"{home}/.config/aprsd/aprsd.yml"
|
|
||||||
|
|
||||||
|
|
||||||
def synchronized(wrapped):
|
def synchronized(wrapped):
|
||||||
@ -175,239 +60,6 @@ def end_substr(original, substr):
|
|||||||
return idx
|
return idx
|
||||||
|
|
||||||
|
|
||||||
def dump_default_cfg():
|
|
||||||
return add_config_comments(
|
|
||||||
yaml.dump(
|
|
||||||
DEFAULT_CONFIG_DICT,
|
|
||||||
indent=4,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def add_config_comments(raw_yaml):
|
|
||||||
end_idx = end_substr(raw_yaml, "aprs:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # Set enabled to False if there is no internet connectivity."
|
|
||||||
"\n # This is useful for a direwolf KISS aprs connection only. "
|
|
||||||
"\n"
|
|
||||||
"\n # Get the passcode for your callsign here: "
|
|
||||||
"\n # https://apps.magicbug.co.uk/passcode",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_idx = end_substr(raw_yaml, "aprs.fi:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # Get the apiKey from your aprs.fi account here: "
|
|
||||||
"\n # http://aprs.fi/account",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_idx = end_substr(raw_yaml, "opencagedata:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # (Optional for TimeOpenCageDataPlugin) "
|
|
||||||
"\n # Get the apiKey from your opencagedata account here: "
|
|
||||||
"\n # https://opencagedata.com/dashboard#api-keys",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_idx = end_substr(raw_yaml, "openweathermap:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # (Optional for OWMWeatherPlugin) "
|
|
||||||
"\n # Get the apiKey from your "
|
|
||||||
"\n # openweathermap account here: "
|
|
||||||
"\n # https://home.openweathermap.org/api_keys",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
end_idx = end_substr(raw_yaml, "avwx:")
|
|
||||||
if end_idx != -1:
|
|
||||||
# lets insert a comment
|
|
||||||
raw_yaml = insert_str(
|
|
||||||
raw_yaml,
|
|
||||||
"\n # (Optional for AVWXWeatherPlugin) "
|
|
||||||
"\n # Use hosted avwx-api here: https://avwx.rest "
|
|
||||||
"\n # or deploy your own from here: "
|
|
||||||
"\n # https://github.com/avwx-rest/avwx-api",
|
|
||||||
end_idx,
|
|
||||||
)
|
|
||||||
|
|
||||||
return raw_yaml
|
|
||||||
|
|
||||||
|
|
||||||
def create_default_config():
|
|
||||||
"""Create a default config file."""
|
|
||||||
# make sure the directory location exists
|
|
||||||
config_file_expanded = os.path.expanduser(DEFAULT_CONFIG_FILE)
|
|
||||||
config_dir = os.path.dirname(config_file_expanded)
|
|
||||||
if not os.path.exists(config_dir):
|
|
||||||
click.echo(f"Config dir '{config_dir}' doesn't exist, creating.")
|
|
||||||
mkdir_p(config_dir)
|
|
||||||
with open(config_file_expanded, "w+") as cf:
|
|
||||||
cf.write(dump_default_cfg())
|
|
||||||
|
|
||||||
|
|
||||||
def get_config(config_file):
|
|
||||||
"""This tries to read the yaml config from <config_file>."""
|
|
||||||
config_file_expanded = os.path.expanduser(config_file)
|
|
||||||
if os.path.exists(config_file_expanded):
|
|
||||||
with open(config_file_expanded) as stream:
|
|
||||||
config = yaml.load(stream, Loader=yaml.FullLoader)
|
|
||||||
return config
|
|
||||||
else:
|
|
||||||
if config_file == DEFAULT_CONFIG_FILE:
|
|
||||||
click.echo(
|
|
||||||
f"{config_file_expanded} is missing, creating config file",
|
|
||||||
)
|
|
||||||
create_default_config()
|
|
||||||
msg = (
|
|
||||||
"Default config file created at {}. Please edit with your "
|
|
||||||
"settings.".format(config_file)
|
|
||||||
)
|
|
||||||
click.echo(msg)
|
|
||||||
else:
|
|
||||||
# The user provided a config file path different from the
|
|
||||||
# Default, so we won't try and create it, just bitch and bail.
|
|
||||||
msg = f"Custom config file '{config_file}' is missing."
|
|
||||||
click.echo(msg)
|
|
||||||
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
|
|
||||||
def conf_option_exists(conf, chain):
|
|
||||||
_key = chain.pop(0)
|
|
||||||
if _key in conf:
|
|
||||||
return conf_option_exists(conf[_key], chain) if chain else conf[_key]
|
|
||||||
|
|
||||||
|
|
||||||
def check_config_option(config, chain, default_fail=None):
|
|
||||||
result = conf_option_exists(config, chain.copy())
|
|
||||||
if result is None:
|
|
||||||
raise Exception(
|
|
||||||
"'{}' was not in config file".format(
|
|
||||||
chain,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if default_fail:
|
|
||||||
if result == default_fail:
|
|
||||||
# We have to fail and bail if the user hasn't edited
|
|
||||||
# this config option.
|
|
||||||
raise Exception(
|
|
||||||
"Config file needs to be edited from provided defaults for {}.".format(
|
|
||||||
chain,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
# This method tries to parse the config yaml file
|
|
||||||
# and consume the settings.
|
|
||||||
# If the required params don't exist,
|
|
||||||
# it will look in the environment
|
|
||||||
def parse_config(config_file):
|
|
||||||
# for now we still use globals....ugh
|
|
||||||
global CONFIG
|
|
||||||
|
|
||||||
def fail(msg):
|
|
||||||
click.echo(msg)
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
def check_option(config, chain, default_fail=None):
|
|
||||||
try:
|
|
||||||
config = check_config_option(config, chain, default_fail=default_fail)
|
|
||||||
except Exception as ex:
|
|
||||||
fail(repr(ex))
|
|
||||||
else:
|
|
||||||
return config
|
|
||||||
|
|
||||||
config = get_config(config_file)
|
|
||||||
|
|
||||||
# special check here to make sure user has edited the config file
|
|
||||||
# and changed the ham callsign
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
[
|
|
||||||
"ham",
|
|
||||||
"callsign",
|
|
||||||
],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["ham"]["callsign"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["services", "aprs.fi", "apiKey"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["services"]["aprs.fi"]["apiKey"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprs", "login"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprs"]["login"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprs", "password"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprs"]["password"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure they change the admin password
|
|
||||||
if config["aprsd"]["web"]["enabled"] is True:
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "web", "users", "admin"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["web"]["users"]["admin"],
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["aprsd"]["watch_list"]["enabled"] is True:
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "watch_list", "alert_callsign"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["watch_list"]["alert_callsign"],
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["aprsd"]["email"]["enabled"] is True:
|
|
||||||
# Check IMAP server settings
|
|
||||||
check_option(config, ["aprsd", "email", "imap", "host"])
|
|
||||||
check_option(config, ["aprsd", "email", "imap", "port"])
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "email", "imap", "login"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["imap"]["login"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "email", "imap", "password"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["imap"]["password"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check SMTP server settings
|
|
||||||
check_option(config, ["aprsd", "email", "smtp", "host"])
|
|
||||||
check_option(config, ["aprsd", "email", "smtp", "port"])
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "email", "smtp", "login"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["smtp"]["login"],
|
|
||||||
)
|
|
||||||
check_option(
|
|
||||||
config,
|
|
||||||
["aprsd", "email", "smtp", "password"],
|
|
||||||
default_fail=DEFAULT_CONFIG_DICT["aprsd"]["email"]["smtp"]["password"],
|
|
||||||
)
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def human_size(bytes, units=None):
|
def human_size(bytes, units=None):
|
||||||
"""Returns a human readable string representation of bytes"""
|
"""Returns a human readable string representation of bytes"""
|
||||||
if not units:
|
if not units:
|
||||||
|
@ -28,7 +28,7 @@ RUN addgroup --gid $GID $APRS_USER
|
|||||||
RUN useradd -m -u $UID -g $APRS_USER $APRS_USER
|
RUN useradd -m -u $UID -g $APRS_USER $APRS_USER
|
||||||
|
|
||||||
# Install aprsd
|
# Install aprsd
|
||||||
RUN /usr/local/bin/pip3 install aprsd==2.3.0
|
RUN /usr/local/bin/pip3 install aprsd==2.3.1
|
||||||
|
|
||||||
# Ensure /config is there with a default config file
|
# Ensure /config is there with a default config file
|
||||||
USER root
|
USER root
|
||||||
|
@ -36,7 +36,7 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
VERSION="2.2.1"
|
VERSION="2.3.1"
|
||||||
|
|
||||||
if [ $ALL_PLATFORMS -eq 1 ]
|
if [ $ALL_PLATFORMS -eq 1 ]
|
||||||
then
|
then
|
||||||
|
@ -4,7 +4,7 @@ from unittest import mock
|
|||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
import aprsd
|
import aprsd
|
||||||
from aprsd import messaging, packets, stats, utils
|
from aprsd import config, messaging, packets, stats
|
||||||
from aprsd.fuzzyclock import fuzzy
|
from aprsd.fuzzyclock import fuzzy
|
||||||
from aprsd.plugins import fortune as fortune_plugin
|
from aprsd.plugins import fortune as fortune_plugin
|
||||||
from aprsd.plugins import ping as ping_plugin
|
from aprsd.plugins import ping as ping_plugin
|
||||||
@ -19,7 +19,7 @@ class TestPlugin(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.fromcall = fake.FAKE_FROM_CALLSIGN
|
self.fromcall = fake.FAKE_FROM_CALLSIGN
|
||||||
self.ack = 1
|
self.ack = 1
|
||||||
self.config = utils.DEFAULT_CONFIG_DICT
|
self.config = config.DEFAULT_CONFIG_DICT
|
||||||
self.config["ham"]["callsign"] = self.fromcall
|
self.config["ham"]["callsign"] = self.fromcall
|
||||||
self.config["aprs"]["login"] = fake.FAKE_TO_CALLSIGN
|
self.config["aprs"]["login"] = fake.FAKE_TO_CALLSIGN
|
||||||
# Inintialize the stats object with the config
|
# Inintialize the stats object with the config
|
||||||
|
Loading…
Reference in New Issue
Block a user