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,
+    }