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:
Hemna 2021-09-16 17:08:30 -04:00
parent 65ea33290a
commit 23e3876e7b
15 changed files with 435 additions and 428 deletions

View File

@ -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
View 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

View File

@ -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__}")

View File

@ -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:

View 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__}")

View File

@ -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

View File

@ -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

View File

@ -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 = {}

View File

@ -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"

View File

@ -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"],
) )

View File

@ -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"

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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