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

- Linux: - cortex.deb (Coming soon: Linux installation script) + Linux debian based distros: + cortex-linux-local-installer.deb

- 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. + +

+ + Windows: + cortex-windows-network-installer.exe +

+ +

+ + MacOS (Universal): + cortex-mac-network-installer.pkg +

+ +

+ + Linux debian based distros: + cortex-linux-network-installer.deb +

### 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