// veloxd — the Velox download-manager daemon. // // Wires up both RPC transports (Unix socket + loopback WebSocket), the SQLite store, // and a dispatcher skeleton so the CLI and GUI have a real server to speak to // (AGENT-DAEMON.md build order, steps 1 and 3). The scheduler and the engine link land // next. #include #include #include #include #include #include #include #include #include "rpc/dispatcher.hpp" #include "rpc/event_loop.hpp" #include "rpc/pairing.hpp" #include "rpc/runtime_dir.hpp" #include "rpc/uds_server.hpp" #include "rpc/ws_server.hpp" #include "store/migrations.hpp" #include "store/sqlite.hpp" #include "version.hpp" namespace { velox::daemon::rpc::EventLoop* g_loop = nullptr; void on_signal(int) { if (g_loop != nullptr) g_loop->stop(); // stop() is async-signal-safe (writes an eventfd) } // Single-instance guard: bind an abstract-namespace Unix socket whose name is unique to // this user. A second daemon gets EADDRINUSE and exits. The kernel reclaims an // abstract-namespace address when the holding process dies, so a crash never wedges it // (docs/01 §2). Returns the held fd (kept open for the process lifetime) or -1. int acquire_single_instance_lock() { const int fd = ::socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) return -1; const std::string name = std::string("velox-daemon-") + std::to_string(::geteuid()); sockaddr_un addr{}; addr.sun_family = AF_UNIX; // Leading NUL selects the abstract namespace; the name follows, not NUL-terminated. addr.sun_path[0] = '\0'; std::memcpy(addr.sun_path + 1, name.c_str(), name.size()); const socklen_t len = static_cast(offsetof(sockaddr_un, sun_path) + 1 + name.size()); if (::bind(fd, reinterpret_cast(&addr), len) != 0) { ::close(fd); return -1; } return fd; } } // namespace int main() { std::cout << "veloxd " << velox::daemon::kDaemonVersion << " (protocol " << velox::proto::kProtocolVersion << ")\n"; const int lock_fd = acquire_single_instance_lock(); if (lock_fd < 0) { std::cerr << "veloxd: another instance is already running for this user\n"; return 1; } velox::daemon::rpc::RuntimeDir rt; if (const auto ec = velox::daemon::rpc::resolve_runtime_dir(rt)) { std::cerr << "veloxd: cannot prepare runtime directory: " << ec.message() << "\n"; return 1; } velox::daemon::rpc::EventLoop loop; g_loop = &loop; struct sigaction sa{}; sa.sa_handler = on_signal; ::sigemptyset(&sa.sa_mask); ::sigaction(SIGINT, &sa, nullptr); ::sigaction(SIGTERM, &sa, nullptr); ::signal(SIGPIPE, SIG_IGN); // a client vanishing mid-write is EPIPE, never a signal std::string data_dir; if (const auto ec = velox::daemon::rpc::resolve_data_dir(data_dir)) { std::cerr << "veloxd: cannot prepare data directory: " << ec.message() << "\n"; return 1; } auto db = velox::daemon::store::Db::open(data_dir + "/velox.db"); if (!db) { std::cerr << "veloxd: cannot open " << data_dir << "/velox.db: " << db.error().to_string() << "\n"; return 1; } if (const auto m = velox::daemon::store::migrate_to_head(*db); !m) { std::cerr << "veloxd: schema migration failed: " << m.error().to_string() << "\n"; return 1; } velox::daemon::rpc::VeloxDispatcher dispatcher; velox::daemon::rpc::UdsServer uds(loop, dispatcher, rt.socket_path()); if (const auto ec = uds.start()) { std::cerr << "veloxd: cannot listen on " << rt.socket_path() << ": " << ec.message() << "\n"; return 1; } std::cout << "veloxd: listening on " << uds.socket_path() << "\n"; // The WebSocket transport is the extension's fallback (docs/05 §4); the Unix socket is // the primary. If every port in 52000-52016 is taken, log it and carry on rather than // refusing to start — capture must fail open, and the GUI/CLI still have the socket. // TODO(build step 7): replace EnvAutoApprover with a GUI-dialog / desktop-notification // approver. Until then pairing needs VELOX_PAIR_AUTO=1. velox::daemon::rpc::EnvAutoApprover approver; velox::daemon::rpc::WsServer ws(loop, dispatcher, *db, approver, rt); if (const auto ec = ws.start()) { std::cerr << "veloxd: WebSocket transport unavailable (" << ec.message() << "); the extension fallback will not work this run\n"; } else { std::cout << "veloxd: WebSocket transport on 127.0.0.1:" << ws.port() << "\n"; } loop.run(); std::cout << "veloxd: shutting down\n"; g_loop = nullptr; ::close(lock_fd); return 0; }