/* * HyBBX instance daemon — hybbxd (Main) / hybbxsd (Secondary) / hybbxpd (Proxy). * Role is linked via instance_role_*.c. Plugins: CMake HYBBX_PLUGIN_*. * Config: -c path/to/hybbx.ini */ #if defined(__linux__) #define _DEFAULT_SOURCE #endif #include "hybbx/hybbx.h" #include "hybbx/instance.h" #include "hybbx/service.h" #include "hybbx/registry.h" #include "hybbx/config.h" #include "hybbx/command.h" #include "hybbx/commands_registry.h" #include "hybbx/daemon_wrap.h" #include "hybbx/util.h" #include "hybbx/limits.h" #include "hybbx/instance.h" #include "hybbx/privilege.h" #include #include #include #include #include #include #ifdef HYBBX_HAVE_PLUGIN_TELNET extern const hybbx_transport_plugin_t hybbx_plugin_telnet; #endif #ifdef HYBBX_HAVE_PLUGIN_PACKET_RADIO extern const hybbx_transport_plugin_t hybbx_plugin_packet_radio; #endif #ifdef HYBBX_HAVE_PLUGIN_ARDOP extern const hybbx_transport_plugin_t hybbx_plugin_ardop; #endif #ifdef HYBBX_HAVE_PLUGIN_CRDOP extern const hybbx_transport_plugin_t hybbx_plugin_crdop; #endif #ifdef HYBBX_HAVE_PLUGIN_SSH extern const hybbx_transport_plugin_t hybbx_plugin_ssh; #endif #ifdef HYBBX_HAVE_PLUGIN_WEBSOCKET extern const hybbx_transport_plugin_t hybbx_plugin_websocket; #endif #ifdef HYBBX_HAVE_PLUGIN_BAYCOM extern const hybbx_transport_plugin_t hybbx_plugin_baycom; #endif #ifdef HYBBX_HAVE_PLUGIN_MAINS_PROXY extern const hybbx_transport_plugin_t hybbx_plugin_mains_proxy; #endif static void register_builtin_plugins(void) { /* Register every compiled plugin; instance/INI gating is at apply time. */ #ifdef HYBBX_HAVE_PLUGIN_TELNET hybbx_registry_register(&hybbx_plugin_telnet); #endif #ifdef HYBBX_HAVE_PLUGIN_PACKET_RADIO hybbx_registry_register(&hybbx_plugin_packet_radio); #endif #ifdef HYBBX_HAVE_PLUGIN_ARDOP hybbx_registry_register(&hybbx_plugin_ardop); #endif #ifdef HYBBX_HAVE_PLUGIN_CRDOP hybbx_registry_register(&hybbx_plugin_crdop); #endif #ifdef HYBBX_HAVE_PLUGIN_SSH hybbx_registry_register(&hybbx_plugin_ssh); #endif #ifdef HYBBX_HAVE_PLUGIN_WEBSOCKET hybbx_registry_register(&hybbx_plugin_websocket); #endif #ifdef HYBBX_HAVE_PLUGIN_BAYCOM hybbx_registry_register(&hybbx_plugin_baycom); #endif #ifdef HYBBX_HAVE_PLUGIN_MAINS_PROXY hybbx_registry_register(&hybbx_plugin_mains_proxy); #endif } static void print_usage(const char *prog) { fprintf(stderr, "HyBBX %s — %s instance (%s)\n" "\n" "Usage: %s [options]\n" "\n" "Options:\n" " -c, --config Load INI configuration file\n" " -f, --foreground Run in foreground (default)\n" " --screen [name] Run detached in GNU screen (default: %s)\n" " --tmux [name] Run detached in tmux (default: %s)\n" " --attach Attach to screen/tmux session (with --screen/--tmux)\n" " -h, --help Show this help\n" " -V, --version Show version\n" "\n" "Instances: hybbxd=Main hybbxsd=Secondary hybbxpd=Proxy\n" "\n", HYBBX_VERSION_STRING, hybbx_instance_role_name(), hybbx_instance_binary_name(), prog, hybbx_instance_binary_name(), hybbx_instance_binary_name()); } static void print_version(void) { printf("HyBBX %s — %s (%s)%s\n", HYBBX_VERSION_STRING, hybbx_instance_role_name(), hybbx_instance_binary_name(), hybbx_instance_offers_user_bbx() ? "" : " [no user BBX]"); } static void list_plugins_cb(const hybbx_transport_plugin_t *plugin, void *userdata) { (void)userdata; printf(" - %s (kind=%d, version=%u)\n", plugin->name, (int)plugin->kind, plugin->version); } static int path_is_readable(const char *path) { return path != NULL && path[0] != '\0' && access(path, R_OK) == 0; } /** * When -c is omitted: HYBBX_CONFIG, then /hybbx.ini (install layout). */ static const char *discover_config_path(char *buf, size_t buflen, const char *argv0) { const char *env; if (buf == NULL || buflen == 0) { return NULL; } env = getenv("HYBBX_CONFIG"); if (path_is_readable(env)) { snprintf(buf, buflen, "%s", env); return buf; } if (argv0 == NULL || argv0[0] == '\0') { return NULL; } #if defined(__linux__) { char exe[HYBBX_PATH_MAX]; char etc[HYBBX_PATH_MAX]; ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); if (n > 0) { char *slash; size_t dir_len; exe[n] = '\0'; slash = strrchr(exe, '/'); if (slash != NULL) { dir_len = (size_t)(slash - exe); if (dir_len + 1 < sizeof(etc)) { memcpy(etc, exe, dir_len); etc[dir_len] = '\0'; if (hybbx_path_join(buf, buflen, etc, HYBBX_FILE_CONFIG) == HYBBX_OK && path_is_readable(buf)) { return buf; } } } } } #endif snprintf(buf, buflen, "%s", argv0); { char path_copy[HYBBX_PATH_MAX]; char candidate[HYBBX_PATH_MAX]; char *dir; snprintf(path_copy, sizeof(path_copy), "%s", argv0); dir = dirname(path_copy); if (hybbx_path_join(candidate, sizeof(candidate), dir, HYBBX_FILE_CONFIG) == HYBBX_OK && path_is_readable(candidate)) { snprintf(buf, buflen, "%s", candidate); return buf; } } return NULL; } static void setup_install_root(const char *config_path) { const char *env; env = getenv(HYBBX_ENV_ROOT); if (env != NULL && env[0] != '\0') { hybbx_install_root_set(env); return; } if (config_path != NULL && config_path[0] != '\0') { char dir[HYBBX_PATH_MAX]; if (hybbx_path_dirname(config_path, dir, sizeof(dir)) == HYBBX_OK) { hybbx_install_root_set(dir); } } } /* ── Signal handling ────────────────────────────────────────── */ static volatile sig_atomic_t g_signal_received; static void signal_handler(int sig) { g_signal_received = sig; /* SIGPIPE: just ignore — send() with MSG_NOSIGNAL handles most cases */ if (sig == SIGPIPE) { return; } /* SIGTERM/SIGINT: main loop will see g_signal_received and exit */ } static void install_signal_handlers(void) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); /* SIGPIPE: ignore — prevents silent death on broken socket writes */ sa.sa_handler = SIG_IGN; sa.sa_flags = 0; sigaction(SIGPIPE, &sa, NULL); /* SIGTERM: graceful shutdown */ sa.sa_handler = signal_handler; sigaction(SIGTERM, &sa, NULL); /* SIGINT: graceful shutdown (Ctrl+C) */ sigaction(SIGINT, &sa, NULL); g_signal_received = 0; } int main(int argc, char *argv[]) { hybbx_service_t *service; hybbx_config_t config; const char *config_path = NULL; char discovered_config[HYBBX_PATH_MAX]; hybbx_daemon_launch_opts_t launch; int have_config = 0; int i; hybbx_result_t rc; hybbx_daemon_launch_opts_defaults(&launch); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { print_usage(argv[0]); return EXIT_SUCCESS; } if (strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) { print_version(); return EXIT_SUCCESS; } if (strcmp(argv[i], "-f") == 0 || strcmp(argv[i], "--foreground") == 0) { launch.foreground = 1; continue; } if (strcmp(argv[i], "--attach") == 0) { launch.attach = 1; continue; } if (strcmp(argv[i], "--screen") == 0) { launch.use_screen = 1; if (i + 1 < argc && argv[i + 1][0] != '-' && strcmp(argv[i + 1], "-c") != 0 && strcmp(argv[i + 1], "--config") != 0) { hybbx_strlcpy(launch.session, argv[++i], sizeof(launch.session)); } continue; } if (strcmp(argv[i], "--tmux") == 0) { launch.use_tmux = 1; if (i + 1 < argc && argv[i + 1][0] != '-' && strcmp(argv[i + 1], "-c") != 0 && strcmp(argv[i + 1], "--config") != 0) { hybbx_strlcpy(launch.session, argv[++i], sizeof(launch.session)); } continue; } if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config") == 0) { if (i + 1 >= argc) { fprintf(stderr, "Missing argument for %s\n", argv[i]); return EXIT_FAILURE; } config_path = argv[++i]; continue; } fprintf(stderr, "Unknown option: %s\n", argv[i]); print_usage(argv[0]); return EXIT_FAILURE; } rc = hybbx_daemon_apply_launch_opts(&launch, argv[0], argc, argv); if (rc != HYBBX_OK) { return EXIT_FAILURE; } if (config_path == NULL) { config_path = discover_config_path(discovered_config, sizeof(discovered_config), argv[0]); } register_builtin_plugins(); printf("HyBBX %s starting (%s / %s)\n", HYBBX_VERSION_STRING, hybbx_instance_binary_name(), hybbx_instance_role_name()); service = hybbx_service_create(NULL); if (service == NULL) { fprintf(stderr, "Failed to create service (out of memory?)\n"); return EXIT_FAILURE; } if (config_path != NULL) { rc = hybbx_config_load(&config, config_path); if (rc != HYBBX_OK) { fprintf(stderr, "Failed to load config '%s'\n", config_path); hybbx_service_destroy(service); return EXIT_FAILURE; } have_config = 1; printf("Loaded configuration: %s\n", config_path); rc = hybbx_privilege_apply_from_config(&config); if (rc != HYBBX_OK) { fprintf(stderr, "Privilege setup failed — %s\n", hybbx_result_name(rc)); hybbx_config_free(&config); hybbx_service_destroy(service); return EXIT_FAILURE; } setup_install_root(config_path); rc = hybbx_commands_registry_init(); if (rc != HYBBX_OK) { fprintf(stderr, "Failed to load command registry\n"); hybbx_config_free(&config); hybbx_service_destroy(service); return EXIT_FAILURE; } rc = hybbx_service_apply_config(service, &config, config_path); if (rc != HYBBX_OK) { fprintf(stderr, "Failed to apply configuration (storage path writable? " "telnet/circuit bind ok?) — %s\n", hybbx_result_name(rc)); hybbx_commands_registry_shutdown(); hybbx_config_free(&config); hybbx_service_destroy(service); return EXIT_FAILURE; } printf("Available transport plugins:\n"); hybbx_registry_foreach(list_plugins_cb, NULL); } else { fprintf(stderr, "No configuration loaded. Use -c , set HYBBX_CONFIG, " "or run hybbx-start.\n"); fprintf(stderr, "No transports will listen without a config file.\n"); } hybbx_service_set_launch_binary(service, argv[0]); install_signal_handlers(); printf("HyBBX %s running (%s). Signals: SIGPIPE=ignored, SIGTERM/SIGINT=graceful stop.\n", HYBBX_VERSION_STRING, hybbx_instance_binary_name()); hybbx_service_run(service); if (g_signal_received) { printf("HyBBX: received signal %d (%s) — shutting down.\n", g_signal_received, g_signal_received == SIGTERM ? "SIGTERM" : g_signal_received == SIGINT ? "SIGINT" : "unknown"); } else { printf("HyBBX: service loop exited normally.\n"); } if (hybbx_service_shutdown_mode(service) == HYBBX_SHUTDOWN_RESTART) { hybbx_service_restart_exec(service); } if (have_config) { hybbx_config_free(&config); } hybbx_commands_registry_shutdown(); hybbx_service_destroy(service); printf("HyBBX: shutdown complete (exit %d, signal=%d).\n", EXIT_SUCCESS, (int)g_signal_received); return EXIT_SUCCESS; }