Package meshtasticd for Windows as an MSI (#11289)
* Package meshtasticd for Windows as an MSI Adds a --service flag connecting meshtasticd to the Service Control Manager, a WiX MSI installing it as an auto-start LocalSystem service with config in %ProgramData%\Meshtastic, and a CI step attaching the MSI to releases. * Address review comments Bind workflow expressions to env vars in run: bodies, and build the service status per call with an atomic checkpoint. * Fix service stop state and CI lint Latch the stop under a mutex so a startup report cannot walk the state back. Ignore the new workflows in semgrep and checkov, as main_matrix already is. * Drop the checkov ignore for the winget workflow Resolve the newest release inside the job instead of taking workflow_dispatch inputs, so CKV_GHA_7 no longer fires and checkov stays active on the file. * Carry the MSI architecture into the winget manifest Parse it from the asset name instead of defaulting to x64, and fail on a multi-arch release rather than validating one at random. * Restore release/.gitignore * Leave the main matrix alone Release attachment moves to the matrix rework in #11151. The MSI is still built and uploaded as a CI artifact. --------- Co-authored-by: Austin <vidplace7@gmail.com>
This commit is contained in:
co-authored by
Austin
parent
02ea5baad4
commit
ecd59e3120
10 files changed
+615
No files matched your search
@@ -0,0 +1,119 @@
|
||||
#include "WindowsService.h"
|
||||
|
||||
#if defined(ARCH_PORTDUINO) && defined(_WIN32)
|
||||
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
// SERVICE_WIN32_OWN_PROCESS ignores the name, but the SCM still wants a non-null entry.
|
||||
static const wchar_t *serviceName = L"meshtasticd";
|
||||
|
||||
// Re-reported while setup() runs and again while the shutdown path saves state. The SCM
|
||||
// only enforces it against the checkpoint counter, so a generous hint costs nothing.
|
||||
static const DWORD PENDING_WAIT_HINT_MS = 15000;
|
||||
|
||||
// The SCM calls the control handler on its own thread, so a stop landing during the
|
||||
// START_PENDING poll races the startup reports. statusMutex orders them.
|
||||
static std::atomic<SERVICE_STATUS_HANDLE> statusHandle{nullptr};
|
||||
static std::atomic<bool> stopping{false};
|
||||
static std::mutex statusMutex;
|
||||
static DWORD checkPoint = 1;
|
||||
static HANDLE readyEvent = nullptr;
|
||||
|
||||
static void reportStatus(DWORD state, DWORD waitHintMs)
|
||||
{
|
||||
SERVICE_STATUS_HANDLE handle = statusHandle.load();
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(statusMutex);
|
||||
// Latch the stop so a startup report queued behind it cannot walk the state back.
|
||||
if (state == SERVICE_STOP_PENDING || state == SERVICE_STOPPED)
|
||||
stopping.store(true);
|
||||
else if (stopping.load())
|
||||
return;
|
||||
|
||||
SERVICE_STATUS status = {};
|
||||
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
status.dwCurrentState = state;
|
||||
status.dwControlsAccepted = (state == SERVICE_RUNNING) ? (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN) : 0;
|
||||
status.dwWin32ExitCode = NO_ERROR;
|
||||
status.dwServiceSpecificExitCode = 0;
|
||||
status.dwWaitHint = waitHintMs;
|
||||
// A stale checkpoint on a settled state makes the SCM think the transition hung.
|
||||
status.dwCheckPoint = (state == SERVICE_RUNNING || state == SERVICE_STOPPED) ? 0 : checkPoint++;
|
||||
SetServiceStatus(handle, &status);
|
||||
}
|
||||
|
||||
static void reportStopped()
|
||||
{
|
||||
reportStatus(SERVICE_STOPPED, 0);
|
||||
}
|
||||
|
||||
static DWORD WINAPI controlHandler(DWORD control, DWORD, LPVOID, LPVOID)
|
||||
{
|
||||
switch (control) {
|
||||
case SERVICE_CONTROL_STOP:
|
||||
case SERVICE_CONTROL_SHUTDOWN:
|
||||
reportStatus(SERVICE_STOP_PENDING, PENDING_WAIT_HINT_MS);
|
||||
// Teardown belongs on the main thread: powerCommandsCheck() saves and exits, and the
|
||||
// atexit hook reports SERVICE_STOPPED on the way out.
|
||||
shutdownAtMsec = millis();
|
||||
return NO_ERROR;
|
||||
case SERVICE_CONTROL_INTERROGATE:
|
||||
return NO_ERROR;
|
||||
default:
|
||||
return ERROR_CALL_NOT_IMPLEMENTED;
|
||||
}
|
||||
}
|
||||
|
||||
static void WINAPI serviceMain(DWORD, LPWSTR *)
|
||||
{
|
||||
statusHandle.store(RegisterServiceCtrlHandlerExW(serviceName, controlHandler, nullptr));
|
||||
if (!statusHandle.load())
|
||||
return;
|
||||
|
||||
// A cold node DB can push setup() past the SCM's 30 s start timeout, so keep the
|
||||
// transition alive with a fresh checkpoint until setup() signals ready.
|
||||
reportStatus(SERVICE_START_PENDING, PENDING_WAIT_HINT_MS);
|
||||
while (!stopping.load() && WaitForSingleObject(readyEvent, 5000) == WAIT_TIMEOUT)
|
||||
reportStatus(SERVICE_START_PENDING, PENDING_WAIT_HINT_MS);
|
||||
reportStatus(SERVICE_RUNNING, 0);
|
||||
|
||||
// Returning would tell the SCM the service stopped while the node is still running.
|
||||
// The process ends from exit() on the main thread instead.
|
||||
Sleep(INFINITE);
|
||||
}
|
||||
|
||||
void windowsServiceInit()
|
||||
{
|
||||
readyEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (!readyEvent) {
|
||||
LOG_ERROR("Service: CreateEvent failed (%lu), continuing in the foreground", GetLastError());
|
||||
return;
|
||||
}
|
||||
atexit(reportStopped);
|
||||
|
||||
std::thread([] {
|
||||
SERVICE_TABLE_ENTRYW table[] = {{const_cast<LPWSTR>(serviceName), serviceMain}, {nullptr, nullptr}};
|
||||
// Fails immediately with ERROR_FAILED_SERVICE_CONTROLLER_CONNECT when --service was
|
||||
// typed at a console instead of coming from the SCM. Not fatal: the node runs anyway.
|
||||
if (!StartServiceCtrlDispatcherW(table))
|
||||
LOG_WARN("Service: not started by the SCM (%lu), running in the foreground", GetLastError());
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void windowsServiceReportRunning()
|
||||
{
|
||||
if (readyEvent)
|
||||
SetEvent(readyEvent);
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user