diff --git a/engine/commands/cortex_upd_cmd.cc b/engine/commands/cortex_upd_cmd.cc new file mode 100644 index 000000000..9027203e7 --- /dev/null +++ b/engine/commands/cortex_upd_cmd.cc @@ -0,0 +1,171 @@ +// clang-format off +#include "utils/cortex_utils.h" +// clang-format on +#include "cortex_upd_cmd.h" +#include "httplib.h" +#include "nlohmann/json.hpp" +#include "services/download_service.h" +#include "utils/archive_utils.h" +#include "utils/file_manager_utils.h" +#include "utils/logging_utils.h" +#include "utils/system_info_utils.h" + +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 + // 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; + } + CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch); + + // Download file + constexpr auto github_host = "https://api.github.com"; + // std::string version = v.empty() ? "latest" : std::move(v); + // TODO(sang): support download with version + std::string version = "latest"; + std::ostringstream release_path; + release_path << "/repos/janhq/cortex.cpp/releases/" << version; + CTL_INF("Engine release path: " << github_host << release_path.str()); + + httplib::Client cli(github_host); + if (auto res = cli.Get(release_path.str())) { + if (res->status == httplib::StatusCode::OK_200) { + try { + auto jsonResponse = nlohmann::json::parse(res->body); + auto assets = jsonResponse["assets"]; + auto os_arch{system_info.os + "-" + system_info.arch}; + + std::string matched_variant = ""; + for (auto& asset : assets) { + auto asset_name = asset["name"].get(); + if (asset_name.find("cortex-cpp") != std::string::npos && + asset_name.find(os_arch) != std::string::npos) { + matched_variant = asset_name; + break; + } + CTL_INF(asset_name); + } + if (matched_variant.empty()) { + CTL_ERR("No variant found for " << os_arch); + return; + } + CTL_INF("Matched variant: " << matched_variant); + + for (auto& asset : assets) { + auto asset_name = asset["name"].get(); + if (asset_name == matched_variant) { + std::string host{"https://github.com"}; + + auto full_url = asset["browser_download_url"].get(); + std::string path = full_url.substr(host.length()); + + auto fileName = asset["name"].get(); + CTL_INF("URL: " << full_url); + + auto download_task = DownloadTask{.id = "cortex", + .type = DownloadType::Cortex, + .error = std::nullopt, + .items = {DownloadItem{ + .id = "cortex", + .host = host, + .fileName = fileName, + .type = DownloadType::Cortex, + .path = path, + }}}; + + 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()); + + // 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; + } + } else { + CTL_ERR("HTTP error: " << res->status); + return; + } + } else { + auto err = res.error(); + CTL_ERR("HTTP error: " << httplib::to_string(err)); + return; + } +#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(src.string().c_str()), + const_cast(dst.string().c_str()), false); + auto download_folder = executable_path / "cortex"; + remove(download_folder); + remove(temp.string().c_str()); +#else + 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; + } +#endif + CLI_LOG("Update cortex sucessfully"); +} +} // namespace commands \ No newline at end of file diff --git a/engine/commands/cortex_upd_cmd.h b/engine/commands/cortex_upd_cmd.h new file mode 100644 index 000000000..2606dbbd9 --- /dev/null +++ b/engine/commands/cortex_upd_cmd.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include + +namespace commands { + +class CortexUpdCmd{ + public: + CortexUpdCmd(); + void Exec(std::string version); +}; + +} // namespace commands \ No newline at end of file diff --git a/engine/controllers/command_line_parser.cc b/engine/controllers/command_line_parser.cc index 17a8afd80..8c8c706a3 100644 --- a/engine/controllers/command_line_parser.cc +++ b/engine/controllers/command_line_parser.cc @@ -1,6 +1,7 @@ #include "command_line_parser.h" #include "commands/chat_cmd.h" #include "commands/cmd_info.h" +#include "commands/cortex_upd_cmd.h" #include "commands/engine_get_cmd.h" #include "commands/engine_init_cmd.h" #include "commands/engine_list_cmd.h" @@ -13,6 +14,7 @@ #include "commands/run_cmd.h" #include "commands/server_stop_cmd.h" #include "config/yaml_config.h" +#include "httplib.h" #include "services/engine_service.h" #include "utils/cortex_utils.h" #include "utils/logging_utils.h" @@ -21,6 +23,7 @@ CommandLineParser::CommandLineParser() : app_("Cortex.cpp CLI"), engine_service_{EngineService()} {} bool CommandLineParser::SetupCommand(int argc, char** argv) { + std::string model_id; // Models group commands @@ -158,7 +161,65 @@ bool CommandLineParser::SetupCommand(int argc, char** argv) { app_.add_flag("--verbose", log_verbose, "Verbose logging"); + // cortex version + auto cb = [&](int c) { +#ifdef CORTEX_CPP_VERSION + CLI_LOG(CORTEX_CPP_VERSION); +#else + CLI_LOG("default"); +#endif + }; + app_.add_flag_function("-v", cb, "Cortex version"); + + std::string cortex_version; + bool check_update = true; + { + auto update_cmd = app_.add_subcommand("update", "Update cortex version"); + + update_cmd->add_option("-v", cortex_version, ""); + update_cmd->callback([&cortex_version, &check_update] { + commands::CortexUpdCmd cuc; + cuc.Exec(cortex_version); + check_update = false; + }); + } + CLI11_PARSE(app_, argc, argv); + + // Check new update, only check for stable release for now +#ifdef CORTEX_CPP_VERSION + if (check_update) { + constexpr auto github_host = "https://api.github.com"; + std::ostringstream release_path; + release_path << "/repos/janhq/cortex.cpp/releases/latest"; + CTL_INF("Engine release path: " << github_host << release_path.str()); + + httplib::Client cli(github_host); + if (auto res = cli.Get(release_path.str())) { + 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 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()); + } + } 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)); + } + } +#endif + return true; } diff --git a/engine/services/download_service.h b/engine/services/download_service.h index a8f7f109b..b3c405c9a 100644 --- a/engine/services/download_service.h +++ b/engine/services/download_service.h @@ -4,7 +4,7 @@ #include #include -enum class DownloadType { Model, Engine, Miscellaneous, CudaToolkit }; +enum class DownloadType { Model, Engine, Miscellaneous, CudaToolkit, Cortex }; enum class DownloadStatus { Pending, diff --git a/engine/utils/file_manager_utils.h b/engine/utils/file_manager_utils.h index c958ec8e0..fd2723078 100644 --- a/engine/utils/file_manager_utils.h +++ b/engine/utils/file_manager_utils.h @@ -136,6 +136,8 @@ inline std::filesystem::path GetContainerFolderPath( container_folder_path = GetEnginesContainerPath(); } else if (type == "CudaToolkit") { container_folder_path = current_path; + } else if (type == "Cortex") { + container_folder_path = current_path / "cortex"; } else { container_folder_path = current_path / "misc"; } @@ -158,6 +160,8 @@ inline std::string downloadTypeToString(DownloadType type) { return "Misc"; case DownloadType::CudaToolkit: return "CudaToolkit"; + case DownloadType::Cortex: + return "Cortex"; default: return "UNKNOWN"; } diff --git a/engine/utils/logging_utils.h b/engine/utils/logging_utils.h index 62c44421c..77311c13e 100644 --- a/engine/utils/logging_utils.h +++ b/engine/utils/logging_utils.h @@ -29,4 +29,5 @@ inline bool log_verbose = false; LOG_INFO << msg; \ } else { \ std::cout << msg << std::endl; \ - } \ No newline at end of file + } +