diff --git a/README.md b/README.md
index 2c944815a..8ab39aba8 100644
--- a/README.md
+++ b/README.md
@@ -56,14 +56,18 @@ Cortex also has a [Network Installer](#network-installer) which downloads the ne
- For Linux: Download the installer and run the following command in terminal:
```bash
- sudo apt install ./cortex-local-installer.deb
+ # Linux debian based distros
+ curl -s https://raw.githubusercontent.com/janhq/cortex/main/engine/templates/linux/install.sh | sudo bash -s -- --deb_local
+
+ # Other Linux distros
+ curl -s https://raw.githubusercontent.com/janhq/cortex/main/engine/templates/linux/install.sh | sudo bash -s
```
- The binary will be installed in the `/usr/bin/` directory.
@@ -149,6 +153,28 @@ Select a model (1-9):
```
## Advanced Installation
+
+### Network Installer (Stable)
+
+Cortex.cpp is available with a Network Installer, which is a smaller installer but requires internet connection during installation to download the necessary dependencies.
+
+
+
+
+
+
### Beta & Nightly Versions (Local Installer)
@@ -163,7 +189,7 @@ Cortex releases Beta and Nightly versions for advanced users to try new features
Version |
Windows |
MacOS |
- Linux |
+ Linux debian based distros |
| Beta (Preview) |
@@ -218,7 +244,7 @@ Cortex.cpp is available with a Network Installer, which is a smaller installer b
Version Type |
Windows |
MacOS |
- Linux |
+ Linux debian based distros |
| Stable (Recommended) |
diff --git a/docs/docs/installation/linux.mdx b/docs/docs/installation/linux.mdx
index 23e538a52..a14450f47 100644
--- a/docs/docs/installation/linux.mdx
+++ b/docs/docs/installation/linux.mdx
@@ -20,43 +20,35 @@ This instruction is for stable releases. For beta and nightly releases, please r
### Prerequisites
- OpenMPI
+- curl
+- jq
+- tar
### Install Cortex.cpp
-1. Download the Linux installer:
- - From release: https://github.com/janhq/cortex.cpp/releases
- - From quick download links:
- - Local installer `.deb`:
- - Stable: https://app.cortexcpp.com/download/latest/linux-amd64-local
- - Beta: https://app.cortexcpp.com/download/beta/linux-amd64-local
- - Nightly: https://app.cortexcpp.com/download/nightly/linux-amd64-local
- - Network installer `.deb`:
- - Stable: https://app.cortexcpp.com/download/latest/linux-amd64-network
- - Beta: https://app.cortexcpp.com/download/beta/linux-amd64-network
- - Nightly: https://app.cortexcpp.com/download/nightly/linux-amd64-network
- - Binary:
- - Stable: https://app.cortexcpp.com/download/latest/linux-amd64-binary
- - Beta: https://app.cortexcpp.com/download/beta/linux-amd64-binary
- - Nightly: https://app.cortexcpp.com/download/nightly/linux-amd64-binary
-
-2. Install Cortex.cpp using the following command:
- ```bash
- # Installer
- sudo apt install ./cortex--linux-amd64-network-installer.deb
+1. Install cortex with one command
+- Linux debian base distros
+ ```bash
+ # Network installer
+ curl -s https://raw.githubusercontent.com/janhq/cortex/main/engine/templates/linux/install.sh | sudo bash -s
- # Binary
- tar -xvf cortex--linux-amd64.tar.gz
- cd cortex
- sudo mv cortex /usr/bin/cortex
- sudo chmod +x /usr/bin/Cortexs
- sudo mv cortex-server /usr/bin/cortex-server
+ # Local installer
+ curl -s https://raw.githubusercontent.com/janhq/cortex/main/engine/templates/linux/install.sh | sudo bash -s -- --deb_local
+ ```
- ## For binary, you need to install engine manually after extracting the binary
- cortex engines install llama-cpp
- ```
+- Other linux distros
+ ```bash
+ curl -s https://raw.githubusercontent.com/janhq/cortex/main/engine/templates/linux/install.sh | sudo bash -s
+ ```
+
+- Parameters
+ - `--channel ` cortex channel will be installed `stable`, `beta` or `nightly`. Default vaule is `stable`
+ - `--version ` version cortex want to install Ex `--version 1.0.2`. Default the script will get latest version of corresponding channel
+ - `--is_update` the current command run is for update
+ - `--deb_local` Using local installer for linux debian base distros
-3. Ensure that Cortex.cpp is sucessfulyy installed:
+2. Ensure that Cortex.cpp is sucessfulyy installed:
```bash
# Stable
cortex -v
@@ -79,7 +71,7 @@ By default, Cortex.cpp is installed in the following directory:
## Uninstall Cortex.cpp
```bash
# Stable version
-sudo apt remove cortexcpp
+sudo /usr/bin/cortex-uninstall.sh
```
## Build from Source
@@ -115,9 +107,10 @@ sudo apt remove cortexcpp
```
## Update cortex to latest version
-:::info
-The script requires sudo permission. Supported for debians based systems only (Ubuntu, Debian, etc).
+:::warning
+🚧 The script requires sudo permissions and works only if the user follows the installation instructions above or if the cortex binary file and the cortex-server binary file are installed in /usr/bin for all Linux distributions. If your binary files are located in a different folder, please manually update the binary files.
:::
+
```bash
sudo cortex update
```
\ No newline at end of file
diff --git a/engine/cli/commands/cortex_upd_cmd.cc b/engine/cli/commands/cortex_upd_cmd.cc
index 30d1ed3e2..231594346 100644
--- a/engine/cli/commands/cortex_upd_cmd.cc
+++ b/engine/cli/commands/cortex_upd_cmd.cc
@@ -36,9 +36,6 @@ std::unique_ptr GetSystemInfoWithUniversal() {
return system_info;
}
-// https://delta.jan.ai/cortex/v1.0.0-176/windows-amd64/cortex-1.0.0-176-windows-amd64-network-installer.exe
-// https://delta.jan.ai/cortex/v1.0.0-176/mac-universal/cortex-1.0.0-176-mac-universal-network-installer.pkg
-// https://delta.jan.ai/cortex/v1.0.0-176/linux-amd64/cortex-1.0.0-176-linux-amd64-network-installer.deb
std::string GetNightlyInstallerName(const std::string& v,
const std::string& os_arch) {
const std::string kCortex = "cortex";
@@ -53,13 +50,14 @@ std::string GetNightlyInstallerName(const std::string& v,
#endif
}
-// C:\Users\vansa\AppData\Local\Temp\cortex\cortex-windows-amd64-network-installer.exe
std::string GetInstallCmd(const std::string& exe_path) {
#if defined(__APPLE__) && defined(__MACH__)
- return "sudo touch /var/tmp/cortex_installer_skip_postinstall_check && sudo installer "
+ return "sudo touch /var/tmp/cortex_installer_skip_postinstall_check && sudo "
+ "installer "
"-pkg " +
exe_path +
- " -target / && sudo rm /var/tmp/cortex_installer_skip_postinstall_check";
+ " -target / && sudo rm "
+ "/var/tmp/cortex_installer_skip_postinstall_check";
#elif defined(__linux__)
return "echo -e \"n\\n\" | sudo SKIP_POSTINSTALL=true apt install -y "
"--allow-downgrades " +
@@ -70,8 +68,22 @@ std::string GetInstallCmd(const std::string& exe_path) {
#endif
}
+std::string GetInstallCmdLinux(const std::string& script_path,
+ const std::string& channel,
+ const std::string& version) {
+ std::string cmd = "sudo " + script_path;
+ if (!channel.empty()) {
+ cmd += " --channel " + channel;
+ }
+ if (!version.empty()) {
+ cmd += " --version " + version.substr(1);
+ }
+ return cmd + " --is_update";
+}
+
bool InstallNewVersion(const std::filesystem::path& dst,
- const std::string& exe_path) {
+ const std::string& exe_script_path,
+ const std::string& channel, const std::string& version) {
std::filesystem::path temp = dst.parent_path() / "cortex_temp";
auto restore_binary = [&temp, &dst]() {
if (std::filesystem::exists(temp)) {
@@ -86,7 +98,14 @@ bool InstallNewVersion(const std::filesystem::path& dst,
// rename binary
std::rename(dst.string().c_str(), temp.string().c_str());
// install here
- CommandExecutor c(GetInstallCmd(exe_path));
+ std::string install_cmd;
+#if defined(__linux__)
+ install_cmd = GetInstallCmdLinux(exe_script_path, channel, version);
+#else
+ install_cmd = GetInstallCmd(exe_script_path);
+#endif
+ CTL_INF("Cmd: " << install_cmd);
+ CommandExecutor c(install_cmd);
auto output = c.execute();
if (!std::filesystem::exists(dst)) {
CLI_LOG_ERROR("Something went wrong: could not execute command");
@@ -110,7 +129,6 @@ bool InstallNewVersion(const std::filesystem::path& dst,
}
return true;
}
-
} // namespace
std::optional CheckNewUpdate(
@@ -200,76 +218,6 @@ std::optional CheckNewUpdate(
return std::nullopt;
}
-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 = dst.parent_path() / "cortex_temp";
- auto restore_binary = [&temp, &dst]() {
- if (std::filesystem::exists(temp)) {
- std::rename(temp.string().c_str(), dst.string().c_str());
- CLI_LOG("Restored binary file");
- }
- };
-
- try {
- if (std::filesystem::exists(temp)) {
- std::filesystem::remove(temp);
- }
-#if !defined(_WIN32)
- // Get permissions of the executable file
- struct stat dst_file_stat;
- if (stat(dst.string().c_str(), &dst_file_stat) != 0) {
- CLI_LOG_ERROR(
- "Error getting permissions of executable file: " << dst.string());
- return false;
- }
-
- // Get owner and group of the executable file
- uid_t dst_file_owner = dst_file_stat.st_uid;
- gid_t dst_file_group = dst_file_stat.st_gid;
-#endif
-
- std::rename(dst.string().c_str(), temp.string().c_str());
- std::filesystem::copy_file(
- src, dst, std::filesystem::copy_options::overwrite_existing);
-
-#if !defined(_WIN32)
- // Set permissions of the executable file
- if (chmod(dst.string().c_str(), dst_file_stat.st_mode) != 0) {
- CLI_LOG_ERROR(
- "Error setting permissions of executable file: " << dst.string());
- restore_binary();
- return false;
- }
-
- // Set owner and group of the executable file
- if (chown(dst.string().c_str(), dst_file_owner, dst_file_group) != 0) {
- CLI_LOG_ERROR(
- "Error setting owner and group of executable file: " << dst.string());
- restore_binary();
- return false;
- }
-
- // Remove cortex_temp
- if (unlink(temp.string().c_str()) != 0) {
- CLI_LOG_ERROR("Error deleting self: " << strerror(errno));
- restore_binary();
- return false;
- }
-#endif
- } catch (const std::exception& e) {
- CLI_LOG_ERROR("Something went wrong: " << e.what());
- restore_binary();
- return false;
- }
-
- return true;
-}
-
void CortexUpdCmd::Exec(const std::string& v, bool force) {
// Check for update, if current version is the latest, notify to user
if (auto latest_version = commands::CheckNewUpdate(std::nullopt);
@@ -314,6 +262,9 @@ void CortexUpdCmd::Exec(const std::string& v, bool force) {
}
bool CortexUpdCmd::GetStable(const std::string& v) {
+#if defined(__linux__)
+ return GetLinuxInstallScript(v, "stable");
+#else
std::optional downloaded_exe_path;
auto system_info = GetSystemInfoWithUniversal();
CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch);
@@ -366,10 +317,14 @@ bool CortexUpdCmd::GetStable(const std::string& v) {
});
assert(!!downloaded_exe_path);
- return InstallNewVersion(dst, downloaded_exe_path.value());
+ return InstallNewVersion(dst, downloaded_exe_path.value(), "", "");
+#endif
}
bool CortexUpdCmd::GetBeta(const std::string& v) {
+#if defined(__linux__)
+ return GetLinuxInstallScript(v, "beta");
+#else
std::optional downloaded_exe_path;
auto system_info = GetSystemInfoWithUniversal();
CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch);
@@ -434,7 +389,8 @@ bool CortexUpdCmd::GetBeta(const std::string& v) {
});
assert(!!downloaded_exe_path);
- return InstallNewVersion(dst, downloaded_exe_path.value());
+ return InstallNewVersion(dst, downloaded_exe_path.value(), "", "");
+#endif
}
std::optional CortexUpdCmd::HandleGithubRelease(
@@ -500,6 +456,9 @@ std::optional CortexUpdCmd::HandleGithubRelease(
}
bool CortexUpdCmd::GetNightly(const std::string& v) {
+#if defined(__linux__)
+ return GetLinuxInstallScript(v, "nightly");
+#else
auto system_info = GetSystemInfoWithUniversal();
CTL_INF("OS: " << system_info->os << ", Arch: " << system_info->arch);
@@ -566,6 +525,82 @@ bool CortexUpdCmd::GetNightly(const std::string& v) {
}
});
- return InstallNewVersion(dst, localPath.string());
+ return InstallNewVersion(dst, localPath.string(), "", "");
+#endif
+}
+
+bool CortexUpdCmd::GetLinuxInstallScript(const std::string& v,
+ const std::string& channel) {
+ std::vector path_list;
+ if (channel == "nightly") {
+ path_list = {"janhq", "cortex.cpp", "dev", "engine",
+ "templates", "linux", "install.sh"};
+ } else {
+ path_list = {"janhq", "cortex.cpp", "main", "engine",
+ "templates", "linux", "install.sh"};
+ }
+ auto url_obj = url_parser::Url{
+ .protocol = "https",
+ .host = "raw.githubusercontent.com",
+ .pathParams = path_list,
+ };
+
+ CTL_INF("Linux installer script path: " << url_parser::FromUrl(url_obj));
+
+ std::filesystem::path localPath =
+ std::filesystem::temp_directory_path() / "cortex" / path_list.back();
+ try {
+ if (!std::filesystem::exists(localPath.parent_path())) {
+ std::filesystem::create_directories(localPath.parent_path());
+ }
+ } catch (const std::filesystem::filesystem_error& e) {
+ CLI_LOG_ERROR("Failed to create directories: " << e.what());
+ return false;
+ }
+ auto download_task =
+ DownloadTask{.id = "cortex",
+ .type = DownloadType::Cortex,
+ .items = {DownloadItem{
+ .id = "cortex",
+ .downloadUrl = url_parser::FromUrl(url_obj),
+ .localPath = localPath,
+ }}};
+
+ auto result = download_service_->AddDownloadTask(
+ download_task, [](const DownloadTask& finishedTask) {
+ // try to unzip the downloaded file
+ CTL_INF("Downloaded cortex path: "
+ << finishedTask.items[0].localPath.string());
+
+ CTL_INF("Finished!");
+ });
+ if (result.has_error()) {
+ CLI_LOG_ERROR("Failed to download: " << result.error());
+ return false;
+ }
+
+ auto executable_path = file_manager_utils::GetExecutableFolderContainerPath();
+ auto dst = executable_path / GetCortexBinary();
+ cortex::utils::ScopeExit se([]() {
+ auto cortex_tmp = std::filesystem::temp_directory_path() / "cortex";
+ try {
+ auto n = std::filesystem::remove_all(cortex_tmp);
+ CTL_INF("Deleted " << n << " files or directories");
+ } catch (const std::exception& e) {
+ CTL_WRN(e.what());
+ }
+ });
+ try {
+ std::filesystem::permissions(localPath,
+ std::filesystem::perms::owner_exec |
+ std::filesystem::perms::group_exec |
+ std::filesystem::perms::others_exec,
+ std::filesystem::perm_options::add);
+ } catch (const std::filesystem::filesystem_error& e) {
+ CTL_WRN("Error: " << e.what());
+ return false;
+ }
+
+ return InstallNewVersion(dst, localPath.string(), channel, v);
}
} // namespace commands
diff --git a/engine/cli/commands/cortex_upd_cmd.h b/engine/cli/commands/cortex_upd_cmd.h
index bd3fc51df..9c500a999 100644
--- a/engine/cli/commands/cortex_upd_cmd.h
+++ b/engine/cli/commands/cortex_upd_cmd.h
@@ -86,9 +86,6 @@ inline std::string GetReleasePath() {
std::optional CheckNewUpdate(
std::optional timeout);
-bool ReplaceBinaryInflight(const std::filesystem::path& src,
- const std::filesystem::path& dst);
-
// This class manages the 'cortex update' command functionality
// There are three release types available:
// - Stable: Only retrieves the latest version
@@ -109,5 +106,11 @@ class CortexUpdCmd {
std::optional HandleGithubRelease(const Json::Value& assets,
const std::string& os_arch);
bool GetNightly(const std::string& v);
+
+ // For Linux, we use different approach to update
+ // The installation bash script will perform the following tasks (all logic for update will be put into the bash script):
+ // - Detect whether the user is performing a new installation or an update.
+ // - Detect whether a .deb package needs to be installed or if the binary file should be installed directly.
+ bool GetLinuxInstallScript(const std::string& v, const std::string& channel);
};
} // namespace commands
diff --git a/engine/templates/linux/install.sh b/engine/templates/linux/install.sh
index e907150a8..e11b879c6 100644
--- a/engine/templates/linux/install.sh
+++ b/engine/templates/linux/install.sh
@@ -278,4 +278,4 @@ fi
install_cortex $CHANNEL $VERSION $IS_DEB
create_uninstall_script $IS_DEB
-echo "Installation complete. Run cortex-uninstall.sh to uninstall."
\ No newline at end of file
+echo "Installation complete. Run cortex-uninstall.sh to uninstall."
diff --git a/engine/test/components/test_cortex_upd_cmd.cc b/engine/test/components/test_cortex_upd_cmd.cc
index a8815f4f4..772889fbd 100644
--- a/engine/test/components/test_cortex_upd_cmd.cc
+++ b/engine/test/components/test_cortex_upd_cmd.cc
@@ -34,37 +34,3 @@ class CortexUpdCmdTest : public ::testing::Test {
}
}
};
-
-TEST_F(CortexUpdCmdTest, return_true_if_self_replace) {
- EXPECT_TRUE(commands::ReplaceBinaryInflight("test", "test"));
-}
-
-TEST_F(CortexUpdCmdTest, replace_binary_successfully) {
- std::filesystem::path new_binary(kNewReleaseFile);
- std::filesystem::path cur_binary(kCurReleaseFile);
-#if !defined(_WIN32)
- struct stat cur_file_stat;
- EXPECT_TRUE(stat(cur_binary.string().c_str(), &cur_file_stat) == 0);
-#endif
-
- EXPECT_TRUE(commands::ReplaceBinaryInflight(new_binary, cur_binary));
-
-#if !defined(_WIN32)
- EXPECT_FALSE(std::filesystem::exists(kCortexTemp));
-
- struct stat new_file_stat;
- EXPECT_TRUE(stat(cur_binary.string().c_str(), &new_file_stat) == 0);
- EXPECT_EQ(cur_file_stat.st_uid, new_file_stat.st_uid);
- EXPECT_EQ(cur_file_stat.st_gid, new_file_stat.st_gid);
- EXPECT_EQ(cur_file_stat.st_mode, new_file_stat.st_mode);
-#else
- EXPECT_TRUE(std::filesystem::exists(kCortexTemp));
-#endif
-}
-
-TEST_F(CortexUpdCmdTest, should_restore_old_binary_if_has_error) {
- std::filesystem::path new_binary("Non-exist");
- std::filesystem::path cur_binary(kCurReleaseFile);
- EXPECT_FALSE(commands::ReplaceBinaryInflight(new_binary, cur_binary));
- EXPECT_FALSE(std::filesystem::exists(kCortexTemp));
-}
\ No newline at end of file