'Generic Commit'
All checks were successful
Build-Test-Publish / build (push) Successful in 56s

This commit is contained in:
Your Name 2025-06-16 23:01:32 +12:00
parent 5275f02ee3
commit ff7d84bb3b
6 changed files with 257 additions and 238 deletions

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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<std::string> 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<std::string> DropshellScriptManager::listAliases() const {
ensureExists();
std::ifstream infile(dropshelltool::DROPSHELL_RC_PATH);
std::string line;
std::vector<std::string> 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;
}

View File

@ -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<std::string> listAliases() const;
};

View File

@ -10,6 +10,7 @@
#include <chrono>
#include <cstdio>
#include <map>
#include <atomic>
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<bool> success(false);
std::atomic<bool> 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<bool> success(false);
std::atomic<bool> 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<bool> success(false);
std::atomic<bool> 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<std::string>();
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;

View File

@ -2,37 +2,31 @@
getpkg
getpkg <tool_name>
- installs the specified tool (same as getpkg install <tool_name>)
- 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/<tool_name>/
- 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 <tool_name> autocomplete <args>
- creates a ~/.config/getpkg/tool_name.json file, which contains the tool's name, version (by running <tool_name> 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/<tool_name>/
- 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 <tool_name> autocomplete <args>
- creates a ~/.config/getpkg/tool_name.json file, which contains the tool's name, version (by running <tool_name> version), hash from tools.dropshell.app, and architecture
- if setup_script.sh exists, run the script
getpkg install <tool_name>
- legacy syntax for installing a tool (same as getpkg <tool_name>)
getpkg publish <tool_name:ARCH> <folder>
- 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 <tool_name>
- 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 <args>
- shows autocomplete for getpkg, and then exits
@ -43,9 +37,6 @@
getpkg create <tool_name> <directory_name>
- creates a new tool source directory in relative path <directory_name> 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 <vector>
#include <filesystem>
#include <fstream>
#include <cstring>
#include <nlohmann/json.hpp>
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<std::string>();
if (!scriptManager.hasAlias(aliasStr)) {
scriptManager.addAlias(aliasStr, toolName);
}
}
}
if (config.contains("setup_script")) {
std::string setupScript = config["setup_script"].get<std::string>();
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 <tool_name|all>" << 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 <command> [args...]" << std::endl;
std::cout << "Usage: getpkg <tool_name> | <command> [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 <command> [args...]" << std::endl;
std::cout << "Usage: getpkg <tool_name> | <command> [args...]" << std::endl;
std::cout << "Commands:" << std::endl;
std::cout << " install <tool_name>" << std::endl;
std::cout << " publish <tool_name:ARCH> <folder>" << std::endl;
std::cout << " update <tool_name>" << std::endl;
std::cout << " version" << std::endl;
std::cout << " <tool_name> Install/update the specified tool" << std::endl;
std::cout << " install <tool_name> Install/update tool (legacy)" << std::endl;
std::cout << " publish <tool_name:ARCH> <folder> 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 <tool_name> <directory> 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;
}