Skip to content

Commit

Permalink
feat: nightly updater (#1175)
Browse files Browse the repository at this point in the history
  • Loading branch information
vansangpfiev authored Sep 10, 2024
1 parent f25f6dd commit 8c5fe88
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 119 deletions.
2 changes: 1 addition & 1 deletion engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ if(DEFINED CMAKE_JS_INC)
# define NPI_VERSION
add_compile_definitions(NAPI_VERSION=8)
endif()

add_compile_definitions(CORTEX_VARIANT="${CORTEX_VARIANT}")
add_compile_definitions(CORTEX_CPP_VERSION="${CORTEX_CPP_VERSION}")
add_compile_definitions(CORTEX_CONFIG_FILE_PATH="${CORTEX_CONFIG_FILE_PATH}")
Expand Down
145 changes: 86 additions & 59 deletions engine/commands/cortex_upd_cmd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "cortex_upd_cmd.h"
#include "httplib.h"
#include "nlohmann/json.hpp"
#include "server_stop_cmd.h"
#include "services/download_service.h"
#include "utils/archive_utils.h"
#include "utils/file_manager_utils.h"
Expand All @@ -12,21 +13,38 @@

namespace commands {

namespace {
const std::string kCortexBinary = "cortex-cpp";
}

CortexUpdCmd::CortexUpdCmd() {}

void CortexUpdCmd::Exec(std::string v) {
// TODO(sang) stop server if it is running
{
auto config = file_manager_utils::GetCortexConfig();
httplib::Client cli(config.apiServerHost + ":" + config.apiServerPort);
auto res = cli.Get("/health/healthz");
if (res) {
CLI_LOG("Server is running. Stopping server before updating!");
commands::ServerStopCmd ssc(config.apiServerHost,
std::stoi(config.apiServerPort));
ssc.Exec();
}
}
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
if (!GetNightly(v))
return;
} else {
if (!GetStableAndBeta(v))
return;
}
CLI_LOG("Update cortex sucessfully");
}

bool CortexUpdCmd::GetStableAndBeta(const std::string& v) {
// Check if the architecture and OS are supported
auto system_info = system_info_utils::GetSystemInfo();
if (system_info.arch == system_info_utils::kUnsupported ||
system_info.os == system_info_utils::kUnsupported) {
CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", "
<< system_info.arch);
return;
return false;
}
CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch);

Expand All @@ -50,7 +68,7 @@ void CortexUpdCmd::Exec(std::string v) {
std::string matched_variant = "";
for (auto& asset : assets) {
auto asset_name = asset["name"].get<std::string>();
if (asset_name.find("cortex-cpp") != std::string::npos &&
if (asset_name.find(kCortexBinary) != std::string::npos &&
asset_name.find(os_arch) != std::string::npos) {
matched_variant = asset_name;
break;
Expand All @@ -59,7 +77,7 @@ void CortexUpdCmd::Exec(std::string v) {
}
if (matched_variant.empty()) {
CTL_ERR("No variant found for " << os_arch);
return;
return false;
}
CTL_INF("Matched variant: " << matched_variant);

Expand Down Expand Up @@ -99,73 +117,82 @@ void CortexUpdCmd::Exec(std::string v) {
archive_utils::ExtractArchive(download_path.string(),
extract_path.string());

// remove the downloaded file
// TODO(any) Could not delete file on Windows because it is currently hold by httplib(?)
// Not sure about other platforms
try {
std::filesystem::remove(absolute_path);
} catch (const std::exception& e) {
CTL_WRN("Could not delete file: " << e.what());
}
CTL_INF("Finished!");
});
break;
}
}
} catch (const nlohmann::json::parse_error& e) {
std::cerr << "JSON parse error: " << e.what() << std::endl;
return;
return false;
}
} else {
CTL_ERR("HTTP error: " << res->status);
return;
return false;
}
} else {
auto err = res.error();
CTL_ERR("HTTP error: " << httplib::to_string(err));
return;
return false;
}
#if defined(_WIN32)
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
auto temp = executable_path / "cortex_tmp.exe";
remove(temp.string().c_str()); // ignore return code

auto src =
executable_path / "cortex" / kCortexBinary / (kCortexBinary + ".exe");
auto dst = executable_path / (kCortexBinary + ".exe");
// Rename
rename(dst.string().c_str(), temp.string().c_str());
// Update
CopyFile(const_cast<char*>(src.string().c_str()),
const_cast<char*>(dst.string().c_str()), false);
auto download_folder = executable_path / "cortex";
remove(download_folder);
remove(temp.string().c_str());
#else

// Replace binary file
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
auto temp = executable_path / "cortex_tmp";
auto src = executable_path / "cortex" / kCortexBinary / kCortexBinary;
auto dst = executable_path / kCortexBinary;
if (std::rename(dst.string().c_str(), temp.string().c_str())) {
CTL_ERR("Failed to rename from " << dst.string() << " to "
<< temp.string());
return;
}
try {
std::filesystem::copy_file(
src, dst, std::filesystem::copy_options::overwrite_existing);
std::filesystem::permissions(dst, std::filesystem::perms::owner_all |
std::filesystem::perms::group_all |
std::filesystem::perms::others_read |
std::filesystem::perms::others_exec);
std::filesystem::remove(temp);
auto download_folder = executable_path / "cortex/";
std::filesystem::remove_all(download_folder);
} catch (const std::exception& e) {
CTL_WRN("Something wrong happened: " << e.what());
return;
auto src = executable_path / "cortex" / kCortexBinary / GetCortexBinary();
auto dst = executable_path / GetCortexBinary();
return ReplaceBinaryInflight(src, dst);
}

