Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit 390111c

Browse files
authored
feat: cortex update command (#1095)
1 parent 209c06a commit 390111c

File tree

6 files changed

+252
-2
lines changed

6 files changed

+252
-2
lines changed

engine/commands/cortex_upd_cmd.cc

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// clang-format off
2+
#include "utils/cortex_utils.h"
3+
// clang-format on
4+
#include "cortex_upd_cmd.h"
5+
#include "httplib.h"
6+
#include "nlohmann/json.hpp"
7+
#include "services/download_service.h"
8+
#include "utils/archive_utils.h"
9+
#include "utils/file_manager_utils.h"
10+
#include "utils/logging_utils.h"
11+
#include "utils/system_info_utils.h"
12+
13+
namespace commands {
14+
15+
namespace {
16+
const std::string kCortexBinary = "cortex-cpp";
17+
}
18+
19+
CortexUpdCmd::CortexUpdCmd() {}
20+
21+
void CortexUpdCmd::Exec(std::string v) {
22+
// TODO(sang) stop server if it is running
23+
// Check if the architecture and OS are supported
24+
auto system_info = system_info_utils::GetSystemInfo();
25+
if (system_info.arch == system_info_utils::kUnsupported ||
26+
system_info.os == system_info_utils::kUnsupported) {
27+
CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", "
28+
<< system_info.arch);
29+
return;
30+
}
31+
CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch);
32+
33+
// Download file
34+
constexpr auto github_host = "https://api.github.com";
35+
// std::string version = v.empty() ? "latest" : std::move(v);
36+
// TODO(sang): support download with version
37+
std::string version = "latest";
38+
std::ostringstream release_path;
39+
release_path << "/repos/janhq/cortex.cpp/releases/" << version;
40+
CTL_INF("Engine release path: " << github_host << release_path.str());
41+
42+
httplib::Client cli(github_host);
43+
if (auto res = cli.Get(release_path.str())) {
44+
if (res->status == httplib::StatusCode::OK_200) {
45+
try {
46+
auto jsonResponse = nlohmann::json::parse(res->body);
47+
auto assets = jsonResponse["assets"];
48+
auto os_arch{system_info.os + "-" + system_info.arch};
49+
50+
std::string matched_variant = "";
51+
for (auto& asset : assets) {
52+
auto asset_name = asset["name"].get<std::string>();
53+
if (asset_name.find("cortex-cpp") != std::string::npos &&
54+
asset_name.find(os_arch) != std::string::npos) {
55+
matched_variant = asset_name;
56+
break;
57+
}
58+
CTL_INF(asset_name);
59+
}
60+
if (matched_variant.empty()) {
61+
CTL_ERR("No variant found for " << os_arch);
62+
return;
63+
}
64+
CTL_INF("Matched variant: " << matched_variant);
65+
66+
for (auto& asset : assets) {
67+
auto asset_name = asset["name"].get<std::string>();
68+
if (asset_name == matched_variant) {
69+
std::string host{"https://github.com"};
70+
71+
auto full_url = asset["browser_download_url"].get<std::string>();
72+
std::string path = full_url.substr(host.length());
73+
74+
auto fileName = asset["name"].get<std::string>();
75+
CTL_INF("URL: " << full_url);
76+
77+
auto download_task = DownloadTask{.id = "cortex",
78+
.type = DownloadType::Cortex,
79+
.error = std::nullopt,
80+
.items = {DownloadItem{
81+
.id = "cortex",
82+
.host = host,
83+
.fileName = fileName,
84+
.type = DownloadType::Cortex,
85+
.path = path,
86+
}}};
87+
88+
DownloadService download_service;
89+
download_service.AddDownloadTask(
90+
download_task,
91+
[this](const std::string& absolute_path, bool unused) {
92+
// try to unzip the downloaded file
93+
std::filesystem::path download_path{absolute_path};
94+
CTL_INF("Downloaded engine path: " << download_path.string());
95+
96+
std::filesystem::path extract_path =
97+
download_path.parent_path().parent_path();
98+
99+
archive_utils::ExtractArchive(download_path.string(),
100+
extract_path.string());
101+
102+
// remove the downloaded file
103+
// TODO(any) Could not delete file on Windows because it is currently hold by httplib(?)
104+
// Not sure about other platforms
105+
try {
106+
std::filesystem::remove(absolute_path);
107+
} catch (const std::exception& e) {
108+
CTL_WRN("Could not delete file: " << e.what());
109+
}
110+
CTL_INF("Finished!");
111+
});
112+
break;
113+
}
114+
}
115+
} catch (const nlohmann::json::parse_error& e) {
116+
std::cerr << "JSON parse error: " << e.what() << std::endl;
117+
return;
118+
}
119+
} else {
120+
CTL_ERR("HTTP error: " << res->status);
121+
return;
122+
}
123+
} else {
124+
auto err = res.error();
125+
CTL_ERR("HTTP error: " << httplib::to_string(err));
126+
return;
127+
}
128+
#if defined(_WIN32)
129+
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
130+
auto temp = executable_path / "cortex_tmp.exe";
131+
remove(temp.string().c_str()); // ignore return code
132+
133+
auto src =
134+
executable_path / "cortex" / kCortexBinary / (kCortexBinary + ".exe");
135+
auto dst = executable_path / (kCortexBinary + ".exe");
136+
// Rename
137+
rename(dst.string().c_str(), temp.string().c_str());
138+
// Update
139+
CopyFile(const_cast<char*>(src.string().c_str()),
140+
const_cast<char*>(dst.string().c_str()), false);
141+
auto download_folder = executable_path / "cortex";
142+
remove(download_folder);
143+
remove(temp.string().c_str());
144+
#else
145+
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
146+
auto temp = executable_path / "cortex_tmp";
147+
auto src = executable_path / "cortex" / kCortexBinary / kCortexBinary;
148+
auto dst = executable_path / kCortexBinary;
149+
if (std::rename(dst.string().c_str(), temp.string().c_str())) {
150+
CTL_ERR("Failed to rename from " << dst.string() << " to "
151+
<< temp.string());
152+
return;
153+
}
154+
try {
155+
std::filesystem::copy_file(
156+
src, dst, std::filesystem::copy_options::overwrite_existing);
157+
std::filesystem::permissions(dst, std::filesystem::perms::owner_all |
158+
std::filesystem::perms::group_all |
159+
std::filesystem::perms::others_read |
160+
std::filesystem::perms::others_exec);
161+
std::filesystem::remove(temp);
162+
auto download_folder = executable_path / "cortex/";
163+
std::filesystem::remove_all(download_folder);
164+
} catch (const std::exception& e) {
165+
CTL_WRN("Something wrong happened: " << e.what());
166+
return;
167+
}
168+
#endif
169+
CLI_LOG("Update cortex sucessfully");
170+
}
171+
} // namespace commands

