diff --git a/aprsd/conf/__init__.py b/aprsd/conf/__init__.py new file mode 100644 index 0000000..db28c55 --- /dev/null +++ b/aprsd/conf/__init__.py @@ -0,0 +1,56 @@ +from oslo_config import cfg + +from aprsd.conf import client, common, log, plugin_common, plugin_email + + +CONF = cfg.CONF + +log.register_opts(CONF) +common.register_opts(CONF) +client.register_opts(CONF) + +# plugins +plugin_common.register_opts(CONF) +plugin_email.register_opts(CONF) + + +def set_lib_defaults(): + """Update default value for configuration options from other namespace. + Example, oslo lib config options. This is needed for + config generator tool to pick these default value changes. + https://docs.openstack.org/oslo.config/latest/cli/ + generator.html#modifying-defaults-from-other-namespaces + """ + + # Update default value of oslo_log default_log_levels and + # logging_context_format_string config option. + set_log_defaults() + + +def set_log_defaults(): + # logging.set_defaults(default_log_levels=logging.get_default_log_levels()) + pass + + +def conf_to_dict(): + """Convert the CONF options to a single level dictionary.""" + entries = {} + + def _sanitize(opt, value): + """Obfuscate values of options declared secret.""" + return value if not opt.secret else "*" * 4 + + for opt_name in sorted(CONF._opts): + opt = CONF._get_opt_info(opt_name)["opt"] + val = str(_sanitize(opt, getattr(CONF, opt_name))) + entries[str(opt)] = val + + for group_name in list(CONF._groups): + group_attr = CONF.GroupAttr(CONF, CONF._get_group(group_name)) + for opt_name in sorted(CONF._groups[group_name]._opts): + opt = CONF._get_opt_info(opt_name, group_name)["opt"] + val = str(_sanitize(opt, getattr(group_attr, opt_name))) + gname_opt_name = f"{group_name}.{opt_name}" + entries[gname_opt_name] = val + + return entries diff --git a/aprsd/conf/client.py b/aprsd/conf/client.py new file mode 100644 index 0000000..e7e85de --- /dev/null +++ b/aprsd/conf/client.py @@ -0,0 +1,102 @@ +""" +The options for logging setup +""" + +from oslo_config import cfg + + +DEFAULT_LOGIN = "NOCALL" + +aprs_group = cfg.OptGroup( + name="aprs_network", + title="APRS-IS Network settings", +) +kiss_serial_group = cfg.OptGroup( + name="kiss_serial", + title="KISS Serial device connection", +) +kiss_tcp_group = cfg.OptGroup( + name="kiss_tcp", + title="KISS TCP/IP Device connection", +) +aprs_opts = [ + cfg.BoolOpt( + "enabled", + default=True, + help="Set enabled to False if there is no internet connectivity." + "This is useful for a direwolf KISS aprs connection only.", + ), + cfg.StrOpt( + "login", + default=DEFAULT_LOGIN, + help="APRS Username", + ), + cfg.StrOpt( + "password", + secret=True, + help="APRS Password " + "Get the passcode for your callsign here: " + "https://apps.magicbug.co.uk/passcode", + ), + cfg.HostnameOpt( + "host", + default="noam.aprs2.net", + help="The APRS-IS hostname", + ), + cfg.PortOpt( + "port", + default=14580, + help="APRS-IS port", + ), +] + +kiss_serial_opts = [ + cfg.BoolOpt( + "enabled", + default=False, + help="Enable Serial KISS interface connection.", + ), + cfg.StrOpt( + "device", + help="Serial Device file to use. /dev/ttyS0", + ), + cfg.IntOpt( + "baudrate", + default=9600, + help="The Serial device baud rate for communication", + ), +] + +kiss_tcp_opts = [ + cfg.BoolOpt( + "enabled", + default=False, + help="Enable Serial KISS interface connection.", + ), + cfg.HostnameOpt( + "host", + help="The KISS TCP Host to connect to.", + ), + cfg.PortOpt( + "port", + default=8001, + help="The KISS TCP/IP network port", + ), +] + + +def register_opts(config): + config.register_group(aprs_group) + config.register_opts(aprs_opts, group=aprs_group) + config.register_group(kiss_serial_group) + config.register_group(kiss_tcp_group) + config.register_opts(kiss_serial_opts, group=kiss_serial_group) + config.register_opts(kiss_tcp_opts, group=kiss_tcp_group) + + +def list_opts(): + return { + aprs_group.name: aprs_opts, + kiss_serial_group.name: kiss_serial_opts, + kiss_tcp_group.name: kiss_tcp_opts, + } diff --git a/aprsd/conf/common.py b/aprsd/conf/common.py new file mode 100644 index 0000000..0691d3c --- /dev/null +++ b/aprsd/conf/common.py @@ -0,0 +1,133 @@ +from oslo_config import cfg + + +admin_group = cfg.OptGroup( + name="admin", + title="Admin web interface settings", +) +watch_list_group = cfg.OptGroup( + name="watch_list", + title="Watch List settings", +) + + +aprsd_opts = [ + cfg.StrOpt( + "callsign", + required=True, + help="Callsign to use for messages sent by APRSD", + ), + cfg.BoolOpt( + "enable_save", + default=True, + help="Enable saving of watch list, packet tracker between restarts.", + ), + cfg.StrOpt( + "save_location", + default="~/.config/aprsd", + help="Save location for packet tracking files.", + ), + cfg.BoolOpt( + "trace_enabled", + default=False, + help="Enable code tracing", + ), + cfg.StrOpt( + "units", + default="imperial", + help="Units for display, imperial or metric", + ), +] + +watch_list_opts = [ + cfg.BoolOpt( + "enabled", + default=False, + help="Enable the watch list feature. Still have to enable " + "the correct plugin. Built-in plugin to use is " + "aprsd.plugins.notify.NotifyPlugin", + ), + cfg.ListOpt( + "callsigns", + help="Callsigns to watch for messsages", + ), + cfg.StrOpt( + "alert_callsign", + help="The Ham Callsign to send messages to for watch list alerts.", + ), + cfg.IntOpt( + "packet_keep_count", + default=10, + help="The number of packets to store.", + ), + cfg.IntOpt( + "alert_time_seconds", + default=3600, + help="Time to wait before alert is sent on new message for " + "users in callsigns.", + ), +] + +admin_opts = [ + cfg.BoolOpt( + "web_enabled", + default=False, + help="Enable the Admin Web Interface", + ), + cfg.IPOpt( + "web_ip", + default="0.0.0.0", + help="The ip address to listen on", + ), + cfg.PortOpt( + "web_port", + default=8001, + help="The port to listen on", + ), + cfg.StrOpt( + "user", + default="admin", + help="The admin user for the admin web interface", + ), + cfg.StrOpt( + "password", + secret=True, + help="Admin interface password", + ), +] + +enabled_plugins_opts = [ + cfg.ListOpt( + "enabled_plugins", + default=[ + "aprsd.plugins.email.EmailPlugin", + "aprsd.plugins.fortune.FortunePlugin", + "aprsd.plugins.location.LocationPlugin", + "aprsd.plugins.ping.PingPlugin", + "aprsd.plugins.query.QueryPlugin", + "aprsd.plugins.time.TimePlugin", + "aprsd.plugins.weather.OWMWeatherPlugin", + "aprsd.plugins.version.VersionPlugin", + ], + help="Comma separated list of enabled plugins for APRSD." + "To enable installed external plugins add them here." + "The full python path to the class name must be used", + ), +] + + +def register_opts(config): + config.register_opts(aprsd_opts) + config.register_opts(enabled_plugins_opts) + config.register_group(admin_group) + config.register_opts(admin_opts, group=admin_group) + config.register_group(watch_list_group) + config.register_opts(watch_list_opts, group=watch_list_group) + + +def list_opts(): + return { + "DEFAULT": (aprsd_opts + enabled_plugins_opts), + admin_group.name: admin_opts, + watch_list_group.name: watch_list_opts, + } diff --git a/aprsd/conf/log.py b/aprsd/conf/log.py new file mode 100644 index 0000000..d48ae30 --- /dev/null +++ b/aprsd/conf/log.py @@ -0,0 +1,61 @@ +""" +The options for logging setup +""" +import logging + +from oslo_config import cfg + + +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]" +) + +logging_group = cfg.OptGroup( + name="logging", + title="Logging options", +) +logging_opts = [ + cfg.StrOpt( + "date_format", + default=DEFAULT_DATE_FORMAT, + help="Date format for log entries", + ), + cfg.BoolOpt( + "rich_logging", + default=True, + help="Enable Rich logging", + ), + cfg.StrOpt( + "logfile", + default=None, + help="File to log to", + ), + cfg.StrOpt( + "logformat", + default=DEFAULT_LOG_FORMAT, + help="Log file format, unless rich_logging enabled.", + ), +] + + +def register_opts(config): + config.register_group(logging_group) + config.register_opts(logging_opts, group=logging_group) + + +def list_opts(): + return { + logging_group.name: ( + logging_opts + ), + } diff --git a/aprsd/conf/opts.py b/aprsd/conf/opts.py new file mode 100644 index 0000000..70618d1 --- /dev/null +++ b/aprsd/conf/opts.py @@ -0,0 +1,80 @@ +# Copyright 2015 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +This is the single point of entry to generate the sample configuration +file for Nova. It collects all the necessary info from the other modules +in this package. It is assumed that: + +* every other module in this package has a 'list_opts' function which + return a dict where + * the keys are strings which are the group names + * the value of each key is a list of config options for that group +* the nova.conf package doesn't have further packages with config options +* this module is only used in the context of sample file generation +""" + +import collections +import importlib +import os +import pkgutil + + +LIST_OPTS_FUNC_NAME = "list_opts" + + +def _tupleize(dct): + """Take the dict of options and convert to the 2-tuple format.""" + return [(key, val) for key, val in dct.items()] + + +def list_opts(): + opts = collections.defaultdict(list) + module_names = _list_module_names() + imported_modules = _import_modules(module_names) + _append_config_options(imported_modules, opts) + return _tupleize(opts) + + +def _list_module_names(): + module_names = [] + package_path = os.path.dirname(os.path.abspath(__file__)) + for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]): + if modname == "opts" or ispkg: + continue + else: + module_names.append(modname) + return module_names + + +def _import_modules(module_names): + imported_modules = [] + for modname in module_names: + mod = importlib.import_module("aprsd.conf." + modname) + if not hasattr(mod, LIST_OPTS_FUNC_NAME): + msg = "The module 'aprsd.conf.%s' should have a '%s' "\ + "function which returns the config options." % \ + (modname, LIST_OPTS_FUNC_NAME) + raise Exception(msg) + else: + imported_modules.append(mod) + return imported_modules + + +def _append_config_options(imported_modules, config_options): + for mod in imported_modules: + configs = mod.list_opts() + for key, val in configs.items(): + config_options[key].extend(val) diff --git a/aprsd/conf/plugin_common.py b/aprsd/conf/plugin_common.py new file mode 100644 index 0000000..4d43f3e --- /dev/null +++ b/aprsd/conf/plugin_common.py @@ -0,0 +1,83 @@ +from oslo_config import cfg + + +aprsfi_group = cfg.OptGroup( + name="aprs_fi", + title="APRS.FI website settings", +) +query_group = cfg.OptGroup( + name="query_plugin", + title="Options for the Query Plugin", +) +avwx_group = cfg.OptGroup( + name="avwx_plugin", + title="Options for the AVWXWeatherPlugin", +) +owm_wx_group = cfg.OptGroup( + name="owm_weather_plugin", + title="Options for the OWMWeatherPlugin", +) + +aprsfi_opts = [ + cfg.StrOpt( + "apiKey", + help="Get the apiKey from your aprs.fi account here:" + "http://aprs.fi/account", + ), +] + +query_plugin_opts = [ + cfg.StrOpt( + "callsign", + help="The Ham callsign to allow access to the query plugin from RF.", + ), +] + +owm_wx_opts = [ + cfg.StrOpt( + "apiKey", + help="OWMWeatherPlugin api key to OpenWeatherMap's API." + "This plugin uses the openweathermap API to fetch" + "location and weather information." + "To use this plugin you need to get an openweathermap" + "account and apikey." + "https://home.openweathermap.org/api_keys", + ), +] + +avwx_opts = [ + cfg.StrOpt( + "apiKey", + help="avwx-api is an opensource project that has" + "a hosted service here: https://avwx.rest/" + "You can launch your own avwx-api in a container" + "by cloning the githug repo here:" + "https://github.com/avwx-rest/AVWX-API", + ), + cfg.StrOpt( + "base_url", + default="https://avwx.rest", + help="The base url for the avwx API. If you are hosting your own" + "Here is where you change the url to point to yours.", + ), +] + + +def register_opts(config): + config.register_group(aprsfi_group) + config.register_opts(aprsfi_opts, group=aprsfi_group) + config.register_group(query_group) + config.register_opts(query_plugin_opts, group=query_group) + config.register_group(owm_wx_group) + config.register_opts(owm_wx_opts, group=owm_wx_group) + config.register_group(avwx_group) + config.register_opts(avwx_opts, group=avwx_group) + + +def list_opts(): + return { + aprsfi_group.name: aprsfi_opts, + query_group.name: query_plugin_opts, + owm_wx_group.name: owm_wx_opts, + avwx_group.name: avwx_opts, + } diff --git a/aprsd/conf/plugin_email.py b/aprsd/conf/plugin_email.py new file mode 100644 index 0000000..2c60281 --- /dev/null +++ b/aprsd/conf/plugin_email.py @@ -0,0 +1,106 @@ +from oslo_config import cfg + + +email_group = cfg.OptGroup( + name="email_plugin", + title="Options for the APRSD Email plugin", +) + +email_opts = [ + cfg.StrOpt( + "callsign", + required=True, + help="(Required) Callsign to validate for doing email commands." + "Only this callsign can check email. This is also where the " + "email notifications for new emails will be sent.", + ), + cfg.BoolOpt( + "enabled", + default=False, + help="Enable the Email plugin?", + ), + cfg.BoolOpt( + "debug", + default=False, + help="Enable the Email plugin Debugging?", + ), +] + +email_imap_opts = [ + cfg.StrOpt( + "imap_login", + help="Login username/email for IMAP server", + ), + cfg.StrOpt( + "imap_password", + secret=True, + help="Login password for IMAP server", + ), + cfg.HostnameOpt( + "imap_host", + help="Hostname/IP of the IMAP server", + ), + cfg.PortOpt( + "imap_port", + default=993, + help="Port to use for IMAP server", + ), + cfg.BoolOpt( + "imap_use_ssl", + default=True, + help="Use SSL for connection to IMAP Server", + ), +] + +email_smtp_opts = [ + cfg.StrOpt( + "smtp_login", + help="Login username/email for SMTP server", + ), + cfg.StrOpt( + "smtp_password", + secret=True, + help="Login password for SMTP server", + ), + cfg.HostnameOpt( + "smtp_host", + help="Hostname/IP of the SMTP server", + ), + cfg.PortOpt( + "smtp_port", + default=465, + help="Port to use for SMTP server", + ), + cfg.BoolOpt( + "smtp_use_ssl", + default=True, + help="Use SSL for connection to SMTP Server", + ), +] + +email_shortcuts_opts = [ + cfg.ListOpt( + "email_shortcuts", + help="List of email shortcuts for checking/sending email " + "For Exmaple: wb=walt@walt.com,cl=cl@cl.com\n" + "Means use 'wb' to send an email to walt@walt.com", + ), +] + +ALL_OPTS = ( + email_opts + + email_imap_opts + + email_smtp_opts + + email_shortcuts_opts +) + + +def register_opts(config): + config.register_group(email_group) + config.register_opts(ALL_OPTS, group=email_group) + + +def list_opts(): + return { + email_group.name: ALL_OPTS, + }