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

Commit 8c5fe88

Browse files
authored
feat: nightly updater (#1175)
1 parent f25f6dd commit 8c5fe88

File tree

7 files changed

+293
-119
lines changed

7 files changed

+293
-119
lines changed

engine/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ if(DEFINED CMAKE_JS_INC)
8686
# define NPI_VERSION
8787
add_compile_definitions(NAPI_VERSION=8)
8888
endif()
89-
89+
9090
add_compile_definitions(CORTEX_VARIANT="${CORTEX_VARIANT}")
9191
add_compile_definitions(CORTEX_CPP_VERSION="${CORTEX_CPP_VERSION}")
9292
add_compile_definitions(CORTEX_CONFIG_FILE_PATH="${CORTEX_CONFIG_FILE_PATH}")

engine/commands/cortex_upd_cmd.cc

Lines changed: 86 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "cortex_upd_cmd.h"
55
#include "httplib.h"
66
#include "nlohmann/json.hpp"
7+
#include "server_stop_cmd.h"
78
#include "services/download_service.h"
89
#include "utils/archive_utils.h"
910
#include "utils/file_manager_utils.h"
@@ -12,21 +13,38 @@
1213

1314
namespace commands {
1415

15-
namespace {
16-
const std::string kCortexBinary = "cortex-cpp";
17-
}
18-
1916
CortexUpdCmd::CortexUpdCmd() {}
2017

2118
void CortexUpdCmd::Exec(std::string v) {
22-
// TODO(sang) stop server if it is running
19+
{
20+
auto config = file_manager_utils::GetCortexConfig();
21+
httplib::Client cli(config.apiServerHost + ":" + config.apiServerPort);
22+
auto res = cli.Get("/health/healthz");
23+
if (res) {
24+
CLI_LOG("Server is running. Stopping server before updating!");
25+
commands::ServerStopCmd ssc(config.apiServerHost,
26+
std::stoi(config.apiServerPort));
27+
ssc.Exec();
28+
}
29+
}
30+
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
31+
if (!GetNightly(v))
32+
return;
33+
} else {
34+
if (!GetStableAndBeta(v))
35+
return;
36+
}
37+
CLI_LOG("Update cortex sucessfully");
38+
}
39+
40+
bool CortexUpdCmd::GetStableAndBeta(const std::string& v) {
2341
// Check if the architecture and OS are supported
2442
auto system_info = system_info_utils::GetSystemInfo();
2543
if (system_info.arch == system_info_utils::kUnsupported ||
2644
system_info.os == system_info_utils::kUnsupported) {
2745
CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", "
2846
<< system_info.arch);
29-
return;
47+
return false;
3048
}
3149
CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch);
3250

@@ -50,7 +68,7 @@ void CortexUpdCmd::Exec(std::string v) {
5068
std::string matched_variant = "";
5169
for (auto& asset : assets) {
5270
auto asset_name = asset["name"].get<std::string>();
53-
if (asset_name.find("cortex-cpp") != std::string::npos &&
71+
if (asset_name.find(kCortexBinary) != std::string::npos &&
5472
asset_name.find(os_arch) != std::string::npos) {
5573
matched_variant = asset_name;
5674
break;
@@ -59,7 +77,7 @@ void CortexUpdCmd::Exec(std::string v) {
5977
}
6078
if (matched_variant.empty()) {
6179
CTL_ERR("No variant found for " << os_arch);
62-
return;
80+
return false;
6381
}
6482
CTL_INF("Matched variant: " << matched_variant);
6583

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

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-
}
110120
CTL_INF("Finished!");
111121
});
112122
break;
113123
}
114124
}
115125
} catch (const nlohmann::json::parse_error& e) {
116126
std::cerr << "JSON parse error: " << e.what() << std::endl;
117-
return;
127+
return false;
118128
}
119129
} else {
120130
CTL_ERR("HTTP error: " << res->status);
121-
return;
131+
return false;
122132
}
123133
} else {
124134
auto err = res.error();
125135
CTL_ERR("HTTP error: " << httplib::to_string(err));
126-
return;
136+
return false;
127137
}
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
138+
139+
// Replace binary file
145140
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;
141+
auto src = executable_path / "cortex" / kCortexBinary / GetCortexBinary();
142+
auto dst = executable_path / GetCortexBinary();
143+
return ReplaceBinaryInflight(src, dst);
144+
}
145+
146+
bool CortexUpdCmd::GetNightly(const std::string& v) {
147+
// Check if the architecture and OS are supported
148+
auto system_info = system_info_utils::GetSystemInfo();
149+
if (system_info.arch == system_info_utils::kUnsupported ||
150+
system_info.os == system_info_utils::kUnsupported) {
151+
CTL_ERR("Unsupported OS or architecture: " << system_info.os << ", "
152+
<< system_info.arch);
153+
return false;
167154
}
168-
#endif
169-
CLI_LOG("Update cortex sucessfully");
155+
CTL_INF("OS: " << system_info.os << ", Arch: " << system_info.arch);
156+
157+
// Download file
158+
std::string version = v.empty() ? "latest" : std::move(v);
159+
std::ostringstream release_path;
160+
release_path << "cortex/" << version << "/" << system_info.os << "-"
161+
<< system_info.arch << "/" << kNightlyFileName;
162+
CTL_INF("Engine release path: " << kNightlyHost << release_path.str());
163+
164+
auto download_task = DownloadTask{.id = "cortex",
165+
.type = DownloadType::Cortex,
166+
.error = std::nullopt,
167+
.items = {DownloadItem{
168+
.id = "cortex",
169+
.host = kNightlyHost,
170+
.fileName = kNightlyFileName,
171+
.type = DownloadType::Cortex,
172+
.path = release_path.str(),
173+
}}};
174+
175+
DownloadService download_service;
176+
download_service.AddDownloadTask(
177+
download_task, [this](const std::string& absolute_path, bool unused) {
178+
// try to unzip the downloaded file
179+
std::filesystem::path download_path{absolute_path};
180+
CTL_INF("Downloaded engine path: " << download_path.string());
181+
182+
std::filesystem::path extract_path =
183+
download_path.parent_path().parent_path();
184+
185+
archive_utils::ExtractArchive(download_path.string(),
186+
extract_path.string());
187+
188+
CTL_INF("Finished!");
189+
});
190+
191+
// Replace binay file
192+
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
193+
auto src = executable_path / "cortex" / GetCortexBinary();
194+
auto dst = executable_path / GetCortexBinary();
195+
return ReplaceBinaryInflight(src, dst);
170196
}
197+
171198
} // namespace commands

