@hivetechs/hive-ai
Version:
Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API
565 lines • 22.4 kB
JavaScript
/**
* Backup Location Manager - Flexible Backup Destination Management
*
* Features:
* - Default local backup location
* - User-configurable backup destinations
* - Cloud storage folder integration (OneDrive, iCloud, Dropbox, Google Drive)
* - Multiple backup destinations with sync
* - Path validation and availability checking
* - Cross-platform path handling
*/
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { logger } from '../../utils/logging.js';
export class BackupLocationManager {
config;
defaultBackupPath;
constructor() {
this.defaultBackupPath = path.join(os.homedir(), '.hive-ai', 'backups');
// Initialize asynchronously
this.initialize();
}
/**
* Initialize the backup location manager
*/
async initialize() {
await this.loadConfig();
this.initializeDefaultLocation();
}
/**
* Load configuration from unified database or create default
*/
async loadConfig() {
try {
const { getConfig } = await import('../../storage/unified-database.js');
const configData = await getConfig('backup_locations_config');
if (configData) {
this.config = JSON.parse(configData);
this.validateConfig();
}
else {
this.config = this.createDefaultConfig();
await this.saveConfig();
}
}
catch (error) {
logger.warn(`Failed to load backup location config from database, using defaults: ${error instanceof Error ? error.message : 'Unknown error'}`);
this.config = this.createDefaultConfig();
}
}
/**
* Create default configuration with smart cloud detection
*/
createDefaultConfig() {
const detectedCloudPaths = this.detectCloudStoragePaths();
const locations = [
{
name: 'Default Local',
path: this.defaultBackupPath,
type: 'local',
enabled: true,
priority: 1,
auto_sync: false,
last_verified: new Date().toISOString(),
available: true
}
];
// Add detected cloud storage locations
let priority = 2;
for (const cloudPath of detectedCloudPaths) {
locations.push({
name: `${cloudPath.provider} Sync`,
path: path.join(cloudPath.path, 'hive-ai-backups'),
type: 'cloud_sync',
cloud_provider: cloudPath.provider,
enabled: false, // User must explicitly enable
priority: priority++,
auto_sync: true,
last_verified: new Date().toISOString(),
available: cloudPath.available
});
}
return {
default_location: this.defaultBackupPath,
locations,
sync_enabled: false,
verification_interval_hours: 6,
max_locations: 5,
require_primary_available: true
};
}
/**
* Detect common cloud storage paths across platforms
*/
detectCloudStoragePaths() {
const homeDir = os.homedir();
const platform = os.platform();
const detected = [];
// Common cloud storage paths by platform
const cloudPaths = {
onedrive: [
path.join(homeDir, 'OneDrive'),
path.join(homeDir, 'OneDrive - Personal'),
path.join(homeDir, 'OneDrive - Business'),
...(platform === 'win32' ? [
path.join(homeDir, 'OneDrive'),
'C:\\Users\\' + os.userInfo().username + '\\OneDrive'
] : [])
],
icloud: [
...(platform === 'darwin' ? [
path.join(homeDir, 'Library', 'Mobile Documents', 'com~apple~CloudDocs'),
path.join(homeDir, 'iCloud Drive (Archive)'),
path.join(homeDir, 'iCloud')
] : [])
],
dropbox: [
path.join(homeDir, 'Dropbox'),
path.join(homeDir, 'Dropbox (Personal)'),
path.join(homeDir, 'Dropbox (Business)'),
...(platform === 'win32' ? [
'C:\\Users\\' + os.userInfo().username + '\\Dropbox'
] : [])
],
google_drive: [
path.join(homeDir, 'Google Drive'),
path.join(homeDir, 'GoogleDrive'),
...(platform === 'win32' ? [
'C:\\Users\\' + os.userInfo().username + '\\Google Drive'
] : []),
...(platform === 'darwin' ? [
path.join(homeDir, 'Google Drive'),
'/Volumes/GoogleDrive'
] : [])
]
};
// Check each provider's paths
for (const [provider, paths] of Object.entries(cloudPaths)) {
for (const cloudPath of paths) {
try {
if (fs.existsSync(cloudPath) && fs.statSync(cloudPath).isDirectory()) {
// Verify it's actually a cloud sync folder by checking for common indicators
const isCloudFolder = this.verifyCloudSyncFolder(cloudPath, provider);
if (isCloudFolder) {
detected.push({
provider: provider,
path: cloudPath,
available: true
});
break; // Only add the first valid path per provider
}
}
}
catch (error) {
// Path not accessible, skip
}
}
}
return detected;
}
/**
* Verify if a path is actually a cloud sync folder
*/
verifyCloudSyncFolder(folderPath, provider) {
try {
// Check for provider-specific indicators
const indicators = {
onedrive: ['.849C9593-D756-4E56-8D6E-42412F2A707B', 'OneDriveTemp'],
icloud: ['.com.apple.clouddocs', '.icloud'],
dropbox: ['.dropbox', '.dropbox.cache'],
google_drive: ['.tmp.drivedownload', '.tmp.driveupload']
};
const providerIndicators = indicators[provider] || [];
// Check if any indicator files/folders exist (these are usually hidden)
for (const indicator of providerIndicators) {
const indicatorPath = path.join(folderPath, indicator);
if (fs.existsSync(indicatorPath)) {
return true;
}
}
// Additional check: see if we can write to the folder
const testFile = path.join(folderPath, '.hive-ai-test-write');
try {
fs.writeFileSync(testFile, 'test');
fs.unlinkSync(testFile);
return true;
}
catch {
return false;
}
}
catch {
return false;
}
}
/**
* Initialize default backup location
*/
initializeDefaultLocation() {
try {
if (!fs.existsSync(this.defaultBackupPath)) {
fs.mkdirSync(this.defaultBackupPath, { recursive: true });
// Create subdirectories
const subdirs = ['daily', 'weekly', 'monthly', 'emergency', 'manual'];
for (const subdir of subdirs) {
const fullPath = path.join(this.defaultBackupPath, subdir);
fs.mkdirSync(fullPath, { recursive: true });
}
logger.info(`✅ Default backup location initialized: ${this.defaultBackupPath}`);
}
}
catch (error) {
logger.error(`❌ Failed to initialize default backup location: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Add a new backup location
*/
async addBackupLocation(location) {
try {
// Validate the path exists and is writable
const validation = await this.validateLocation(location.path);
if (!validation.valid) {
return { success: false, error: validation.error };
}
// Check if location already exists
const existingLocation = this.config.locations.find(loc => loc.path === location.path);
if (existingLocation) {
return { success: false, error: 'Location already exists' };
}
// Check location limits
if (this.config.locations.length >= this.config.max_locations) {
return { success: false, error: `Maximum ${this.config.max_locations} locations allowed` };
}
// Create the backup location with verification
const newLocation = {
...location,
last_verified: new Date().toISOString(),
available: validation.available || false,
free_space_gb: validation.free_space_gb
};
// Create backup structure in the new location
await this.createBackupStructure(newLocation.path);
// Add to configuration
this.config.locations.push(newLocation);
this.config.locations.sort((a, b) => a.priority - b.priority);
await this.saveConfig();
logger.info(`✅ Added backup location: ${newLocation.name} at ${newLocation.path}`);
return { success: true, location: newLocation };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error adding location'
};
}
}
/**
* Remove a backup location
*/
async removeBackupLocation(locationName) {
try {
const locationIndex = this.config.locations.findIndex(loc => loc.name === locationName);
if (locationIndex === -1) {
return { success: false, error: 'Location not found' };
}
const location = this.config.locations[locationIndex];
// Prevent removing the primary location
if (location.priority === 1) {
return { success: false, error: 'Cannot remove primary backup location' };
}
// Remove from configuration
this.config.locations.splice(locationIndex, 1);
await this.saveConfig();
logger.info(`🗑️ Removed backup location: ${locationName}`);
return { success: true };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error removing location'
};
}
}
/**
* Get all backup locations with current status
*/
async getBackupLocations() {
// Ensure config is loaded first
if (!this.config) {
await this.loadConfig();
this.initializeDefaultLocation();
}
// Verify all locations and update availability
for (const location of this.config.locations) {
const validation = await this.validateLocation(location.path);
location.available = validation.valid && validation.available || false;
location.free_space_gb = validation.free_space_gb;
location.last_verified = new Date().toISOString();
}
await this.saveConfig();
return [...this.config.locations].sort((a, b) => a.priority - b.priority);
}
/**
* Get available backup locations for writing
*/
async getAvailableLocations() {
const locations = await this.getBackupLocations();
return locations.filter(loc => loc.enabled && loc.available);
}
/**
* Get primary backup location
*/
async getPrimaryLocation() {
const locations = await this.getBackupLocations();
return locations.find(loc => loc.priority === 1) || null;
}
/**
* Update backup location settings
*/
async updateLocationSettings(locationName, updates) {
try {
const location = this.config.locations.find(loc => loc.name === locationName);
if (!location) {
return { success: false, error: 'Location not found' };
}
// Apply updates
Object.assign(location, updates);
// Re-validate if path changed
if (updates.path) {
const validation = await this.validateLocation(updates.path);
if (!validation.valid) {
return { success: false, error: validation.error };
}
location.available = validation.available || false;
location.free_space_gb = validation.free_space_gb;
}
location.last_verified = new Date().toISOString();
// Re-sort by priority
this.config.locations.sort((a, b) => a.priority - b.priority);
await this.saveConfig();
logger.info(`✅ Updated backup location: ${locationName}`);
return { success: true };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error updating location'
};
}
}
/**
* Validate a backup location path
*/
async validateLocation(locationPath) {
try {
// Check if path exists
if (!fs.existsSync(locationPath)) {
// Try to create it
try {
fs.mkdirSync(locationPath, { recursive: true });
}
catch (createError) {
return {
valid: false,
error: `Cannot create directory: ${createError instanceof Error ? createError.message : 'Unknown error'}`
};
}
}
// Check if it's a directory
const stats = fs.statSync(locationPath);
if (!stats.isDirectory()) {
return { valid: false, error: 'Path is not a directory' };
}
// Check write permissions
const testFile = path.join(locationPath, '.hive-ai-write-test');
try {
fs.writeFileSync(testFile, 'test');
fs.unlinkSync(testFile);
}
catch (writeError) {
return {
valid: false,
error: `No write permission: ${writeError instanceof Error ? writeError.message : 'Unknown error'}`
};
}
// Check available space (simplified - in production you'd use a proper disk space library)
let freeSpaceGB = 0;
try {
// This is a simplified approach - you'd want to use a proper library like 'check-disk-space'
const diskUsage = fs.statSync(locationPath);
freeSpaceGB = 10; // Placeholder - implement proper disk space checking
}
catch {
freeSpaceGB = 0;
}
return {
valid: true,
available: true,
free_space_gb: freeSpaceGB
};
}
catch (error) {
return {
valid: false,
error: error instanceof Error ? error.message : 'Unknown validation error'
};
}
}
/**
* Create backup directory structure at location
*/
async createBackupStructure(locationPath) {
const subdirs = ['daily', 'weekly', 'monthly', 'emergency', 'manual'];
for (const subdir of subdirs) {
const fullPath = path.join(locationPath, subdir);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
}
}
// Create a marker file to identify this as a Hive.AI backup location
const markerFile = path.join(locationPath, '.hive-ai-backup-location');
const markerContent = {
created: new Date().toISOString(),
version: '1.0.0',
description: 'Hive.AI Backup Location - Do not delete this folder'
};
fs.writeFileSync(markerFile, JSON.stringify(markerContent, null, 2));
}
/**
* Save configuration to unified database
*/
async saveConfig() {
try {
const { setConfig } = await import('../../storage/unified-database.js');
await setConfig('backup_locations_config', JSON.stringify(this.config, null, 2));
}
catch (error) {
logger.error(`Failed to save backup location config to database: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Validate configuration integrity
*/
validateConfig() {
// Ensure at least one location exists
if (!this.config.locations || this.config.locations.length === 0) {
this.config = this.createDefaultConfig();
return;
}
// Ensure there's a primary location
const hasPrimary = this.config.locations.some(loc => loc.priority === 1);
if (!hasPrimary && this.config.locations.length > 0) {
this.config.locations[0].priority = 1;
}
// Set default values for missing properties
if (!this.config.default_location) {
this.config.default_location = this.defaultBackupPath;
}
if (typeof this.config.sync_enabled !== 'boolean') {
this.config.sync_enabled = false;
}
if (!this.config.verification_interval_hours) {
this.config.verification_interval_hours = 6;
}
if (!this.config.max_locations) {
this.config.max_locations = 5;
}
}
/**
* Get configuration for external access
*/
getConfig() {
return { ...this.config };
}
/**
* Sync backups across all enabled locations
*/
async syncBackupsAcrossLocations(backupFileName) {
const availableLocations = await this.getAvailableLocations();
const syncedLocations = [];
const failedLocations = [];
const errors = [];
if (availableLocations.length <= 1) {
return {
success: true,
synced_locations: [],
failed_locations: [],
errors: ['Only one location available, no sync needed']
};
}
// Find the source file in the primary location
const primaryLocation = availableLocations.find(loc => loc.priority === 1);
if (!primaryLocation) {
return {
success: false,
synced_locations: [],
failed_locations: [],
errors: ['No primary location available']
};
}
// Try to find the backup file in primary location subdirectories
const subdirs = ['daily', 'weekly', 'monthly', 'emergency', 'manual'];
let sourceFilePath = null;
for (const subdir of subdirs) {
const candidatePath = path.join(primaryLocation.path, subdir, backupFileName);
if (fs.existsSync(candidatePath)) {
sourceFilePath = candidatePath;
break;
}
}
if (!sourceFilePath) {
return {
success: false,
synced_locations: [],
failed_locations: [],
errors: ['Source backup file not found in primary location']
};
}
// Copy to other locations
const secondaryLocations = availableLocations.filter(loc => loc.priority !== 1 && loc.auto_sync);
for (const location of secondaryLocations) {
try {
// Determine which subdirectory to use based on backup filename
let targetSubdir = 'manual';
for (const subdir of subdirs) {
if (backupFileName.includes(`-${subdir}-`)) {
targetSubdir = subdir;
break;
}
}
const targetPath = path.join(location.path, targetSubdir, backupFileName);
const targetDir = path.dirname(targetPath);
// Ensure target directory exists
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true });
}
// Copy the file
fs.copyFileSync(sourceFilePath, targetPath);
// Also copy metadata file if it exists
const metadataFileName = backupFileName.replace('.db', '.metadata.json');
const sourceMetadataPath = sourceFilePath.replace('.db', '.metadata.json');
const targetMetadataPath = targetPath.replace('.db', '.metadata.json');
if (fs.existsSync(sourceMetadataPath)) {
fs.copyFileSync(sourceMetadataPath, targetMetadataPath);
}
syncedLocations.push(location.name);
logger.info(`📋 Synced backup to: ${location.name}`);
}
catch (error) {
failedLocations.push(location.name);
errors.push(`${location.name}: ${error instanceof Error ? error.message : 'Unknown sync error'}`);
logger.warn(`⚠️ Failed to sync backup to ${location.name}: ${error instanceof Error ? error.message : 'Unknown sync error'}`);
}
}
return {
success: failedLocations.length === 0,
synced_locations: syncedLocations,
failed_locations: failedLocations,
errors
};
}
}
//# sourceMappingURL=backup-location-manager.js.map