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

Commit 856128c

Browse files
committed
fix: update updater to support install by bash script
1 parent 47e8283 commit 856128c

File tree

3 files changed

+123
-119
lines changed

3 files changed

+123
-119
lines changed

engine/cli/commands/cortex_upd_cmd.cc

Lines changed: 117 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ std::unique_ptr<system_info_utils::SystemInfo> GetSystemInfoWithUniversal() {
3636
return system_info;
3737
}
3838

39-
// https://delta.jan.ai/cortex/v1.0.0-176/windows-amd64/cortex-1.0.0-176-windows-amd64-network-installer.exe
40-
// https://delta.jan.ai/cortex/v1.0.0-176/mac-universal/cortex-1.0.0-176-mac-universal-network-installer.pkg
41-
// https://delta.jan.ai/cortex/v1.0.0-176/linux-amd64/cortex-1.0.0-176-linux-amd64-network-installer.deb
4239
std::string GetNightlyInstallerName(const std::string& v,
4340
const std::string& os_arch) {
4441
const std::string kCortex = "cortex";
@@ -53,13 +50,14 @@ std::string GetNightlyInstallerName(const std::string& v,
5350
#endif
5451
}
5552

56-
// C:\Users\vansa\AppData\Local\Temp\cortex\cortex-windows-amd64-network-installer.exe
5753
std::string GetInstallCmd(const std::string& exe_path) {
5854
#if defined(__APPLE__) && defined(__MACH__)
59-
return "sudo touch /var/tmp/cortex_installer_skip_postinstall_check && sudo installer "
55+
return "sudo touch /var/tmp/cortex_installer_skip_postinstall_check && sudo "
56+
"installer "
6057
"-pkg " +
6158
exe_path +
62-
" -target / && sudo rm /var/tmp/cortex_installer_skip_postinstall_check";
59+
" -target / && sudo rm "
60+
"/var/tmp/cortex_installer_skip_postinstall_check";
6361
#elif defined(__linux__)
6462
return "echo -e \"n\\n\" | sudo SKIP_POSTINSTALL=true apt install -y "
6563
"--allow-downgrades " +
@@ -70,8 +68,22 @@ std::string GetInstallCmd(const std::string& exe_path) {
7068
#endif
7169
}
7270

71+
std::string GetInstallCmdLinux(const std::string& script_path,
72+
const std::string& channel,
73+
const std::string& version) {
74+
std::string cmd = "sudo " + script_path;
75+
if (!channel.empty()) {
76+
cmd += " --channel " + channel;
77+
}
78+
if (!version.empty()) {
79+
cmd += " --version " + version.substr(1);
80+
}
81+
return cmd + " --is_update";
82+
}
83+
7384
bool InstallNewVersion(const std::filesystem::path& dst,
74-
const std::string& exe_path) {
85+
const std::string& exe_script_path,
86+
const std::string& channel, const std::string& version) {
7587
std::filesystem::path temp = dst.parent_path() / "cortex_temp";
7688
auto restore_binary = [&temp, &dst]() {
7789
if (std::filesystem::exists(temp)) {
@@ -86,7 +98,14 @@ bool InstallNewVersion(const std::filesystem::path& dst,
8698
// rename binary
8799
std::rename(dst.string().c_str(), temp.string().c_str());
88100
// install here
89-
CommandExecutor c(GetInstallCmd(exe_path));
101+
std::string install_cmd;
102+
#if defined(__linux__)
103+
install_cmd = GetInstallCmdLinux(exe_script_path, channel, version);
104+
#else
105+
install_cmd = GetInstallCmd(exe_script_path);
106+
#endif
107+
CTL_INF("Cmd: " << install_cmd);
108+
CommandExecutor c(install_cmd);
90109
auto output = c.execute();
91110
if (!std::filesystem::exists(dst)) {
92111
CLI_LOG_ERROR("Something went wrong: could not execute command");
@@ -110,7 +129,6 @@ bool InstallNewVersion(const std::filesystem::path& dst,
110129
}
111130
return true;
112131
}
113-
114132
} // namespace
115133

116134
std::optional<std::string> CheckNewUpdate(
@@ -200,76 +218,6 @@ std::optional<std::string> CheckNewUpdate(
200218
return std::nullopt;
201219
}
202220

203-
bool ReplaceBinaryInflight(const std::filesystem::path& src,
204-
const std::filesystem::path& dst) {
205-
if (src == dst) {
206-
// Already has the newest
207-
return true;
208-
}
209-
210-
std::filesystem::path temp = dst.parent_path() / "cortex_temp";
211-
auto restore_binary = [&temp, &dst]() {
212-
if (std::filesystem::exists(temp)) {
213-
std::rename(temp.string().c_str(), dst.string().c_str());
214-
CLI_LOG("Restored binary file");
215-
}
216-
};
217-
218-
try {
219-
if (std::filesystem::exists(temp)) {
220-
std::filesystem::remove(temp);
221-
}
222-
#if !defined(_WIN32)
223-
// Get permissions of the executable file
224-
struct stat dst_file_stat;
225-
if (stat(dst.string().c_str(), &dst_file_stat) != 0) {
226-
CLI_LOG_ERROR(
227-
"Error getting permissions of executable file: " << dst.string());
228-
return false;
229-
}
230-
231-
// Get owner and group of the executable file
232-
uid_t dst_file_owner = dst_file_stat.st_uid;
233-
gid_t dst_file_group = dst_file_stat.st_gid;
234-
#endif
235-
236-
std::rename(dst.string().c_str(), temp.string().c_str());
237-
std::filesystem::copy_file(
238-
src, dst, std::filesystem::copy_options::overwrite_existing);
239-
240-
#if !defined(_WIN32)
241-
// Set permissions of the executable file
242-
if (chmod(dst.string().c_str(), dst_file_stat.st_mode) != 0) {
243-
CLI_LOG_ERROR(
244-
"Error setting permissions of executable file: " << dst.string());
245-
restore_binary();
246-
return false;
247-
}
248-
249-
// Set owner and group of the executable file
250-
if (chown(dst.string().c_str(), dst_file_owner, dst_file_group) != 0) {
251-
CLI_LOG_ERROR(
252-
"Error setting owner and group of executable file: " << dst.string());
253-
restore_binary();
254-
return false;
255-
}
256-
257-
// Remove cortex_temp
258-
if (unlink(temp.string().c_str()) != 0) {
259-
CLI_LOG_ERROR("Error deleting self: " << strerror(errno));
260-
restore_binary();
261-
return false;
262-
}
263-
#endif
264-
} catch (const std::exception& e) {
265-
CLI_LOG_ERROR("Something went wrong: " << e.what());
266-
restore_binary();
267-
return false;
268-
}
269-
270-
return true;
271-
}
272-
273221
void CortexUpdCmd::Exec(const std::string& v, bool force) {
274222
// Check for update, if current version is the latest, notify to user
275223
if (auto latest_version = commands::CheckNewUpdate(std::nullopt);
@@ -314,6 +262,9 @@ void CortexUpdCmd::Exec(const std::string& v, bool force) {
314262
}
315263

316264
bool CortexUpdCmd::GetStable(const std::string& v) {
265+
#if defined(__linux__)
266+
return GetLinuxInstallScript(v, "stable");
267+
#else
317268
std::optional<std::string> downloaded_exe_path;
318269
auto system_info = GetSystemInfoWithUniversal();
319270
CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch);
@@ -366,10 +317,14 @@ bool CortexUpdCmd::GetStable(const std::string& v) {
366317
});
367318

368319
assert(!!downloaded_exe_path);
369-
return InstallNewVersion(dst, downloaded_exe_path.value());
320+
return InstallNewVersion(dst, downloaded_exe_path.value(), "", "");
321+
#endif
370322
}
371323

372324
bool CortexUpdCmd::GetBeta(const std::string& v) {
325+
#if defined(__linux__)
326+
return GetLinuxInstallScript(v, "beta");
327+
#else
373328
std::optional<std::string> downloaded_exe_path;
374329
auto system_info = GetSystemInfoWithUniversal();
375330
CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch);
@@ -434,7 +389,8 @@ bool CortexUpdCmd::GetBeta(const std::string& v) {
434389
});
435390

436391
assert(!!downloaded_exe_path);
437-
return InstallNewVersion(dst, downloaded_exe_path.value());
392+
return InstallNewVersion(dst, downloaded_exe_path.value(), "", "");
393+
#endif
438394
}
439395

440396
std::optional<std::string> CortexUpdCmd::HandleGithubRelease(
@@ -500,6 +456,9 @@ std::optional<std::string> CortexUpdCmd::HandleGithubRelease(
500456
}
501457

502458
bool CortexUpdCmd::GetNightly(const std::string& v) {
459+
#if defined(__linux__)
460+
return GetLinuxInstallScript(v, "nightly");
461+
#else
503462
auto system_info = GetSystemInfoWithUniversal();
504463
CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch);
505464

@@ -566,6 +525,82 @@ bool CortexUpdCmd::GetNightly(const std::string& v) {
566525
}
567526
});
568527

569-
return InstallNewVersion(dst, localPath.string());
528+
return InstallNewVersion(dst, localPath.string(), "", "");
529+
#endif
530+
}
531+
532+
bool CortexUpdCmd::GetLinuxInstallScript(const std::string& v,
533+
const std::string& channel) {
534+
std::vector<std::string> path_list;
535+
if (channel == "nightly") {
536+
path_list = {"janhq", "cortex.cpp", "feat", "linux-bash-install-script",
537+
"engine", "templates", "linux", "install.sh"};
538+
} else {
539+
path_list = {"janhq", "cortex.cpp", "main", "engine",
540+
"templates", "linux", "install.sh"};
541+
}
542+
auto url_obj = url_parser::Url{
543+
.protocol = "https",
544+
.host = "raw.githubusercontent.com",
545+
.pathParams = path_list,
546+
};
547+
548+
CTL_INF("Linux installer script path: " << url_parser::FromUrl(url_obj));
549+
550+
std::filesystem::path localPath =
551+
std::filesystem::temp_directory_path() / "cortex" / path_list.back();
552+
try {
553+
if (!std::filesystem::exists(localPath.parent_path())) {
554+
std::filesystem::create_directories(localPath.parent_path());
555+
}
556+
} catch (const std::filesystem::filesystem_error& e) {
557+
CLI_LOG_ERROR("Failed to create directories: " << e.what());
558+
return false;
559+
}
560+
auto download_task =
561+
DownloadTask{.id = "cortex",
562+
.type = DownloadType::Cortex,
563+
.items = {DownloadItem{
564+
.id = "cortex",
565+
.downloadUrl = url_parser::FromUrl(url_obj),
566+
.localPath = localPath,
567+
}}};
568+
569+
auto result = download_service_->AddDownloadTask(
570+
download_task, [](const DownloadTask& finishedTask) {
571+
// try to unzip the downloaded file
572+
CTL_INF("Downloaded cortex path: "
573+
<< finishedTask.items[0].localPath.string());
574+
575+
CTL_INF("Finished!");
576+
});
577+
if (result.has_error()) {
578+
CLI_LOG_ERROR("Failed to download: " << result.error());
579+
return false;
580+
}
581+
582+
auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
583+
auto dst = executable_path / GetCortexBinary();
584+
cortex::utils::ScopeExit se([]() {
585+
auto cortex_tmp = std::filesystem::temp_directory_path() / "cortex";
586+
try {
587+
auto n = std::filesystem::remove_all(cortex_tmp);
588+
CTL_INF("Deleted " << n << " files or directories");
589+
} catch (const std::exception& e) {
590+
CTL_WRN(e.what());
591+
}
592+
});
593+
try {
594+
std::filesystem::permissions(localPath,
595+
std::filesystem::perms::owner_exec |
596+
std::filesystem::perms::group_exec |
597+
std::filesystem::perms::others_exec,
598+
std::filesystem::perm_options::add);
599+
} catch (const std::filesystem::filesystem_error& e) {
600+
CTL_WRN("Error: " << e.what());
601+
return false;
602+
}
603+
604+
return InstallNewVersion(dst, localPath.string(), channel, v);
570605
}
571606
} // namespace commands

engine/cli/commands/cortex_upd_cmd.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,6 @@ inline std::string GetReleasePath() {
8686
std::optional<std::string> CheckNewUpdate(
8787
std::optional<std::chrono::milliseconds> timeout);
8888

89-
bool ReplaceBinaryInflight(const std::filesystem::path& src,
90-
const std::filesystem::path& dst);
91-
9289
// This class manages the 'cortex update' command functionality
9390
// There are three release types available:
9491
// - Stable: Only retrieves the latest version
@@ -109,5 +106,11 @@ class CortexUpdCmd {
109106
std::optional<std::string> HandleGithubRelease(const Json::Value& assets,
110107
const std::string& os_arch);
111108
bool GetNightly(const std::string& v);
109+
110+
// For Linux, we use different approach to update
111+
// The installation bash script will perform the following tasks (all logic for update will be put into the bash script):
112+
// - Detect whether the user is performing a new installation or an update.
113+
// - Detect whether a .deb package needs to be installed or if the binary file should be installed directly.
114+
bool GetLinuxInstallScript(const std::string& v, const std::string& channel);
112115
};
113116
} // namespace commands

engine/test/components/test_cortex_upd_cmd.cc

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,37 +34,3 @@ class CortexUpdCmdTest : public ::testing::Test {
3434
}
3535
}
3636
};
37-
38-
TEST_F(CortexUpdCmdTest, return_true_if_self_replace) {
39-
EXPECT_TRUE(commands::ReplaceBinaryInflight("test", "test"));
40-
}
41-
42-
TEST_F(CortexUpdCmdTest, replace_binary_successfully) {
43-
std::filesystem::path new_binary(kNewReleaseFile);
44-
std::filesystem::path cur_binary(kCurReleaseFile);
45-
#if !defined(_WIN32)
46-
struct stat cur_file_stat;
47-
EXPECT_TRUE(stat(cur_binary.string().c_str(), &cur_file_stat) == 0);
48-
#endif
49-
50-
EXPECT_TRUE(commands::ReplaceBinaryInflight(new_binary, cur_binary));
51-
52-
#if !defined(_WIN32)
53-
EXPECT_FALSE(std::filesystem::exists(kCortexTemp));
54-
55-
struct stat new_file_stat;
56-
EXPECT_TRUE(stat(cur_binary.string().c_str(), &new_file_stat) == 0);
57-
EXPECT_EQ(cur_file_stat.st_uid, new_file_stat.st_uid);
58-
EXPECT_EQ(cur_file_stat.st_gid, new_file_stat.st_gid);
59-
EXPECT_EQ(cur_file_stat.st_mode, new_file_stat.st_mode);
60-
#else
61-
EXPECT_TRUE(std::filesystem::exists(kCortexTemp));
62-
#endif
63-
}
64-
65-
TEST_F(CortexUpdCmdTest, should_restore_old_binary_if_has_error) {
66-
std::filesystem::path new_binary("Non-exist");
67-
std::filesystem::path cur_binary(kCurReleaseFile);
68-
EXPECT_FALSE(commands::ReplaceBinaryInflight(new_binary, cur_binary));
69-
EXPECT_FALSE(std::filesystem::exists(kCortexTemp));
70-
}

0 commit comments

Comments
 (0)