Compare commits

..

4 Commits

Author SHA1 Message Date
507897d9a1 docs: Update 3 files
Some checks failed
Build-Test-Publish / build (linux/amd64) (push) Failing after 41s
Build-Test-Publish / build (linux/arm64) (push) Failing after 1m3s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Has been skipped
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Has been skipped
2025-07-20 22:02:54 +12:00
9c98ffcb86 docs: Update 2 files
Some checks failed
Build-Test-Publish / build (linux/amd64) (push) Failing after 48s
Build-Test-Publish / build (linux/arm64) (push) Failing after 1m13s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Has been skipped
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Has been skipped
2025-07-20 17:54:42 +12:00
938f4ac323 docs: Update 2 files
Some checks failed
Build-Test-Publish / build (linux/amd64) (push) Failing after 43s
Build-Test-Publish / build (linux/arm64) (push) Failing after 1m18s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Has been skipped
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Has been skipped
2025-07-20 17:19:17 +12:00
c507b1405e Update .kiro/specs/multi-server-support/tasks.md
Some checks failed
Build-Test-Publish / build (linux/amd64) (push) Failing after 43s
Build-Test-Publish / build (linux/arm64) (push) Failing after 1m18s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Has been skipped
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Has been skipped
2025-07-20 16:04:39 +12:00
3 changed files with 480 additions and 111 deletions

View File

@ -45,7 +45,9 @@ Based on analysis of the current codebase, the multi-server support feature need
## Migration and Compatibility Tasks ## Migration and Compatibility Tasks
- [-] 4. Implement migration system for existing installations - [x] 4. Implement migration system for existing installations
@ -56,7 +58,13 @@ Based on analysis of the current codebase, the multi-server support feature need
- Add migration error handling and rollback capabilities - Add migration error handling and rollback capabilities
- _Requirements: 4.4, 4.5, 6.1, 6.2, 6.3, 6.5_ - _Requirements: 4.4, 4.5, 6.1, 6.2, 6.3, 6.5_
- [ ] 5. Ensure backward compatibility - [x] 5. Ensure backward compatibility
- Implement default server configuration (getpkg.xyz) when no config exists - Implement default server configuration (getpkg.xyz) when no config exists
- Maintain existing CLI behavior for users without custom server configuration - Maintain existing CLI behavior for users without custom server configuration
- Preserve existing token storage location compatibility - Preserve existing token storage location compatibility
@ -65,14 +73,24 @@ Based on analysis of the current codebase, the multi-server support feature need
## CLI Integration Tasks ## CLI Integration Tasks
- [ ] 6. Add server management commands to main.cpp - [x] 6. Add server management commands to main.cpp
- Implement `getpkg server add <url>` command - Implement `getpkg server add <url>` command
- Implement `getpkg server remove <url>` command - Implement `getpkg server remove <url>` command
- Implement `getpkg server list` command - Implement `getpkg server list` command
- Add server URL validation and user feedback - Add server URL validation and user feedback
- _Requirements: 1.1, 1.2, 1.3_ - _Requirements: 1.1, 1.2, 1.3_
- [ ] 7. Update existing commands for multi-server support - [x] 7. Update existing commands for multi-server support
- Modify install command to use ServerManager and multi-server GetbinClient - Modify install command to use ServerManager and multi-server GetbinClient
- Update publish command to support --server option and default server selection - Update publish command to support --server option and default server selection
- Update unpublish command to support --server option and default server selection - Update unpublish command to support --server option and default server selection
@ -81,14 +99,22 @@ Based on analysis of the current codebase, the multi-server support feature need
## Integration and Testing Tasks ## Integration and Testing Tasks
- [ ] 8. Integrate all components in main application flow - [x] 8. Integrate all components in main application flow
- Initialize ServerManager in main.cpp startup - Initialize ServerManager in main.cpp startup
- Trigger migration process on first run with new version - Trigger migration process on first run with new version
- Update package installation flow to use enhanced metadata - Update package installation flow to use enhanced metadata
- Ensure proper error handling and user messaging throughout - Ensure proper error handling and user messaging throughout
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_ - _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
- [ ] 9. Add comprehensive error handling and validation - [-] 9. Add comprehensive error handling and validation
- Implement network error handling with server fallback - Implement network error handling with server fallback
- Add configuration file corruption recovery - Add configuration file corruption recovery
- Create user-friendly error messages for server connectivity issues - Create user-friendly error messages for server connectivity issues

