Compare commits
3 Commits
v2025.0720
...
v2025.0720
Author | SHA1 | Date | |
---|---|---|---|
7c785e1a32 | |||
3e4f327426 | |||
187f1a250d |
@ -4,7 +4,10 @@ Based on analysis of the current codebase, the multi-server support feature need
|
|||||||
|
|
||||||
## Core Infrastructure Tasks
|
## Core Infrastructure Tasks
|
||||||
|
|
||||||
- [-] 1. Create ServerManager class and server configuration system
|
- [x] 1. Create ServerManager class and server configuration system
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -14,7 +17,13 @@ Based on analysis of the current codebase, the multi-server support feature need
|
|||||||
- Implement write token management per server
|
- Implement write token management per server
|
||||||
- _Requirements: 1.1, 1.2, 1.3, 5.1, 5.2, 5.4_
|
- _Requirements: 1.1, 1.2, 1.3, 5.1, 5.2, 5.4_
|
||||||
|
|
||||||
- [ ] 2. Enhance GetbinClient for multi-server support
|
- [x] 2. Enhance GetbinClient for multi-server support
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- Modify GetbinClient constructor to accept server list instead of hardcoded host
|
- Modify GetbinClient constructor to accept server list instead of hardcoded host
|
||||||
- Implement multi-server fallback logic for downloads
|
- Implement multi-server fallback logic for downloads
|
||||||
- Add server-specific upload and hash operations
|
- Add server-specific upload and hash operations
|
||||||
|
@ -69,4 +69,4 @@
|
|||||||
## Configuration Files
|
## Configuration Files
|
||||||
- **.gitignore**: Standard ignore patterns for build artifacts
|
- **.gitignore**: Standard ignore patterns for build artifacts
|
||||||
- **.vscode/**: VS Code workspace settings
|
- **.vscode/**: VS Code workspace settings
|
||||||
- **CMakeLists.txt**: Follows standard template with PROJECT_NAME parameter
|
- **CMakeLists.txt**: Follows standard template with PROJECT_NAME parameter for the name of the project
|
||||||
|
@ -10,20 +10,37 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
const std::string GetbinClient::SERVER_HOST = "getpkg.xyz";
|
const std::string GetbinClient::DEFAULT_SERVER_HOST = "getpkg.xyz";
|
||||||
|
|
||||||
GetbinClient::GetbinClient() {
|
GetbinClient::GetbinClient(const std::vector<std::string>& servers) : servers_(servers) {
|
||||||
// Initialize CPR (done automatically, but we could add global config here)
|
// Initialize CPR (done automatically, but we could add global config here)
|
||||||
|
if (servers_.empty()) {
|
||||||
|
servers_.push_back(DEFAULT_SERVER_HOST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GetbinClient::GetbinClient() : servers_({DEFAULT_SERVER_HOST}) {
|
||||||
|
// Backward compatibility constructor
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string GetbinClient::getUserAgent() const {
|
std::string GetbinClient::getUserAgent() const {
|
||||||
return "getpkg/1.0";
|
return "getpkg/1.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath,
|
std::string GetbinClient::buildUrl(const std::string& serverUrl, const std::string& endpoint) const {
|
||||||
ProgressCallback progressCallback) {
|
std::string url = "https://" + serverUrl;
|
||||||
|
if (!endpoint.empty() && endpoint[0] != '/') {
|
||||||
|
url += "/";
|
||||||
|
}
|
||||||
|
url += endpoint;
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetbinClient::downloadFromServer(const std::string& serverUrl, const std::string& toolName,
|
||||||
|
const std::string& arch, const std::string& outPath,
|
||||||
|
ProgressCallback progressCallback) {
|
||||||
try {
|
try {
|
||||||
std::string url = "https://" + SERVER_HOST + "/object/" + toolName + ":" + arch;
|
std::string url = buildUrl(serverUrl, "/object/" + toolName + ":" + arch);
|
||||||
|
|
||||||
cpr::Session session;
|
cpr::Session session;
|
||||||
session.SetUrl(cpr::Url{url});
|
session.SetUrl(cpr::Url{url});
|
||||||
@ -52,20 +69,34 @@ bool GetbinClient::download(const std::string& toolName, const std::string& arch
|
|||||||
// Not found - this is expected for arch fallback
|
// Not found - this is expected for arch fallback
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[GetbinClient::download] HTTP " << response.status_code << ": " << response.error.message << std::endl;
|
std::cerr << "[GetbinClient::downloadFromServer] HTTP " << response.status_code << " from " << serverUrl << ": " << response.error.message << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "[GetbinClient::download] Exception: " << e.what() << std::endl;
|
std::cerr << "[GetbinClient::downloadFromServer] Exception with " << serverUrl << ": " << e.what() << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash,
|
bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath,
|
||||||
const std::string& token, ProgressCallback progressCallback) {
|
ProgressCallback progressCallback) {
|
||||||
|
// Multi-server fallback logic: try each server in order
|
||||||
|
for (const auto& server : servers_) {
|
||||||
|
if (downloadFromServer(server, toolName, arch, outPath, progressCallback)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, no server had the package
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetbinClient::upload(const std::string& serverUrl, const std::string& archivePath,
|
||||||
|
std::string& outUrl, std::string& outHash, const std::string& token,
|
||||||
|
ProgressCallback progressCallback) {
|
||||||
try {
|
try {
|
||||||
std::string url = "https://" + SERVER_HOST + "/upload";
|
std::string url = buildUrl(serverUrl, "/upload");
|
||||||
|
|
||||||
cpr::Session session;
|
cpr::Session session;
|
||||||
session.SetUrl(cpr::Url{url});
|
session.SetUrl(cpr::Url{url});
|
||||||
@ -110,7 +141,7 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s
|
|||||||
try {
|
try {
|
||||||
auto resp_json = json::parse(response.text);
|
auto resp_json = json::parse(response.text);
|
||||||
if (resp_json.contains("hash") && resp_json.contains("result") && resp_json["result"] == "success") {
|
if (resp_json.contains("hash") && resp_json.contains("result") && resp_json["result"] == "success") {
|
||||||
outUrl = "https://" + SERVER_HOST + "/object/" + resp_json["hash"].get<std::string>();
|
outUrl = buildUrl(serverUrl, "/object/" + resp_json["hash"].get<std::string>());
|
||||||
outHash = resp_json["hash"].get<std::string>();
|
outHash = resp_json["hash"].get<std::string>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -125,7 +156,7 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s
|
|||||||
return !outHash.empty();
|
return !outHash.empty();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[GetbinClient::upload] HTTP " << response.status_code << ": " << response.error.message << std::endl;
|
std::cerr << "[GetbinClient::upload] HTTP " << response.status_code << " to " << serverUrl << ": " << response.error.message << std::endl;
|
||||||
if (!response.text.empty()) {
|
if (!response.text.empty()) {
|
||||||
std::cerr << "[GetbinClient::upload] Response: " << response.text << std::endl;
|
std::cerr << "[GetbinClient::upload] Response: " << response.text << std::endl;
|
||||||
}
|
}
|
||||||
@ -133,14 +164,24 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "[GetbinClient::upload] Exception: " << e.what() << std::endl;
|
std::cerr << "[GetbinClient::upload] Exception with " << serverUrl << ": " << e.what() << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) {
|
bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash,
|
||||||
|
const std::string& token, ProgressCallback progressCallback) {
|
||||||
|
// Backward compatibility: use first server
|
||||||
|
if (servers_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return upload(servers_[0], archivePath, outUrl, outHash, token, progressCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetbinClient::getHash(const std::string& serverUrl, const std::string& toolName,
|
||||||
|
const std::string& arch, std::string& outHash) {
|
||||||
try {
|
try {
|
||||||
std::string url = "https://" + SERVER_HOST + "/hash/" + toolName + ":" + arch;
|
std::string url = buildUrl(serverUrl, "/hash/" + toolName + ":" + arch);
|
||||||
|
|
||||||
auto response = cpr::Get(cpr::Url{url},
|
auto response = cpr::Get(cpr::Url{url},
|
||||||
cpr::Header{{"User-Agent", getUserAgent()}},
|
cpr::Header{{"User-Agent", getUserAgent()}},
|
||||||
@ -168,19 +209,63 @@ bool GetbinClient::getHash(const std::string& toolName, const std::string& arch,
|
|||||||
// Not found - this is expected for non-existent tools/archs
|
// Not found - this is expected for non-existent tools/archs
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "[GetbinClient::getHash] HTTP " << response.status_code << ": " << response.error.message << std::endl;
|
std::cerr << "[GetbinClient::getHash] HTTP " << response.status_code << " from " << serverUrl << ": " << response.error.message << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
std::cerr << "[GetbinClient::getHash] Exception: " << e.what() << std::endl;
|
std::cerr << "[GetbinClient::getHash] Exception with " << serverUrl << ": " << e.what() << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) {
|
||||||
|
// Multi-server fallback: try each server in order
|
||||||
|
for (const auto& server : servers_) {
|
||||||
|
if (getHash(server, toolName, arch, outHash)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, no server had the package
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GetbinClient::findPackageServer(const std::string& toolName, const std::string& arch,
|
||||||
|
std::string& foundServer) const {
|
||||||
|
// Check each server to see which one has the package
|
||||||
|
for (const auto& server : servers_) {
|
||||||
|
try {
|
||||||
|
std::string url = buildUrl(server, "/hash/" + toolName + ":" + arch);
|
||||||
|
|
||||||
|
auto response = cpr::Get(cpr::Url{url},
|
||||||
|
cpr::Header{{"User-Agent", getUserAgent()}},
|
||||||
|
cpr::Timeout{10000}, // 10 seconds
|
||||||
|
cpr::VerifySsl{true});
|
||||||
|
|
||||||
|
if (response.status_code == 200) {
|
||||||
|
// Package found on this server
|
||||||
|
foundServer = server;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Continue to next server if 404 or other error
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// Continue to next server on exception
|
||||||
|
std::cerr << "[GetbinClient::findPackageServer] Exception with " << server << ": " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Package not found on any server
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool GetbinClient::deleteObject(const std::string& hash, const std::string& token) {
|
bool GetbinClient::deleteObject(const std::string& hash, const std::string& token) {
|
||||||
try {
|
try {
|
||||||
std::string url = "https://" + SERVER_HOST + "/deleteobject?hash=" + hash;
|
// Use first server for backward compatibility
|
||||||
|
if (servers_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string url = buildUrl(servers_[0], "/deleteobject?hash=" + hash);
|
||||||
|
|
||||||
auto response = cpr::Get(cpr::Url{url},
|
auto response = cpr::Get(cpr::Url{url},
|
||||||
cpr::Header{
|
cpr::Header{
|
||||||
@ -208,7 +293,11 @@ bool GetbinClient::deleteObject(const std::string& hash, const std::string& toke
|
|||||||
|
|
||||||
bool GetbinClient::listPackages(std::vector<std::string>& outPackages) {
|
bool GetbinClient::listPackages(std::vector<std::string>& outPackages) {
|
||||||
try {
|
try {
|
||||||
std::string url = "https://" + SERVER_HOST + "/dir";
|
// Use first server for backward compatibility
|
||||||
|
if (servers_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string url = buildUrl(servers_[0], "/dir");
|
||||||
|
|
||||||
auto response = cpr::Get(cpr::Url{url},
|
auto response = cpr::Get(cpr::Url{url},
|
||||||
cpr::Header{{"User-Agent", getUserAgent()}},
|
cpr::Header{{"User-Agent", getUserAgent()}},
|
||||||
@ -271,7 +360,11 @@ bool GetbinClient::listPackages(std::vector<std::string>& outPackages) {
|
|||||||
|
|
||||||
bool GetbinClient::listAllEntries(std::vector<std::pair<std::string, std::vector<std::string>>>& outEntries) {
|
bool GetbinClient::listAllEntries(std::vector<std::pair<std::string, std::vector<std::string>>>& outEntries) {
|
||||||
try {
|
try {
|
||||||
std::string url = "https://" + SERVER_HOST + "/dir";
|
// Use first server for backward compatibility
|
||||||
|
if (servers_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string url = buildUrl(servers_[0], "/dir");
|
||||||
|
|
||||||
auto response = cpr::Get(cpr::Url{url},
|
auto response = cpr::Get(cpr::Url{url},
|
||||||
cpr::Header{{"User-Agent", getUserAgent()}},
|
cpr::Header{{"User-Agent", getUserAgent()}},
|
||||||
|
@ -5,21 +5,53 @@
|
|||||||
|
|
||||||
class GetbinClient {
|
class GetbinClient {
|
||||||
public:
|
public:
|
||||||
|
// Constructor accepting server list for multi-server support
|
||||||
|
GetbinClient(const std::vector<std::string>& servers);
|
||||||
|
|
||||||
|
// Backward compatibility constructor (uses default server)
|
||||||
GetbinClient();
|
GetbinClient();
|
||||||
|
|
||||||
// Progress callback: (downloaded_bytes, total_bytes) -> should_continue
|
// Progress callback: (downloaded_bytes, total_bytes) -> should_continue
|
||||||
using ProgressCallback = std::function<bool(size_t, size_t)>;
|
using ProgressCallback = std::function<bool(size_t, size_t)>;
|
||||||
|
|
||||||
|
// Multi-server download with fallback logic
|
||||||
bool download(const std::string& toolName, const std::string& arch, const std::string& outPath,
|
bool download(const std::string& toolName, const std::string& arch, const std::string& outPath,
|
||||||
ProgressCallback progressCallback = nullptr);
|
ProgressCallback progressCallback = nullptr);
|
||||||
|
|
||||||
|
// Server-specific download
|
||||||
|
bool downloadFromServer(const std::string& serverUrl, const std::string& toolName,
|
||||||
|
const std::string& arch, const std::string& outPath,
|
||||||
|
ProgressCallback progressCallback = nullptr);
|
||||||
|
|
||||||
|
// Server-specific upload
|
||||||
|
bool upload(const std::string& serverUrl, const std::string& archivePath,
|
||||||
|
std::string& outUrl, std::string& outHash, const std::string& token,
|
||||||
|
ProgressCallback progressCallback = nullptr);
|
||||||
|
|
||||||
|
// Backward compatibility upload (uses first server)
|
||||||
bool upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token,
|
bool upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token,
|
||||||
ProgressCallback progressCallback = nullptr);
|
ProgressCallback progressCallback = nullptr);
|
||||||
|
|
||||||
|
// Server-specific hash retrieval
|
||||||
|
bool getHash(const std::string& serverUrl, const std::string& toolName,
|
||||||
|
const std::string& arch, std::string& outHash);
|
||||||
|
|
||||||
|
// Multi-server hash retrieval with fallback
|
||||||
bool getHash(const std::string& toolName, const std::string& arch, std::string& outHash);
|
bool getHash(const std::string& toolName, const std::string& arch, std::string& outHash);
|
||||||
|
|
||||||
|
// Find which server has a specific package
|
||||||
|
bool findPackageServer(const std::string& toolName, const std::string& arch,
|
||||||
|
std::string& foundServer) const;
|
||||||
|
|
||||||
|
// Legacy methods (use first server for backward compatibility)
|
||||||
bool deleteObject(const std::string& hash, const std::string& token);
|
bool deleteObject(const std::string& hash, const std::string& token);
|
||||||
bool listPackages(std::vector<std::string>& outPackages);
|
bool listPackages(std::vector<std::string>& outPackages);
|
||||||
bool listAllEntries(std::vector<std::pair<std::string, std::vector<std::string>>>& outEntries);
|
bool listAllEntries(std::vector<std::pair<std::string, std::vector<std::string>>>& outEntries);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static const std::string SERVER_HOST;
|
static const std::string DEFAULT_SERVER_HOST;
|
||||||
|
std::vector<std::string> servers_;
|
||||||
|
|
||||||
std::string getUserAgent() const;
|
std::string getUserAgent() const;
|
||||||
|
std::string buildUrl(const std::string& serverUrl, const std::string& endpoint) const;
|
||||||
};
|
};
|
||||||
|
353
getpkg/src/ServerManager.cpp
Normal file
353
getpkg/src/ServerManager.cpp
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
#include "ServerManager.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <regex>
|
||||||
|
#include <cpr/cpr.h>
|
||||||
|
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// ServerConfig implementation
|
||||||
|
json ServerConfig::toJson() const {
|
||||||
|
return json{
|
||||||
|
{"url", url},
|
||||||
|
{"name", name},
|
||||||
|
{"default", isDefault},
|
||||||
|
{"writeToken", writeToken},
|
||||||
|
{"added", addedDate}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerConfig ServerConfig::fromJson(const json& j) {
|
||||||
|
ServerConfig config;
|
||||||
|
config.url = j.value("url", "");
|
||||||
|
config.name = j.value("name", "");
|
||||||
|
config.isDefault = j.value("default", false);
|
||||||
|
config.writeToken = j.value("writeToken", "");
|
||||||
|
config.addedDate = j.value("added", "");
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerManager implementation
|
||||||
|
ServerManager::ServerManager() {
|
||||||
|
const char* home = getenv("HOME");
|
||||||
|
if (home) {
|
||||||
|
configPath_ = std::filesystem::path(home) / ".config" / "getpkg" / "servers.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::addServer(const std::string& serverUrl, const std::string& writeToken) {
|
||||||
|
if (!validateServerUrl(serverUrl)) {
|
||||||
|
std::cerr << "Invalid server URL: " << serverUrl << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if server already exists
|
||||||
|
if (findServer(serverUrl) != nullptr) {
|
||||||
|
std::cerr << "Server already exists: " << serverUrl << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if server is reachable
|
||||||
|
if (!isServerReachable(serverUrl)) {
|
||||||
|
std::cerr << "Warning: Server may not be reachable: " << serverUrl << std::endl;
|
||||||
|
// Continue anyway - server might be temporarily down
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerConfig config;
|
||||||
|
config.url = serverUrl;
|
||||||
|
config.name = serverUrl; // Use URL as default name
|
||||||
|
config.isDefault = servers_.empty(); // First server becomes default
|
||||||
|
config.writeToken = writeToken;
|
||||||
|
config.addedDate = getCurrentTimestamp();
|
||||||
|
|
||||||
|
servers_.push_back(config);
|
||||||
|
|
||||||
|
return saveConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::removeServer(const std::string& serverUrl) {
|
||||||
|
auto it = std::find_if(servers_.begin(), servers_.end(),
|
||||||
|
[&serverUrl](const ServerConfig& config) {
|
||||||
|
return config.url == serverUrl;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (it == servers_.end()) {
|
||||||
|
std::cerr << "Server not found: " << serverUrl << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow removing the last server
|
||||||
|
if (servers_.size() == 1) {
|
||||||
|
std::cerr << "Cannot remove the last server. Add another server first." << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wasDefault = it->isDefault;
|
||||||
|
servers_.erase(it);
|
||||||
|
|
||||||
|
// If we removed the default server, make the first remaining server default
|
||||||
|
if (wasDefault && !servers_.empty()) {
|
||||||
|
servers_[0].isDefault = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return saveConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ServerManager::getServers() const {
|
||||||
|
std::vector<std::string> urls;
|
||||||
|
for (const auto& server : servers_) {
|
||||||
|
urls.push_back(server.url);
|
||||||
|
}
|
||||||
|
return urls;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ServerManager::getDefaultServer() const {
|
||||||
|
for (const auto& server : servers_) {
|
||||||
|
if (server.isDefault) {
|
||||||
|
return server.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no default is set, return the first server
|
||||||
|
if (!servers_.empty()) {
|
||||||
|
return servers_[0].url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "getpkg.xyz"; // Fallback to original default
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ServerManager::getDefaultPublishServer() const {
|
||||||
|
// Return first server with a write token
|
||||||
|
for (const auto& server : servers_) {
|
||||||
|
if (!server.writeToken.empty()) {
|
||||||
|
return server.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no server has a token, return the default server
|
||||||
|
return getDefaultServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::setWriteToken(const std::string& serverUrl, const std::string& token) {
|
||||||
|
ServerConfig* server = findServer(serverUrl);
|
||||||
|
if (server == nullptr) {
|
||||||
|
std::cerr << "Server not found: " << serverUrl << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
server->writeToken = token;
|
||||||
|
return saveConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ServerManager::getWriteToken(const std::string& serverUrl) const {
|
||||||
|
const ServerConfig* server = findServer(serverUrl);
|
||||||
|
if (server != nullptr) {
|
||||||
|
return server->writeToken;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::hasWriteToken(const std::string& serverUrl) const {
|
||||||
|
const ServerConfig* server = findServer(serverUrl);
|
||||||
|
return server != nullptr && !server->writeToken.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ServerManager::getServersWithTokens() const {
|
||||||
|
std::vector<std::string> serversWithTokens;
|
||||||
|
for (const auto& server : servers_) {
|
||||||
|
if (!server.writeToken.empty()) {
|
||||||
|
serversWithTokens.push_back(server.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return serversWithTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::loadConfiguration() {
|
||||||
|
if (!std::filesystem::exists(configPath_)) {
|
||||||
|
ensureDefaultConfiguration();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::ifstream file(configPath_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Failed to open server configuration file: " << configPath_ << std::endl;
|
||||||
|
ensureDefaultConfiguration();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
json config;
|
||||||
|
file >> config;
|
||||||
|
|
||||||
|
if (!config.contains("servers") || !config["servers"].is_array()) {
|
||||||
|
std::cerr << "Invalid server configuration format" << std::endl;
|
||||||
|
ensureDefaultConfiguration();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
servers_.clear();
|
||||||
|
for (const auto& serverJson : config["servers"]) {
|
||||||
|
try {
|
||||||
|
servers_.push_back(ServerConfig::fromJson(serverJson));
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Warning: Skipping invalid server config: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have at least one server
|
||||||
|
if (servers_.empty()) {
|
||||||
|
ensureDefaultConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error loading server configuration: " << e.what() << std::endl;
|
||||||
|
ensureDefaultConfiguration();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::saveConfiguration() {
|
||||||
|
try {
|
||||||
|
// Ensure directory exists
|
||||||
|
std::filesystem::create_directories(configPath_.parent_path());
|
||||||
|
|
||||||
|
json config;
|
||||||
|
config["version"] = "1.0";
|
||||||
|
config["lastUpdated"] = getCurrentTimestamp();
|
||||||
|
|
||||||
|
json serversArray = json::array();
|
||||||
|
for (const auto& server : servers_) {
|
||||||
|
serversArray.push_back(server.toJson());
|
||||||
|
}
|
||||||
|
config["servers"] = serversArray;
|
||||||
|
|
||||||
|
std::ofstream file(configPath_);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
std::cerr << "Failed to open server configuration file for writing: " << configPath_ << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file << config.dump(2);
|
||||||
|
return file.good();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error saving server configuration: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerManager::ensureDefaultConfiguration() {
|
||||||
|
servers_.clear();
|
||||||
|
|
||||||
|
ServerConfig defaultServer;
|
||||||
|
defaultServer.url = "getpkg.xyz";
|
||||||
|
defaultServer.name = "Official getpkg Registry";
|
||||||
|
defaultServer.isDefault = true;
|
||||||
|
defaultServer.writeToken = "";
|
||||||
|
defaultServer.addedDate = getCurrentTimestamp();
|
||||||
|
|
||||||
|
servers_.push_back(defaultServer);
|
||||||
|
|
||||||
|
saveConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::migrateFromLegacy() {
|
||||||
|
const char* home = getenv("HOME");
|
||||||
|
if (!home) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path legacyTokenPath = std::filesystem::path(home) / ".config" / "getpkg.xyz" / "write_token.txt";
|
||||||
|
|
||||||
|
if (std::filesystem::exists(legacyTokenPath)) {
|
||||||
|
try {
|
||||||
|
std::ifstream tokenFile(legacyTokenPath);
|
||||||
|
std::string token;
|
||||||
|
std::getline(tokenFile, token);
|
||||||
|
|
||||||
|
if (!token.empty()) {
|
||||||
|
// Set the token for getpkg.xyz server
|
||||||
|
setWriteToken("getpkg.xyz", token);
|
||||||
|
|
||||||
|
// Optionally remove the legacy token file
|
||||||
|
// std::filesystem::remove(legacyTokenPath);
|
||||||
|
|
||||||
|
std::cout << "Migrated legacy write token for getpkg.xyz" << std::endl;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Warning: Failed to migrate legacy token: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::validateServerUrl(const std::string& url) const {
|
||||||
|
if (url.empty() || url.length() > 253) { // DNS name length limit
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic URL validation - should be a valid hostname or IP
|
||||||
|
// Allow formats like: example.com, sub.example.com, 192.168.1.1, localhost
|
||||||
|
std::regex urlPattern(R"(^[a-zA-Z0-9]([a-zA-Z0-9\-\.]*[a-zA-Z0-9])?$)");
|
||||||
|
|
||||||
|
if (!std::regex_match(url, urlPattern)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional checks
|
||||||
|
if (url.find("..") != std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.front() == '.' || url.back() == '.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ServerManager::isServerReachable(const std::string& url) const {
|
||||||
|
try {
|
||||||
|
std::string testUrl = "https://" + url + "/";
|
||||||
|
|
||||||
|
auto response = cpr::Head(cpr::Url{testUrl},
|
||||||
|
cpr::Timeout{5000}, // 5 seconds
|
||||||
|
cpr::VerifySsl{true});
|
||||||
|
|
||||||
|
// Accept any response that indicates the server is reachable
|
||||||
|
// (200, 404, 403, etc. - as long as we get a response)
|
||||||
|
return response.status_code > 0;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerConfig* ServerManager::findServer(const std::string& url) {
|
||||||
|
auto it = std::find_if(servers_.begin(), servers_.end(),
|
||||||
|
[&url](const ServerConfig& config) {
|
||||||
|
return config.url == url;
|
||||||
|
});
|
||||||
|
return (it != servers_.end()) ? &(*it) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServerConfig* ServerManager::findServer(const std::string& url) const {
|
||||||
|
auto it = std::find_if(servers_.begin(), servers_.end(),
|
||||||
|
[&url](const ServerConfig& config) {
|
||||||
|
return config.url == url;
|
||||||
|
});
|
||||||
|
return (it != servers_.end()) ? &(*it) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ServerManager::getCurrentTimestamp() const {
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto time_t = std::chrono::system_clock::to_time_t(now);
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::put_time(std::gmtime(&time_t), "%Y-%m-%dT%H:%M:%SZ");
|
||||||
|
return ss.str();
|
||||||
|
}
|
53
getpkg/src/ServerManager.hpp
Normal file
53
getpkg/src/ServerManager.hpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
struct ServerConfig {
|
||||||
|
std::string url;
|
||||||
|
std::string name;
|
||||||
|
bool isDefault = false;
|
||||||
|
std::string writeToken;
|
||||||
|
std::string addedDate;
|
||||||
|
|
||||||
|
// JSON serialization
|
||||||
|
nlohmann::json toJson() const;
|
||||||
|
static ServerConfig fromJson(const nlohmann::json& j);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ServerManager {
|
||||||
|
public:
|
||||||
|
ServerManager();
|
||||||
|
|
||||||
|
// Server management
|
||||||
|
bool addServer(const std::string& serverUrl, const std::string& writeToken = "");
|
||||||
|
bool removeServer(const std::string& serverUrl);
|
||||||
|
std::vector<std::string> getServers() const;
|
||||||
|
std::string getDefaultServer() const;
|
||||||
|
std::string getDefaultPublishServer() const; // First server with write token
|
||||||
|
|
||||||
|
// Token management
|
||||||
|
bool setWriteToken(const std::string& serverUrl, const std::string& token);
|
||||||
|
std::string getWriteToken(const std::string& serverUrl) const;
|
||||||
|
bool hasWriteToken(const std::string& serverUrl) const;
|
||||||
|
std::vector<std::string> getServersWithTokens() const;
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
bool loadConfiguration();
|
||||||
|
bool saveConfiguration();
|
||||||
|
void ensureDefaultConfiguration();
|
||||||
|
|
||||||
|
// Migration
|
||||||
|
bool migrateFromLegacy();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<ServerConfig> servers_;
|
||||||
|
std::filesystem::path configPath_;
|
||||||
|
|
||||||
|
bool validateServerUrl(const std::string& url) const;
|
||||||
|
bool isServerReachable(const std::string& url) const;
|
||||||
|
ServerConfig* findServer(const std::string& url);
|
||||||
|
const ServerConfig* findServer(const std::string& url) const;
|
||||||
|
std::string getCurrentTimestamp() const;
|
||||||
|
};
|
13
gp/gp
13
gp/gp
@ -202,12 +202,23 @@ generate_commit_message() {
|
|||||||
echo "$message"
|
echo "$message"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check if we're in a git repository
|
# Function to check if we're in a git repository and change to repo root
|
||||||
check_git_repo() {
|
check_git_repo() {
|
||||||
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
print_error "Not in a git repository"
|
print_error "Not in a git repository"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Change to the git repository root to ensure we operate on the entire repo
|
||||||
|
local git_root
|
||||||
|
git_root=$(git rev-parse --show-toplevel)
|
||||||
|
if [ "$PWD" != "$git_root" ]; then
|
||||||
|
print_info "Changing to git repository root: $git_root"
|
||||||
|
cd "$git_root" || {
|
||||||
|
print_error "Failed to change to git repository root"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Function to check for uncommitted changes and unpushed commits
|
# Function to check for uncommitted changes and unpushed commits
|
||||||
|
Reference in New Issue
Block a user