Browse Source

Claim on Windows (#18410)

thiagoftsm 6 months ago
parent
commit
f4531bc522

+ 25 - 0
CMakeLists.txt

@@ -118,6 +118,10 @@ elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "CYGWIN" OR "${CMAKE_SYSTEM_NAME}" STREQU
         endif()
 
         set(BINDIR usr/bin)
+        set(CMAKE_RC_COMPILER_INIT windres)
+        ENABLE_LANGUAGE(RC)
+
+        SET(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
         add_definitions(-D_GNU_SOURCE)
 
         if($ENV{CLION_IDE})
@@ -1296,6 +1300,13 @@ set(CLAIM_PLUGIN_FILES
         src/claim/cloud-status.h
 )
 
+set(CLAIM_WINDOWS_FILES
+        src/claim/netdata_claim.c
+        src/claim/netdata_claim.h
+        src/claim/netdata_claim_window.c
+        src/claim/netdata_claim_window.h
+)
+
 set(ACLK_ALWAYS_BUILD
         src/aclk/aclk_rrdhost_state.h
         src/aclk/aclk_proxy.c
@@ -2238,6 +2249,13 @@ add_executable(netdata
         "$<$<BOOL:${ENABLE_EXPORTER_PROMETHEUS_REMOTE_WRITE}>:${PROMETHEUS_REMOTE_WRITE_EXPORTING_FILES}>"
 )
 
+if(OS_WINDOWS)
+        set(NETDATA_CLAIM_RES_FILES "packaging/windows/resources/netdata_claim.rc")
+
+        add_executable(netdata_claim ${CLAIM_WINDOWS_FILES} ${NETDATA_CLAIM_RES_FILES})
+        target_link_libraries(netdata_claim shell32;gdi32;msftedit)
+endif()
+
 target_compile_definitions(netdata PRIVATE
         "$<$<BOOL:${ENABLE_ML}>:DLIB_NO_GUI_SUPPORT>"
 )
@@ -2426,6 +2444,13 @@ install(PROGRAMS
         COMPONENT netdata
         DESTINATION "${BINDIR}")
 
+if(OS_WINDOWS)
+        install(PROGRAMS
+                ${CMAKE_BINARY_DIR}/netdata_claim.exe
+                COMPONENT netdata
+                DESTINATION "${BINDIR}")
+endif()
+
 #
 # We don't check ENABLE_PLUGIN_CGROUP_NETWORK because rpm builds assume
 # the files exists unconditionally.

+ 66 - 29
packaging/windows/installer.nsi

@@ -34,8 +34,14 @@ var startMsys
 
 var hCloudToken
 var cloudToken
-var hCloudRoom
-var cloudRoom
+var hCloudRooms
+var cloudRooms
+var hProxy
+var proxy
+var hInsecure
+var insecure
+
+var avoidClaim
 
 Function .onInit
         nsExec::ExecToLog '$SYSDIR\sc.exe stop Netdata'
@@ -46,6 +52,8 @@ Function .onInit
         ${EndIf}
 
         StrCpy $startMsys ${BST_UNCHECKED}
+        StrCpy $insecure ${BST_UNCHECKED}
+        StrCpy $avoidClaim ${BST_UNCHECKED}
 FunctionEnd
 
 Function NetdataConfigPage
@@ -57,40 +65,76 @@ Function NetdataConfigPage
             Abort
         ${EndIf}
 
-        ${NSD_CreateLabel} 0 0 100% 12u "Enter your Token and Cloud Room."
+        IfFileExists "$INSTDIR\etc\netdata\claim.conf" NotNeeded
+
+        ${NSD_CreateLabel} 0 0 100% 12u "Enter your Token and Cloud Room(s)."
         ${NSD_CreateLabel} 0 15% 100% 12u "Optionally, you can open a terminal to execute additional commands."
 
-        ${NSD_CreateLabel} 0 35% 20% 10% "Token"
+        ${NSD_CreateLabel} 0 30% 20% 10% "Token"
         Pop $0
-        ${NSD_CreateText} 21% 35% 79% 10% ""
+        ${NSD_CreateText} 21% 30% 79% 10% ""
         Pop $hCloudToken
 
-        ${NSD_CreateLabel} 0 55% 20% 10% "Room"
+        ${NSD_CreateLabel} 0 45% 20% 10% "Room(s)"
         Pop $0
-        ${NSD_CreateText} 21% 55% 79% 10% ""
-        Pop $hCloudRoom
+        ${NSD_CreateText} 21% 45% 79% 10% ""
+        Pop $hCloudRooms
+
+        ${NSD_CreateLabel} 0 60% 20% 10% "Proxy"
+        Pop $0
+        ${NSD_CreateText} 21% 60% 79% 10% ""
+        Pop $hProxy
+
+        ${NSD_CreateCheckbox} 0 75% 100% 10u "Insecure connection"
+        Pop $hInsecure
 
-        ${NSD_CreateCheckbox} 0 70% 100% 10u "Open terminal"
+        ${NSD_CreateCheckbox} 0 90% 100% 10u "Open terminal"
         Pop $hStartMsys
+        Goto EndDialogDraw
+
+        NotNeeded:
+        StrCpy $avoidClaim ${BST_CHECKED}
+        ${NSD_CreateLabel} 0 0 100% 12u "Your host has already been claimed. You can proceed with the update."
+
+        EndDialogDraw:
         nsDialogs::Show
 FunctionEnd
 
 Function NetdataConfigLeave
-        ${NSD_GetText} $hCloudToken $cloudToken
-        ${NSD_GetText} $hCloudRoom $cloudRoom
-        ${NSD_GetState} $hStartMsys $startMsys
-
-        StrLen $0 $cloudToken
-        StrLen $1 $cloudRoom
-        ${If} $0 == 125
-        ${AndIf} $0 == 36
-                # We should start our new claiming software here
-                MessageBox MB_OK "$cloudToken | $cloudRoom | $startMsys"
+        ${If} $avoidClaim == ${BST_UNCHECKED}
+                ${NSD_GetText} $hCloudToken $cloudToken
+                ${NSD_GetText} $hCloudRooms $cloudRooms
+                ${NSD_GetText} $hProxy $proxy
+                ${NSD_GetState} $hStartMsys $startMsys
+                ${NSD_GetState} $hInsecure $insecure
+
+                StrLen $0 $cloudToken
+                StrLen $1 $cloudRooms
+                ${If} $0 == 0
+                ${OrIf} $1 == 0
+                        Goto runMsys
+                ${EndIf}
+
+                ${If} $0 == 135
+                ${AndIf} $1 >= 36
+                        nsExec::ExecToLog '$INSTDIR\usr\bin\netdata_claim.exe /T $cloudToken /R $cloudRooms /P $proxy /I $insecure'
+                        pop $0
+                ${Else}
+                        MessageBox MB_OK "The Cloud information does not have the expected length."
+                ${EndIf}
+
+                runMsys:
+                ${If} $startMsys == ${BST_CHECKED}
+                        nsExec::ExecToLog '$INSTDIR\msys2.exe'
+                        pop $0
+                ${EndIf}
         ${EndIf}
 
-        ${If} $startMsys == 1
-            nsExec::ExecToLog '$INSTDIR\msys2.exe'
-            pop $0
+        ClearErrors
+        nsExec::ExecToLog '$SYSDIR\sc.exe start Netdata'
+        pop $0
+        ${If} $0 != 0
+	        MessageBox MB_OK "Warning: Failed to start Netdata service."
         ${EndIf}
 FunctionEnd
 
@@ -152,13 +196,6 @@ Section "Install Netdata"
 	    DetailPrint "Warning: Failed to add Netdata service description."
         ${EndIf}
 
-	ClearErrors
-        nsExec::ExecToLog '$SYSDIR\sc.exe start Netdata'
-        pop $0
-        ${If} $0 != 0
-	    DetailPrint "Warning: Failed to start Netdata service."
-        ${EndIf}
-
 	WriteUninstaller "$INSTDIR\Uninstall.exe"
 
         Call NetdataUninstallRegistry

+ 16 - 0
packaging/windows/resources/netdata_claim.manifest

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+	<assemblyIdentity version="1.99.0.0"
+     processorArchitecture="*"
+     name="netdata_claim"
+     type="win32"/>
+	<description>Netdata Claim!</description>
+	<!-- Identify the application security requirements. -->
+	<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+		<security>
+			<requestedPrivileges>
+				<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
+			</requestedPrivileges>
+		</security>
+	</trustInfo>
+</assembly>

+ 3 - 0
packaging/windows/resources/netdata_claim.rc

@@ -0,0 +1,3 @@
+#include "winuser.h"
+1 RT_MANIFEST "netdata_claim.manifest"
+11 ICON "../NetdataWhite.ico"

+ 250 - 0
src/claim/netdata_claim.c

@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#define UNICODE
+#define _UNICODE
+#include <windows.h>
+#include <shellapi.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+
+#include "netdata_claim.h"
+
+LPWSTR token = NULL;
+LPWSTR room = NULL;
+LPWSTR proxy = NULL;
+LPWSTR *argv = NULL;
+
+char *aToken = NULL;
+char *aRoom = NULL;
+char *aProxy = NULL;
+int insecure = 0;
+
+LPWSTR netdata_claim_get_formatted_message(LPWSTR pMessage, ...)
+{
+    LPWSTR pBuffer = NULL;
+
+    va_list args = NULL;
+    va_start(args, pMessage);
+
+    FormatMessage(FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ALLOCATE_BUFFER, pMessage, 0, 0, (LPWSTR)&pBuffer,
+    0, &args);
+    va_end(args);
+
+    return pBuffer;
+}
+
+// Common Functions
+void netdata_claim_error_exit(wchar_t *function)
+{
+    DWORD error = GetLastError();
+    LPWSTR pMessage = L"The function %1 failed with error %2.";
+    LPWSTR pBuffer = netdata_claim_get_formatted_message(pMessage, function, error);
+
+    if (pBuffer) {
+        MessageBoxW(NULL, pBuffer, L"Error", MB_OK|MB_ICONERROR);
+        LocalFree(pBuffer);
+    }
+
+    ExitProcess(error);
+}
+
+/**
+ *  Parse Args
+ *
+ *  Parse arguments identifying necessity to make a window
+ *
+ * @param argc number of arguments
+ * @param argv A pointer for all arguments given
+ *
+ * @return it return the number of arguments parsed.
+ */
+int nd_claim_parse_args(int argc, LPWSTR *argv)
+{
+    int i;
+    for (i = 1 ; i < argc; i++) {
+        // We are working with Microsoft, thus it does not make sense wait for only smallcase
+        if(wcscasecmp(L"/T", argv[i]) == 0) {
+            if (argc <= i + 1)
+                continue;
+            i++;
+            token = argv[i];
+        }
+
+        if(wcscasecmp(L"/R", argv[i]) == 0) {
+            if (argc <= i + 1)
+                continue;
+            i++;
+            room = argv[i];
+        }
+
+        if(wcscasecmp(L"/P", argv[i]) == 0) {
+            if (argc <= i + 1)
+                continue;
+            i++;
+            // Minimum IPV4
+            if(wcslen(argv[i]) >= 8) {
+                proxy = argv[i];
+            }
+        }
+
+        if(wcscasecmp(L"/I", argv[i]) == 0) {
+            if (argc <= i + 1)
+                continue;
+
+            i++;
+            size_t length = wcslen(argv[i]);
+            char *tmp = calloc(sizeof(char), length);
+            if (!tmp)
+                ExitProcess(1);
+
+            netdata_claim_convert_str(tmp, argv[i], length - 1);
+            if (i < argc)
+                insecure = atoi(tmp);
+            else
+                insecure = 1;
+
+            free(tmp);
+        }
+    }
+
+    if (!token || !room)
+        return 0;
+
+    return argc;
+}
+
+static int netdata_claim_prepare_strings()
+{
+    if (!token || !room)
+        return -1;
+
+    size_t length = wcslen(token) + 1;
+    aToken = calloc(sizeof(char), length);
+    if (!aToken)
+        return -1;
+
+    netdata_claim_convert_str(aToken, token, length - 1);
+
+    length = wcslen(room) + 1;
+    aRoom = calloc(sizeof(char), length - 1);
+    if (!aRoom)
+        return -1;
+
+    netdata_claim_convert_str(aRoom, room, length - 1);
+
+    if (proxy) {
+        length = wcslen(proxy) + 1;
+        aProxy = calloc(sizeof(char), length - 1);
+        if (!aProxy)
+            return -1;
+
+        netdata_claim_convert_str(aProxy, proxy, length - 1);
+    }
+    return 0;
+}
+
+static void netdata_claim_exit_callback(int signal)
+{
+    (void)signal;
+    if (aToken)
+        free(aToken);
+
+    if (aRoom)
+        free(aRoom);
+
+    if (aProxy)
+        free(aProxy);
+
+    if (argv)
+        LocalFree(argv);
+}
+
+static inline int netdata_claim_prepare_data(char *out, size_t length)
+{
+    char *proxyLabel = (aProxy) ? "proxy = " : "#    proxy = ";
+    char *proxyValue = (aProxy) ? aProxy : "";
+    return snprintf(out,
+                    length,
+                    "[global]\n    url = https://app.netdata.cloud\n    token = %s\n    rooms = %s\n    %s%s\n    insecure = %s",
+                    aToken,
+                    aRoom,
+                    proxyLabel,
+                    proxyValue,
+                    (insecure) ? "YES" : "NO"
+                    );
+}
+
+static int netdata_claim_get_path(char *path)
+{
+    char *usrPath = { "\\usr\\bin" };
+    DWORD length = GetCurrentDirectoryA(WINDOWS_MAX_PATH, path);
+    if (!length) {
+        return -1;
+    }
+
+    if (strstr(path, usrPath)) {
+        length -= 7;
+        path[length] = '\0';
+    }
+
+    return 0;
+}
+
+static void netdata_claim_write_config(char *path)
+{
+    char configPath[WINDOWS_MAX_PATH + 1];
+    char data[WINDOWS_MAX_PATH + 1];
+    snprintf(configPath, WINDOWS_MAX_PATH - 1, "%s\\etc\\netdata\\claim.conf", path);
+
+    HANDLE hf = CreateFileA(configPath, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+    if (hf == INVALID_HANDLE_VALUE)
+        netdata_claim_error_exit(L"CreateFileA");
+
+    DWORD length = netdata_claim_prepare_data(data, WINDOWS_MAX_PATH);
+    DWORD written = 0;
+
+    BOOL ret = WriteFile(hf, data, length, &written, NULL);
+    if (!ret) {
+        CloseHandle(hf);
+        netdata_claim_error_exit(L"WriteFileA");
+    }
+
+    if (length != written)
+        MessageBoxW(NULL, L"Cannot write claim.conf.", L"Error", MB_OK|MB_ICONERROR);
+
+    CloseHandle(hf);
+}
+
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
+{
+    signal(SIGABRT, netdata_claim_exit_callback);
+    signal(SIGINT, netdata_claim_exit_callback);
+    signal(SIGTERM, netdata_claim_exit_callback);
+
+    int argc;
+    LPWSTR *argv = CommandLineToArgvW(GetCommandLineW(), &argc);
+    if (argc)
+        argc = nd_claim_parse_args(argc, argv);
+
+    // When no data is given, user must to use graphic mode
+    int ret = 0;
+    if (!argc) {
+        ret = netdata_claim_window_loop(hInstance, nCmdShow);
+    } else {
+        if (netdata_claim_prepare_strings()) {
+            goto exit_claim;
+        }
+
+        char basePath[WINDOWS_MAX_PATH];
+        if (!netdata_claim_get_path(basePath)) {
+            netdata_claim_write_config(basePath);
+        }
+    }
+
+exit_claim:
+    netdata_claim_exit_callback(0);
+
+    return ret;
+}

+ 19 - 0
src/claim/netdata_claim.h

@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_CLAIM_H_
+# define NETDATA_CLAIM_H_ 1
+
+#include <wchar.h>
+#include "netdata_claim_window.h"
+
+extern LPWSTR token;
+extern LPWSTR room;
+extern LPWSTR proxy;
+
+void netdata_claim_error_exit(wchar_t *function);
+static inline void netdata_claim_convert_str(char *dst, wchar_t *src, size_t len) {
+    size_t copied = wcstombs(dst, src, len);
+    dst[copied] = '\0';
+}
+
+#endif //NETDATA_CLAIM_H_

+ 108 - 0
src/claim/netdata_claim_window.c

@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#define UNICODE
+#define _UNICODE
+#include <windows.h>
+#include "richedit.h"
+#include "tchar.h"
+#include "netdata_claim.h"
+
+static LPCTSTR szWindowClass = _T("DesktopApp");
+
+static HINSTANCE hInst;
+static HWND hToken;
+static HWND hRoom;
+
+LRESULT CALLBACK WndProc(HWND hNetdatawnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+    PAINTSTRUCT ps;
+    HDC hdc;
+    LPCTSTR topMsg[] = { L"                                         Help",
+                         L" ",
+                         L"In this initial version of the software, there are no fields for data",
+                         L" entry. To claim your agent, you must use the following options:",
+                         L" ",
+                         L"/T TOKEN: The cloud token;",
+                         L"/R ROOMS: A list of rooms to claim;",
+                         L"/P PROXY: The proxy information;",
+                         L"/I      : Use insecure connection;"
+    };
+
+    switch (message)
+    {
+        case WM_PAINT: {
+            hdc = BeginPaint(hNetdatawnd, &ps);
+
+            int i;
+            for (i = 0; i < sizeof(topMsg) / sizeof(LPCTSTR); i++) {
+                TextOut(hdc, 5, 5 + 15*i, topMsg[i], wcslen(topMsg[i]));
+            }
+            EndPaint(hNetdatawnd, &ps);
+            break;
+        }
+        case WM_COMMAND:
+        case WM_DESTROY: {
+            PostQuitMessage(0);
+            break;
+        }
+        default: {
+            return DefWindowProc(hNetdatawnd, message, wParam, lParam);
+            break;
+        }
+    }
+
+    return 0;
+}
+
+int netdata_claim_window_loop(HINSTANCE hInstance, int nCmdShow)
+{
+    WNDCLASSEX wcex;
+
+    wcex.cbSize         = sizeof(WNDCLASSEX);
+    wcex.style          = CS_HREDRAW | CS_VREDRAW;
+    wcex.lpfnWndProc    = WndProc;
+    wcex.cbClsExtra     = 0;
+    wcex.cbWndExtra     = 0;
+    wcex.hInstance      = hInstance;
+    wcex.hIcon          = LoadIcon(wcex.hInstance, MAKEINTRESOURCEW(11));
+    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
+    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
+    wcex.lpszMenuName   = NULL;
+    wcex.lpszClassName  = szWindowClass;
+    wcex.hIconSm        = LoadIcon(wcex.hInstance, IDI_APPLICATION);
+
+    if (!RegisterClassEx(&wcex)) {
+        MessageBoxW(NULL, L"Call to RegisterClassEx failed!", L"Error", 0);
+        return 1;
+    }
+
+    hInst = hInstance;
+
+    HWND hNetdatawnd = CreateWindowExW(WS_EX_OVERLAPPEDWINDOW,
+                                      szWindowClass,
+                                      L"Netdata Claim",
+                                      WS_OVERLAPPEDWINDOW,
+                                      CW_USEDEFAULT, CW_USEDEFAULT,
+                                      460, 220,
+                                      NULL,
+                                      NULL,
+                                      hInstance,
+                                      NULL
+        );
+
+    if (!hNetdatawnd) {
+        MessageBoxW(NULL, L"Call to CreateWindow failed!", L"Error", 0);
+        return 1;
+    }
+
+    ShowWindow(hNetdatawnd, nCmdShow);
+    UpdateWindow(hNetdatawnd);
+
+    MSG msg;
+    while (GetMessage(&msg, NULL, 0, 0)) {
+        TranslateMessage(&msg);
+        DispatchMessage(&msg);
+    }
+
+    return (int) msg.wParam;
+}

+ 12 - 0
src/claim/netdata_claim_window.h

@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_CLAIM_WINDOW_H_
+# define NETDATA_CLAIM_WINDOW_H_ 1
+
+// https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/command-line-string-limitation
+// https://sourceforge.net/p/mingw/mailman/mingw-users/thread/4C8FD4EB.4050503@xs4all.nl/
+#define WINDOWS_MAX_PATH 8191
+
+int netdata_claim_window_loop(HINSTANCE hInstance, int nCmdShow);
+
+#endif //NETDATA_CLAIM_WINDOW_H_