diff --git a/build/ObsidianGitSync b/build/ObsidianGitSync new file mode 100755 index 0000000..71ec677 Binary files /dev/null and b/build/ObsidianGitSync differ diff --git a/include/FsManipulator.hpp b/include/FsManipulator.hpp new file mode 100644 index 0000000..fff58cd --- /dev/null +++ b/include/FsManipulator.hpp @@ -0,0 +1,20 @@ +#ifndef FSMANIPULATOR_HPP +#define FSMANIPULATOR_HPP + +#include + +class FsManipulator { +public: + int is_dir_present(const std::string dir_path); + int create_dir(const std::string dir_path); + int delete_dir(const std::string dir_path); + int is_file_present(const std::string file_path); + int create_file(const std::string file_path); + int delete_file(const std::string file_path); + std::string read_config_var(const std::string file_path, const std::string config_var_name); + std::string add_config_var(const std::string file_path, const std::string config_var_name, const std::string input_value); + std::string update_config_var(const std::string file_path, const std::string config_var_name, const std::string input_value); + std::string delete_config_var(const std::string file_path, const std::string config_var_name); +}; + +#endif // FSMANIPULATOR_HPP \ No newline at end of file diff --git a/include/GitOperations.hpp b/include/GitOperations.hpp new file mode 100644 index 0000000..d52c03b --- /dev/null +++ b/include/GitOperations.hpp @@ -0,0 +1,38 @@ +#ifndef GITOPERATIONS_HPP +#define GITOPERATIONS_HPP + +#include +#include + +class GitOperations { +public: + // For the is_repo_present method, use the var workspace_dir to look for a folder with the same name as var main_repository. + // If the folder is available, verify that it is a valid git repo using the var imported from the config. + // Try connecting to it using the provided credentials and links from the config. + // If all passes, return 0, if not, return a non-zero and log the error using Logging class methods. + int is_repo_present(const std::string config_path); + // Clone the repo using git2 and return a 0 for success and a non zero for error while logging everything. + int clone_repo(const std::string config_path); + git_repository* open_repo(const std::string config_path); + // are_changes_present checks for changes that are not commited and returns a 0 for true and non-zero for errors. Log everything. + int are_unindexed_changes_present(git_repository *repo); + // add_to_index would detect any new changes in the repo and add them to index to prepare them for a commit. + int add_to_index(git_repository *repo); + int are_uncommitted_changes_present(git_repository *repo); + int are_unpushed_commits_present(git_repository *repo); + // create_commit commits everything in the index and uses the vars git_username and commits_email from the config file. Return 0 or non-zero as above and log everthing. + int create_commit(const std::string config_path, git_repository *repo); + // push_to_remote pushes the commits to remote. Return 0 or non-zero like above and log everything. + int push_to_remote(const std::string config_path, git_repository *repo); + // Fetches latest changes from the remote repository. + int fetch_remote(const std::string config_path, git_repository *repo); + // Method to handle non-fast-forward error + int handle_non_fast_forward_error(const std::string config_path); + // Backup everything in repo_dir except .git dir to backup_dir + int backup_user_notes(const std::string repo_dir, const std::string backup_dir); + // Replace the contents of repo_dir except .git dir with the contents of backup_dir and delete backup_dir if success. + int recover_user_notes(const std::string repo_dir, const std::string backup_dir); + +}; + +#endif // GITOPERATIONS_HPP \ No newline at end of file diff --git a/include/Init.hpp b/include/Init.hpp new file mode 100644 index 0000000..aa70c88 --- /dev/null +++ b/include/Init.hpp @@ -0,0 +1,12 @@ +#ifndef INIT_HPP +#define INIT_HPP + +#include + +class Init { +public: + // root_dir in linux is "~/.config/ObsidianGitSync". + std::string init_for_linux_desktop(const std::string root_dir, const std::string workspace_dir); +}; + +#endif // INIT_HPP \ No newline at end of file diff --git a/include/LoadEnv.hpp b/include/LoadEnv.hpp new file mode 100644 index 0000000..f3c7109 --- /dev/null +++ b/include/LoadEnv.hpp @@ -0,0 +1,13 @@ +#ifndef LOADENV_HPP +#define LOADENV_HPP + +#include +#include + +class LoadEnv { +public: + std::string load_env_variables(const std::string file_path, const nlohmann::json json_data, const bool update_var); + std::string get_env_var(const std::string file_path, const std::string var_name, const bool update_var); +}; + +#endif // LOADENV_HPP \ No newline at end of file diff --git a/include/Logging.hpp b/include/Logging.hpp new file mode 100644 index 0000000..bf21e2d --- /dev/null +++ b/include/Logging.hpp @@ -0,0 +1,11 @@ +#ifndef LOGGING_HPP +#define LOGGING_HPP + +#include + +class Logging { +public: + int log_to_file(const std::string new_data); +}; + +#endif // LOGGING_HPP \ No newline at end of file diff --git a/include/NetworkChecker.hpp b/include/NetworkChecker.hpp new file mode 100644 index 0000000..8707a84 --- /dev/null +++ b/include/NetworkChecker.hpp @@ -0,0 +1,9 @@ +#ifndef NETWORKCHECKER_HPP +#define NETWORKCHECKER_HPP + +class NetworkChecker { +public: + int checkConnectivity(); +}; + +#endif // NETWORKCHECKER_HPP diff --git a/include/Prompting.hpp b/include/Prompting.hpp new file mode 100644 index 0000000..b074e04 --- /dev/null +++ b/include/Prompting.hpp @@ -0,0 +1,12 @@ +#ifndef PROMPTING_HPP +#define PROMPTING_HPP + +#include + +class Prompting { +public: + std::string prompt_user_for_var(const std::string file_path, const std::string var_name, const std::string default_value = ""); + std::string is_prompt_required(const std::string file_path, const std::string var_name, const std::string default_value = "", const bool update_var = false); +}; + +#endif // PROMPTING_HPP \ No newline at end of file diff --git a/include/SshWorker.hpp b/include/SshWorker.hpp new file mode 100644 index 0000000..09abe8c --- /dev/null +++ b/include/SshWorker.hpp @@ -0,0 +1,17 @@ +#ifndef SSHWORKER_HPP +#define SSHWORKER_HPP + +#include + +class SshWorker { +public: + // This class will use libssh library and retrieve vars like SSH_KEY_NAME, ROOT_DIR, SSH_DIR, COMMITS_EMAIL from config_path using the FsManipulator method read_config_var. + // Use the Logging method log_to_file that takes in a string to log everything, from successes to errors with errors being prepended with a string 'Error: '. + // Then use method from FsManipulator to check if the 2 ssh keys exist using the file path ssh_dir/ssh_key_name and ssh_dir/ssh_key_name.pub, if they exist, try to verify them that they are actually SSH_KEYTYPE_ED25519 and not just random file with the right names that we looked for, if they are verified ssh keys, return 0, else return a non-zero number and use logging class to log all the returns or log worthy events. + // + int is_ssh_setup(const std::string config_path); + // Create a new SSH_KEYTYPE_ED25519 keypair using the keynames ssh_key_name and add .pub to the public key, then create a private function that will be used by the inhabitants of ssh_worker.cpp only to change the email at the end of the pub key file to the var commits_email. Log everything including errors in the way specified above. + std::string setup_new_ssh_keypair(const std::string config_path); +}; + +#endif // SSHWORKER_HPP \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..19cfde4 --- /dev/null +++ b/install.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Function to install dependencies on Debian-based systems +install_on_debian() { + echo "Detected a Debian-based Linux distribution." + sudo apt-get update + sudo apt-get install -y libgit2-dev libssh2-1-dev libconfig++-dev libssh-dev libcurl4-openssl-dev +} + +# Function to install dependencies on Red Hat-based systems +install_on_redhat() { + echo "Detected a Red Hat-based Linux distribution." + sudo yum install -y epel-release + sudo yum install -y libgit2-devel libssh2-devel libconfig-devel libssh-devel libcurl-devel +} + +# Function to install dependencies on Fedora +install_on_fedora() { + echo "Detected Fedora." + sudo dnf install -y libgit2-devel libssh2-devel libconfig-devel libssh-devel libcurl-devel +} + +# Function to install dependencies on Arch Linux +install_on_arch() { + echo "Detected Arch Linux." + sudo pacman -Syu --noconfirm + sudo pacman -S --noconfirm libgit2 libssh2 libconfig libssh curl +} + +# Detect the distribution +if [ -f /etc/os-release ]; then + . /etc/os-release + case $ID in + ubuntu | debian) + install_on_debian + ;; + rhel | centos) + install_on_redhat + ;; + fedora) + install_on_fedora + ;; + arch | manjaro) + install_on_arch + ;; + *) + echo "Your distribution ($ID) is not recognized. Please manually install the dependencies." + exit 1 + ;; + esac +else + echo "Cannot identify the Linux distribution. Please manually install the dependencies." + exit 1 +fi + +# Function to download and run the binary package +download_and_run() { + echo "Downloading the binary package from GitHub releases..." + # Replace with the direct link to your binary in GitHub releases + binary_url="https://github.com/username/repository/releases/download/v1.0/your-binary" + curl -L -o your-binary "$binary_url" + + echo "Making the binary executable..." + chmod +x your-binary + + echo "Running the binary..." + ./your-binary +} + +# After installing dependencies, download and run the binary +download_and_run \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..5827af4 --- /dev/null +++ b/main.cpp @@ -0,0 +1,14 @@ +#include "Init.hpp" +#include +#include +#include // For getenv + +int main() { + Init init; + std::string home_dir = getenv("HOME"); + std::string root_dir = home_dir + "/.config/ObsidianGitSync"; + std::string workspace_dir = home_dir + "/Documents/Documents/Obsidian"; + + init.init_for_linux_desktop(root_dir, workspace_dir); + return 0; +} diff --git a/platform.cpp b/platform.cpp new file mode 100644 index 0000000..4a332bd --- /dev/null +++ b/platform.cpp @@ -0,0 +1,18 @@ +#include + +int main() { + #ifdef _WIN32 + std::cout << "Running on Windows" << std::endl; + #elif __APPLE__ + std::cout << "Running on Apple" << std::endl; + #elif __linux__ + #ifdef __ANDROID__ + std::cout << "Running on Android" << std::endl; + #else + std::cout << "Running on Real Linux" << std::endl; + #endif + #else + std::cout << "Unknown platform" << std::endl; + #endif + return 0; +} diff --git a/utils/fs_manipulator.cpp b/utils/fs_manipulator.cpp new file mode 100644 index 0000000..920e9d8 --- /dev/null +++ b/utils/fs_manipulator.cpp @@ -0,0 +1,174 @@ +#include "FsManipulator.hpp" +#include "Logging.hpp" +#include +#include +#include +#include +#include + +Logging logging; + +int FsManipulator::is_dir_present(const std::string dir_path) { + try { + if (std::filesystem::exists(dir_path) && std::filesystem::is_directory(dir_path)) { + logging.log_to_file("Directory '" + dir_path + "' is Present."); + return 0; + } + logging.log_to_file("Directory '" + dir_path + "' is not Present."); + return -1; + } catch (const std::filesystem::filesystem_error& e) { + logging.log_to_file(e.what()); + return -1; + } +} + +int FsManipulator::create_dir(const std::string dir_path) { + try { + if (std::filesystem::create_directories(dir_path)) { + logging.log_to_file("Directory '" + dir_path + "' was Created."); + return 0; + } + logging.log_to_file("Directory '" + dir_path + "' was not Created."); + return -1; + } catch (const std::filesystem::filesystem_error& e) { + logging.log_to_file(e.what()); + return -1; + } +} + +int FsManipulator::delete_dir(const std::string dir_path) { + try { + if (std::filesystem::remove_all(dir_path)) { + logging.log_to_file("Directory '" + dir_path + "' was Deleted."); + return 0; + } + logging.log_to_file("Directory '" + dir_path + "' was not Deleted."); + return -1; + } catch (const std::filesystem::filesystem_error& e) { + logging.log_to_file(e.what()); + return -1; + } +} + +int FsManipulator::is_file_present(const std::string file_path) { + try { + if (std::filesystem::exists(file_path) && std::filesystem::is_regular_file(file_path)) { + logging.log_to_file("File '" + file_path + "' is Present."); + return 0; + } + logging.log_to_file("File '" + file_path + "' is not Present."); + return -1; + } catch (const std::filesystem::filesystem_error& e) { + logging.log_to_file(e.what()); + return -1; + } +} + +int FsManipulator::create_file(const std::string file_path) { + try { + std::ofstream file(file_path); + if (file.is_open()) { + file.close(); + logging.log_to_file("File '" + file_path + "' was Created."); + return 0; + } + logging.log_to_file("File '" + file_path + "' was not Created."); + return -1; + } catch (const std::ios_base::failure& e) { + logging.log_to_file(e.what()); + return -1; + } +} + +int FsManipulator::delete_file(const std::string file_path) { + try { + if (std::filesystem::remove(file_path)) { + logging.log_to_file("File '" + file_path + "' was Deleted."); + return 0; + } + logging.log_to_file("File '" + file_path + "' was not Deleted."); + return -1; + } catch (const std::filesystem::filesystem_error& e) { + logging.log_to_file(e.what()); + return -1; + } +} + +std::string FsManipulator::read_config_var(const std::string file_path, const std::string config_var_name) { + libconfig::Config config; + try { + config.readFile(file_path.c_str()); + const char *out_value; + if (config.lookupValue(config_var_name, out_value)) { + std::string result = out_value; + logging.log_to_file("Config variable '" + config_var_name + "' was Read."); + return result; + } else { + logging.log_to_file("Error: Config variable '" + config_var_name + "' was Not found."); + return "VAR_NOT_FOUND"; + } + } catch (const libconfig::FileIOException &fioex) { + logging.log_to_file("Error: I/O error while reading file '" + file_path + "' for config variable '" + config_var_name + "'."); + return "ERROR"; + } catch (const libconfig::ParseException &pex) { + int getLine = pex.getLine(); + std::string getError = pex.getError(); + logging.log_to_file("Error: Parse error at " + file_path + ":" + std::to_string(getLine) + " - " + getError); + return "ERROR"; + } +} + +std::string FsManipulator::add_config_var(const std::string file_path, const std::string config_var_name, const std::string input_value) { + libconfig::Config config; + try { + config.readFile(file_path.c_str()); + config.getRoot().add(config_var_name.c_str(), libconfig::Setting::TypeString) = input_value.c_str(); + config.writeFile(file_path.c_str()); + logging.log_to_file("Config variable '" + config_var_name + "' was Added."); + return "SUCCESS"; + } catch (const libconfig::SettingException &sex) { + std::string what = sex.what(); + logging.log_to_file("Error: Setting error: '" + what + "'."); + return "ERROR"; + } catch (...) { + logging.log_to_file("Error: An error occurred while adding the config var '" + config_var_name + "'."); + return "ERROR"; + } +} + +std::string FsManipulator::update_config_var(const std::string file_path, const std::string config_var_name, const std::string input_value) { + libconfig::Config config; + try { + config.readFile(file_path.c_str()); + libconfig::Setting &setting = config.lookup(config_var_name); + setting = input_value.c_str(); + config.writeFile(file_path.c_str()); + logging.log_to_file("Config variable '" + config_var_name + "' was updated."); + return "SUCCESS"; + } catch (const libconfig::SettingNotFoundException &snfex) { + logging.log_to_file("Error: No such config var '" + config_var_name + "'."); + return "ERROR"; + } catch (...) { + logging.log_to_file("Error: An error occurred while updating the config var '" + config_var_name + "'."); + return "ERROR"; + } +} + +std::string FsManipulator::delete_config_var(const std::string file_path, const std::string config_var_name) { + libconfig::Config config; + try { + libconfig::Setting &root = config.getRoot(); + if(root.exists(config_var_name.c_str())) { + root.remove(config_var_name.c_str()); + config.writeFile(file_path.c_str()); + logging.log_to_file("Config variable '" + config_var_name + "' was deleted."); + return "SUCCESS"; + } else { + logging.log_to_file("Error: No such config var '" + config_var_name + "'."); + return "VAR_NOT_FOUND"; + } + } catch (...) { + logging.log_to_file("Error: An error occurred while deleting the config var '" + config_var_name + "'."); + return "ERROR"; + } +} diff --git a/utils/git_operations.cpp b/utils/git_operations.cpp new file mode 100644 index 0000000..e6b64f9 --- /dev/null +++ b/utils/git_operations.cpp @@ -0,0 +1,488 @@ +#include "GitOperations.hpp" +#include "FsManipulator.hpp" +#include "Logging.hpp" +#include +#include +#include + +namespace fs = std::filesystem; + +int cred_acquire_cb(git_cred **cred, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload) { + FsManipulator fs; + + std::string* config_path_ptr = reinterpret_cast(payload); + std::string config_path = *config_path_ptr; + + std::string ssh_dir = fs.read_config_var(config_path, "SSH_DIR"); + std::string ssh_key_name = fs.read_config_var(config_path, "SSH_KEY_NAME"); + + std::string ssh_pub_key_path = ssh_dir + "/" + ssh_key_name + ".pub"; + std::string ssh_priv_key_path = ssh_dir + "/" + ssh_key_name; + + return git_cred_ssh_key_new(cred, username_from_url, ssh_pub_key_path.c_str(), ssh_priv_key_path.c_str(), ""); +} + +int GitOperations::is_repo_present(const std::string config_path) { + git_libgit2_init(); + FsManipulator fs; + Logging log; + std::string repo_dir = fs.read_config_var(config_path, "WORKSPACE_DIR") + "/" + fs.read_config_var(config_path, "MAIN_REPOSITORY"); + if (fs.is_dir_present(repo_dir) != 0) { + log.log_to_file("Error: Repository directory not found"); + git_libgit2_shutdown(); + return -1; + } + git_repository *repo = nullptr; + if (git_repository_open(&repo, repo_dir.c_str()) != 0) { + log.log_to_file("Error: Failed to open repository"); + git_libgit2_shutdown(); + return -1; + } + git_repository_free(repo); + log.log_to_file("Repository is present and valid"); + git_libgit2_shutdown(); + return 0; +} + +int GitOperations::clone_repo(const std::string config_path) { + git_libgit2_init(); + FsManipulator fs; + Logging log; + git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT; + git_repository *repo = nullptr; + + clone_opts.fetch_opts.callbacks.credentials = cred_acquire_cb; + clone_opts.fetch_opts.callbacks.payload = (void*)&config_path; + + std::string url = "ssh://git@" + fs.read_config_var(config_path, "GIT_HOST") + "/" + fs.read_config_var(config_path, "GIT_USERNAME") + "/" + fs.read_config_var(config_path, "MAIN_REPOSITORY") + ".git"; + std::string path = fs.read_config_var(config_path, "WORKSPACE_DIR") + "/" + fs.read_config_var(config_path, "MAIN_REPOSITORY"); + + int error = git_clone(&repo, url.c_str(), path.c_str(), &clone_opts); + if (error != 0) { + const git_error *err = giterr_last(); + std::string message = err->message; + log.log_to_file("Error: Failed to clone repository with message: '" + message + "'."); + git_libgit2_shutdown(); + return -1; + } + git_repository_free(repo); + log.log_to_file("Repository cloned successfully"); + + git_libgit2_shutdown(); + return 0; +} + +git_repository* GitOperations::open_repo(const std::string config_path) { + git_libgit2_init(); + // Changed return type to git_repository* + FsManipulator fs; + Logging log; + std::string repo_dir = fs.read_config_var(config_path, "WORKSPACE_DIR") + "/" + fs.read_config_var(config_path, "MAIN_REPOSITORY"); + git_repository *repo = nullptr; + if (git_repository_open(&repo, repo_dir.c_str()) != 0) { + log.log_to_file("Error: Failed to open repository"); + git_libgit2_shutdown(); + return nullptr; // Changed to return nullptr on error + } + log.log_to_file("Repository opened successfully"); + git_libgit2_shutdown(); + return repo; +} + +int GitOperations::are_unindexed_changes_present(git_repository *repo) { + git_libgit2_init(); + Logging log; + git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; + status_opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; // Show working directory only status + status_opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; // Include untracked and renamed files + + git_status_list *status; + if (git_status_list_new(&status, repo, &status_opts) != 0) { + log.log_to_file("Error: Failed to retrieve status"); + git_libgit2_shutdown(); + return -1; + } + size_t status_count = git_status_list_entrycount(status); + git_status_list_free(status); + if (status_count == 0) { + log.log_to_file("No Unindexed changes detected"); + git_libgit2_shutdown(); + return 1; + } + log.log_to_file("Unindexed Changes detected"); + git_libgit2_shutdown(); + return 0; +} + +int GitOperations::add_to_index(git_repository *repo) { + git_libgit2_init(); + Logging log; + git_index *index = nullptr; + if (git_repository_index(&index, repo) != 0) { + log.log_to_file("Error: Failed to get repository index"); + git_libgit2_shutdown(); + return -1; + } + if (git_index_add_all(index, nullptr, 0, nullptr, nullptr) != 0) { + log.log_to_file("Error: Failed to add changes to index"); + git_index_free(index); + git_libgit2_shutdown(); + return -1; + } + if (git_index_write(index) != 0) { + log.log_to_file("Error: Failed to write index"); + git_index_free(index); + git_libgit2_shutdown(); + return -1; + } + git_index_free(index); + log.log_to_file("Successfully added changes to index"); + git_libgit2_shutdown(); + return 0; +} + +int GitOperations::are_uncommitted_changes_present(git_repository *repo) { + git_libgit2_init(); + Logging log; + git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; + status_opts.show = GIT_STATUS_SHOW_INDEX_ONLY; // Show index only status + status_opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; // Include untracked and renamed files + + git_status_list *status; + if (git_status_list_new(&status, repo, &status_opts) != 0) { + log.log_to_file("Error: Failed to retrieve status"); + git_libgit2_shutdown(); + return -1; + } + size_t status_count = git_status_list_entrycount(status); + git_status_list_free(status); + if (status_count == 0) { + log.log_to_file("No uncommitted changes detected"); + git_libgit2_shutdown(); + return 1; + } + log.log_to_file("Uncommitted changes detected"); + git_libgit2_shutdown(); + return 0; +} + +int GitOperations::create_commit(const std::string config_path, git_repository *repo) { + git_libgit2_init(); + FsManipulator fs; + Logging log; + git_signature *sig = nullptr; + git_index *index = nullptr; + git_oid tree_id, commit_id; + git_tree *tree = nullptr; + + if (git_signature_now(&sig, fs.read_config_var(config_path, "GIT_USERNAME").c_str(), fs.read_config_var(config_path, "COMMITS_EMAIL").c_str()) != 0) { + log.log_to_file("Error: Failed to create signature"); + git_libgit2_shutdown(); + return -1; + } + if (git_repository_index(&index, repo) != 0 || git_index_write_tree(&tree_id, index) != 0 || git_tree_lookup(&tree, repo, &tree_id) != 0) { + log.log_to_file("Error: Index or tree operation failed"); + git_signature_free(sig); + git_index_free(index); + git_libgit2_shutdown(); + return -1; + } + git_oid parent_id; + git_reference_name_to_id(&parent_id, repo, "HEAD"); + git_commit *parent = nullptr; + git_commit_lookup(&parent, repo, &parent_id); + if (git_commit_create_v(&commit_id, repo, "HEAD", sig, sig, NULL, "Commit made via ObsidianGitSync", tree, 1, parent) != 0) { + log.log_to_file("Error: Failed to create commit"); + git_signature_free(sig); + git_index_free(index); + git_tree_free(tree); + git_libgit2_shutdown(); + return -1; + } + git_signature_free(sig); + git_index_free(index); + git_tree_free(tree); + log.log_to_file("Commit created successfully"); + git_libgit2_shutdown(); + return 0; +} + +int GitOperations::are_unpushed_commits_present(git_repository *repo) { + git_libgit2_init(); + Logging log; + int error = 0; + + git_oid local_oid, remote_oid; + git_reference *head_ref = nullptr, *remote_ref = nullptr; + const char *branch_name = nullptr; + char remote_branch_ref_name[1024]; + + // Get the current HEAD + error = git_repository_head(&head_ref, repo); + if (error != 0) { + log.log_to_file("Error: Failed to get HEAD reference"); + git_libgit2_shutdown(); + return -1; + } + + // Get the branch name from HEAD + branch_name = git_reference_shorthand(head_ref); + if (!branch_name) { + log.log_to_file("Error: Failed to get branch name from HEAD reference"); + git_reference_free(head_ref); + git_libgit2_shutdown(); + return -1; + } + + // Construct the reference name of the remote tracking branch + snprintf(remote_branch_ref_name, sizeof(remote_branch_ref_name), "refs/remotes/origin/%s", branch_name); + + // Look up the remote tracking branch + error = git_reference_lookup(&remote_ref, repo, remote_branch_ref_name); + if (error != 0) { + log.log_to_file("Error: Failed to find remote tracking branch"); + git_reference_free(head_ref); + git_libgit2_shutdown(); + return -1; + } + + // Get the OIDs for local and remote + const git_oid *local_oid_ptr = git_reference_target(head_ref); + const git_oid *remote_oid_ptr = git_reference_target(remote_ref); + if (!local_oid_ptr || !remote_oid_ptr) { + log.log_to_file("Error: Failed to get OIDs for local or remote branch"); + git_reference_free(head_ref); + git_reference_free(remote_ref); + git_libgit2_shutdown(); + return -1; + } + git_oid_cpy(&local_oid, local_oid_ptr); + git_oid_cpy(&remote_oid, remote_oid_ptr); + + git_reference_free(head_ref); + git_reference_free(remote_ref); + + // Compare OIDs + if (git_oid_cmp(&local_oid, &remote_oid) != 0) { + log.log_to_file("Unpushed commits detected"); + git_libgit2_shutdown(); + return 0; + } + + log.log_to_file("No unpushed commits detected"); + git_libgit2_shutdown(); + return 1; +} + +int GitOperations::fetch_remote(const std::string config_path, git_repository *repo) { + git_libgit2_init(); + Logging log; + git_remote *remote = nullptr; + int error = git_remote_lookup(&remote, repo, "origin"); + + if (error != 0) { + const git_error *last_error = giterr_last(); + log.log_to_file("Error: Failed to lookup remote - " + std::string(last_error->message)); + git_libgit2_shutdown(); + return -1; + } + + git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; + fetch_opts.callbacks.credentials = cred_acquire_cb; + fetch_opts.callbacks.payload = (void*)&config_path; + + error = git_remote_fetch(remote, nullptr, &fetch_opts, "Fetch completed"); + + if (error != 0) { + const git_error *last_error = giterr_last(); + log.log_to_file("Error: Failed to fetch from remote - " + std::string(last_error->message)); + git_remote_free(remote); + git_libgit2_shutdown(); + return -1; + } + + log.log_to_file("Fetched from remote successfully"); + git_remote_free(remote); + git_libgit2_shutdown(); + return 0; +} + +int GitOperations::backup_user_notes(const std::string repo_dir, const std::string backup_dir) { + FsManipulator fs; + Logging log; + + // Check if repo_dir and backup_dir exist + if (fs.is_dir_present(repo_dir) != 0 && fs.is_dir_present(backup_dir) != 0) { + log.log_to_file("Error: Repository directory or Backup directory not found."); + return -1; + } + + // Create backup directory if it doesn't exist + if (fs.is_dir_present(backup_dir) != 0 && fs.create_dir(backup_dir) != 0) { + log.log_to_file("Error: Failed to create backup directory."); + return -1; + } + + // Backup the contents + try { + // Iterate through the files and directories in repo_dir + for (const auto& entry : fs::recursive_directory_iterator(repo_dir)) { + const auto& path = entry.path(); + auto relative_path = fs::relative(path, repo_dir); + if (relative_path == ".git" || relative_path.string().rfind(".git/", 0) == 0) { + // Skip the .git directory and its contents + continue; + } + + fs::path target_path = backup_dir / relative_path; + if (entry.is_directory()) { + fs::create_directory(target_path); + } else if (entry.is_regular_file()) { + fs::copy(path, target_path, fs::copy_options::overwrite_existing); + } + } + } catch (const std::exception& e) { + log.log_to_file("Error: Failed to backup user notes. Exception: " + std::string(e.what())); + return -1; + } + + log.log_to_file("User notes backed up successfully."); + return 0; +} + +int GitOperations::recover_user_notes(const std::string repo_dir, const std::string backup_dir) { + FsManipulator fs; + Logging log; + + // Check if backup_dir exists + if (fs.is_dir_present(backup_dir) != 0) { + log.log_to_file("Error: Backup directory not found for recovery."); + return -1; + } + + try { + // Clear repo_dir except for the .git directory + for (const auto& entry : fs::directory_iterator(repo_dir)) { + if (entry.path().filename() == ".git") { + // Skip the .git directory + continue; + } + + if (entry.is_directory()) { + fs::remove_all(entry.path()); + } else { + fs::remove(entry.path()); + } + } + + // Recover the contents from backup_dir + for (const auto& entry : fs::recursive_directory_iterator(backup_dir)) { + fs::path target_path = repo_dir / fs::relative(entry.path(), backup_dir); + if (entry.is_directory()) { + fs::create_directory(target_path); + } else if (entry.is_regular_file()) { + fs::copy(entry.path(), target_path, fs::copy_options::overwrite_existing); + } + } + } catch (const std::exception& e) { + log.log_to_file("Error: Failed to recover user notes. Exception: " + std::string(e.what())); + return -1; + } + + // Delete backup directory + if (fs.delete_dir(backup_dir) != 0) { + log.log_to_file("Warning: Failed to delete backup directory after recovery."); + // Not returning -1 here as the primary operation of recovery is successful + } + + log.log_to_file("User notes recovered successfully."); + return 0; +} + +int GitOperations::handle_non_fast_forward_error(const std::string config_path) { + git_libgit2_init(); + Logging log; + FsManipulator fs; + + std::string repo_dir = fs.read_config_var(config_path, "WORKSPACE_DIR") + "/" + fs.read_config_var(config_path, "MAIN_REPOSITORY"); + std::string backup_dir = fs.read_config_var(config_path, "BACKUP_DIR"); + + // Backup everything except .git dir in the repo_dir to backup_dir + if(backup_user_notes(repo_dir, backup_dir) != 0) { + log.log_to_file("Error: Failed to backup user notes before dealing with non-fast-forward error."); + } + + // Clear the contents of the local repository directory + try { + for (const auto& entry : fs::directory_iterator(repo_dir)) { + if (entry.is_directory()) { + fs::remove_all(entry.path()); + } else { + fs::remove(entry.path()); + } + } + } catch (const std::exception& e) { + log.log_to_file("Error: Failed to clear the local repository directory. Exception: " + std::string(e.what())); + git_libgit2_shutdown(); + return -1; + } + + // Clone the repository again + if (clone_repo(config_path) != 0) { + log.log_to_file("Error: Failed to clone the repository after starting the handling of non-fast-forward error."); + git_libgit2_shutdown(); + return -1; + } + + // Replace the contents of repo_dir except .git dir with the contents of backup_dir and delete backup_dir if success. + if(recover_user_notes(repo_dir, backup_dir) != 0) { + log.log_to_file("Error: Failed to Recover user notes After dealing with non-fast-forward error."); + } + + git_libgit2_shutdown(); + return 0; +} + +int GitOperations::push_to_remote(const std::string config_path, git_repository *repo) { + git_libgit2_init(); + Logging log; + git_remote *remote = nullptr; + int error = git_remote_lookup(&remote, repo, "origin"); + + if (error != 0) { + const git_error *last_error = giterr_last(); + log.log_to_file("Error: Failed to lookup remote - " + std::string(last_error->message)); + git_libgit2_shutdown(); + return -1; + } + git_push_options push_opts = GIT_PUSH_OPTIONS_INIT; + push_opts.callbacks.credentials = cred_acquire_cb; + push_opts.callbacks.payload = (void*)&config_path; + + git_strarray refspecs = {NULL, 0}; + refspecs.strings = (char **)malloc(sizeof(char*)); + refspecs.strings[0] = const_cast("refs/heads/main:refs/heads/main"); + refspecs.count = 1; + error = git_remote_push(remote, &refspecs, &push_opts); + if (error != 0) { + const git_error *last_error = giterr_last(); + log.log_to_file("Error: Failed to push to remote - '" + std::string(last_error->message) + "', handling it..."); + // if(auto_fetch_and_merge_with_conflict_markers(config_path) != 0) { + // log.log_to_file("Error: Segementation fault tracking, line 575"); + // log.log_to_file("Error: Failed to Auto fetch and merge the remote to local."); + // git_libgit2_shutdown(); + // return -1; + // } + if(handle_non_fast_forward_error(config_path) == 0) { + log.log_to_file("Successfully maneuvered the non-fast-forward error above."); + return 3; + } + git_remote_free(remote); + git_libgit2_shutdown(); + return -1; + } + git_remote_free(remote); + log.log_to_file("Pushed to remote successfully"); + git_libgit2_shutdown(); + return 0; +} \ No newline at end of file diff --git a/utils/init.cpp b/utils/init.cpp new file mode 100644 index 0000000..e093101 --- /dev/null +++ b/utils/init.cpp @@ -0,0 +1,135 @@ +#include "Init.hpp" +#include "FsManipulator.hpp" +#include "LoadEnv.hpp" +#include "Logging.hpp" +#include "SshWorker.hpp" +#include "GitOperations.hpp" +#include "NetworkChecker.hpp" +#include +#include +#include +#include +#include +#include + + +std::string Init::init_for_linux_desktop(const std::string root_dir, const std::string workspace_dir) { + std::string config_file_path = root_dir + "/config.txt"; + GitOperations gitOperations; + FsManipulator fsManipulator; + NetworkChecker networkChecker; + SshWorker sshWorker; + LoadEnv loadEnv; + Logging logging; + + logging.log_to_file("\n\n\n------------STARTED THE PROGRAM FROM THE COMMAND LINE--------------\n"); + + // Check for root directory and create if not present + if (fsManipulator.is_dir_present(root_dir) != 0) { + int dir_creation_result = fsManipulator.create_dir(root_dir); + if (dir_creation_result != 0) { + logging.log_to_file("Error: Error creating root directory: '" + root_dir + "'."); + return "Error"; + } + } + + // Check for config file and create if not present + if (fsManipulator.is_file_present(config_file_path) != 0) { + int file_creation_result = fsManipulator.create_file(config_file_path); + if (file_creation_result != 0) { + logging.log_to_file("Error: Error creating config file: '" + config_file_path + "'."); + return "Error"; + } + } + + // Load environment variables from config file + nlohmann::json json_data = nlohmann::json::array({ + {{"name", "GIT_HOST"}, {"default", "github.com"}}, + {{"name", "GIT_USERNAME"}, {"default", "username"}}, + {{"name", "MAIN_REPOSITORY"}, {"default", "ObsidianGitVault"}}, + {{"name", "SSH_KEY_NAME"}, {"default", "ObsidianGitSync"}} + }); + std::string load_env_result = loadEnv.load_env_variables(config_file_path, json_data, false); + + if (load_env_result != "Success!") { + logging.log_to_file("Error: Initialization failed: '" + load_env_result + "'."); + return "Failed!"; // Return an error code + } + + std::string git_host = fsManipulator.read_config_var(config_file_path, "GIT_HOST"); + std::string git_username = fsManipulator.read_config_var(config_file_path, "GIT_USERNAME"); + std::string git_repo_name = fsManipulator.read_config_var(config_file_path, "MAIN_REPOSITORY"); + + std::string programEmail = "264351479+" + git_username + "+ObsidianGitSync@users.noreply." + git_host; + std::string backup_dir = workspace_dir + "/." + git_repo_name; + + if(fsManipulator.read_config_var(config_file_path, "ROOT_DIR") != root_dir){ + if(fsManipulator.read_config_var(config_file_path, "ROOT_DIR") == "VAR_NOT_FOUND"){ + fsManipulator.add_config_var(config_file_path, "ROOT_DIR", root_dir); + } else { + fsManipulator.update_config_var(config_file_path, "ROOT_DIR", root_dir); + } + } + if(fsManipulator.read_config_var(config_file_path, "WORKSPACE_DIR") != workspace_dir){ + if(fsManipulator.read_config_var(config_file_path, "WORKSPACE_DIR") == "VAR_NOT_FOUND"){ + fsManipulator.add_config_var(config_file_path, "WORKSPACE_DIR", workspace_dir); + } else { + fsManipulator.update_config_var(config_file_path, "WORKSPACE_DIR", workspace_dir); + } + } + if(fsManipulator.read_config_var(config_file_path, "BACKUP_DIR") != backup_dir){ + if(fsManipulator.read_config_var(config_file_path, "BACKUP_DIR") == "VAR_NOT_FOUND"){ + fsManipulator.add_config_var(config_file_path, "BACKUP_DIR", backup_dir); + } else { + fsManipulator.update_config_var(config_file_path, "BACKUP_DIR", backup_dir); + } + } + if(fsManipulator.read_config_var(config_file_path, "SSH_DIR") != root_dir + "/.ssh"){ + if(fsManipulator.read_config_var(config_file_path, "SSH_DIR") == "VAR_NOT_FOUND"){ + fsManipulator.add_config_var(config_file_path, "SSH_DIR", root_dir + "/.ssh"); + } else { + fsManipulator.update_config_var(config_file_path, "SSH_DIR", root_dir + "/.ssh"); + } + } + if(fsManipulator.read_config_var(config_file_path, "COMMITS_EMAIL") != programEmail){ + if(fsManipulator.read_config_var(config_file_path, "COMMITS_EMAIL") == "VAR_NOT_FOUND"){ + fsManipulator.add_config_var(config_file_path, "COMMITS_EMAIL", programEmail); + } else { + fsManipulator.update_config_var(config_file_path, "COMMITS_EMAIL", programEmail); + } + } + + if(sshWorker.is_ssh_setup(config_file_path) != 0) { + sshWorker.setup_new_ssh_keypair(config_file_path); + } + + if(gitOperations.is_repo_present(config_file_path) != 0) { + gitOperations.clone_repo(config_file_path); + } + + git_repository *repo = gitOperations.open_repo(config_file_path); + while (repo != nullptr) { // This will run indefinitely + logging.log_to_file("\n\n------------------Infinite Scanning Cycle Start---------------\n"); + if(networkChecker.checkConnectivity() == 0) { + if(gitOperations.are_unindexed_changes_present(repo) == 0) { + if (gitOperations.are_unindexed_changes_present(repo) == 0) { + gitOperations.add_to_index(repo); + } + } + + if(gitOperations.are_uncommitted_changes_present(repo) == 0) { + gitOperations.create_commit(config_file_path, repo); + } + + if(gitOperations.are_unpushed_commits_present(repo) == 0) { + gitOperations.push_to_remote(config_file_path, repo); + } + + } + + std::this_thread::sleep_for(std::chrono::seconds(60)); // Sleep for 60 seconds + logging.log_to_file("\n\n------------------Infinite Scanning Cycle End---------------\n"); + } + + return "Success!"; +} diff --git a/utils/load_env.cpp b/utils/load_env.cpp new file mode 100644 index 0000000..21e52bd --- /dev/null +++ b/utils/load_env.cpp @@ -0,0 +1,23 @@ +#include "LoadEnv.hpp" +#include "Prompting.hpp" +#include +#include +#include +#include + +std::string LoadEnv::load_env_variables(const std::string file_path, const nlohmann::json json_data, const bool update_var) { + Prompting prompting; + std::string result; + for (const auto& var_obj : json_data) { + std::string var_name = var_obj["name"]; + std::string default_value = var_obj["default"]; + std::string value = prompting.is_prompt_required(file_path, var_name, default_value, update_var); + } + return "Success!"; +} + +std::string LoadEnv::get_env_var(const std::string file_path, const std::string var_name, const bool update_var) { + Prompting prompting; + std::string value = prompting.is_prompt_required(file_path, var_name, "", update_var); + return value; +} diff --git a/utils/logging.cpp b/utils/logging.cpp new file mode 100644 index 0000000..247d45c --- /dev/null +++ b/utils/logging.cpp @@ -0,0 +1,99 @@ +#include "Logging.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +int is_dir_present(const std::string dir_path) { + try { + if (fs::exists(dir_path) && fs::is_directory(dir_path)) { + return 0; + } + return -1; + } catch (const fs::filesystem_error& e) { + std::cerr << e.what() << std::endl; + return -1; + } +} + +int is_file_present(const std::string file_path) { + try { + if (fs::exists(file_path) && fs::is_regular_file(file_path)) { + return 0; + } + return -1; + } catch (const fs::filesystem_error& e) { + std::cerr << e.what() << std::endl; + return -1; + } +} + +int Logging::log_to_file(const std::string new_data) { + std::string temp_log_path = fs::path(std::string(std::getenv("HOME")) + "/.tempOGRlog.txt"); + std::string ideal_log_dir = fs::path(std::string(std::getenv("HOME")) + "/.config/ObsidianGitSync"); + std::string ideal_log_path = ideal_log_dir + "/logfile.txt"; + + auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + std::stringstream ss; + ss << std::put_time(std::localtime(&now), "%Y-%m-%d %H:%M:%S") << " -:- " << new_data; + std::string timestamped_data = ss.str(); + + if (is_file_present(ideal_log_path) == 0) { + std::ofstream log_file(ideal_log_path, std::ios::app); + log_file << timestamped_data << std::endl; + log_file.close(); + } else { + if (is_dir_present(ideal_log_dir) == 0) { + if (is_file_present(temp_log_path) == 0) { + fs::rename(temp_log_path, ideal_log_path); + std::ofstream log_file(ideal_log_path, std::ios::app); + log_file << timestamped_data << std::endl; + log_file.close(); + } else { + std::ofstream log_file(ideal_log_path); + log_file << timestamped_data << std::endl; + log_file.close(); + } + } else { + if (is_file_present(temp_log_path) == 0) { + std::ofstream log_file(temp_log_path, std::ios::app); + log_file << timestamped_data << std::endl; + log_file.close(); + } else { + std::ofstream log_file(temp_log_path); + log_file << timestamped_data << std::endl; + log_file.close(); + } + } + } + + // Use the ideal log path only when present + std::string log_path = temp_log_path; + if (is_file_present(ideal_log_path) == 0) { + log_path = ideal_log_path; + } + + // Ensure log file size does not exceed 10MB. + if (fs::file_size(log_path) > 10 * 1024 * 1024) { + std::vector lines; + std::string line; + std::ifstream log_file(log_path); + while (std::getline(log_file, line)) { + lines.push_back(line); + } + log_file.close(); + lines.erase(lines.begin(), lines.begin() + (lines.size() / 10)); // Remove first half of the log entries. + std::ofstream log_file_out(log_path); + for (const auto& log_line : lines) { + log_file_out << log_line << std::endl; + } + log_file_out.close(); + } + + return 0; +} diff --git a/utils/network_checker.cpp b/utils/network_checker.cpp new file mode 100644 index 0000000..fcf953a --- /dev/null +++ b/utils/network_checker.cpp @@ -0,0 +1,30 @@ +#include "NetworkChecker.hpp" +#include "Logging.hpp" +#include + +int NetworkChecker::checkConnectivity() { + CURL *curl; + CURLcode res; + Logging logging; + + curl_global_init(CURL_GLOBAL_DEFAULT); + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://www.google.com"); + curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); // HEAD request, no body + res = curl_easy_perform(curl); + if(res == CURLE_OK) { + curl_easy_cleanup(curl); + curl_global_cleanup(); + logging.log_to_file("Network connection is Up, Continuing..."); + return 0; + } + curl_easy_cleanup(curl); + curl_global_cleanup(); + logging.log_to_file("Error: Network connection is Down, Aborting push to remote for now."); + return -1; + } + curl_global_cleanup(); + logging.log_to_file("Error: Failed to initialize curl."); + return -1; // Return -1 if curl initialization failed +} diff --git a/utils/prompting.cpp b/utils/prompting.cpp new file mode 100644 index 0000000..36053cc --- /dev/null +++ b/utils/prompting.cpp @@ -0,0 +1,69 @@ +// Prompting.cpp +#include "Prompting.hpp" +#include "FsManipulator.hpp" +#include +#include +#include // For std::numeric_limits + +std::string Prompting::prompt_user_for_var(const std::string file_path, const std::string var_name, const std::string default_value) { + FsManipulator fs; + std::string current_value = fs.read_config_var(file_path, var_name); + + std::cout << "Enter value for " << var_name; + if (!default_value.empty()) { + std::cout << " (default: " << default_value << ")"; + } + std::cout << ": "; + + std::string user_input; + std::getline(std::cin, user_input); + if (user_input.empty() && !default_value.empty()) { + user_input = default_value; + } + while (user_input.empty()) { + std::cout << var_name << " is required. Please enter value for " << var_name << ": "; + std::getline(std::cin, user_input); + } + + if(current_value == "VAR_NOT_FOUND") { + fs.add_config_var(file_path, var_name, user_input); + } + + fs.update_config_var(file_path, var_name, user_input); + return user_input; +} + +std::string Prompting::is_prompt_required(const std::string file_path, const std::string var_name, const std::string default_value, const bool update_var) { + FsManipulator fs; + std::string current_value = fs.read_config_var(file_path, var_name); + // std::cout << current_value << "\n"; + if (current_value == "VAR_NOT_FOUND" || current_value == "ERROR" || current_value.empty()) { + return prompt_user_for_var(file_path, var_name, default_value); + } else if(!update_var){ + return current_value; + } else { + std::cout << "Current value of " << var_name << " is " << current_value << "." << std::endl; + std::string input; + int choice = 2; // Default choice is 2 + std::cout << "Would you like to change it? (1 - Change, 2 - Don't change): "; + std::getline(std::cin, input); // Read a line of input + + // If input is not empty, try to convert it to an integer + if (!input.empty()) { + try { + choice = std::stoi(input); // Convert string to int + } catch (const std::invalid_argument&) { + std::cout << "Invalid input. Assuming choice 2." << std::endl; + choice = 2; + } + } + + // Now process the choice + if (choice == 1) { + return prompt_user_for_var(file_path, var_name, default_value); + } else { + return current_value; + } + } + return current_value; +} diff --git a/utils/ssh_worker.cpp b/utils/ssh_worker.cpp new file mode 100644 index 0000000..3cbce13 --- /dev/null +++ b/utils/ssh_worker.cpp @@ -0,0 +1,97 @@ +#include "SshWorker.hpp" +#include "FsManipulator.hpp" +#include "Logging.hpp" +#include +#include + +void change_email_in_key(const std::string& pub_key_path, const std::string& email) { + Logging logging; + + std::ifstream pub_key_file(pub_key_path); + if (!pub_key_file) { + logging.log_to_file("Error: Failed to open public key file for reading."); + return; + } + + std::string pub_key_content((std::istreambuf_iterator(pub_key_file)), std::istreambuf_iterator()); + size_t pos = pub_key_content.rfind(' '); + if (pos != std::string::npos) { + pub_key_content.replace(pos + 1, std::string::npos, email); + } else { + logging.log_to_file("Error: Space not found in public key file."); + return; + } + + std::ofstream pub_key_out(pub_key_path); + if (!pub_key_out) { + logging.log_to_file("Error: Failed to open public key file for writing."); + return; + } + pub_key_out << pub_key_content; + logging.log_to_file("Changed email in public key file to '" + email + "'."); +} + +int SshWorker::is_ssh_setup(const std::string config_path) { + Logging logging; + FsManipulator fsManipulator; + + std::string ssh_key_name = fsManipulator.read_config_var(config_path, "SSH_KEY_NAME"); + std::string ssh_dir = fsManipulator.read_config_var(config_path, "SSH_DIR"); + std::string commits_email = fsManipulator.read_config_var(config_path, "COMMITS_EMAIL"); + + std::string private_key_path = ssh_dir + "/" + ssh_key_name; + std::string public_key_path = private_key_path + ".pub"; + + if (fsManipulator.is_file_present(private_key_path) != 0 || fsManipulator.is_file_present(public_key_path) != 0) { + fsManipulator.create_dir(ssh_dir); + logging.log_to_file("Error: SSH key files do not exist."); + return -1; + } + + // Verifying the key type for both private and public keys + ssh_key private_key, public_key; + if (ssh_pki_import_privkey_file(private_key_path.c_str(), nullptr, nullptr, nullptr, &private_key) != SSH_OK || + ssh_pki_import_pubkey_file(public_key_path.c_str(), &public_key) != SSH_OK) { + logging.log_to_file("Error: Failed to import SSH keys."); + return -2; + } + + int key_type = ssh_key_type(private_key); + if (key_type != SSH_KEYTYPE_ED25519) { + logging.log_to_file("Error: SSH key type is not ED25519."); + ssh_key_free(private_key); + ssh_key_free(public_key); + return -3; + } + + ssh_key_free(private_key); + ssh_key_free(public_key); + logging.log_to_file("SSH keys are set up and verified."); + return 0; +} + +std::string SshWorker::setup_new_ssh_keypair(const std::string config_path) { + Logging logging; + FsManipulator fsManipulator; + + std::string ssh_key_name = fsManipulator.read_config_var(config_path, "SSH_KEY_NAME"); + std::string ssh_dir = fsManipulator.read_config_var(config_path, "SSH_DIR"); + std::string commits_email = fsManipulator.read_config_var(config_path, "COMMITS_EMAIL"); + + std::string private_key_path = ssh_dir + "/" + ssh_key_name; + std::string public_key_path = private_key_path + ".pub"; + + ssh_key key; + if (ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &key) != SSH_OK || + ssh_pki_export_privkey_file(key, nullptr, nullptr, nullptr, private_key_path.c_str()) != SSH_OK || + ssh_pki_export_pubkey_file(key, public_key_path.c_str()) != SSH_OK) { + logging.log_to_file("Error: Generating or exporting SSH key pair failed."); + ssh_key_free(key); + return "Error"; + } + + change_email_in_key(public_key_path, commits_email); + ssh_key_free(key); + logging.log_to_file("New SSH key pair has been set up."); + return "Success!"; +}