engine/commands/cortex_upd_cmd.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
#include <string>
3+
#include <optional>
4+
5+
namespace commands {
6+
7+
class CortexUpdCmd{
8+
public:
9+
CortexUpdCmd();
10+
void Exec(std::string version);
11+
};
12+
13+
} // namespace commands

engine/controllers/command_line_parser.cc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "command_line_parser.h"
22
#include "commands/chat_cmd.h"
33
#include "commands/cmd_info.h"
4+
#include "commands/cortex_upd_cmd.h"
45
#include "commands/engine_get_cmd.h"
56
#include "commands/engine_init_cmd.h"
67
#include "commands/engine_list_cmd.h"
@@ -13,6 +14,7 @@
1314
#include "commands/run_cmd.h"
1415
#include "commands/server_stop_cmd.h"
1516
#include "config/yaml_config.h"
17+
#include "httplib.h"
1618
#include "services/engine_service.h"
1719
#include "utils/cortex_utils.h"
1820
#include "utils/logging_utils.h"
@@ -21,6 +23,7 @@ CommandLineParser::CommandLineParser()
2123
: app_("Cortex.cpp CLI"), engine_service_{EngineService()} {}
2224

2325
bool CommandLineParser::SetupCommand(int argc, char** argv) {
26+
2427
std::string model_id;
2528

2629
// Models group commands
@@ -158,7 +161,65 @@ bool CommandLineParser::SetupCommand(int argc, char** argv) {
158161

159162
app_.add_flag("--verbose", log_verbose, "Verbose logging");
160163

164+
// cortex version
165+
auto cb = [&](int c) {
166+
#ifdef CORTEX_CPP_VERSION
167+
CLI_LOG(CORTEX_CPP_VERSION);
168+
#else
169+
CLI_LOG("default");
170+
#endif
171+
};
172+
app_.add_flag_function("-v", cb, "Cortex version");
173+
174+
std::string cortex_version;
175+
bool check_update = true;
176+
{
177+
auto update_cmd = app_.add_subcommand("update", "Update cortex version");
178+
179+
update_cmd->add_option("-v", cortex_version, "");
180+
update_cmd->callback([&cortex_version, &check_update] {
181+
commands::CortexUpdCmd cuc;
182+
cuc.Exec(cortex_version);
183+
check_update = false;
184+
});
185+
}
186+
161187
CLI11_PARSE(app_, argc, argv);
188+
189+
// Check new update, only check for stable release for now
190+
#ifdef CORTEX_CPP_VERSION
191+
if (check_update) {
192+
constexpr auto github_host = "https://api.github.com";
193+
std::ostringstream release_path;
194+
release_path << "/repos/janhq/cortex.cpp/releases/latest";
195+
CTL_INF("Engine release path: " << github_host << release_path.str());
196+
197+
httplib::Client cli(github_host);
198+
if (auto res = cli.Get(release_path.str())) {
199+
if (res->status == httplib::StatusCode::OK_200) {
200+
try {
201+
auto json_res = nlohmann::json::parse(res->body);
202+
std::string latest_version = json_res["tag_name"].get<std::string>();
203+
std::string current_version = CORTEX_CPP_VERSION;
204+
if (current_version != latest_version) {
205+
CLI_LOG("\nA new release of cortex is available: "
206+
<< current_version << " -> " << latest_version);
207+
CLI_LOG("To upgrade, run: cortex update");
208+
CLI_LOG(json_res["html_url"].get<std::string>());
209+
}
210+
} catch (const nlohmann::json::parse_error& e) {
211+
CTL_INF("JSON parse error: " << e.what());
212+
}
213+
} else {
214+
CTL_INF("HTTP error: " << res->status);
215+
}
216+
} else {
217+
auto err = res.error();
218+
CTL_INF("HTTP error: " << httplib::to_string(err));
219+
}
220+
}
221+
#endif
222+
162223
return true;
163224
}
164225

engine/services/download_service.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include <optional>
55
#include <vector>
66

7-
enum class DownloadType { Model, Engine, Miscellaneous, CudaToolkit };
7+
enum class DownloadType { Model, Engine, Miscellaneous, CudaToolkit, Cortex };
88

99
enum class DownloadStatus {
1010
Pending,

engine/utils/file_manager_utils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ inline std::filesystem::path GetContainerFolderPath(
136136
container_folder_path = GetEnginesContainerPath();
137137
} else if (type == "CudaToolkit") {
138138
container_folder_path = current_path;
139+
} else if (type == "Cortex") {
140+
container_folder_path = current_path / "cortex";
139141
} else {
140142
container_folder_path = current_path / "misc";
141143
}
@@ -158,6 +160,8 @@ inline std::string downloadTypeToString(DownloadType type) {
158160
return "Misc";
159161
case DownloadType::CudaToolkit:
160162
return "CudaToolkit";
163+
case DownloadType::Cortex:
164+
return "Cortex";
161165
default:
162166
return "UNKNOWN";
163167
}

engine/utils/logging_utils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ inline bool log_verbose = false;
2929
LOG_INFO << msg; \
3030
} else { \
3131
std::cout << msg << std::endl; \
32-
}
32+
}
33+

0 commit comments

Comments
 (0)