From 9c98ffcb860b165910ba0b4c913ed34ae97b8a1e Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 20 Jul 2025 17:54:42 +1200 Subject: [PATCH] docs: Update 2 files --- .kiro/specs/multi-server-support/tasks.md | 4 +- getpkg/src/main.cpp | 344 +++++++++++++++------- 2 files changed, 243 insertions(+), 105 deletions(-) diff --git a/.kiro/specs/multi-server-support/tasks.md b/.kiro/specs/multi-server-support/tasks.md index a6b7599..a594f22 100644 --- a/.kiro/specs/multi-server-support/tasks.md +++ b/.kiro/specs/multi-server-support/tasks.md @@ -85,7 +85,9 @@ Based on analysis of the current codebase, the multi-server support feature need - Add server URL validation and user feedback - _Requirements: 1.1, 1.2, 1.3_ -- [-] 7. Update existing commands for multi-server support +- [x] 7. Update existing commands for multi-server support + + diff --git a/getpkg/src/main.cpp b/getpkg/src/main.cpp index b1c60e1..52f9c0f 100644 --- a/getpkg/src/main.cpp +++ b/getpkg/src/main.cpp @@ -165,25 +165,47 @@ int install_tool(int argc, char* argv[]) { std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; std::filesystem::path binDir = std::filesystem::path(home) / ".getpkg" / toolName; std::filesystem::path archivePath = tempDir.path() / (toolName + ".tgz"); - std::filesystem::path toolInfoPath = configDir / (toolName + ".json"); + + // Initialize ServerManager and get server list + ServerManager serverManager; + if (!serverManager.loadConfiguration()) { + std::cerr << "Failed to load server configuration" << std::endl; + return 1; + } + + std::vector servers = serverManager.getServers(); + if (servers.empty()) { + std::cerr << "No servers configured" << std::endl; + return 1; + } + + // Initialize PackageMetadataManager + PackageMetadataManager packageManager(configDir); + if (!packageManager.ensurePackagesDirectory()) { + std::cerr << "Failed to create packages directory" << std::endl; + return 1; + } // Check if tool needs update or install - if (std::filesystem::exists(toolInfoPath)) { + bool isUpdate = false; + PackageMetadata existingMetadata; + if (packageManager.packageExists(toolName)) { // Tool exists, check if update needed - std::ifstream tfile(toolInfoPath); - json toolInfo; - tfile >> toolInfo; - tfile.close(); + existingMetadata = packageManager.loadPackageMetadata(toolName); + if (!existingMetadata.isValid()) { + std::cerr << "Warning: Invalid existing package metadata for " << toolName << std::endl; + } - std::string localHash = toolInfo.value("hash", ""); - std::string localArch = toolInfo.value("arch", arch); + std::string localHash = existingMetadata.hash; + std::string localArch = existingMetadata.arch.empty() ? arch : existingMetadata.arch; - // Get remote hash to compare - use the same arch that was originally installed - GetbinClient getbin; + // Get remote hash to compare - use multi-server GetbinClient + GetbinClient getbin(servers); std::string remoteHash; if (getbin.getHash(toolName, localArch, remoteHash) && !remoteHash.empty()) { if (localHash != remoteHash) { std::cout << "Updating " << toolName << "..." << std::endl; + isUpdate = true; } else { std::cout << toolName << " is already up to date." << std::endl; return 0; @@ -191,6 +213,7 @@ int install_tool(int argc, char* argv[]) { } else { // If we can't get remote hash, assume update is needed std::cout << "Updating " << toolName << "..." << std::endl; + isUpdate = true; } } else { std::cout << "Installing " << toolName << "..." << std::endl; @@ -210,9 +233,10 @@ int install_tool(int argc, char* argv[]) { if (std::filesystem::exists(binDir)) std::filesystem::remove_all(binDir); - // Download tool - try arch-specific version first, then universal fallback - GetbinClient getbin2; + // Download tool using multi-server GetbinClient - try arch-specific version first, then universal fallback + GetbinClient getbin2(servers); std::string downloadArch = arch; + std::string sourceServer; // Progress callback for downloads auto progressCallback = [&toolName](size_t downloaded, size_t total) -> bool { @@ -237,6 +261,12 @@ int install_tool(int argc, char* argv[]) { } clearAndPrint("Downloading " + toolName + "... done\n"); + // Find which server provided the package + if (!getbin2.findPackageServer(toolName, downloadArch, sourceServer)) { + // Fallback to first server if we can't determine the source + sourceServer = servers[0]; + } + // Unpack tool std::cout << "Unpacking..." << std::flush; if (!common::unpack_tgz(archivePath.string(), binDir.string())) { @@ -272,16 +302,11 @@ int install_tool(int argc, char* argv[]) { std::cerr << "Warning: Failed to get version for " << toolName << std::endl; } - // Save tool info - json toolInfo = { - {"name", toolName}, - {"version", version}, - {"hash", hash}, - {"arch", downloadArch} - }; - std::ofstream toolInfoFile(toolInfoPath); - toolInfoFile << toolInfo.dump(2); - toolInfoFile.close(); + // Create and save enhanced package metadata + PackageMetadata metadata(toolName, version, hash, downloadArch, sourceServer); + if (!packageManager.savePackageMetadata(metadata)) { + std::cerr << "Warning: Failed to save package metadata for " << toolName << std::endl; + } // Run setup script if exists std::filesystem::path setupScriptPath = binDir / "setup_script.sh"; @@ -297,11 +322,27 @@ int install_tool(int argc, char* argv[]) { int publish_tool(int argc, char* argv[]) { if (argc < 4) { - std::cerr << "Usage: getpkg publish " << std::endl; + std::cerr << "Usage: getpkg publish [--server ] " << std::endl; + std::cerr << " getpkg publish " << std::endl; return 1; } - std::string labeltag = argv[2]; - std::string folder = argv[3]; + + // Parse arguments for --server option + std::string targetServer; + std::string labeltag; + std::string folder; + int argIndex = 2; + + if (argc >= 5 && std::string(argv[2]) == "--server") { + targetServer = argv[3]; + labeltag = argv[4]; + folder = argv[5]; + argIndex = 5; + } else { + labeltag = argv[2]; + folder = argv[3]; + argIndex = 3; + } // If no ARCH is provided (no colon in labeltag), append ":universal" for cross-platform tools if (labeltag.find(':') == std::string::npos) { @@ -316,6 +357,49 @@ int publish_tool(int argc, char* argv[]) { } } } + + // Initialize ServerManager + ServerManager serverManager; + if (!serverManager.loadConfiguration()) { + std::cerr << "Failed to load server configuration" << std::endl; + return 1; + } + + // Determine target server + std::string publishServer; + if (!targetServer.empty()) { + // User specified a server, validate it exists in configuration + std::vector servers = serverManager.getServers(); + if (std::find(servers.begin(), servers.end(), targetServer) == servers.end()) { + std::cerr << "Error: Server '" << targetServer << "' is not configured" << std::endl; + std::cerr << "Use 'getpkg server add " << targetServer << "' to add it first" << std::endl; + return 1; + } + publishServer = targetServer; + } else { + // Use default publish server (first server with write token) + publishServer = serverManager.getDefaultPublishServer(); + if (publishServer.empty()) { + std::cerr << "Error: No servers with write tokens configured" << std::endl; + std::cerr << "Use 'getpkg server add ' and provide a write token" << std::endl; + return 1; + } + } + + // Get write token for the target server + std::string token = serverManager.getWriteToken(publishServer); + if (token.empty()) { + // Check environment variable as fallback + const char* envToken = std::getenv("SOS_WRITE_TOKEN"); + if (envToken && std::strlen(envToken) > 0) { + token = envToken; + } else { + std::cerr << "Error: No write token found for server '" << publishServer << "'" << std::endl; + std::cerr << "Set SOS_WRITE_TOKEN environment variable or configure token for this server" << std::endl; + return 1; + } + } + std::string home = get_home(); std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz"); std::filesystem::create_directories(archivePath.parent_path()); @@ -324,24 +408,10 @@ int publish_tool(int argc, char* argv[]) { std::cerr << "Failed to create archive." << std::endl; return 1; } - std::string token; - const char* envToken = std::getenv("SOS_WRITE_TOKEN"); - if (envToken && std::strlen(envToken) > 0) { - token = envToken; - } else { - std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/getpkg.xyz/write_token.txt"; - if (std::filesystem::exists(tokenPath)) { - std::ifstream tfile(tokenPath); - std::getline(tfile, token); - } else { - std::cout << "Enter getpkg.xyz write token: "; - std::getline(std::cin, token); - std::filesystem::create_directories(tokenPath.parent_path()); - std::ofstream tfile(tokenPath); - tfile << token << std::endl; - } - } - GetbinClient getbin; + + // Initialize GetbinClient with server list + std::vector servers = serverManager.getServers(); + GetbinClient getbin(servers); std::string url, hash; // Progress callback for upload @@ -355,13 +425,14 @@ int publish_tool(int argc, char* argv[]) { return true; // Continue upload }; + std::cout << "Publishing to " << publishServer << "..." << std::endl; std::cout << "Uploading..." << std::flush; - if (!getbin.upload(archivePath.string(), url, hash, token, uploadProgressCallback)) { - std::cerr << "\rFailed to upload archive." << std::endl; + if (!getbin.upload(publishServer, archivePath.string(), url, hash, token, uploadProgressCallback)) { + std::cerr << "\rFailed to upload archive to " << publishServer << std::endl; return 1; } clearAndPrint("Uploading... done\n"); - std::cout << "Published! URL: " << url << "\nHash: " << hash << std::endl; + std::cout << "Published to " << publishServer << "! URL: " << url << "\nHash: " << hash << std::endl; return 0; } @@ -369,6 +440,25 @@ int update_tool(int argc, char* argv[]) { std::string home = get_home(); std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; + // Initialize ServerManager and PackageMetadataManager + ServerManager serverManager; + if (!serverManager.loadConfiguration()) { + std::cerr << "Failed to load server configuration" << std::endl; + return 1; + } + + std::vector servers = serverManager.getServers(); + if (servers.empty()) { + std::cerr << "No servers configured" << std::endl; + return 1; + } + + PackageMetadataManager packageManager(configDir); + if (!packageManager.ensurePackagesDirectory()) { + std::cerr << "Failed to create packages directory" << std::endl; + return 1; + } + // Structure to hold tool information struct ToolInfo { std::string name; @@ -376,29 +466,43 @@ int update_tool(int argc, char* argv[]) { std::string remoteHash; std::string arch; std::string version; + std::string sourceServer; bool needsUpdate = false; std::string status = "Up to date"; }; std::vector tools; - // Collect all installed tools - if (std::filesystem::exists(configDir)) { - for (const auto& entry : std::filesystem::directory_iterator(configDir)) { - if (entry.path().extension() == ".json") { - std::string tname = entry.path().stem(); - - ToolInfo tool; - tool.name = tname; - - // Read local tool info - std::ifstream tfile(entry.path()); + // Collect all installed tools using PackageMetadataManager + std::vector installedPackages = packageManager.listInstalledPackages(); + for (const std::string& toolName : installedPackages) { + ToolInfo tool; + tool.name = toolName; + + // Load package metadata + PackageMetadata metadata = packageManager.loadPackageMetadata(toolName); + if (metadata.isValid()) { + tool.localHash = metadata.hash; + tool.arch = metadata.arch.empty() ? get_arch() : metadata.arch; + tool.version = metadata.version; + tool.sourceServer = metadata.sourceServer; + + if (tool.version.empty() || tool.version == "-") { + tool.version = "installed"; + } + } else { + // Fallback to legacy format if new format fails + std::filesystem::path legacyPath = configDir / (toolName + ".json"); + if (std::filesystem::exists(legacyPath)) { + std::ifstream tfile(legacyPath); if (tfile.good()) { json toolInfo; tfile >> toolInfo; tool.localHash = toolInfo.value("hash", ""); tool.arch = toolInfo.value("arch", get_arch()); tool.version = toolInfo.value("version", "-"); + tool.sourceServer = "getpkg.xyz"; // Default for legacy + if (!tool.version.empty() && tool.version.back() == '\n') { tool.version.pop_back(); } @@ -406,10 +510,10 @@ int update_tool(int argc, char* argv[]) { tool.version = "installed"; } } - - tools.push_back(tool); } } + + tools.push_back(tool); } if (tools.empty()) { @@ -420,14 +524,14 @@ int update_tool(int argc, char* argv[]) { // Step 1: Check for updates (with progress) std::cout << "Checking " << tools.size() << " tools for updates..." << std::endl; - GetbinClient getbin; + GetbinClient getbin(servers); for (size_t i = 0; i < tools.size(); ++i) { auto& tool = tools[i]; // Show progress std::cout << "\r[" << (i + 1) << "/" << tools.size() << "] Checking " << tool.name << "..." << std::flush; - // Check remote hash + // Check remote hash - use multi-server fallback std::string remoteHash; if (getbin.getHash(tool.name, tool.arch, remoteHash) && !remoteHash.empty()) { tool.remoteHash = remoteHash; @@ -499,16 +603,10 @@ int update_tool(int argc, char* argv[]) { tool.status = "Updated"; clearAndPrint("Updated\n"); - // Re-read version after update - std::filesystem::path toolInfoPath = configDir / (tool.name + ".json"); - if (std::filesystem::exists(toolInfoPath)) { - std::ifstream tfile(toolInfoPath); - json toolInfo; - tfile >> toolInfo; - tool.version = toolInfo.value("version", tool.version); - if (!tool.version.empty() && tool.version.back() == '\n') { - tool.version.pop_back(); - } + // Re-read version after update using PackageMetadataManager + PackageMetadata updatedMetadata = packageManager.loadPackageMetadata(tool.name); + if (updatedMetadata.isValid()) { + tool.version = updatedMetadata.version; if (tool.version.empty() || tool.version == "-") { tool.version = "installed"; } @@ -622,38 +720,73 @@ int hash_command(int argc, char* argv[]) { int unpublish_tool(int argc, char* argv[]) { if (argc < 3) { - std::cerr << "Usage: getpkg unpublish " << std::endl; + std::cerr << "Usage: getpkg unpublish [--server ] " << std::endl; + std::cerr << " getpkg unpublish [--server ] " << std::endl; + std::cerr << " getpkg unpublish " << std::endl; std::cerr << " getpkg unpublish " << std::endl; return 1; } - std::string target = argv[2]; - // Get token - std::string token; - const char* envToken = std::getenv("SOS_WRITE_TOKEN"); - if (envToken && std::strlen(envToken) > 0) { - token = envToken; - } else { - std::string home = get_home(); - std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/getpkg.xyz/write_token.txt"; - if (std::filesystem::exists(tokenPath)) { - std::ifstream tfile(tokenPath); - std::getline(tfile, token); - } else { - std::cout << "Enter getpkg.xyz write token: "; - std::getline(std::cin, token); - std::filesystem::create_directories(tokenPath.parent_path()); - std::ofstream tfile(tokenPath); - tfile << token << std::endl; + // Parse arguments for --server option + std::string targetServer; + std::string target; + + if (argc >= 4 && std::string(argv[2]) == "--server") { + if (argc < 5) { + std::cerr << "Usage: getpkg unpublish --server " << std::endl; + return 1; } + targetServer = argv[3]; + target = argv[4]; + } else { + target = argv[2]; } - if (token.empty()) { - std::cerr << "Error: No write token provided" << std::endl; + // Initialize ServerManager + ServerManager serverManager; + if (!serverManager.loadConfiguration()) { + std::cerr << "Failed to load server configuration" << std::endl; return 1; } - GetbinClient getbin; + // Determine target server + std::string unpublishServer; + if (!targetServer.empty()) { + // User specified a server, validate it exists in configuration + std::vector servers = serverManager.getServers(); + if (std::find(servers.begin(), servers.end(), targetServer) == servers.end()) { + std::cerr << "Error: Server '" << targetServer << "' is not configured" << std::endl; + std::cerr << "Use 'getpkg server add " << targetServer << "' to add it first" << std::endl; + return 1; + } + unpublishServer = targetServer; + } else { + // Use default publish server (first server with write token) + unpublishServer = serverManager.getDefaultPublishServer(); + if (unpublishServer.empty()) { + std::cerr << "Error: No servers with write tokens configured" << std::endl; + std::cerr << "Use 'getpkg server add ' and provide a write token" << std::endl; + return 1; + } + } + + // Get write token for the target server + std::string token = serverManager.getWriteToken(unpublishServer); + if (token.empty()) { + // Check environment variable as fallback + const char* envToken = std::getenv("SOS_WRITE_TOKEN"); + if (envToken && std::strlen(envToken) > 0) { + token = envToken; + } else { + std::cerr << "Error: No write token found for server '" << unpublishServer << "'" << std::endl; + std::cerr << "Set SOS_WRITE_TOKEN environment variable or configure token for this server" << std::endl; + return 1; + } + } + + // Initialize GetbinClient with server list + std::vector servers = serverManager.getServers(); + GetbinClient getbin(servers); std::string hash = target; // Check if target looks like a hash (all digits) or a tool name @@ -678,8 +811,8 @@ int unpublish_tool(int argc, char* argv[]) { // If a specific architecture was requested, only unpublish that one if (!specificArch.empty()) { - if (!getbin.getHash(toolName, specificArch, hash)) { - std::cerr << "Failed to get hash for " << target << std::endl; + if (!getbin.getHash(unpublishServer, toolName, specificArch, hash)) { + std::cerr << "Failed to get hash for " << target << " on server " << unpublishServer << std::endl; return 1; } @@ -703,14 +836,14 @@ int unpublish_tool(int argc, char* argv[]) { return 1; } - std::cout << "Found hash " << hash << " for " << target << std::endl; + std::cout << "Found hash " << hash << " for " << target << " on " << unpublishServer << std::endl; // Delete the specific architecture if (getbin.deleteObject(hash, token)) { - std::cout << "Successfully unpublished " << target << " (hash: " << hash << ")" << std::endl; + std::cout << "Successfully unpublished " << target << " from " << unpublishServer << " (hash: " << hash << ")" << std::endl; return 0; } else { - std::cerr << "Failed to unpublish " << target << std::endl; + std::cerr << "Failed to unpublish " << target << " from " << unpublishServer << std::endl; return 1; } } else { @@ -1105,14 +1238,15 @@ void show_help() { std::cout << " uninstall Remove an installed tool" << std::endl; std::cout << " Removes tool files, PATH entries, and autocomplete" << std::endl; std::cout << std::endl; - std::cout << " publish Upload a tool to getpkg.xyz" << std::endl; + std::cout << " publish [--server ] " << std::endl; + std::cout << " Upload a tool to a package server" << std::endl; std::cout << " ARCH is optional (defaults to 'universal')" << std::endl; - std::cout << " Requires SOS_WRITE_TOKEN environment variable" << std::endl; + std::cout << " Uses default publish server if --server not specified" << std::endl; std::cout << std::endl; - std::cout << " unpublish Remove ALL architectures of a tool" << std::endl; - std::cout << " unpublish Remove specific architecture only" << std::endl; - std::cout << " unpublish Remove a tool by hash" << std::endl; - std::cout << " Requires SOS_WRITE_TOKEN environment variable" << std::endl; + std::cout << " unpublish [--server ] Remove ALL architectures of a tool" << std::endl; + std::cout << " unpublish [--server ] Remove specific architecture only" << std::endl; + std::cout << " unpublish [--server ] Remove a tool by hash" << std::endl; + std::cout << " Uses default publish server if --server not specified" << std::endl; std::cout << " Without :ARCH, removes x86_64, aarch64, and universal versions" << std::endl; std::cout << std::endl; std::cout << " update Update getpkg and all installed tools" << std::endl; @@ -1147,8 +1281,10 @@ void show_help() { std::cout << " getpkg install myapp Install myapp" << std::endl; std::cout << " getpkg publish myapp:x86_64 ./build Publish architecture-specific build" << std::endl; std::cout << " getpkg publish myapp ./build Publish universal build" << std::endl; + std::cout << " getpkg publish --server example.com myapp ./build Publish to specific server" << std::endl; std::cout << " getpkg unpublish myapp Remove ALL architectures of myapp" << std::endl; std::cout << " getpkg unpublish myapp:x86_64 Remove only x86_64 version" << std::endl; + std::cout << " getpkg unpublish --server example.com myapp Remove from specific server" << std::endl; std::cout << " getpkg uninstall myapp Remove myapp from system" << std::endl; std::cout << " getpkg update Update everything" << std::endl; std::cout << " getpkg server add packages.example.com Add a custom package server" << std::endl;