From ff7d84bb3b4d275271cc61d4cf4d86fdac557191 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 16 Jun 2025 23:01:32 +1200 Subject: [PATCH] 'Generic Commit' --- getpkg/src/ArchiveManager.cpp | 68 +++--- getpkg/src/ArchiveManager.hpp | 2 - getpkg/src/DropshellScriptManager.cpp | 55 ----- getpkg/src/DropshellScriptManager.hpp | 3 - getpkg/src/GetbinClient.cpp | 64 ++++-- getpkg/src/main.cpp | 303 ++++++++++++++++---------- 6 files changed, 257 insertions(+), 238 deletions(-) diff --git a/getpkg/src/ArchiveManager.cpp b/getpkg/src/ArchiveManager.cpp index 9141908..6e9a2e5 100644 --- a/getpkg/src/ArchiveManager.cpp +++ b/getpkg/src/ArchiveManager.cpp @@ -11,12 +11,32 @@ namespace fs = std::filesystem; +// Escape string for shell command +static std::string shellEscape(const std::string& str) { + std::string result; + result.reserve(str.length() + 20); + result += "'"; + for (char c : str) { + if (c == '\'') { + result += "'\\''"; + } else { + result += c; + } + } + result += "'"; + return result; +} + ArchiveManager::ArchiveManager() {} bool ArchiveManager::pack(const std::string& folderPath, const std::string& archivePath) { // Use system tar to create gzipped tarball std::ostringstream cmd; - cmd << "tar -czf '" << archivePath << "' -C '" << folderPath << "' ."; + cmd << "tar -czf "; + cmd << shellEscape(archivePath); + cmd << " -C "; + cmd << shellEscape(folderPath); + cmd << " ."; int ret = std::system(cmd.str().c_str()); return ret == 0; } @@ -24,48 +44,10 @@ bool ArchiveManager::pack(const std::string& folderPath, const std::string& arch bool ArchiveManager::unpack(const std::string& archivePath, const std::string& outDir) { fs::create_directories(outDir); std::ostringstream cmd; - cmd << "tar -xzf '" << archivePath << "' -C '" << outDir << "'"; + cmd << "tar -xzf "; + cmd << shellEscape(archivePath); + cmd << " -C "; + cmd << shellEscape(outDir); int ret = std::system(cmd.str().c_str()); return ret == 0; -} - -bool ArchiveManager::readConfigJson(const std::string& archivePath, std::string& outJson) { - // Extract config json to stdout - std::ostringstream cmd; - cmd << "tar -xOzf '" << archivePath << "' getpkg-config.json"; - FILE* pipe = popen(cmd.str().c_str(), "r"); - if (!pipe) return false; - char buffer[4096]; - std::ostringstream ss; - size_t n; - while ((n = fread(buffer, 1, sizeof(buffer), pipe)) > 0) { - ss.write(buffer, n); - } - int ret = pclose(pipe); - if (ret != 0) return false; - outJson = ss.str(); - return true; -} - -bool ArchiveManager::writeConfigJson(const std::string& archivePath, const std::string& json) { - // 1. Extract archive to temp dir - std::string tmpDir = "/tmp/dropshell_tool_tmp_" + std::to_string(::getpid()); - fs::create_directories(tmpDir); - std::ostringstream extractCmd; - extractCmd << "tar -xzf '" << archivePath << "' -C '" << tmpDir << "'"; - if (std::system(extractCmd.str().c_str()) != 0) return false; - // 2. Write new config json - std::ofstream ofs(tmpDir + "/getpkg-config.json", std::ios::binary); - if (!ofs) return false; - ofs << json; - ofs.close(); - // 3. Repack - std::ostringstream packCmd; - packCmd << "tar -czf '" << archivePath << "' -C '" << tmpDir << "' ."; - int ret = std::system(packCmd.str().c_str()); - // 4. Cleanup - std::ostringstream rmCmd; - rmCmd << "rm -rf '" << tmpDir << "'"; - std::system(rmCmd.str().c_str()); - return ret == 0; } \ No newline at end of file diff --git a/getpkg/src/ArchiveManager.hpp b/getpkg/src/ArchiveManager.hpp index bd168fb..3a91b3c 100644 --- a/getpkg/src/ArchiveManager.hpp +++ b/getpkg/src/ArchiveManager.hpp @@ -6,6 +6,4 @@ public: ArchiveManager(); bool pack(const std::string& folderPath, const std::string& archivePath); bool unpack(const std::string& archivePath, const std::string& outDir); - bool readConfigJson(const std::string& archivePath, std::string& outJson); - bool writeConfigJson(const std::string& archivePath, const std::string& json); }; \ No newline at end of file diff --git a/getpkg/src/DropshellScriptManager.cpp b/getpkg/src/DropshellScriptManager.cpp index 6267012..0dac648 100644 --- a/getpkg/src/DropshellScriptManager.cpp +++ b/getpkg/src/DropshellScriptManager.cpp @@ -67,28 +67,6 @@ void DropshellScriptManager::removeToolEntry(const std::string& toolName) { outfile.close(); } -void DropshellScriptManager::addAlias(const std::string& alias, const std::string& toolName) { - ensureExists(); - std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH); - std::vector lines; - std::string line; - std::string aliasLine = "alias " + alias + "='" + toolName + "' # dropshell-alias:" + alias; - bool found = false; - while (std::getline(infile, line)) { - if (line.find("# dropshell-alias:" + alias) != std::string::npos) { - found = true; - lines.push_back(aliasLine); - } else { - lines.push_back(line); - } - } - infile.close(); - if (!found) lines.push_back(aliasLine); - std::ofstream outfile(dropshelltool::DROPSHELL_RC_PATH, std::ios::trunc); - for (const auto& l : lines) outfile << l << "\n"; - outfile.close(); -} - void DropshellScriptManager::addAutocomplete(const std::string& toolName) { ensureExists(); std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH); @@ -134,37 +112,4 @@ void DropshellScriptManager::addAutocomplete(const std::string& toolName) { std::ofstream outfile(dropshelltool::DROPSHELL_RC_PATH, std::ios::trunc); for (const auto& l : lines) outfile << l << "\n"; outfile.close(); -} - -bool DropshellScriptManager::hasAlias(const std::string& alias) const { - ensureExists(); - std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH); - std::string line; - while (std::getline(infile, line)) { - if (line.find("# dropshell-alias:" + alias) != std::string::npos) { - return true; - } - } - return false; -} - -std::vector DropshellScriptManager::listAliases() const { - ensureExists(); - std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH); - std::string line; - std::vector aliases; - while (std::getline(infile, line)) { - auto pos = line.find("# dropshell-alias:"); - if (pos != std::string::npos) { - auto aliasStart = line.find("alias "); - if (aliasStart != std::string::npos) { - auto eq = line.find('=', aliasStart + 6); - if (eq != std::string::npos) { - std::string alias = line.substr(aliasStart + 6, eq - (aliasStart + 6)); - aliases.push_back(alias); - } - } - } - } - return aliases; } \ No newline at end of file diff --git a/getpkg/src/DropshellScriptManager.hpp b/getpkg/src/DropshellScriptManager.hpp index cde5473..fa9ff79 100644 --- a/getpkg/src/DropshellScriptManager.hpp +++ b/getpkg/src/DropshellScriptManager.hpp @@ -8,8 +8,5 @@ public: void ensureExists() const; void addToolEntry(const std::string& toolName, const std::string& toolDir); void removeToolEntry(const std::string& toolName); - void addAlias(const std::string& alias, const std::string& toolName); void addAutocomplete(const std::string& toolName); - bool hasAlias(const std::string& alias) const; - std::vector listAliases() const; }; \ No newline at end of file diff --git a/getpkg/src/GetbinClient.cpp b/getpkg/src/GetbinClient.cpp index a1f9ac8..60313f6 100644 --- a/getpkg/src/GetbinClient.cpp +++ b/getpkg/src/GetbinClient.cpp @@ -10,6 +10,7 @@ #include #include #include +#include using json = nlohmann::json; @@ -25,26 +26,38 @@ bool GetbinClient::download(const std::string& toolName, const std::string& arch req->setMethod(drogon::Get); req->setPath(object_path); - bool success = false; + std::atomic success(false); + std::atomic completed(false); + client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) { std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl; + completed = true; return; } std::ofstream ofs(outPath, std::ios::binary); - if (!ofs) return; + if (!ofs) { + completed = true; + return; + } const auto& body = response->getBody(); ofs.write(body.data(), body.size()); success = ofs.good(); + completed = true; }); - // Wait for the async request to complete - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - while (!success) { + // Wait for the async request to complete with timeout + auto start = std::chrono::steady_clock::now(); + const auto timeout = std::chrono::seconds(30); // 30 second timeout + + while (!completed) { + if (std::chrono::steady_clock::now() - start > timeout) { + std::cerr << "[GetbinClient::download] Request timed out after 30 seconds." << std::endl; + return false; + } std::this_thread::sleep_for(std::chrono::milliseconds(10)); - // Add timeout logic here if needed } return success; @@ -78,7 +91,8 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s req->addHeader("X-Metadata", metadata.dump()); req->setBody(file_content); - bool success = false; + std::atomic success(false); + std::atomic completed(false); std::string response_body; int status_code = 0; @@ -89,6 +103,7 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s std::cerr << response->getStatusCode() << std::endl; std::cerr << response->getBody() << std::endl; } + completed = true; return; } @@ -98,6 +113,7 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s if (status_code != 200 && status_code != 201) { std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl; std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; + completed = true; return; } @@ -114,13 +130,19 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s std::cerr << "[GetbinClient::upload] Unknown error while parsing JSON response." << std::endl; std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl; } + completed = true; }); - // Wait for the async request to complete - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - while (!success && status_code == 0) { + // Wait for the async request to complete with timeout + auto start = std::chrono::steady_clock::now(); + const auto timeout = std::chrono::seconds(60); // 60 second timeout for upload + + while (!completed) { + if (std::chrono::steady_clock::now() - start > timeout) { + std::cerr << "[GetbinClient::upload] Request timed out after 60 seconds." << std::endl; + return false; + } std::this_thread::sleep_for(std::chrono::milliseconds(10)); - // Add timeout logic here if needed } return success; @@ -134,11 +156,13 @@ bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, req->setMethod(drogon::Get); req->setPath(exists_path); - bool success = false; + std::atomic success(false); + std::atomic completed(false); std::string response_body; client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) { + completed = true; return; } @@ -151,21 +175,29 @@ bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, if (resp_json.contains("hash")) { outHash = resp_json["hash"].get(); success = true; + completed = true; return; } } catch (...) { // Not JSON, treat as plain text outHash = response_body; success = !outHash.empty(); + completed = true; return; } + completed = true; }); - // Wait for the async request to complete - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - while (!success && response_body.empty()) { + // Wait for the async request to complete with timeout + auto start = std::chrono::steady_clock::now(); + const auto timeout = std::chrono::seconds(10); // 10 second timeout for hash check + + while (!completed) { + if (std::chrono::steady_clock::now() - start > timeout) { + std::cerr << "[GetbinClient::getHash] Request timed out after 10 seconds." << std::endl; + return false; + } std::this_thread::sleep_for(std::chrono::milliseconds(10)); - // Add timeout logic here if needed } return success; diff --git a/getpkg/src/main.cpp b/getpkg/src/main.cpp index 0dd56b2..a8c76be 100644 --- a/getpkg/src/main.cpp +++ b/getpkg/src/main.cpp @@ -2,37 +2,31 @@ getpkg getpkg + - installs the specified tool (same as getpkg install ) - confirms getpkg is fully initialised: - adds a line to the user's .bashrc file to source the getpkg ~/.bashrc_dropshell_tool script, if it doesn't exist - creates an empty ~/.bashrc_dropshell_tool script if it doesn't exist - creates the ~/.config/getpkg directory, if it does not exist - creates the ~/.local/bin/getpkg directory, if it does not exist - - removes the tool from the user's system if it is already installed, and the tool's entries in the ~/.bashrc_dropshell_tool script - - downloads the tool archive (tgz) from tools.dropshell.app (tool_name:ARCH), where ARCH is the architecture of the user's system, and unpacks it to the new tool directory: ~/.local/bin/getpkg// - - sets the PATH to include the tool directory, by modifying the ~/.bashrc_dropshell_tool script - - adds an entry for autocompletion for the tool to the ~/.bashrc_dropshell_tool script, where autocomplete just runs autocomplete - - creates a ~/.config/getpkg/tool_name.json file, which contains the tool's name, version (by running version), hash from tools.dropshell.app, and architecture - - reads the json file from the tgz called getpkg-config.json: - - for each alias in the aliases array: - - check if another command is using the alias, and continue only if not - - check that the alias is not already in the ~/.bashrc_dropshell_tool script and continue only if not - - add an entry to the ~/.bashrc_dropshell_tool script to run the tool via the alias - - if the json file has a setup_script entry, run the script named in the entry in the tool directory - using sudo if the entry has sudo set to true. + - checks if ~/.config/getpkg/tool_name.json file exists. If it does, it will update the tool if the version is older than the remote version. If it doesn't exist, it will install the tool. + - removes the tool from the user's system if it is already installed, and the tool's entries in the ~/.bashrc_dropshell_tool script + - downloads the tool archive (tgz) from tools.dropshell.app (tool_name:ARCH), where ARCH is the architecture of the user's system, and unpacks it to the new tool directory: ~/.local/bin/getpkg// + - sets the PATH to include the tool directory, by modifying the ~/.bashrc_dropshell_tool script + - adds an entry for autocompletion for the tool to the ~/.bashrc_dropshell_tool script, where autocomplete just runs autocomplete + - creates a ~/.config/getpkg/tool_name.json file, which contains the tool's name, version (by running version), hash from tools.dropshell.app, and architecture + - if setup_script.sh exists, run the script + + getpkg install + - legacy syntax for installing a tool (same as getpkg ) getpkg publish - - checks that getpkg-config.json exists in the folder, and is valid (see above) - creates a tgz archive of the folder, and uploads it to tools.dropshell.app, a simple object server. - prints the URL and hash of the uploaded archive - uses the token from env variable SOS_WRITE_TOKEN to write to tools.dropshell.app - getpkg update - - compares the hash from the ~/.config/getpkg/tool_name.json file with the hash from tools.dropshell.app (tool_name:ARCH), and continues only if they are different - - checks the version from the ~/.config/getpkg/tool_name.json file with the version from tools.dropshell.app (tool_name:ARCH), and continues only if the remote version is newer (installed is older) - - installs the tool as per the install command - - getpkg update all - - runs update on all installed tools + getpkg update + - updates getpkg itself, and then runs install on all already installed tools getpkg autocomplete - shows autocomplete for getpkg, and then exits @@ -43,9 +37,6 @@ getpkg create - creates a new tool source directory in relative path if it doesn't exist - - creates a getpkg-config.json file in the tool source directory if it doesn't exist, with the following entries: - - aliases: an array of aliases for the tool - - setup_script: the name of the setup script to run (setup_script.sh) - creates a setup_script.sh file in the tool source directory if it doesn't exist, that just prints a completion message and exits getpkg help @@ -63,11 +54,54 @@ #include #include #include +#include #include namespace { using json = nlohmann::json; +// Compare versions (returns true if v1 < v2) +bool isVersionOlder(const std::string& v1, const std::string& v2) { + // Simple version comparison - assumes versions are in YYYY.MMDD.HHMM format + // or semantic versioning format (e.g., 1.2.3) + return v1 < v2; +} + +// Validate tool name to prevent path traversal and other issues +bool isValidToolName(const std::string& name) { + if (name.empty() || name.length() > 100) return false; + + // Check for path traversal attempts + if (name.find("..") != std::string::npos) return false; + if (name.find("/") != std::string::npos) return false; + if (name.find("\\") != std::string::npos) return false; + + // Only allow alphanumeric, dash, underscore, and dot + for (char c : name) { + if (!std::isalnum(c) && c != '-' && c != '_' && c != '.') { + return false; + } + } + + return true; +} + +// Escape string for shell command +std::string shellEscape(const std::string& str) { + std::string result; + result.reserve(str.length() + 20); + result += "'"; + for (char c : str) { + if (c == '\'') { + result += "'\\''"; + } else { + result += c; + } + } + result += "'"; + return result; +} + std::string get_arch() { #if defined(__x86_64__) || defined(_M_X64) return "x86_64"; @@ -89,73 +123,126 @@ int install_tool(int argc, char* argv[]) { return 1; } std::string toolName = argv[2]; + + // Validate tool name + if (!isValidToolName(toolName)) { + std::cerr << "Invalid tool name. Tool names can only contain letters, numbers, dash, underscore, and dot." << std::endl; + return 1; + } + std::string arch = get_arch(); std::string home = get_home(); std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; std::filesystem::path binDir = std::filesystem::path(home) / ".local/bin/getpkg" / toolName; std::filesystem::path archivePath = configDir / (toolName + ".tgz"); + std::filesystem::path toolInfoPath = configDir / (toolName + ".json"); + + // Initialize directories std::filesystem::create_directories(configDir); - std::filesystem::create_directories(binDir); + std::filesystem::create_directories(binDir.parent_path()); + + // Initialize bashrc dropshelltool::BashrcEditor bashrcEditor; bashrcEditor.addSourceLine(); + + // Check if tool needs update or install + bool needsUpdate = false; + if (std::filesystem::exists(toolInfoPath)) { + // Tool exists, check if update needed + std::ifstream tfile(toolInfoPath); + json toolInfo; + tfile >> toolInfo; + tfile.close(); + + std::string localHash = toolInfo.value("hash", ""); + + // Get remote hash to compare + GetbinClient getbin; + std::string remoteHash; + if (getbin.getHash(toolName, arch, remoteHash) && !remoteHash.empty()) { + if (localHash != remoteHash) { + needsUpdate = true; + std::cout << "Updating " << toolName << "..." << std::endl; + } else { + std::cout << toolName << " is already up to date." << std::endl; + return 0; + } + } else { + // If we can't get remote hash, assume update is needed + needsUpdate = true; + std::cout << "Updating " << toolName << "..." << std::endl; + } + } else { + std::cout << "Installing " << toolName << "..." << std::endl; + } + + // Remove existing installation DropshellScriptManager scriptManager; scriptManager.removeToolEntry(toolName); if (std::filesystem::exists(binDir)) std::filesystem::remove_all(binDir); - GetbinClient getbin; + + // Download tool + GetbinClient getbin2; std::cout << "Downloading " << toolName << ":" << arch << "..." << std::endl; - if (!getbin.download(toolName, arch, archivePath.string())) { + if (!getbin2.download(toolName, arch, archivePath.string())) { std::cerr << "Failed to download tool archive." << std::endl; return 1; } + + // Unpack tool ArchiveManager archiver; if (!archiver.unpack(archivePath.string(), binDir.string())) { std::cerr << "Failed to unpack tool archive." << std::endl; return 1; } + + // Clean up the archive file + std::filesystem::remove(archivePath); + + // Add to PATH and autocomplete scriptManager.addToolEntry(toolName, binDir.string()); scriptManager.addAutocomplete(toolName); + + // Get tool info std::string hash; - getbin.getHash(toolName, arch, hash); + getbin2.getHash(toolName, arch, hash); std::string version; std::string toolPath = binDir.string() + "/" + toolName; - FILE* fp = popen((toolPath + " version").c_str(), "r"); + std::string versionCmd = shellEscape(toolPath) + " version"; + FILE* fp = popen(versionCmd.c_str(), "r"); if (fp) { char buf[128]; - if (fgets(buf, sizeof(buf), fp)) version = std::string(buf); + if (fgets(buf, sizeof(buf), fp)) { + version = std::string(buf); + // Remove trailing newline if present + if (!version.empty() && version.back() == '\n') { + version.pop_back(); + } + } pclose(fp); + } else { + std::cerr << "Warning: Failed to get version for " << toolName << std::endl; } + + // Save tool info json toolInfo = { {"name", toolName}, {"version", version}, {"hash", hash}, {"arch", arch} }; - std::ofstream toolInfoFile(configDir / (toolName + ".json")); + std::ofstream toolInfoFile(toolInfoPath); toolInfoFile << toolInfo.dump(2); toolInfoFile.close(); - std::string configJson; - if (archiver.readConfigJson(archivePath.string(), configJson)) { - try { - auto config = json::parse(configJson); - if (config.contains("aliases")) { - for (const auto& alias : config["aliases"]) { - std::string aliasStr = alias.get(); - if (!scriptManager.hasAlias(aliasStr)) { - scriptManager.addAlias(aliasStr, toolName); - } - } - } - if (config.contains("setup_script")) { - std::string setupScript = config["setup_script"].get(); - bool useSudo = config.value("sudo", false); - std::string setupPath = binDir.string() + "/" + setupScript; - std::string cmd = (useSudo ? "sudo " : "") + setupPath; - std::system(cmd.c_str()); - } - } catch (...) { - std::cerr << "Warning: failed to parse getpkg-config.json" << std::endl; - } + + // Run setup script if exists + std::filesystem::path setupScriptPath = binDir / "setup_script.sh"; + if (std::filesystem::exists(setupScriptPath)) { + std::cout << "Running setup script..." << std::endl; + std::string cmd = setupScriptPath.string(); + std::system(cmd.c_str()); } + std::cout << "Installed " << toolName << " successfully." << std::endl; return 0; } @@ -168,11 +255,6 @@ int publish_tool(int argc, char* argv[]) { std::string labeltag = argv[2]; std::string folder = argv[3]; std::string home = get_home(); - std::filesystem::path configPath = std::filesystem::path(folder) / "getpkg-config.json"; - if (!std::filesystem::exists(configPath)) { - std::cerr << "getpkg-config.json not found in " << folder << std::endl; - return 1; - } std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz"); std::filesystem::create_directories(archivePath.parent_path()); @@ -181,17 +263,22 @@ int publish_tool(int argc, char* argv[]) { std::cerr << "Failed to create archive." << std::endl; return 1; } - std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/tools.dropshell.app/write_token.txt"; std::string token; - if (std::filesystem::exists(tokenPath)) { - std::ifstream tfile(tokenPath); - std::getline(tfile, token); + const char* envToken = std::getenv("SOS_WRITE_TOKEN"); + if (envToken && std::strlen(envToken) > 0) { + token = envToken; } else { - std::cout << "Enter tools.dropshell.app write token: "; - std::getline(std::cin, token); - std::filesystem::create_directories(tokenPath.parent_path()); - std::ofstream tfile(tokenPath); - tfile << token << std::endl; + std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/tools.dropshell.app/write_token.txt"; + if (std::filesystem::exists(tokenPath)) { + std::ifstream tfile(tokenPath); + std::getline(tfile, token); + } else { + std::cout << "Enter tools.dropshell.app write token: "; + std::getline(std::cin, token); + std::filesystem::create_directories(tokenPath.parent_path()); + std::ofstream tfile(tokenPath); + tfile << token << std::endl; + } } GetbinClient getbin; std::string url, hash; @@ -204,45 +291,33 @@ int publish_tool(int argc, char* argv[]) { } int update_tool(int argc, char* argv[]) { - if (argc < 3) { - std::cerr << "Usage: getpkg update " << std::endl; - return 1; + std::cout << "Updating getpkg itself..." << std::endl; + + // First update getpkg itself + char* fakeArgv[] = {argv[0], (char*)"install", (char*)"getpkg"}; + int result = install_tool(3, fakeArgv); + if (result != 0) { + std::cerr << "Failed to update getpkg" << std::endl; + return result; } - std::string toolName = argv[2]; + + // Then update all installed tools std::string home = get_home(); std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; - if (toolName == "all") { - for (const auto& entry : std::filesystem::directory_iterator(configDir)) { - if (entry.path().extension() == ".json") { - std::string tname = entry.path().stem(); - char* fakeArgv[] = {argv[0], (char*)"update", (char*)tname.c_str()}; - update_tool(3, fakeArgv); + std::cout << "Updating all installed tools..." << std::endl; + + for (const auto& entry : std::filesystem::directory_iterator(configDir)) { + if (entry.path().extension() == ".json") { + std::string tname = entry.path().stem(); + if (tname != "getpkg") { // Skip getpkg since we already updated it + char* toolArgv[] = {argv[0], (char*)"install", (char*)tname.c_str()}; + install_tool(3, toolArgv); } } - return 0; } - std::filesystem::path toolInfoPath = configDir / (toolName + ".json"); - if (!std::filesystem::exists(toolInfoPath)) { - std::cerr << "Tool not installed: " << toolName << std::endl; - return 1; - } - std::ifstream tfile(toolInfoPath); - json toolInfo; - tfile >> toolInfo; - tfile.close(); - std::string arch = toolInfo.value("arch", get_arch()); - std::string localHash = toolInfo.value("hash", ""); - std::string localVersion = toolInfo.value("version", ""); - GetbinClient getbin; - std::string remoteHash; - getbin.getHash(toolName, arch, remoteHash); - if (remoteHash.empty() || remoteHash == localHash) { - std::cout << "No update needed for " << toolName << std::endl; - return 0; - } - std::cout << "Updating " << toolName << "..." << std::endl; - char* fakeArgv[] = {argv[0], (char*)"install", (char*)toolName.c_str()}; - return install_tool(3, fakeArgv); + + std::cout << "Update complete." << std::endl; + return 0; } int create_tool(int argc, char* argv[]) { @@ -259,19 +334,6 @@ int create_tool(int argc, char* argv[]) { } else { std::cout << "Directory already exists: " << toolDir << std::endl; } - std::filesystem::path configPath = toolDir / "getpkg-config.json"; - if (!std::filesystem::exists(configPath)) { - nlohmann::json config = { - {"aliases", nlohmann::json::array()}, - {"setup_script", "setup_script.sh"} - }; - std::ofstream configFile(configPath); - configFile << config.dump(2); - configFile.close(); - std::cout << "Created config: " << configPath << std::endl; - } else { - std::cout << "Config already exists: " << configPath << std::endl; - } std::filesystem::path setupScriptPath = toolDir / "setup_script.sh"; if (!std::filesystem::exists(setupScriptPath)) { std::ofstream setupFile(setupScriptPath); @@ -289,7 +351,7 @@ int create_tool(int argc, char* argv[]) { int main(int argc, char* argv[]) { if (argc < 2) { - std::cout << "Usage: getpkg [args...]" << std::endl; + std::cout << "Usage: getpkg | [args...]" << std::endl; return 1; } std::string command = argv[1]; @@ -313,15 +375,18 @@ help } else if (command == "create") { return create_tool(argc, argv); } else if (command == "help") { - std::cout << "Usage: getpkg [args...]" << std::endl; + std::cout << "Usage: getpkg | [args...]" << std::endl; std::cout << "Commands:" << std::endl; - std::cout << " install " << std::endl; - std::cout << " publish " << std::endl; - std::cout << " update " << std::endl; - std::cout << " version" << std::endl; + std::cout << " Install/update the specified tool" << std::endl; + std::cout << " install Install/update tool (legacy)" << std::endl; + std::cout << " publish Publish a tool" << std::endl; + std::cout << " update Update getpkg and all tools" << std::endl; + std::cout << " version Show getpkg version" << std::endl; + std::cout << " create Create new tool directory" << std::endl; } else { - std::cout << "Unknown command: " << command << std::endl; - return 1; + // Assume it's a tool name and install it + char* fakeArgv[] = {argv[0], (char*)"install", argv[1]}; + return install_tool(3, fakeArgv); } return 0; } \ No newline at end of file