docs: Update 2 files
All checks were successful
Build-Test-Publish / build (linux/amd64) (push) Successful in 1m8s
Build-Test-Publish / build (linux/arm64) (push) Successful in 1m36s
Build-Test-Publish / test-install-from-scratch (linux/amd64) (push) Successful in 6s
Build-Test-Publish / test-install-from-scratch (linux/arm64) (push) Successful in 7s

This commit is contained in:
Your Name
2025-07-20 15:06:51 +12:00
parent 52d8e5b95e
commit 187f1a250d
4 changed files with 419 additions and 2 deletions

View File

@ -69,4 +69,4 @@
## Configuration Files
- **.gitignore**: Standard ignore patterns for build artifacts
- **.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

View 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();
}

View 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
View File

@ -202,12 +202,23 @@ generate_commit_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() {
if ! git rev-parse --git-dir >/dev/null 2>&1; then
print_error "Not in a git repository"
exit 1
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