bool CortexUpdCmd::GetNightly(const std::string& v) {
// Check if the architecture and OS are supported
auto system_info = system_info_utils::GetSystemInfo();
if (system_info.arch == system_info_utils::kUnsupported ||
system_info.os == system_info_utils::kUnsupported) {
CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", "
<< system_info.arch);
return false;
}
#endif
CLI_LOG("Update cortex sucessfully");
CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch);

// Download file
std::string version = v.empty() ? "latest" : std::move(v);
std::ostringstream release_path;
release_path << "cortex/" << version << "/" << system_info.os << "-"
<< system_info.arch << "/" << kNightlyFileName;
CTL_INF("Engine release path: " << kNightlyHost << release_path.str());

auto download_task = DownloadTask{.id = "cortex",
.type = DownloadType::Cortex,
.error = std::nullopt,
.items = {DownloadItem{
.id = "cortex",
.host = kNightlyHost,
.fileName = kNightlyFileName,
.type = DownloadType::Cortex,
.path = release_path.str(),
}}};

DownloadService download_service;
download_service.AddDownloadTask(
download_task, [this](const std::string& absolute_path, bool unused) {
// try to unzip the downloaded file
std::filesystem::path download_path{absolute_path};
CTL_INF("Downloaded engine path: " << download_path.string());

std::filesystem::path extract_path =
download_path.parent_path().parent_path();

archive_utils::ExtractArchive(download_path.string(),
extract_path.string());

CTL_INF("Finished!");
});

// Replace binay file
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
auto src = executable_path / "cortex" / GetCortexBinary();
auto dst = executable_path / GetCortexBinary();
return ReplaceBinaryInflight(src, dst);
}

} // namespace commands
115 changes: 113 additions & 2 deletions engine/commands/cortex_upd_cmd.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,124 @@
#pragma once
#include <string>
#include <optional>
#include <string>

#include "httplib.h"
#include "nlohmann/json.hpp"
#include "utils/file_manager_utils.h"
#include "utils/logging_utils.h"

namespace commands {
#ifndef CORTEX_VARIANT
#define CORTEX_VARIANT file_manager_utils::kProdVariant
#endif
constexpr const auto kNightlyHost = "https://delta.jan.ai";
constexpr const auto kNightlyFileName = "cortex-nightly.tar.gz";
const std::string kCortexBinary = "cortex";

inline std::string GetCortexBinary() {
#if defined(_WIN32)
constexpr const bool has_exe = true;
#else
constexpr const bool has_exe = false;
#endif
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
return has_exe ? kCortexBinary + "-nightly.exe"
: kCortexBinary + "-nightly";
} else if (CORTEX_VARIANT == file_manager_utils::kBetaVariant) {
return has_exe ? kCortexBinary + "-beta.exe" : kCortexBinary + "-beta";
} else {
return has_exe ? kCortexBinary + ".exe" : kCortexBinary;
}
}

inline std::string GetHostName() {
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
return "https://delta.jan.ai";
} else {
return "https://api.github.com";
}
}

inline std::string GetReleasePath() {
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
return "/cortex/latest/version.json";
} else {
return "/repos/janhq/cortex.cpp/releases/latest";
}
}

inline void CheckNewUpdate() {
auto host_name = GetHostName();
auto release_path = GetReleasePath();
CTL_INF("Engine release path: " << host_name << release_path);

class CortexUpdCmd{
httplib::Client cli(host_name);
if (auto res = cli.Get(release_path)) {
if (res->status == httplib::StatusCode::OK_200) {
try {
auto json_res = nlohmann::json::parse(res->body);
std::string latest_version = json_res["tag_name"].get<std::string>();
std::string current_version = CORTEX_CPP_VERSION;
if (current_version != latest_version) {
CLI_LOG("\nA new release of cortex is available: "
<< current_version << " -> " << latest_version);
CLI_LOG("To upgrade, run: cortex update");
// CLI_LOG(json_res["html_url"].get<std::string>());
}
} catch (const nlohmann::json::parse_error& e) {
CTL_INF("JSON parse error: " << e.what());
}
} else {
CTL_INF("HTTP error: " << res->status);
}
} else {
auto err = res.error();
CTL_INF("HTTP error: " << httplib::to_string(err));
}
}

inline bool ReplaceBinaryInflight(const std::filesystem::path& src,
const std::filesystem::path& dst) {
if (src == dst) {
// Already has the newest
return true;
}
std::filesystem::path temp = std::filesystem::temp_directory_path() / "cortex_temp";

try {
if (std::filesystem::exists(temp)) {
std::filesystem::remove(temp);
}

std::rename(dst.string().c_str(), temp.string().c_str());
std::filesystem::copy_file(
src, dst, std::filesystem::copy_options::overwrite_existing);
std::filesystem::permissions(dst, std::filesystem::perms::owner_all |
std::filesystem::perms::group_all |
std::filesystem::perms::others_read |
std::filesystem::perms::others_exec);
auto download_folder = src.parent_path();
std::filesystem::remove_all(download_folder);
} catch (const std::exception& e) {
CTL_ERR("Something wrong happened: " << e.what());
if (std::filesystem::exists(temp)) {
std::rename(temp.string().c_str(), dst.string().c_str());
CLI_LOG("Restored binary file");
}
return false;
}

return true;
}

class CortexUpdCmd {
public:
CortexUpdCmd();
void Exec(std::string version);

private:
bool GetStableAndBeta(const std::string& v);
bool GetNightly(const std::string& v);
};

} // namespace commands
Loading

0 comments on commit 8c5fe88

Please sign in to comment.