View File

@ -90,5 +90,9 @@
"__tree": "cpp", "__tree": "cpp",
"queue": "cpp", "queue": "cpp",
"stack": "cpp" "stack": "cpp"
} },
"kiroAgent.enableTabAutocomplete": true,
"kiroAgent.trustedCommands": [
"bash *"
]
} }

View File

@ -58,6 +58,8 @@
#include "DropshellScriptManager.hpp" #include "DropshellScriptManager.hpp"
#include "GetbinClient.hpp" #include "GetbinClient.hpp"
#include "MigrationManager.hpp" #include "MigrationManager.hpp"
#include "ServerManager.hpp"
#include "PackageMetadata.hpp"
#include "archive_tgz.hpp" #include "archive_tgz.hpp"
#include "hash.hpp" #include "hash.hpp"
#include <iostream> #include <iostream>
@ -164,25 +166,47 @@ int install_tool(int argc, char* argv[]) {
std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg";
std::filesystem::path binDir = std::filesystem::path(home) / ".getpkg" / toolName; std::filesystem::path binDir = std::filesystem::path(home) / ".getpkg" / toolName;
std::filesystem::path archivePath = tempDir.path() / (toolName + ".tgz"); 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<std::string> 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 // 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 // Tool exists, check if update needed
std::ifstream tfile(toolInfoPath); existingMetadata = packageManager.loadPackageMetadata(toolName);
json toolInfo; if (!existingMetadata.isValid()) {
tfile >> toolInfo; std::cerr << "Warning: Invalid existing package metadata for " << toolName << std::endl;
tfile.close(); }
std::string localHash = toolInfo.value("hash", ""); std::string localHash = existingMetadata.hash;
std::string localArch = toolInfo.value("arch", arch); std::string localArch = existingMetadata.arch.empty() ? arch : existingMetadata.arch;
// Get remote hash to compare - use the same arch that was originally installed // Get remote hash to compare - use multi-server GetbinClient
GetbinClient getbin; GetbinClient getbin(servers);
std::string remoteHash; std::string remoteHash;
if (getbin.getHash(toolName, localArch, remoteHash) && !remoteHash.empty()) { if (getbin.getHash(toolName, localArch, remoteHash) && !remoteHash.empty()) {
if (localHash != remoteHash) { if (localHash != remoteHash) {
std::cout << "Updating " << toolName << "..." << std::endl; std::cout << "Updating " << toolName << "..." << std::endl;
isUpdate = true;
} else { } else {
std::cout << toolName << " is already up to date." << std::endl; std::cout << toolName << " is already up to date." << std::endl;
return 0; return 0;
@ -190,6 +214,7 @@ int install_tool(int argc, char* argv[]) {
} else { } else {
// If we can't get remote hash, assume update is needed // If we can't get remote hash, assume update is needed
std::cout << "Updating " << toolName << "..." << std::endl; std::cout << "Updating " << toolName << "..." << std::endl;
isUpdate = true;
} }
} else { } else {
std::cout << "Installing " << toolName << "..." << std::endl; std::cout << "Installing " << toolName << "..." << std::endl;
@ -209,9 +234,10 @@ int install_tool(int argc, char* argv[]) {
if (std::filesystem::exists(binDir)) if (std::filesystem::exists(binDir))
std::filesystem::remove_all(binDir); std::filesystem::remove_all(binDir);
// Download tool - try arch-specific version first, then universal fallback // Download tool using multi-server GetbinClient - try arch-specific version first, then universal fallback
GetbinClient getbin2; GetbinClient getbin2(servers);
std::string downloadArch = arch; std::string downloadArch = arch;
std::string sourceServer;
// Progress callback for downloads // Progress callback for downloads
auto progressCallback = [&toolName](size_t downloaded, size_t total) -> bool { auto progressCallback = [&toolName](size_t downloaded, size_t total) -> bool {
@ -236,6 +262,12 @@ int install_tool(int argc, char* argv[]) {
} }
clearAndPrint("Downloading " + toolName + "... done\n"); 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 // Unpack tool
std::cout << "Unpacking..." << std::flush; std::cout << "Unpacking..." << std::flush;
if (!common::unpack_tgz(archivePath.string(), binDir.string())) { if (!common::unpack_tgz(archivePath.string(), binDir.string())) {
@ -271,16 +303,11 @@ int install_tool(int argc, char* argv[]) {
std::cerr << "Warning: Failed to get version for " << toolName << std::endl; std::cerr << "Warning: Failed to get version for " << toolName << std::endl;
} }
// Save tool info // Create and save enhanced package metadata
json toolInfo = { PackageMetadata metadata(toolName, version, hash, downloadArch, sourceServer);
{"name", toolName}, if (!packageManager.savePackageMetadata(metadata)) {
{"version", version}, std::cerr << "Warning: Failed to save package metadata for " << toolName << std::endl;
{"hash", hash}, }
{"arch", downloadArch}
};
std::ofstream toolInfoFile(toolInfoPath);
toolInfoFile << toolInfo.dump(2);
toolInfoFile.close();
// Run setup script if exists // Run setup script if exists
std::filesystem::path setupScriptPath = binDir / "setup_script.sh"; std::filesystem::path setupScriptPath = binDir / "setup_script.sh";
@ -296,11 +323,27 @@ int install_tool(int argc, char* argv[]) {
int publish_tool(int argc, char* argv[]) { int publish_tool(int argc, char* argv[]) {
if (argc < 4) { if (argc < 4) {
std::cerr << "Usage: getpkg publish <tool_name:ARCH> <folder>" << std::endl; std::cerr << "Usage: getpkg publish [--server <url>] <tool_name:ARCH> <folder>" << std::endl;
std::cerr << " getpkg publish <tool_name:ARCH> <folder>" << std::endl;
return 1; 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 no ARCH is provided (no colon in labeltag), append ":universal" for cross-platform tools
if (labeltag.find(':') == std::string::npos) { if (labeltag.find(':') == std::string::npos) {
@ -315,6 +358,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<std::string> 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 <url>' 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::string home = get_home();
std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz"); std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz");
std::filesystem::create_directories(archivePath.parent_path()); std::filesystem::create_directories(archivePath.parent_path());
@ -323,24 +409,10 @@ int publish_tool(int argc, char* argv[]) {
std::cerr << "Failed to create archive." << std::endl; std::cerr << "Failed to create archive." << std::endl;
return 1; return 1;
} }
std::string token;
const char* envToken = std::getenv("SOS_WRITE_TOKEN"); // Initialize GetbinClient with server list
if (envToken && std::strlen(envToken) > 0) { std::vector<std::string> servers = serverManager.getServers();
token = envToken; GetbinClient getbin(servers);
} 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;
std::string url, hash; std::string url, hash;
// Progress callback for upload // Progress callback for upload
@ -354,13 +426,14 @@ int publish_tool(int argc, char* argv[]) {
return true; // Continue upload return true; // Continue upload
}; };
std::cout << "Publishing to " << publishServer << "..." << std::endl;
std::cout << "Uploading..." << std::flush; std::cout << "Uploading..." << std::flush;
if (!getbin.upload(archivePath.string(), url, hash, token, uploadProgressCallback)) { if (!getbin.upload(publishServer, archivePath.string(), url, hash, token, uploadProgressCallback)) {
std::cerr << "\rFailed to upload archive." << std::endl; std::cerr << "\rFailed to upload archive to " << publishServer << std::endl;
return 1; return 1;
} }
clearAndPrint("Uploading... done\n"); 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; return 0;
} }
@ -368,6 +441,25 @@ int update_tool(int argc, char* argv[]) {
std::string home = get_home(); std::string home = get_home();
std::filesystem::path configDir = std::filesystem::path(home) / ".config/getpkg"; 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<std::string> 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 // Structure to hold tool information
struct ToolInfo { struct ToolInfo {
std::string name; std::string name;
@ -375,29 +467,43 @@ int update_tool(int argc, char* argv[]) {
std::string remoteHash; std::string remoteHash;
std::string arch; std::string arch;
std::string version; std::string version;
std::string sourceServer;
bool needsUpdate = false; bool needsUpdate = false;
std::string status = "Up to date"; std::string status = "Up to date";
}; };
std::vector<ToolInfo> tools; std::vector<ToolInfo> tools;
// Collect all installed tools // Collect all installed tools using PackageMetadataManager
if (std::filesystem::exists(configDir)) { std::vector<std::string> installedPackages = packageManager.listInstalledPackages();
for (const auto& entry : std::filesystem::directory_iterator(configDir)) { for (const std::string& toolName : installedPackages) {
if (entry.path().extension() == ".json") { ToolInfo tool;
std::string tname = entry.path().stem(); tool.name = toolName;
ToolInfo tool; // Load package metadata
tool.name = tname; PackageMetadata metadata = packageManager.loadPackageMetadata(toolName);
if (metadata.isValid()) {
// Read local tool info tool.localHash = metadata.hash;
std::ifstream tfile(entry.path()); 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()) { if (tfile.good()) {
json toolInfo; json toolInfo;
tfile >> toolInfo; tfile >> toolInfo;
tool.localHash = toolInfo.value("hash", ""); tool.localHash = toolInfo.value("hash", "");
tool.arch = toolInfo.value("arch", get_arch()); tool.arch = toolInfo.value("arch", get_arch());
tool.version = toolInfo.value("version", "-"); tool.version = toolInfo.value("version", "-");
tool.sourceServer = "getpkg.xyz"; // Default for legacy
if (!tool.version.empty() && tool.version.back() == '\n') { if (!tool.version.empty() && tool.version.back() == '\n') {
tool.version.pop_back(); tool.version.pop_back();
} }
@ -405,10 +511,10 @@ int update_tool(int argc, char* argv[]) {
tool.version = "installed"; tool.version = "installed";
} }
} }
tools.push_back(tool);
} }
} }
tools.push_back(tool);
} }
if (tools.empty()) { if (tools.empty()) {
@ -419,14 +525,14 @@ int update_tool(int argc, char* argv[]) {
// Step 1: Check for updates (with progress) // Step 1: Check for updates (with progress)
std::cout << "Checking " << tools.size() << " tools for updates..." << std::endl; std::cout << "Checking " << tools.size() << " tools for updates..." << std::endl;
GetbinClient getbin; GetbinClient getbin(servers);
for (size_t i = 0; i < tools.size(); ++i) { for (size_t i = 0; i < tools.size(); ++i) {
auto& tool = tools[i]; auto& tool = tools[i];
// Show progress // Show progress
std::cout << "\r[" << (i + 1) << "/" << tools.size() << "] Checking " << tool.name << "..." << std::flush; 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; std::string remoteHash;
if (getbin.getHash(tool.name, tool.arch, remoteHash) && !remoteHash.empty()) { if (getbin.getHash(tool.name, tool.arch, remoteHash) && !remoteHash.empty()) {
tool.remoteHash = remoteHash; tool.remoteHash = remoteHash;
@ -498,16 +604,10 @@ int update_tool(int argc, char* argv[]) {
tool.status = "Updated"; tool.status = "Updated";
clearAndPrint("Updated\n"); clearAndPrint("Updated\n");
// Re-read version after update // Re-read version after update using PackageMetadataManager
std::filesystem::path toolInfoPath = configDir / (tool.name + ".json"); PackageMetadata updatedMetadata = packageManager.loadPackageMetadata(tool.name);
if (std::filesystem::exists(toolInfoPath)) { if (updatedMetadata.isValid()) {
std::ifstream tfile(toolInfoPath); tool.version = updatedMetadata.version;
json toolInfo;
tfile >> toolInfo;
tool.version = toolInfo.value("version", tool.version);
if (!tool.version.empty() && tool.version.back() == '\n') {
tool.version.pop_back();
}
if (tool.version.empty() || tool.version == "-") { if (tool.version.empty() || tool.version == "-") {
tool.version = "installed"; tool.version = "installed";
} }
@ -621,38 +721,73 @@ int hash_command(int argc, char* argv[]) {
int unpublish_tool(int argc, char* argv[]) { int unpublish_tool(int argc, char* argv[]) {
if (argc < 3) { if (argc < 3) {
std::cerr << "Usage: getpkg unpublish <tool_name[:ARCH]>" << std::endl; std::cerr << "Usage: getpkg unpublish [--server <url>] <tool_name[:ARCH]>" << std::endl;
std::cerr << " getpkg unpublish [--server <url>] <hash>" << std::endl;
std::cerr << " getpkg unpublish <tool_name[:ARCH]>" << std::endl;
std::cerr << " getpkg unpublish <hash>" << std::endl; std::cerr << " getpkg unpublish <hash>" << std::endl;
return 1; return 1;
} }
std::string target = argv[2];
// Get token // Parse arguments for --server option
std::string token; std::string targetServer;
const char* envToken = std::getenv("SOS_WRITE_TOKEN"); std::string target;
if (envToken && std::strlen(envToken) > 0) {
token = envToken; if (argc >= 4 && std::string(argv[2]) == "--server") {
} else { if (argc < 5) {
std::string home = get_home(); std::cerr << "Usage: getpkg unpublish --server <url> <tool_name[:ARCH]|hash>" << std::endl;
std::filesystem::path tokenPath = std::filesystem::path(home) / ".config/getpkg.xyz/write_token.txt"; return 1;
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;
} }
targetServer = argv[3];
target = argv[4];
} else {
target = argv[2];
} }
if (token.empty()) { // Initialize ServerManager
std::cerr << "Error: No write token provided" << std::endl; ServerManager serverManager;
if (!serverManager.loadConfiguration()) {
std::cerr << "Failed to load server configuration" << std::endl;
return 1; return 1;
} }
GetbinClient getbin; // Determine target server
std::string unpublishServer;
if (!targetServer.empty()) {
// User specified a server, validate it exists in configuration
std::vector<std::string> 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 <url>' 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<std::string> servers = serverManager.getServers();
GetbinClient getbin(servers);
std::string hash = target; std::string hash = target;
// Check if target looks like a hash (all digits) or a tool name // Check if target looks like a hash (all digits) or a tool name
@ -677,8 +812,8 @@ int unpublish_tool(int argc, char* argv[]) {
// If a specific architecture was requested, only unpublish that one // If a specific architecture was requested, only unpublish that one
if (!specificArch.empty()) { if (!specificArch.empty()) {
if (!getbin.getHash(toolName, specificArch, hash)) { if (!getbin.getHash(unpublishServer, toolName, specificArch, hash)) {
std::cerr << "Failed to get hash for " << target << std::endl; std::cerr << "Failed to get hash for " << target << " on server " << unpublishServer << std::endl;
return 1; return 1;
} }
@ -702,14 +837,14 @@ int unpublish_tool(int argc, char* argv[]) {
return 1; 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 // Delete the specific architecture
if (getbin.deleteObject(hash, token)) { 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; return 0;
} else { } else {
std::cerr << "Failed to unpublish " << target << std::endl; std::cerr << "Failed to unpublish " << target << " from " << unpublishServer << std::endl;
return 1; return 1;
} }
} else { } else {
@ -1104,14 +1239,15 @@ void show_help() {
std::cout << " uninstall <tool_name> Remove an installed tool" << std::endl; std::cout << " uninstall <tool_name> Remove an installed tool" << std::endl;
std::cout << " Removes tool files, PATH entries, and autocomplete" << std::endl; std::cout << " Removes tool files, PATH entries, and autocomplete" << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << " publish <tool_name[:ARCH]> <folder> Upload a tool to getpkg.xyz" << std::endl; std::cout << " publish [--server <url>] <tool_name[:ARCH]> <folder>" << 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 << " 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 << std::endl;
std::cout << " unpublish <tool_name> Remove ALL architectures of a tool" << std::endl; std::cout << " unpublish [--server <url>] <tool_name> Remove ALL architectures of a tool" << std::endl;
std::cout << " unpublish <tool_name:ARCH> Remove specific architecture only" << std::endl; std::cout << " unpublish [--server <url>] <tool_name:ARCH> Remove specific architecture only" << std::endl;
std::cout << " unpublish <hash> Remove a tool by hash" << std::endl; std::cout << " unpublish [--server <url>] <hash> Remove a tool by hash" << 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 << " Without :ARCH, removes x86_64, aarch64, and universal versions" << std::endl; std::cout << " Without :ARCH, removes x86_64, aarch64, and universal versions" << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << " update Update getpkg and all installed tools" << std::endl; std::cout << " update Update getpkg and all installed tools" << std::endl;
@ -1128,6 +1264,15 @@ void show_help() {
std::cout << " clean Clean up orphaned configs and symlinks" << std::endl; std::cout << " clean Clean up orphaned configs and symlinks" << std::endl;
std::cout << " Removes unused config files and dangling symlinks" << std::endl; std::cout << " Removes unused config files and dangling symlinks" << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << " server add <url> Add a new package server" << std::endl;
std::cout << " Adds a server to the configuration for package discovery" << std::endl;
std::cout << std::endl;
std::cout << " server remove <url> Remove a package server" << std::endl;
std::cout << " Removes a server from the configuration" << std::endl;
std::cout << std::endl;
std::cout << " server list List all configured servers" << std::endl;
std::cout << " Shows all servers with their status and write token info" << std::endl;
std::cout << std::endl;
std::cout << " version Show getpkg version" << std::endl; std::cout << " version Show getpkg version" << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << " help Show this help message" << std::endl; std::cout << " help Show this help message" << std::endl;
@ -1137,10 +1282,15 @@ void show_help() {
std::cout << " getpkg install myapp Install myapp" << std::endl; 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:x86_64 ./build Publish architecture-specific build" << std::endl;
std::cout << " getpkg publish myapp ./build Publish universal 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 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 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 uninstall myapp Remove myapp from system" << std::endl;
std::cout << " getpkg update Update everything" << 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;
std::cout << " getpkg server remove packages.example.com Remove a package server" << std::endl;
std::cout << " getpkg server list List all configured servers" << std::endl;
std::cout << std::endl; std::cout << std::endl;
std::cout << "ENVIRONMENT:" << std::endl; std::cout << "ENVIRONMENT:" << std::endl;
std::cout << " SOS_WRITE_TOKEN Auth token for publishing tools" << std::endl; std::cout << " SOS_WRITE_TOKEN Auth token for publishing tools" << std::endl;
@ -1151,6 +1301,163 @@ void show_help() {
std::cout << " ~/.local/bin/getpkg/ Installed tool binaries" << std::endl; std::cout << " ~/.local/bin/getpkg/ Installed tool binaries" << std::endl;
} }
int server_command(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "Usage: getpkg server <add|remove|list> [args...]" << std::endl;
std::cerr << " getpkg server add <url> Add a new server" << std::endl;
std::cerr << " getpkg server remove <url> Remove a server" << std::endl;
std::cerr << " getpkg server list List all configured servers" << std::endl;
return 1;
}
std::string subcommand = argv[2];
ServerManager serverManager;
// Load existing configuration
if (!serverManager.loadConfiguration()) {
std::cerr << "Failed to load server configuration" << std::endl;
return 1;
}
if (subcommand == "add") {
if (argc < 4) {
std::cerr << "Usage: getpkg server add <url>" << std::endl;
return 1;
}
std::string serverUrl = argv[3];
// Validate server URL format
if (serverUrl.empty()) {
std::cerr << "Error: Server URL cannot be empty" << std::endl;
return 1;
}
// Remove protocol if provided (we'll add it internally)
if (serverUrl.find("http://") == 0) {
serverUrl = serverUrl.substr(7);
} else if (serverUrl.find("https://") == 0) {
serverUrl = serverUrl.substr(8);
}
// Remove trailing slash if present
if (!serverUrl.empty() && serverUrl.back() == '/') {
serverUrl.pop_back();
}
std::cout << "Adding server: " << serverUrl << std::endl;
if (serverManager.addServer(serverUrl)) {
std::cout << "Successfully added server: " << serverUrl << std::endl;
// Ask if user wants to add a write token
std::cout << "Would you like to add a write token for this server? (y/N): ";
std::string response;
std::getline(std::cin, response);
if (response == "y" || response == "Y" || response == "yes" || response == "Yes") {
std::cout << "Enter write token for " << serverUrl << ": ";
std::string token;
std::getline(std::cin, token);
if (!token.empty()) {
if (serverManager.setWriteToken(serverUrl, token)) {
std::cout << "Write token added successfully" << std::endl;
} else {
std::cerr << "Failed to save write token" << std::endl;
}
}
}
return 0;
} else {
std::cerr << "Failed to add server: " << serverUrl << std::endl;
return 1;
}
} else if (subcommand == "remove") {
if (argc < 4) {
std::cerr << "Usage: getpkg server remove <url>" << std::endl;
return 1;
}
std::string serverUrl = argv[3];
// Remove protocol if provided
if (serverUrl.find("http://") == 0) {
serverUrl = serverUrl.substr(7);
} else if (serverUrl.find("https://") == 0) {
serverUrl = serverUrl.substr(8);
}
// Remove trailing slash if present
if (!serverUrl.empty() && serverUrl.back() == '/') {
serverUrl.pop_back();
}
std::cout << "Removing server: " << serverUrl << std::endl;
if (serverManager.removeServer(serverUrl)) {
std::cout << "Successfully removed server: " << serverUrl << std::endl;
return 0;
} else {
std::cerr << "Failed to remove server: " << serverUrl << std::endl;
return 1;
}
} else if (subcommand == "list") {
std::vector<std::string> servers = serverManager.getServers();
if (servers.empty()) {
std::cout << "No servers configured" << std::endl;
return 0;
}
std::cout << std::endl;
std::cout << "Configured servers:" << std::endl;
std::cout << "+" << std::string(30, '-') << "+" << std::string(12, '-') << "+" << std::string(15, '-') << "+" << std::endl;
std::cout << "|" << std::setw(30) << std::left << " Server URL"
<< "|" << std::setw(12) << std::left << " Default"
<< "|" << std::setw(15) << std::left << " Write Token"
<< "|" << std::endl;
std::cout << "+" << std::string(30, '-') << "+" << std::string(12, '-') << "+" << std::string(15, '-') << "+" << std::endl;
std::string defaultServer = serverManager.getDefaultServer();
for (const auto& server : servers) {
bool isDefault = (server == defaultServer);
bool hasToken = serverManager.hasWriteToken(server);
std::string displayUrl = server;
if (displayUrl.length() > 29) {
displayUrl = displayUrl.substr(0, 26) + "...";
}
std::cout << "|" << std::setw(30) << std::left << (" " + displayUrl)
<< "|" << std::setw(12) << std::left << (isDefault ? " Yes" : " No")
<< "|" << std::setw(15) << std::left << (hasToken ? " Yes" : " No")
<< "|" << std::endl;
}
std::cout << "+" << std::string(30, '-') << "+" << std::string(12, '-') << "+" << std::string(15, '-') << "+" << std::endl;
std::cout << std::endl;
std::cout << "Total servers: " << servers.size() << std::endl;
// Show default publish server if different from default
std::string defaultPublishServer = serverManager.getDefaultPublishServer();
if (defaultPublishServer != defaultServer) {
std::cout << "Default publish server: " << defaultPublishServer << std::endl;
}
return 0;
} else {
std::cerr << "Unknown server subcommand: " << subcommand << std::endl;
std::cerr << "Use 'getpkg server' for usage information." << std::endl;
return 1;
}
}
int autocomplete_command(int argc, char* argv[]) { int autocomplete_command(int argc, char* argv[]) {
std::vector<std::string> args(argv + 2, argv + argc); std::vector<std::string> args(argv + 2, argv + argc);
@ -1166,6 +1473,7 @@ int autocomplete_command(int argc, char* argv[]) {
std::cout << "hash\n"; std::cout << "hash\n";
std::cout << "list\n"; std::cout << "list\n";
std::cout << "clean\n"; std::cout << "clean\n";
std::cout << "server\n";
std::cout << "help\n"; std::cout << "help\n";
return 0; return 0;
} }
@ -1180,6 +1488,35 @@ int autocomplete_command(int argc, char* argv[]) {
} else if (subcommand == "uninstall") { } else if (subcommand == "uninstall") {
// For uninstall, list installed tools // For uninstall, list installed tools
std::filesystem::path configDir = std::filesystem::path(std::getenv("HOME")) / ".config" / "getpkg"; std::filesystem::path configDir = std::filesystem::path(std::getenv("HOME")) / ".config" / "getpkg";
if (std::filesystem::exists(configDir)) {
for (const auto& entry : std::filesystem::directory_iterator(configDir)) {
if (entry.path().extension() == ".json") {
std::cout << entry.path().stem().string() << "\n";
}
}
}
return 0;
} else if (subcommand == "server") {
// Handle server subcommand autocompletion
if (args.size() == 1) {
// Show server subcommands
std::cout << "add\n";
std::cout << "remove\n";
std::cout << "list\n";
} else if (args.size() == 2 && args[1] == "remove") {
// For server remove, list configured servers
ServerManager serverManager;
if (serverManager.loadConfiguration()) {
std::vector<std::string> servers = serverManager.getServers();
for (const auto& server : servers) {
std::cout << server << "\n";
}
}
}
return 0;
} else if (subcommand == "unpublish") {
// For unpublish, we could suggest installed tools
std::filesystem::path configDir = std::filesystem::path(std::getenv("HOME")) / ".config" / "getpkg";
if (std::filesystem::exists(configDir)) { if (std::filesystem::exists(configDir)) {
for (const auto& entry : std::filesystem::directory_iterator(configDir)) { for (const auto& entry : std::filesystem::directory_iterator(configDir)) {
if (entry.path().extension() == ".json") { if (entry.path().extension() == ".json") {
@ -1327,6 +1664,8 @@ int main(int argc, char* argv[]) {
return list_packages(argc, argv); return list_packages(argc, argv);
} else if (command == "clean") { } else if (command == "clean") {
return clean_tool(argc, argv); return clean_tool(argc, argv);
} else if (command == "server") {
return server_command(argc, argv);
} else if (command == "help") { } else if (command == "help") {
show_help(); show_help();
} else { } else {