diff options
Diffstat (limited to 'icinga-installer')
-rw-r--r-- | icinga-installer/CMakeLists.txt | 47 | ||||
-rw-r--r-- | icinga-installer/bannrbmp.bmp | bin | 0 -> 29914 bytes | |||
-rw-r--r-- | icinga-installer/dlgbmp.bmp | bin | 0 -> 615402 bytes | |||
-rw-r--r-- | icinga-installer/icinga-installer.cpp | 312 | ||||
-rw-r--r-- | icinga-installer/icinga2.wixpatch.cmake | 52 |
5 files changed, 411 insertions, 0 deletions
diff --git a/icinga-installer/CMakeLists.txt b/icinga-installer/CMakeLists.txt new file mode 100644 index 0000000..6ac5e1f --- /dev/null +++ b/icinga-installer/CMakeLists.txt @@ -0,0 +1,47 @@ +# Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ + +foreach(flag_var + CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE + CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO) + if(${flag_var} MATCHES "/MD") + string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}") + endif(${flag_var} MATCHES "/MD") +endforeach(flag_var) + +set(icinga_installer_SOURCES + icinga-installer.cpp +) + +add_executable(icinga-installer ${icinga_installer_SOURCES}) + +set_target_properties( + icinga-installer PROPERTIES + FOLDER Bin + OUTPUT_NAME icinga2-installer + LINK_FLAGS "/SUBSYSTEM:WINDOWS" +) + +target_link_libraries(icinga-installer shlwapi) + +install(CODE " + execute_process(COMMAND \${CMAKE_COMMAND} -E copy \"${CMAKE_CURRENT_BINARY_DIR}/icinga2.wixpatch.\${BUILD_TYPE}\" + \"${CMAKE_CURRENT_BINARY_DIR}/icinga2.wixpatch\" + RESULT_VARIABLE copy_result + ERROR_VARIABLE error_output) + if(copy_result) + message(FATAL_ERROR \${error_output}) + endif()" +) + +file( + GENERATE + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/icinga2.wixpatch.$<CONFIG>" + INPUT "${CMAKE_CURRENT_SOURCE_DIR}/icinga2.wixpatch.cmake" +) + +set(InstallPath "${CMAKE_INSTALL_SBINDIR}") + +install( + TARGETS icinga-installer + RUNTIME DESTINATION ${InstallPath} +) diff --git a/icinga-installer/bannrbmp.bmp b/icinga-installer/bannrbmp.bmp Binary files differnew file mode 100644 index 0000000..0f68e62 --- /dev/null +++ b/icinga-installer/bannrbmp.bmp diff --git a/icinga-installer/dlgbmp.bmp b/icinga-installer/dlgbmp.bmp Binary files differnew file mode 100644 index 0000000..e46566f --- /dev/null +++ b/icinga-installer/dlgbmp.bmp diff --git a/icinga-installer/icinga-installer.cpp b/icinga-installer/icinga-installer.cpp new file mode 100644 index 0000000..4dc050d --- /dev/null +++ b/icinga-installer/icinga-installer.cpp @@ -0,0 +1,312 @@ +/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ + +#include <string> +#include <vector> +#include <fstream> +#include <direct.h> +#include <windows.h> +#include <shlwapi.h> +#include <shellapi.h> +#include <shlobj.h> +#include <sys/types.h> +#include <sys/stat.h> + +static std::string GetIcingaInstallPath(void) +{ + char szFileName[MAX_PATH]; + if (!GetModuleFileName(nullptr, szFileName, sizeof(szFileName))) + return ""; + + if (!PathRemoveFileSpec(szFileName)) + return ""; + + if (!PathRemoveFileSpec(szFileName)) + return ""; + + return szFileName; +} + + +static bool ExecuteCommand(const std::string& app, const std::string& arguments) +{ + SHELLEXECUTEINFO sei = {}; + sei.cbSize = sizeof(sei); + sei.fMask = SEE_MASK_NOCLOSEPROCESS; + sei.lpFile = app.c_str(); + sei.lpParameters = arguments.c_str(); + sei.nShow = SW_HIDE; + if (!ShellExecuteEx(&sei)) + return false; + + if (!sei.hProcess) + return false; + + WaitForSingleObject(sei.hProcess, INFINITE); + + DWORD exitCode; + BOOL res = GetExitCodeProcess(sei.hProcess, &exitCode); + CloseHandle(sei.hProcess); + + if (!res) + return false; + + return exitCode == 0; +} + +static bool ExecuteIcingaCommand(const std::string& arguments) +{ + return ExecuteCommand(GetIcingaInstallPath() + "\\sbin\\icinga2.exe", arguments); +} + +static std::string DirName(const std::string& path) +{ + char *spath = strdup(path.c_str()); + + if (!PathRemoveFileSpec(spath)) { + free(spath); + throw std::runtime_error("PathRemoveFileSpec failed"); + } + + std::string result = spath; + + free(spath); + + return result; +} + +static bool PathExists(const std::string& path) +{ + struct _stat statbuf; + return (_stat(path.c_str(), &statbuf) >= 0); +} + +static std::string GetIcingaDataPath(void) +{ + char path[MAX_PATH]; + if (!SUCCEEDED(SHGetFolderPath(nullptr, CSIDL_COMMON_APPDATA, nullptr, 0, path))) + throw std::runtime_error("SHGetFolderPath failed"); + return std::string(path) + "\\icinga2"; +} + +static void MkDir(const std::string& path) +{ + if (mkdir(path.c_str()) < 0 && errno != EEXIST) + throw std::runtime_error("mkdir failed"); +} + +static void MkDirP(const std::string& path) +{ + size_t pos = 0; + + while (pos != std::string::npos) { + pos = path.find_first_of("/\\", pos + 1); + + std::string spath = path.substr(0, pos + 1); + struct _stat statbuf; + if (_stat(spath.c_str(), &statbuf) < 0 && errno == ENOENT) + MkDir(path.substr(0, pos)); + } +} + +static std::string GetNSISInstallPath(void) +{ + HKEY hKey; + //TODO: Change hardcoded key + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Icinga Development Team\\ICINGA2", 0, + KEY_QUERY_VALUE | KEY_WOW64_32KEY, &hKey) == ERROR_SUCCESS) { + BYTE pvData[MAX_PATH]; + DWORD cbData = sizeof(pvData) - 1; + DWORD lType; + if (RegQueryValueEx(hKey, nullptr, nullptr, &lType, pvData, &cbData) == ERROR_SUCCESS && lType == REG_SZ) { + pvData[cbData] = '\0'; + + return (char *)pvData; + } + + RegCloseKey(hKey); + } + + return ""; +} + +static bool CopyDirectory(const std::string& source, const std::string& destination) +{ + // SHFileOperation requires file names to be terminated with two \0s + std::string tmpSource = source + std::string(1, '\0'); + std::string tmpDestination = destination + std::string(1, '\0'); + + SHFILEOPSTRUCT fop; + fop.wFunc = FO_COPY; + fop.pFrom = tmpSource.c_str(); + fop.pTo = tmpDestination.c_str(); + fop.fFlags = FOF_NO_UI; + + return (SHFileOperation(&fop) == 0); +} + +static bool DeleteDirectory(const std::string& dir) +{ + // SHFileOperation requires file names to be terminated with two \0s + std::string tmpDir = dir + std::string(1, '\0'); + + SHFILEOPSTRUCT fop; + fop.wFunc = FO_DELETE; + fop.pFrom = tmpDir.c_str(); + fop.fFlags = FOF_NO_UI; + + return (SHFileOperation(&fop) == 0); +} + +static int UpgradeNSIS(void) +{ + std::string installPath = GetNSISInstallPath(); + + if (installPath.empty()) + return 0; + + std::string uninstallerPath = installPath + "\\uninstall.exe"; + + if (!PathExists(uninstallerPath)) + return 0; + + std::string dataPath = GetIcingaDataPath(); + + if (dataPath.empty()) + return 1; + + bool moveUserData = !PathExists(dataPath); + + /* perform open heart surgery on the user's data dirs - yay */ + if (moveUserData) { + MkDir(dataPath.c_str()); + + std::string oldNameEtc = installPath + "\\etc"; + std::string newNameEtc = dataPath + "\\etc"; + if (!CopyDirectory(oldNameEtc, newNameEtc)) + return 1; + + std::string oldNameVar = installPath + "\\var"; + std::string newNameVar = dataPath + "\\var"; + if (!CopyDirectory(oldNameVar, newNameVar)) + return 1; + } + + ExecuteCommand(uninstallerPath, "/S _?=" + installPath); + + _unlink(uninstallerPath.c_str()); + + if (moveUserData) { + std::string oldNameEtc = installPath + "\\etc"; + if (!DeleteDirectory(oldNameEtc)) + return 1; + + std::string oldNameVar = installPath + "\\var"; + if (!DeleteDirectory(oldNameVar)) + return 1; + + _rmdir(installPath.c_str()); + } + + return 0; +} + +static int InstallIcinga(void) +{ + std::string installDir = GetIcingaInstallPath(); + std::string skelDir = installDir + "\\share\\skel"; + std::string dataDir = GetIcingaDataPath(); + + if (!PathExists(dataDir)) { + std::string sourceDir = skelDir + std::string(1, '\0'); + std::string destinationDir = dataDir + std::string(1, '\0'); + + SHFILEOPSTRUCT fop; + fop.wFunc = FO_COPY; + fop.pFrom = sourceDir.c_str(); + fop.pTo = destinationDir.c_str(); + fop.fFlags = FOF_NO_UI | FOF_NOCOPYSECURITYATTRIBS; + + if (SHFileOperation(&fop) != 0) + return 1; + + MkDirP(dataDir + "/etc/icinga2/pki"); + MkDirP(dataDir + "/var/cache/icinga2"); + MkDirP(dataDir + "/var/lib/icinga2/certs"); + MkDirP(dataDir + "/var/lib/icinga2/certificate-requests"); + MkDirP(dataDir + "/var/lib/icinga2/agent/inventory"); + MkDirP(dataDir + "/var/lib/icinga2/api/config"); + MkDirP(dataDir + "/var/lib/icinga2/api/log"); + MkDirP(dataDir + "/var/lib/icinga2/api/zones"); + MkDirP(dataDir + "/var/log/icinga2/compat/archive"); + MkDirP(dataDir + "/var/log/icinga2/crash"); + MkDirP(dataDir + "/var/run/icinga2/cmd"); + MkDirP(dataDir + "/var/spool/icinga2/perfdata"); + MkDirP(dataDir + "/var/spool/icinga2/tmp"); + } + + // Upgrade from versions older than 2.13 by making the windowseventlog feature available, + // enable it by default and disable the old mainlog feature. + if (!PathExists(dataDir + "/etc/icinga2/features-available/windowseventlog.conf")) { + // Disable the old mainlog feature as it is replaced by windowseventlog by default. + std::string mainlogEnabledFile = dataDir + "/etc/icinga2/features-enabled/mainlog.conf"; + if (PathExists(mainlogEnabledFile)) { + if (DeleteFileA(mainlogEnabledFile.c_str()) == 0) { + throw std::runtime_error("deleting '" + mainlogEnabledFile + "' failed"); + } + } + + // Install the new windowseventlog feature. As features-available/windowseventlog.conf is used as a marker file, + // copy it as the last step, so that this is run again should the upgrade be interrupted. + for (const std::string& d : {"features-enabled", "features-available"}) { + std::string sourceFile = skelDir + "/etc/icinga2/" + d + "/windowseventlog.conf"; + std::string destinationFile = dataDir + "/etc/icinga2/" + d + "/windowseventlog.conf"; + + if (CopyFileA(sourceFile.c_str(), destinationFile.c_str(), false) == 0) { + throw std::runtime_error("copying '" + sourceFile + "' to '" + destinationFile + "' failed"); + } + } + } + + // TODO: In Icinga 2.14, rename features-available/mainlog.conf to mainlog.conf.deprecated + // so that it's no longer listed as an available feature. + + ExecuteCommand("icacls", "\"" + dataDir + "\" /grant *S-1-5-20:(oi)(ci)m"); + ExecuteCommand("icacls", "\"" + dataDir + "\\etc\" /inheritance:r /grant:r *S-1-5-20:(oi)(ci)m *S-1-5-32-544:(oi)(ci)f"); + + ExecuteIcingaCommand("--scm-install daemon"); + + return 0; +} + +static int UninstallIcinga(void) +{ + ExecuteIcingaCommand("--scm-uninstall"); + + return 0; +} + +/** +* Entry point for the installer application. +*/ +int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) +{ + CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + + //AllocConsole(); + int rc; + + if (strcmp(lpCmdLine, "install") == 0) { + rc = InstallIcinga(); + } else if (strcmp(lpCmdLine, "uninstall") == 0) { + rc = UninstallIcinga(); + } else if (strcmp(lpCmdLine, "upgrade-nsis") == 0) { + rc = UpgradeNSIS(); + } else { + MessageBox(nullptr, "This application should only be run by the MSI installer package.", "Icinga 2 Installer", MB_ICONWARNING); + rc = 1; + } + + //::Sleep(3000s); + + return rc; +} diff --git a/icinga-installer/icinga2.wixpatch.cmake b/icinga-installer/icinga2.wixpatch.cmake new file mode 100644 index 0000000..eee4027 --- /dev/null +++ b/icinga-installer/icinga2.wixpatch.cmake @@ -0,0 +1,52 @@ +<CPackWiXPatch> + <CPackWiXFragment Id="#PRODUCT"> + <Property Id="ALLUSERS">1</Property> + <Property Id="MSIRESTARTMANAGERCONTROL">Disable</Property> + + <PropertyRef Id="WIX_IS_NETFRAMEWORK_46_OR_LATER_INSTALLED" /> + <Condition Message='This application requires .NET Framework 4.6 or higher. Please install the .NET Framework then run this installer again.'> + <![CDATA[Installed OR WIX_IS_NETFRAMEWORK_46_OR_LATER_INSTALLED]]> + </Condition> + + <CustomAction Id="XtraUpgradeNSIS" BinaryKey="icinga2_installer" ExeCommand="upgrade-nsis" Execute="deferred" Impersonate="no" /> + <CustomAction Id="XtraInstall" FileKey="CM_FP_sbin.icinga2_installer.exe" ExeCommand="install" Execute="deferred" Impersonate="no" /> + <CustomAction Id="XtraUninstall" FileKey="CM_FP_sbin.icinga2_installer.exe" ExeCommand="uninstall" Execute="deferred" Impersonate="no" /> + + <Binary Id="icinga2_installer" SourceFile="$<TARGET_FILE:icinga-installer>" /> + + <InstallExecuteSequence> + <Custom Action="XtraUpgradeNSIS" After="InstallInitialize">$CM_CP_sbin.icinga2_installer.exe>2 AND NOT SUPPRESS_XTRA</Custom> + <Custom Action="XtraInstall" Before="InstallFinalize">$CM_CP_sbin.icinga2_installer.exe>2 AND NOT SUPPRESS_XTRA</Custom> + <Custom Action="XtraUninstall" Before="RemoveExistingProducts">$CM_CP_sbin.icinga2_installer.exe=2 AND NOT SUPPRESS_XTRA</Custom> + </InstallExecuteSequence> + + <!-- + Write the path to eventprovider.dll to the registry so that the Event Viewer is able to find + the message definitions and properly displays our log messages. + + See also: https://docs.microsoft.com/en-us/windows/win32/eventlog/reporting-an-event + --> + <FeatureRef Id="ProductFeature" IgnoreParent="yes"> + <Component Id="EventProviderRegistryEntry" Guid="*" Directory="INSTALL_ROOT"> + <RegistryKey Root="HKLM" Key="SYSTEM\CurrentControlSet\Services\EventLog\Application\Icinga 2" Action="createAndRemoveOnUninstall"> + <RegistryValue Name="EventMessageFile" Type="string" Value="[#CM_FP_sbin.eventprovider.dll]" /> + </RegistryKey> + </Component> + </FeatureRef> + + <Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Run Icinga 2 setup wizard" /> + + <Property Id="WixShellExecTarget" Value="[#CM_FP_sbin.Icinga2SetupAgent.exe]" /> + <CustomAction Id="LaunchIcinga2Wizard" + BinaryKey="WixCA" + DllEntry="WixShellExec" + Impersonate="no" /> + + <UI> + <Publish Dialog="ExitDialog" + Control="Finish" + Event="DoAction" + Value="LaunchIcinga2Wizard">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish> + </UI> + </CPackWiXFragment> +</CPackWiXPatch> |