engine/commands/cortex_upd_cmd.h

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,124 @@
11
#pragma once
2-
#include <string>
32
#include <optional>
3+
#include <string>
4+
5+
#include "httplib.h"
6+
#include "nlohmann/json.hpp"
7+
#include "utils/file_manager_utils.h"
8+
#include "utils/logging_utils.h"
49

510
namespace commands {
11+
#ifndef CORTEX_VARIANT
12+
#define CORTEX_VARIANT file_manager_utils::kProdVariant
13+
#endif
14+
constexpr const auto kNightlyHost = "https://delta.jan.ai";
15+
constexpr const auto kNightlyFileName = "cortex-nightly.tar.gz";
16+
const std::string kCortexBinary = "cortex";
17+
18+
inline std::string GetCortexBinary() {
19+
#if defined(_WIN32)
20+
constexpr const bool has_exe = true;
21+
#else
22+
constexpr const bool has_exe = false;
23+
#endif
24+
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
25+
return has_exe ? kCortexBinary + "-nightly.exe"
26+
: kCortexBinary + "-nightly";
27+
} else if (CORTEX_VARIANT == file_manager_utils::kBetaVariant) {
28+
return has_exe ? kCortexBinary + "-beta.exe" : kCortexBinary + "-beta";
29+
} else {
30+
return has_exe ? kCortexBinary + ".exe" : kCortexBinary;
31+
}
32+
}
33+
34+
inline std::string GetHostName() {
35+
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
36+
return "https://delta.jan.ai";
37+
} else {
38+
return "https://api.github.com";
39+
}
40+
}
41+
42+
inline std::string GetReleasePath() {
43+
if (CORTEX_VARIANT == file_manager_utils::kNightlyVariant) {
44+
return "/cortex/latest/version.json";
45+
} else {
46+
return "/repos/janhq/cortex.cpp/releases/latest";
47+
}
48+
}
49+
50+
inline void CheckNewUpdate() {
51+
auto host_name = GetHostName();
52+
auto release_path = GetReleasePath();
53+
CTL_INF("Engine release path: " << host_name << release_path);
654

7-
class CortexUpdCmd{
55+
httplib::Client cli(host_name);
56+
if (auto res = cli.Get(release_path)) {
57+
if (res->status == httplib::StatusCode::OK_200) {
58+
try {
59+
auto json_res = nlohmann::json::parse(res->body);
60+
std::string latest_version = json_res["tag_name"].get<std::string>();
61+
std::string current_version = CORTEX_CPP_VERSION;
62+
if (current_version != latest_version) {
63+
CLI_LOG("\nA new release of cortex is available: "
64+
<< current_version << " -> " << latest_version);
65+
CLI_LOG("To upgrade, run: cortex update");
66+
// CLI_LOG(json_res["html_url"].get<std::string>());
67+
}
68+
} catch (const nlohmann::json::parse_error& e) {
69+
CTL_INF("JSON parse error: " << e.what());
70+
}
71+
} else {
72+
CTL_INF("HTTP error: " << res->status);
73+
}
74+
} else {
75+
auto err = res.error();
76+
CTL_INF("HTTP error: " << httplib::to_string(err));
77+
}
78+
}
79+
80+
inline bool ReplaceBinaryInflight(const std::filesystem::path& src,
81+
const std::filesystem::path& dst) {
82+
if (src == dst) {
83+
// Already has the newest
84+
return true;
85+
}
86+
std::filesystem::path temp = std::filesystem::temp_directory_path() / "cortex_temp";
87+
88+
try {
89+
if (std::filesystem::exists(temp)) {
90+
std::filesystem::remove(temp);
91+
}
92+
93+
std::rename(dst.string().c_str(), temp.string().c_str());
94+
std::filesystem::copy_file(
95+
src, dst, std::filesystem::copy_options::overwrite_existing);
96+
std::filesystem::permissions(dst, std::filesystem::perms::owner_all |
97+
std::filesystem::perms::group_all |
98+
std::filesystem::perms::others_read |
99+
std::filesystem::perms::others_exec);
100+
auto download_folder = src.parent_path();
101+
std::filesystem::remove_all(download_folder);
102+
} catch (const std::exception& e) {
103+
CTL_ERR("Something wrong happened: " << e.what());
104+
if (std::filesystem::exists(temp)) {
105+
std::rename(temp.string().c_str(), dst.string().c_str());
106+
CLI_LOG("Restored binary file");
107+
}
108+
return false;
109+
}
110+
111+
return true;
112+
}
113+
114+
class CortexUpdCmd {
8115
public:
9116
CortexUpdCmd();
10117
void Exec(std::string version);
118+
119+
private:
120+
bool GetStableAndBeta(const std::string& v);
121+
bool GetNightly(const std::string& v);
11122
};
12123

13124
} // namespace commands

0 commit comments

Comments
 (0)