#include "args.hh" #include "bt.hh" #include "cfg.hh" #include "config.h" #include "http.hh" #include "logger.hh" #include "looper.hh" #include "server.hh" #include "signals.hh" #include "websocket.hh" #include #include #include #include #include #include #include #include #include #include #include #include #ifndef VERSION # define VERSION "unknown" #endif namespace { bool run(logger::Logger& logger, cfg::Config const& cfg, std::unique_ptr port) { auto looper = looper::create(); auto signaler = server::create_signaler(logger, cfg, *looper); auto bt_delegate = server::create_bt_delegate(logger, *signaler); auto http_delegate = server::create_http_delegate(*bt_delegate, *signaler); auto server = http::create_server(logger, cfg, *looper, std::move(port), *http_delegate); auto manager = bt::create_manager(logger, cfg, *looper, *bt_delegate); auto sigint_handler = signals::Handler::create( *looper, signals::Signal::INT, [&looper, &logger]() { logger.info("Received SIGINT, quitting..."); looper->quit(); }); auto sigterm_handler = signals::Handler::create( *looper, signals::Signal::TERM, [&looper, &logger]() { logger.info("Received SIGTERM, quitting..."); looper->quit(); }); return looper->run(logger); } bool start_daemon(logger::Logger& logger, cfg::Config const& cfg, std::unique_ptr port, bool verbose) { auto pid = fork(); if (pid == 0) { // Child process // Move out of current workind directory to not keep it alive chdir("/"); // Create a new process group setpgid(0, 0); // Close stdin, stdout and stderr close(0); close(1); close(2); // Use syslog logger auto logger2 = logger::syslog("bluetooth-jukebox", verbose); _exit(run(*logger2, cfg, std::move(port)) ? 0 : 1); } else if (pid == -1) { // Error logger.err(std::format("Unable to fork: {}", strerror(errno))); return false; } else { // Parent process logger.info(std::format("Forked into pid {}", pid)); return true; } } } // namespace int main(int argc, char** argv) { auto args = Args::create(); auto opt_help = args->option('h', "help", "display this text and exit."); auto opt_version = args->option('V', "version", "display version and exit."); auto opt_config = args->option_argument( 'C', "config", "FILE", "load config from FILE instead of default."); auto opt_verbose = args->option('v', "verbose", "be more verbose."); auto opt_daemon = args->option('d', "daemon", "fork into background."); if (!args->run(argc, argv)) { args->print_error(std::cerr); std::cerr << "Try 'bluetooth-jukebox --help' for more information.\n"; return 1; } if (opt_help->is_set()) { std::cout << "Usage: bluetooth-jukebox [OPTION...]\n" << "\n"; args->print_help(std::cout); return 0; } if (opt_version->is_set()) { std::cout << "bluetooth-jukebox " << VERSION << " written by Joel Klinghed .\n"; return 0; } bool verbose = opt_verbose->is_set(); bool daemon = opt_daemon->is_set(); std::unique_ptr cfg; std::vector errors; if (opt_config->is_set()) { cfg = cfg::load_one(opt_config->argument(), errors); } else { cfg = cfg::load_all("bluetooth-jukebox", errors); } auto logger = logger::stderr(verbose); // Open the port here, in case we fork() later, to fail early. auto port = http::open_port(cfg->get("http.bind").value_or("localhost:5555"), *logger); if (!port) return 1; if (daemon) return start_daemon(*logger, *cfg, std::move(port), verbose) ? 0 : 1; return run(*logger, *cfg, std::move(port)) ? 0 : 1; }