Some checks failed
Build / Test on ubuntu-latest (push) Successful in 3m6s
Lint / lint (push) Successful in 22s
Build / typecheck (ubuntu-latest, 3.9) (push) Successful in 1m49s
Lint / format (push) Successful in 42s
Build / Test on macOS-latest (push) Has been cancelled
Build / Test on windows-latest (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1m31s
177 lines
5.6 KiB
Python
177 lines
5.6 KiB
Python
import logging
|
|
import sys
|
|
|
|
from aw_core.log import setup_logging
|
|
from aw_datastore import get_storage_methods
|
|
|
|
from . import __version__
|
|
from .config import config_section, default_port, load_config
|
|
from .profile import (
|
|
DEFAULT_PROFILE,
|
|
export_profile,
|
|
is_testing,
|
|
resolve_profile,
|
|
)
|
|
from .server import _start
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def main():
|
|
"""Called from the executable and __main__.py"""
|
|
|
|
settings, storage_method = parse_settings()
|
|
|
|
# FIXME: The LogResource API endpoint relies on the log being in JSON format
|
|
# at the path specified by aw_core.log.get_log_file_path(). We probably want
|
|
# to write the LogResource API so that it does not depend on any physical file
|
|
# but instead add a logging handler that it can use privately.
|
|
# That is why log_file_json=True currently.
|
|
# UPDATE: The LogResource API is no longer available so log_file_json is now False.
|
|
setup_logging(
|
|
"aw-server",
|
|
testing=settings.testing,
|
|
verbose=settings.verbose,
|
|
log_stderr=True,
|
|
log_file=True,
|
|
)
|
|
|
|
logger.info(f"Using storage method: {settings.storage}")
|
|
|
|
if settings.testing:
|
|
logger.info("Will run in testing mode")
|
|
|
|
if settings.profile != DEFAULT_PROFILE:
|
|
logger.info(f"Running with profile: {settings.profile}")
|
|
|
|
if settings.custom_static:
|
|
logger.info(f"Using custom_static: {settings.custom_static}")
|
|
|
|
logger.info("Starting up...")
|
|
_start(
|
|
host=settings.host,
|
|
port=settings.port,
|
|
testing=settings.testing,
|
|
storage_method=storage_method,
|
|
cors_origins=settings.cors_origins,
|
|
custom_static=settings.custom_static,
|
|
)
|
|
|
|
|
|
def parse_settings():
|
|
import argparse
|
|
|
|
""" CLI Arguments """
|
|
parser = argparse.ArgumentParser(description="Starts an ActivityWatch server")
|
|
parser.add_argument(
|
|
"--testing",
|
|
action="store_true",
|
|
help="Run aw-server in testing mode using different ports and database (alias for --profile testing)",
|
|
)
|
|
parser.add_argument(
|
|
"--profile",
|
|
dest="profile",
|
|
default=None,
|
|
help="Named instance profile (data, config, port and settings are isolated). --testing is an alias for --profile testing.",
|
|
)
|
|
parser.add_argument("--verbose", action="store_true", help="Be chatty.")
|
|
parser.add_argument(
|
|
"--version",
|
|
action="store_true",
|
|
help="Print version and quit",
|
|
)
|
|
parser.add_argument(
|
|
"--log-json", action="store_true", help="Output the logs in JSON format"
|
|
)
|
|
parser.add_argument(
|
|
"--host", dest="host", help="Which host address to bind the server to"
|
|
)
|
|
parser.add_argument(
|
|
"--port", dest="port", type=int, help="Which port to run the server on"
|
|
)
|
|
parser.add_argument(
|
|
"--storage",
|
|
dest="storage",
|
|
help="The method to use for storing data. Some methods (such as MongoDB) require specific Python packages to be available (in the MongoDB case: pymongo)",
|
|
)
|
|
parser.add_argument(
|
|
"--cors-origins",
|
|
dest="cors_origins",
|
|
help="CORS origins to allow (as a comma separated list)",
|
|
)
|
|
parser.add_argument(
|
|
"--custom-static",
|
|
dest="custom_static",
|
|
help="The custom static directories. Format: watcher_name=path,watcher_name2=path2,...",
|
|
)
|
|
args = parser.parse_args()
|
|
if args.version:
|
|
print(__version__)
|
|
sys.exit(0)
|
|
|
|
try:
|
|
profile = resolve_profile(args.profile, args.testing)
|
|
except ValueError as e:
|
|
parser.error(str(e))
|
|
# Export before loading config so aw-core dirs isolate this profile.
|
|
export_profile(profile)
|
|
testing = is_testing(profile)
|
|
|
|
""" Parse config file """
|
|
config = load_config()
|
|
section = config_section(profile)
|
|
if section not in config:
|
|
if profile not in (DEFAULT_PROFILE,):
|
|
logger.warning(
|
|
"Profile %s has no [%s] section, falling back to [server] "
|
|
"(port %s may collide with the default instance)",
|
|
profile,
|
|
section,
|
|
default_port(profile),
|
|
)
|
|
section = "server"
|
|
settings = argparse.Namespace()
|
|
settings.host = config[section]["host"]
|
|
settings.port = int(config[section]["port"])
|
|
settings.storage = config[section]["storage"]
|
|
settings.cors_origins = config[section]["cors_origins"]
|
|
settings.custom_static = dict(config[section]["custom_static"])
|
|
settings.profile = profile
|
|
settings.testing = testing
|
|
|
|
""" If a argument is not none, override the config value """
|
|
for key, value in vars(args).items():
|
|
if key in ("testing", "profile"):
|
|
# Resolved above; --profile testing must keep testing=True
|
|
# even when the raw --testing flag was absent.
|
|
continue
|
|
if value is not None:
|
|
if key == "custom_static":
|
|
settings.custom_static = parse_str_to_dict(value)
|
|
else:
|
|
vars(settings)[key] = value
|
|
|
|
settings.cors_origins = [o for o in settings.cors_origins.split(",") if o]
|
|
|
|
storage_methods = get_storage_methods()
|
|
storage_method = storage_methods[settings.storage]
|
|
|
|
return settings, storage_method
|
|
|
|
|
|
def parse_str_to_dict(str_value):
|
|
"""Parses a dict from a string in format: key=value,key2=value2,..."""
|
|
output = dict()
|
|
key_value_pairs = str_value.split(",")
|
|
|
|
for pair in key_value_pairs:
|
|
pair_split = pair.split("=")
|
|
|
|
if len(pair_split) != 2:
|
|
raise ValueError(f"Cannot parse key value pair: {pair}")
|
|
|
|
key, value = pair_split
|
|
output[key] = value
|
|
|
|
return output
|