docs: Add 2 and update 2 files
This commit is contained in:
@ -45,7 +45,10 @@ 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
|
- [-] 4. Implement migration system for existing installations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- Create MigrationManager class for legacy data handling
|
- Create MigrationManager class for legacy data handling
|
||||||
- Implement automatic migration from single-server to multi-server config
|
- Implement automatic migration from single-server to multi-server config
|
||||||
- Migrate existing package JSON files to packages subdirectory
|
- Migrate existing package JSON files to packages subdirectory
|
||||||
|
575
getpkg/src/MigrationManager.cpp
Normal file
575
getpkg/src/MigrationManager.cpp
Normal file
@ -0,0 +1,575 @@
|
|||||||
|
#include "MigrationManager.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
MigrationManager::MigrationManager() {
|
||||||
|
const char* home = std::getenv("HOME");
|
||||||
|
if (home) {
|
||||||
|
configDir_ = std::filesystem::path(home) / ".config" / "getpkg";
|
||||||
|
packagesDir_ = configDir_ / PACKAGES_DIRECTORY_NAME;
|
||||||
|
backupDir_ = configDir_ / BACKUP_DIRECTORY_NAME;
|
||||||
|
legacyTokenDir_ = configDir_ / DEFAULT_SERVER_URL;
|
||||||
|
|
||||||
|
packageManager_ = std::make_unique<PackageMetadataManager>(configDir_);
|
||||||
|
serverManager_ = std::make_unique<ServerManager>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MigrationManager::MigrationManager(const std::filesystem::path& configDir)
|
||||||
|
: configDir_(configDir),
|
||||||
|
packagesDir_(configDir / PACKAGES_DIRECTORY_NAME),
|
||||||
|
backupDir_(configDir / BACKUP_DIRECTORY_NAME),
|
||||||
|
legacyTokenDir_(configDir / DEFAULT_SERVER_URL) {
|
||||||
|
|
||||||
|
packageManager_ = std::make_unique<PackageMetadataManager>(configDir);
|
||||||
|
serverManager_ = std::make_unique<ServerManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::needsMigration() const {
|
||||||
|
// Check if we have legacy configuration that needs migration
|
||||||
|
bool hasLegacyConfig = hasLegacyServerConfiguration() || hasLegacyPackageFiles();
|
||||||
|
bool hasNewConfig = hasNewFormatConfiguration();
|
||||||
|
bool hasPackagesDir = std::filesystem::exists(packagesDir_);
|
||||||
|
|
||||||
|
// Need migration if:
|
||||||
|
// 1. We have legacy config (token file or package files in root config dir)
|
||||||
|
// 2. We have new config but no packages directory (incomplete migration)
|
||||||
|
return hasLegacyConfig || (hasNewConfig && !hasPackagesDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::performMigration() {
|
||||||
|
lastResult_ = MigrationResult();
|
||||||
|
|
||||||
|
logInfo("Starting migration from single-server to multi-server configuration");
|
||||||
|
|
||||||
|
// Create backup before starting migration
|
||||||
|
if (!createBackup()) {
|
||||||
|
logError("Failed to create backup before migration");
|
||||||
|
lastResult_.success = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Step 1: Create packages directory
|
||||||
|
if (!createPackagesDirectory()) {
|
||||||
|
logError("Failed to create packages directory");
|
||||||
|
lastResult_.success = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lastResult_.packageDirectoryCreated = true;
|
||||||
|
|
||||||
|
// Step 2: Migrate server configuration
|
||||||
|
if (!migrateServerConfiguration()) {
|
||||||
|
logError("Failed to migrate server configuration");
|
||||||
|
lastResult_.success = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lastResult_.serverConfigMigrated = true;
|
||||||
|
|
||||||
|
// Step 3: Migrate package metadata
|
||||||
|
if (!migratePackageMetadata()) {
|
||||||
|
logError("Failed to migrate package metadata");
|
||||||
|
lastResult_.success = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Validate migration
|
||||||
|
if (!validateMigration()) {
|
||||||
|
logError("Migration validation failed");
|
||||||
|
lastResult_.success = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 5: Clean up legacy files (optional, keep backup)
|
||||||
|
// We don't delete legacy files immediately to allow rollback
|
||||||
|
|
||||||
|
lastResult_.success = true;
|
||||||
|
logInfo("Migration completed successfully");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Migration failed with exception: " + std::string(e.what()));
|
||||||
|
lastResult_.success = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::migrateServerConfiguration() {
|
||||||
|
try {
|
||||||
|
// Load existing server configuration or create default
|
||||||
|
if (!serverManager_->loadConfiguration()) {
|
||||||
|
logWarning("Failed to load existing server configuration, creating default");
|
||||||
|
serverManager_->ensureDefaultConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate legacy token file if it exists
|
||||||
|
if (!migrateLegacyTokenFile()) {
|
||||||
|
logWarning("Failed to migrate legacy token file (may not exist)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the configuration to ensure it's in the new format
|
||||||
|
if (!serverManager_->saveConfiguration()) {
|
||||||
|
logError("Failed to save server configuration");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo("Server configuration migrated successfully");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Error migrating server configuration: " + std::string(e.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::migratePackageMetadata() {
|
||||||
|
try {
|
||||||
|
// Find legacy package files in the config directory
|
||||||
|
std::vector<std::filesystem::path> legacyFiles = findFilesWithExtension(configDir_, ".json");
|
||||||
|
|
||||||
|
// Filter out non-package files
|
||||||
|
std::vector<std::filesystem::path> packageFiles;
|
||||||
|
for (const auto& file : legacyFiles) {
|
||||||
|
std::string filename = file.filename().string();
|
||||||
|
// Skip servers.json and any files already in packages directory
|
||||||
|
if (filename != SERVERS_CONFIG_FILENAME && file.parent_path() == configDir_) {
|
||||||
|
packageFiles.push_back(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastResult_.totalPackages = packageFiles.size();
|
||||||
|
|
||||||
|
if (packageFiles.empty()) {
|
||||||
|
logInfo("No legacy package files found to migrate");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo("Found " + std::to_string(packageFiles.size()) + " legacy package files to migrate");
|
||||||
|
|
||||||
|
// Migrate each package file
|
||||||
|
for (const auto& packageFile : packageFiles) {
|
||||||
|
if (migrateLegacyPackageFile(packageFile)) {
|
||||||
|
lastResult_.migratedPackages++;
|
||||||
|
logInfo("Migrated package file: " + packageFile.filename().string());
|
||||||
|
} else {
|
||||||
|
logError("Failed to migrate package file: " + packageFile.filename().string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo("Migrated " + std::to_string(lastResult_.migratedPackages) + " of " +
|
||||||
|
std::to_string(lastResult_.totalPackages) + " package files");
|
||||||
|
|
||||||
|
return lastResult_.migratedPackages == lastResult_.totalPackages;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Error migrating package metadata: " + std::string(e.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::createPackagesDirectory() {
|
||||||
|
return safeDirectoryCreate(packagesDir_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::validateMigration() const {
|
||||||
|
try {
|
||||||
|
// Validate server configuration
|
||||||
|
if (!validateServerConfiguration()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate package metadata
|
||||||
|
if (!validatePackageMetadata()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate directory structure
|
||||||
|
if (!validateDirectoryStructure()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Error during migration validation: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::canRollback() const {
|
||||||
|
return std::filesystem::exists(backupDir_) && std::filesystem::is_directory(backupDir_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::performRollback() {
|
||||||
|
if (!canRollback()) {
|
||||||
|
logError("Cannot rollback: no backup found");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
logInfo("Starting rollback to previous configuration");
|
||||||
|
|
||||||
|
// Restore from backup
|
||||||
|
if (!restoreFromBackup()) {
|
||||||
|
logError("Failed to restore from backup");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo("Rollback completed successfully");
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Rollback failed with exception: " + std::string(e.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::createBackup() {
|
||||||
|
try {
|
||||||
|
// Create backup directory with timestamp
|
||||||
|
std::string timestamp = generateBackupTimestamp();
|
||||||
|
std::filesystem::path timestampedBackupDir = backupDir_ / timestamp;
|
||||||
|
|
||||||
|
if (!safeDirectoryCreate(timestampedBackupDir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup existing configuration files
|
||||||
|
std::filesystem::path serversConfigPath = configDir_ / SERVERS_CONFIG_FILENAME;
|
||||||
|
if (std::filesystem::exists(serversConfigPath)) {
|
||||||
|
safeFileCopy(serversConfigPath, timestampedBackupDir / SERVERS_CONFIG_FILENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup legacy token directory
|
||||||
|
if (std::filesystem::exists(legacyTokenDir_)) {
|
||||||
|
std::filesystem::path backupTokenDir = timestampedBackupDir / DEFAULT_SERVER_URL;
|
||||||
|
safeDirectoryCreate(backupTokenDir);
|
||||||
|
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(legacyTokenDir_)) {
|
||||||
|
if (entry.is_regular_file()) {
|
||||||
|
safeFileCopy(entry.path(), backupTokenDir / entry.path().filename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup existing package files
|
||||||
|
std::vector<std::filesystem::path> packageFiles = findFilesWithExtension(configDir_, ".json");
|
||||||
|
for (const auto& file : packageFiles) {
|
||||||
|
if (file.parent_path() == configDir_) {
|
||||||
|
safeFileCopy(file, timestampedBackupDir / file.filename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup packages directory if it exists
|
||||||
|
if (std::filesystem::exists(packagesDir_)) {
|
||||||
|
std::filesystem::path backupPackagesDir = timestampedBackupDir / PACKAGES_DIRECTORY_NAME;
|
||||||
|
safeDirectoryCreate(backupPackagesDir);
|
||||||
|
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(packagesDir_)) {
|
||||||
|
if (entry.is_regular_file()) {
|
||||||
|
safeFileCopy(entry.path(), backupPackagesDir / entry.path().filename());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo("Backup created at: " + timestampedBackupDir.string());
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Failed to create backup: " + std::string(e.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::restoreFromBackup() {
|
||||||
|
try {
|
||||||
|
// Find the most recent backup
|
||||||
|
if (!std::filesystem::exists(backupDir_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path latestBackup;
|
||||||
|
std::filesystem::file_time_type latestTime{};
|
||||||
|
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(backupDir_)) {
|
||||||
|
if (entry.is_directory()) {
|
||||||
|
auto writeTime = entry.last_write_time();
|
||||||
|
if (writeTime > latestTime) {
|
||||||
|
latestTime = writeTime;
|
||||||
|
latestBackup = entry.path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestBackup.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore files from backup
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(latestBackup)) {
|
||||||
|
std::filesystem::path targetPath = configDir_ / entry.path().filename();
|
||||||
|
|
||||||
|
if (entry.is_regular_file()) {
|
||||||
|
safeFileCopy(entry.path(), targetPath);
|
||||||
|
} else if (entry.is_directory()) {
|
||||||
|
// Restore directory recursively
|
||||||
|
std::filesystem::remove_all(targetPath);
|
||||||
|
std::filesystem::copy(entry.path(), targetPath, std::filesystem::copy_options::recursive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Failed to restore from backup: " + std::string(e.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private helper methods
|
||||||
|
|
||||||
|
bool MigrationManager::hasLegacyServerConfiguration() const {
|
||||||
|
// Check for legacy token file
|
||||||
|
std::filesystem::path legacyTokenPath = legacyTokenDir_ / LEGACY_TOKEN_FILENAME;
|
||||||
|
return std::filesystem::exists(legacyTokenPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::hasLegacyPackageFiles() const {
|
||||||
|
// Check for JSON files directly in config directory (not in packages subdirectory)
|
||||||
|
std::vector<std::filesystem::path> jsonFiles = findFilesWithExtension(configDir_, ".json");
|
||||||
|
|
||||||
|
for (const auto& file : jsonFiles) {
|
||||||
|
std::string filename = file.filename().string();
|
||||||
|
// If it's not servers.json and it's in the config directory (not packages), it's legacy
|
||||||
|
if (filename != SERVERS_CONFIG_FILENAME && file.parent_path() == configDir_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::hasNewFormatConfiguration() const {
|
||||||
|
std::filesystem::path serversConfigPath = configDir_ / SERVERS_CONFIG_FILENAME;
|
||||||
|
return std::filesystem::exists(serversConfigPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::migrateLegacyTokenFile() {
|
||||||
|
std::filesystem::path legacyTokenPath = legacyTokenDir_ / LEGACY_TOKEN_FILENAME;
|
||||||
|
|
||||||
|
if (!std::filesystem::exists(legacyTokenPath)) {
|
||||||
|
return true; // Nothing to migrate
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::ifstream tokenFile(legacyTokenPath);
|
||||||
|
std::string token;
|
||||||
|
std::getline(tokenFile, token);
|
||||||
|
tokenFile.close();
|
||||||
|
|
||||||
|
if (!token.empty()) {
|
||||||
|
// Set the token for the default server
|
||||||
|
if (serverManager_->setWriteToken(DEFAULT_SERVER_URL, token)) {
|
||||||
|
logInfo("Migrated legacy write token for " + std::string(DEFAULT_SERVER_URL));
|
||||||
|
|
||||||
|
// Move the legacy token file to backup (don't delete immediately)
|
||||||
|
std::filesystem::path backupTokenPath = backupDir_ / "legacy_tokens" / DEFAULT_SERVER_URL / LEGACY_TOKEN_FILENAME;
|
||||||
|
safeDirectoryCreate(backupTokenPath.parent_path());
|
||||||
|
safeFileMove(legacyTokenPath, backupTokenPath);
|
||||||
|
|
||||||
|
// Remove the legacy directory if it's empty
|
||||||
|
try {
|
||||||
|
if (std::filesystem::is_empty(legacyTokenDir_)) {
|
||||||
|
std::filesystem::remove(legacyTokenDir_);
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// Ignore errors when removing empty directory
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Failed to migrate legacy token file: " + std::string(e.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::migrateLegacyPackageFile(const std::filesystem::path& legacyPath) {
|
||||||
|
try {
|
||||||
|
if (!std::filesystem::exists(legacyPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load legacy format
|
||||||
|
std::ifstream file(legacyPath);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
logError("Failed to open legacy file: " + legacyPath.string());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json legacyJson;
|
||||||
|
file >> legacyJson;
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// Convert to new format
|
||||||
|
PackageMetadata metadata = PackageMetadata::fromLegacyJson(legacyJson, DEFAULT_SERVER_URL);
|
||||||
|
|
||||||
|
if (!metadata.isValid()) {
|
||||||
|
logError("Invalid metadata after migration from " + legacyPath.string() + ": " + metadata.getValidationError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save in new location
|
||||||
|
if (!packageManager_->savePackageMetadata(metadata)) {
|
||||||
|
logError("Failed to save migrated metadata for " + metadata.name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move legacy file to backup (don't delete immediately)
|
||||||
|
std::filesystem::path backupPath = backupDir_ / "legacy_packages" / legacyPath.filename();
|
||||||
|
safeDirectoryCreate(backupPath.parent_path());
|
||||||
|
safeFileMove(legacyPath, backupPath);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Error migrating legacy file " + legacyPath.string() + ": " + std::string(e.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::validateServerConfiguration() const {
|
||||||
|
try {
|
||||||
|
// Check if servers.json exists and is valid
|
||||||
|
std::filesystem::path serversConfigPath = configDir_ / SERVERS_CONFIG_FILENAME;
|
||||||
|
if (!std::filesystem::exists(serversConfigPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to load the configuration
|
||||||
|
auto tempServerManager = std::make_unique<ServerManager>();
|
||||||
|
if (!tempServerManager->loadConfiguration()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we have at least one server
|
||||||
|
std::vector<std::string> servers = tempServerManager->getServers();
|
||||||
|
return !servers.empty();
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::validatePackageMetadata() const {
|
||||||
|
try {
|
||||||
|
if (!std::filesystem::exists(packagesDir_)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate all package metadata files
|
||||||
|
return packageManager_->validateAllPackageMetadata();
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::validateDirectoryStructure() const {
|
||||||
|
// Check that packages directory exists and is accessible
|
||||||
|
return std::filesystem::exists(packagesDir_) && std::filesystem::is_directory(packagesDir_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MigrationManager::logError(const std::string& message) const {
|
||||||
|
std::cerr << "[MIGRATION ERROR] " << message << std::endl;
|
||||||
|
lastResult_.errors.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MigrationManager::logWarning(const std::string& message) const {
|
||||||
|
std::cerr << "[MIGRATION WARNING] " << message << std::endl;
|
||||||
|
lastResult_.warnings.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MigrationManager::logInfo(const std::string& message) const {
|
||||||
|
std::cout << "[MIGRATION INFO] " << message << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::safeFileMove(const std::filesystem::path& source, const std::filesystem::path& destination) {
|
||||||
|
try {
|
||||||
|
// Ensure destination directory exists
|
||||||
|
std::filesystem::create_directories(destination.parent_path());
|
||||||
|
|
||||||
|
// Move the file
|
||||||
|
std::filesystem::rename(source, destination);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Failed to move file from " + source.string() + " to " + destination.string() + ": " + e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::safeFileCopy(const std::filesystem::path& source, const std::filesystem::path& destination) {
|
||||||
|
try {
|
||||||
|
// Ensure destination directory exists
|
||||||
|
std::filesystem::create_directories(destination.parent_path());
|
||||||
|
|
||||||
|
// Copy the file
|
||||||
|
std::filesystem::copy_file(source, destination, std::filesystem::copy_options::overwrite_existing);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Failed to copy file from " + source.string() + " to " + destination.string() + ": " + e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MigrationManager::safeDirectoryCreate(const std::filesystem::path& directory) {
|
||||||
|
try {
|
||||||
|
std::filesystem::create_directories(directory);
|
||||||
|
return std::filesystem::exists(directory) && std::filesystem::is_directory(directory);
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Failed to create directory " + directory.string() + ": " + e.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::filesystem::path> MigrationManager::findFilesWithExtension(const std::filesystem::path& directory, const std::string& extension) const {
|
||||||
|
std::vector<std::filesystem::path> files;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!std::filesystem::exists(directory)) {
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(directory)) {
|
||||||
|
if (entry.is_regular_file() && entry.path().extension() == extension) {
|
||||||
|
files.push_back(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
logError("Error finding files with extension " + extension + " in " + directory.string() + ": " + e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MigrationManager::generateBackupTimestamp() 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%d_%H%M%S");
|
||||||
|
return ss.str();
|
||||||
|
}
|
100
getpkg/src/MigrationManager.hpp
Normal file
100
getpkg/src/MigrationManager.hpp
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include "PackageMetadata.hpp"
|
||||||
|
#include "ServerManager.hpp"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration manager for handling the transition from single-server to multi-server configuration
|
||||||
|
* Handles migration of server configuration, package metadata, and directory structure
|
||||||
|
*/
|
||||||
|
class MigrationManager {
|
||||||
|
public:
|
||||||
|
MigrationManager();
|
||||||
|
explicit MigrationManager(const std::filesystem::path& configDir);
|
||||||
|
|
||||||
|
// Main migration interface
|
||||||
|
bool needsMigration() const;
|
||||||
|
bool performMigration();
|
||||||
|
|
||||||
|
// Migration status and reporting
|
||||||
|
struct MigrationResult {
|
||||||
|
bool success = false;
|
||||||
|
int migratedPackages = 0;
|
||||||
|
int totalPackages = 0;
|
||||||
|
bool serverConfigMigrated = false;
|
||||||
|
bool packageDirectoryCreated = false;
|
||||||
|
std::vector<std::string> errors;
|
||||||
|
std::vector<std::string> warnings;
|
||||||
|
};
|
||||||
|
|
||||||
|
MigrationResult getLastMigrationResult() const { return lastResult_; }
|
||||||
|
|
||||||
|
// Individual migration components (for testing and granular control)
|
||||||
|
bool migrateServerConfiguration();
|
||||||
|
bool migratePackageMetadata();
|
||||||
|
bool createPackagesDirectory();
|
||||||
|
bool validateMigration() const;
|
||||||
|
|
||||||
|
// Rollback capabilities
|
||||||
|
bool canRollback() const;
|
||||||
|
bool performRollback();
|
||||||
|
|
||||||
|
// Backup and restore
|
||||||
|
bool createBackup();
|
||||||
|
bool restoreFromBackup();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::filesystem::path configDir_;
|
||||||
|
std::filesystem::path packagesDir_;
|
||||||
|
std::filesystem::path backupDir_;
|
||||||
|
std::filesystem::path legacyTokenDir_;
|
||||||
|
|
||||||
|
std::unique_ptr<PackageMetadataManager> packageManager_;
|
||||||
|
std::unique_ptr<ServerManager> serverManager_;
|
||||||
|
|
||||||
|
mutable MigrationResult lastResult_;
|
||||||
|
|
||||||
|
// Migration detection helpers
|
||||||
|
bool hasLegacyServerConfiguration() const;
|
||||||
|
bool hasLegacyPackageFiles() const;
|
||||||
|
bool hasNewFormatConfiguration() const;
|
||||||
|
|
||||||
|
// Migration implementation helpers
|
||||||
|
bool migrateLegacyTokenFile();
|
||||||
|
bool migrateLegacyPackageFile(const std::filesystem::path& legacyPath);
|
||||||
|
bool movePackageFilesToSubdirectory();
|
||||||
|
bool updatePackageMetadataFormat();
|
||||||
|
bool cleanupLegacyFiles();
|
||||||
|
|
||||||
|
// Backup and rollback helpers
|
||||||
|
bool backupLegacyConfiguration();
|
||||||
|
bool backupExistingConfiguration();
|
||||||
|
std::string generateBackupTimestamp() const;
|
||||||
|
|
||||||
|
// Validation helpers
|
||||||
|
bool validateServerConfiguration() const;
|
||||||
|
bool validatePackageMetadata() const;
|
||||||
|
bool validateDirectoryStructure() const;
|
||||||
|
|
||||||
|
// Error handling and logging
|
||||||
|
void logError(const std::string& message) const;
|
||||||
|
void logWarning(const std::string& message) const;
|
||||||
|
void logInfo(const std::string& message) const;
|
||||||
|
|
||||||
|
// File system utilities
|
||||||
|
bool safeFileMove(const std::filesystem::path& source, const std::filesystem::path& destination);
|
||||||
|
bool safeFileCopy(const std::filesystem::path& source, const std::filesystem::path& destination);
|
||||||
|
bool safeDirectoryCreate(const std::filesystem::path& directory);
|
||||||
|
std::vector<std::filesystem::path> findFilesWithExtension(const std::filesystem::path& directory, const std::string& extension) const;
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
static constexpr const char* LEGACY_TOKEN_FILENAME = "write_token.txt";
|
||||||
|
static constexpr const char* SERVERS_CONFIG_FILENAME = "servers.json";
|
||||||
|
static constexpr const char* PACKAGES_DIRECTORY_NAME = "packages";
|
||||||
|
static constexpr const char* BACKUP_DIRECTORY_NAME = "migration_backup";
|
||||||
|
static constexpr const char* DEFAULT_SERVER_URL = "getpkg.xyz";
|
||||||
|
};
|
@ -57,6 +57,7 @@
|
|||||||
#include "BashrcEditor.hpp"
|
#include "BashrcEditor.hpp"
|
||||||
#include "DropshellScriptManager.hpp"
|
#include "DropshellScriptManager.hpp"
|
||||||
#include "GetbinClient.hpp"
|
#include "GetbinClient.hpp"
|
||||||
|
#include "MigrationManager.hpp"
|
||||||
#include "archive_tgz.hpp"
|
#include "archive_tgz.hpp"
|
||||||
#include "hash.hpp"
|
#include "hash.hpp"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -1229,9 +1230,76 @@ int autocomplete_command(int argc, char* argv[]) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migration check and execution
|
||||||
|
bool checkAndPerformMigration() {
|
||||||
|
try {
|
||||||
|
MigrationManager migrationManager;
|
||||||
|
|
||||||
|
if (migrationManager.needsMigration()) {
|
||||||
|
std::cout << "Migrating getpkg configuration to multi-server format..." << std::endl;
|
||||||
|
|
||||||
|
if (migrationManager.performMigration()) {
|
||||||
|
auto result = migrationManager.getLastMigrationResult();
|
||||||
|
std::cout << "Migration completed successfully!" << std::endl;
|
||||||
|
|
||||||
|
if (result.migratedPackages > 0) {
|
||||||
|
std::cout << " - Migrated " << result.migratedPackages << " package(s)" << std::endl;
|
||||||
|
}
|
||||||
|
if (result.serverConfigMigrated) {
|
||||||
|
std::cout << " - Updated server configuration" << std::endl;
|
||||||
|
}
|
||||||
|
if (result.packageDirectoryCreated) {
|
||||||
|
std::cout << " - Created packages directory structure" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.warnings.empty()) {
|
||||||
|
std::cout << "Migration warnings:" << std::endl;
|
||||||
|
for (const auto& warning : result.warnings) {
|
||||||
|
std::cout << " - " << warning << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
auto result = migrationManager.getLastMigrationResult();
|
||||||
|
std::cerr << "Migration failed!" << std::endl;
|
||||||
|
|
||||||
|
if (!result.errors.empty()) {
|
||||||
|
std::cerr << "Migration errors:" << std::endl;
|
||||||
|
for (const auto& error : result.errors) {
|
||||||
|
std::cerr << " - " << error << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (migrationManager.canRollback()) {
|
||||||
|
std::cerr << "Attempting rollback..." << std::endl;
|
||||||
|
if (migrationManager.performRollback()) {
|
||||||
|
std::cerr << "Rollback successful. Configuration restored to previous state." << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cerr << "Rollback failed. Manual intervention may be required." << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // No migration needed
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Migration error: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // end anonymous namespace
|
} // end anonymous namespace
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
// Perform migration check before any other operations
|
||||||
|
if (!checkAndPerformMigration()) {
|
||||||
|
std::cerr << "Failed to migrate configuration. Some functionality may not work correctly." << std::endl;
|
||||||
|
// Continue execution but warn user
|
||||||
|
}
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
show_help();
|
show_help();
|
||||||
return 0;
|
return 0;
|
||||||
|
Reference in New Issue
Block a user