Files
David Garcia b36ab704ff Server: reliable output flush, DB mutex + upsert, persistent log mirror
Fixes intermittent "command completes with no terminal output" and
"session table shows Unknown/N/A" bugs, plus adds a couple of
diagnostic knobs so future issues are easier to root-cause.

- sendTo() returns bool and now calls sock->flush() after write, so the
  Qt write buffer never sits on stdout under event-loop starvation. All
  broadcast call sites migrated to broadcastToSession(), which prunes
  dead sockets on write failure.
- SessionDBManager: updateSessionUser / updateSessionTenant /
  setSessionAlive / setSessionStatus wrap their SQL in
  QMutexLocker(&m_mutex) to fix intermittent SQLITE_BUSY under
  concurrent writes.
- addSessionToMainDB uses INSERT ... ON CONFLICT DO UPDATE SET so
  re-adding a session preserves CreatedBy instead of clobbering it.
- setSessionAlive() / setSessionStatus() wired into the session
  lifecycle (ensureProcess, session_exited, LOGIN_OK/LOGIN_FAIL) so the
  Alive/Status columns reflect real state.
- Nested-mutex audit: removed a nested QMutexLocker(&g_stateMutex)
  inside readyReadStandardOutput's pending-reinject block. Qt's QMutex
  is non-recursive by default; the inner lock was the "run one command
  and that's it" deadlock.
- Pre-login output allowlist filter (microsoft.com/devicelogin,
  [Animo], WARNING, ...) so the terminal no longer shows pwsh's stdin
  echo fragments during login.
- update_session_meta RPC handler so a client can heal a stale DB row
  it detected on its side.
- server/main.cpp installs a QMutex-guarded qInstallMessageHandler that
  mirrors qInfo/qWarning to /tmp/animo-srv-dbg.log across restarts
  (see DEBUGGING in the repo docs).
2026-07-28 13:16:55 -06:00

86 lines
2.8 KiB
C++
Executable File

#include <QCoreApplication>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QMutex>
#include "Server.h"
namespace {
// Mirror every qDebug/qInfo/qWarning to /tmp/animo-srv-dbg.log so terminal
// scrollback loss can't hide the diagnostic lines we need for triage.
QFile *g_dbgLog = nullptr;
QMutex g_dbgLogMutex;
void animoServerMessageHandler(QtMsgType type, const QMessageLogContext &, const QString &msg) {
const char *lvl = "?";
switch (type) {
case QtDebugMsg: lvl = "D"; break;
case QtInfoMsg: lvl = "I"; break;
case QtWarningMsg: lvl = "W"; break;
case QtCriticalMsg: lvl = "C"; break;
case QtFatalMsg: lvl = "F"; break;
}
const QString line = QString("[%1] [%2] %3\n")
.arg(QDateTime::currentDateTime().toString(Qt::ISODateWithMs), lvl, msg);
// Terminal
fprintf(stderr, "%s", qUtf8Printable(line));
fflush(stderr);
// File
QMutexLocker lk(&g_dbgLogMutex);
if (g_dbgLog && g_dbgLog->isOpen()) {
g_dbgLog->write(line.toUtf8());
g_dbgLog->flush();
}
}
} // namespace
int main(int argc, char *argv[]) {
// Install log-to-file handler before anything else so we catch startup output.
g_dbgLog = new QFile("/tmp/animo-srv-dbg.log");
g_dbgLog->open(QIODevice::WriteOnly | QIODevice::Truncate);
qInstallMessageHandler(animoServerMessageHandler);
QCoreApplication app(argc, argv);
app.setApplicationName("ANIMOServer");
app.setApplicationVersion("1.0");
// Setup command line parser
QCommandLineParser parser;
parser.setApplicationDescription("ANIMO Server");
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption ipOption(QStringList() << "i" << "ip",
"IP address to bind (default: 0.0.0.0)", "ip", "0.0.0.0");
QCommandLineOption portOption(QStringList() << "p" << "port",
"Port to bind (default: 7777)", "port", "7777");
QCommandLineOption passwordOption(QStringList() << "P" << "password",
"Password required for clients to connect (default: empty)", "password", "");
parser.addOption(ipOption);
parser.addOption(portOption);
parser.addOption(passwordOption);
parser.process(app);
QString ip = parser.value(ipOption);
quint16 port = parser.value(portOption).toUShort();
QString password = parser.value(passwordOption);
qDebug() << "Starting ANIMOServer on" << ip << ":" << port;
if (!password.isEmpty())
qDebug() << "Password required for clients.";
// Only password matters, user can be anything
Server server(ip, port, QString(), password);
if (!server.start()) {
qCritical() << "Failed to start server.";
return 1;
}
return app.exec();
}