Migration Guide #
Complete guide for migrating to Server Monitor plugin from other monitoring solutions, upgrading between versions, and handling data migrations.
Migration Scenarios #
From External Monitoring Services #
Supported Migration Sources:
- Uptime Robot
- Pingdom
- StatusCake
- New Relic Synthetics
- Custom monitoring solutions
- CSV/Excel server lists
Migration Strategy:
- Export existing data from current service
- Map data structure to Server Monitor format
- Import servers in batches
- Configure notifications for existing users
- Verify monitoring is working correctly
- Transition gradually to avoid service gaps
Between Server Monitor Versions #
Version Compatibility:
- v1.0.x → v1.1.x: Database migrations required
- v1.1.x → v1.2.x: Notification settings migration
- v1.2.x → v2.0.x: Major structural changes (future)
Upgrade Process:
- Backup current installation
- Review breaking changes
- Update codebase
- Run database migrations
- Test functionality
- Update configurations
Importing Server Data #
CSV Import Format #
Required CSV Structure:
title,endpoint,organization_name
Production Website,https://www.example.com,Acme Corp
API Server,https://api.example.com,Acme Corp
Database Health,https://db.example.com/health,Acme Corp
Optional Fields:
title,endpoint,organization_name,status,notes
Production Website,https://www.example.com,Acme Corp,200,Main website
API Server,https://api.example.com,Acme Corp,200,REST API endpoint
Staging App,https://staging.example.com,Acme Corp,404,Currently down for maintenance
Batch Import Script #
PHP Import Implementation:
<?php
// import-servers.php - Run via artisan command
use Albrightlabs\ServerMonitor\Models\Server;
use Albrightlabs\SaasBase\Models\Organization;
use Illuminate\Console\Command;
class ImportServersCommand extends Command
{
protected $signature = 'servermonitor:import {file} {--dry-run} {--organization=}';
protected $description = 'Import servers from CSV file';
public function handle()
{
$file = $this->argument('file');
$dryRun = $this->option('dry-run');
$organizationId = $this->option('organization');
if (!file_exists($file)) {
$this->error("File not found: {$file}");
return 1;
}
$csv = array_map('str_getcsv', file($file));
$headers = array_shift($csv);
$imported = 0;
$errors = 0;
foreach ($csv as $row) {
$data = array_combine($headers, $row);
try {
if ($dryRun) {
$this->line("Would import: {$data['title']} -> {$data['endpoint']}");
} else {
$this->importServer($data, $organizationId);
}
$imported++;
} catch (\Exception $e) {
$this->error("Error importing {$data['title']}: " . $e->getMessage());
$errors++;
}
}
$this->info("Import completed: {$imported} imported, {$errors} errors");
return 0;
}
protected function importServer($data, $organizationId = null)
{
// Find or create organization
if ($organizationId) {
$organization = Organization::find($organizationId);
} else {
$organization = Organization::firstOrCreate(
['name' => $data['organization_name']],
[
'selected_plan' => 'free',
'subscription_status' => 'active'
]
);
}
if (!$organization) {
throw new \Exception('Organization not found or could not be created');
}
// Create server
$server = new Server();
$server->title = $data['title'];
$server->endpoint = $data['endpoint'];
$server->organization_id = $organization->id;
if (isset($data['status'])) {
$server->status = $data['status'];
}
$server->save();
$this->line("Imported: {$server->title} for {$organization->name}");
}
}
Register Command:
// In Plugin.php register() method
$this->registerConsoleCommand('servermonitor.import', \Albrightlabs\ServerMonitor\Console\ImportServersCommand::class);
Usage Examples:
# Dry run to test import
php artisan servermonitor:import servers.csv --dry-run
# Import to specific organization
php artisan servermonitor:import servers.csv --organization=123
# Full import
php artisan servermonitor:import servers.csv
API Import Script #
REST API Import:
<?php
// api-import.php - Import via API calls
class ApiImporter
{
protected $apiKey;
protected $baseUrl;
public function __construct($apiKey, $baseUrl)
{
$this->apiKey = $apiKey;
$this->baseUrl = rtrim($baseUrl, '/');
}
public function importFromUptimeRobot($apiKey)
{
// Get monitors from Uptime Robot API
$response = $this->callUptimeRobotApi($apiKey);
foreach ($response['monitors'] as $monitor) {
$this->createServer([
'title' => $monitor['friendly_name'],
'endpoint' => $monitor['url'],
'status' => $this->mapUptimeRobotStatus($monitor['status'])
]);
}
}
protected function createServer($data)
{
$response = $this->makeRequest('POST', '/api/servers', $data);
if ($response['success']) {
echo "Created server: {$data['title']}\n";
} else {
echo "Error creating {$data['title']}: {$response['message']}\n";
}
}
protected function makeRequest($method, $endpoint, $data = [])
{
$url = $this->baseUrl . $endpoint;
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode($data)
]);
$response = curl_exec($curl);
curl_close($curl);
return json_decode($response, true);
}
}
// Usage
$importer = new ApiImporter('your-api-key', 'https://yoursite.com');
$importer->importFromUptimeRobot('uptime-robot-api-key');
Service-Specific Migrations #
From Uptime Robot #
Export Data from Uptime Robot:
- Login to Uptime Robot dashboard
- Go to "My Settings" → "API Settings"
- Generate API key
- Use API to export monitor data
Uptime Robot API Export:
// uptime-robot-export.php
function exportUptimeRobotMonitors($apiKey)
{
$url = "https://api.uptimerobot.com/v2/getMonitors";
$post = [
'api_key' => $apiKey,
'format' => 'json',
'logs' => 0
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
// Convert to CSV format
$csv = "title,endpoint,status\n";
foreach ($data['monitors'] as $monitor) {
$status = $monitor['status'] == 2 ? '200' : '500'; // 2 = up, others = down
$csv .= "\"{$monitor['friendly_name']}\",\"{$monitor['url']}\",\"{$status}\"\n";
}
file_put_contents('uptime-robot-export.csv', $csv);
echo "Exported " . count($data['monitors']) . " monitors to uptime-robot-export.csv\n";
}
// Usage
exportUptimeRobotMonitors('your-uptime-robot-api-key');
From Pingdom #
Pingdom Export Process:
- Login to Pingdom
- Go to "Reports" → "Shared Reports"
- Create custom report with check details
- Export as CSV
Pingdom CSV Conversion:
// pingdom-convert.php
function convertPingdomCsv($inputFile, $outputFile)
{
$input = fopen($inputFile, 'r');
$output = fopen($outputFile, 'w');
// Write new headers
fputcsv($output, ['title', 'endpoint', 'status']);
// Skip Pingdom headers
fgetcsv($input);
while (($row = fgetcsv($input)) !== FALSE) {
// Pingdom format: [Name, URL, Status, Last Response Time, etc.]
$title = $row[0];
$endpoint = $row[1];
$status = (strtolower($row[2]) === 'up') ? '200' : '500';
fputcsv($output, [$title, $endpoint, $status]);
}
fclose($input);
fclose($output);
echo "Converted Pingdom data to Server Monitor format\n";
}
// Usage
convertPingdomCsv('pingdom-export.csv', 'servers-import.csv');
From Custom Monitoring #
Generic JSON/XML Import:
// custom-import.php
function importFromJson($jsonFile)
{
$data = json_decode(file_get_contents($jsonFile), true);
$csv = "title,endpoint,organization_name\n";
foreach ($data['servers'] as $server) {
$title = addslashes($server['name']);
$endpoint = $server['url'];
$org = addslashes($server['organization'] ?? 'Default Organization');
$csv .= "\"{$title}\",\"{$endpoint}\",\"{$org}\"\n";
}
file_put_contents('custom-import.csv', $csv);
echo "Converted custom JSON to importable CSV\n";
}
// Example JSON structure:
// {
// "servers": [
// {
// "name": "Production API",
// "url": "https://api.example.com",
// "organization": "Example Corp"
// }
// ]
// }
Notification Migration #
Migrating User Settings #
Export Existing Notification Settings:
// export-notifications.php
use Albrightlabs\ServerMonitor\Models\NotificationSetting;
use Backend\Models\User;
function exportNotificationSettings()
{
$settings = NotificationSetting::with('organization')->get();
$export = [];
foreach ($settings as $setting) {
$orgData = [
'organization_name' => $setting->organization->name,
'organization_id' => $setting->organization_id,
'users' => []
];
foreach ($setting->administrators as $admin) {
$user = User::find($admin['user_id']);
if ($user) {
$orgData['users'][] = [
'name' => $user->first_name . ' ' . $user->last_name,
'email' => $user->email,
'phone' => $user->phone_number,
'email_enabled' => $admin['email_enabled'],
'sms_enabled' => $admin['sms_enabled']
];
}
}
$export[] = $orgData;
}
file_put_contents('notification-settings-export.json', json_encode($export, JSON_PRETTY_PRINT));
echo "Exported notification settings for " . count($export) . " organizations\n";
}
Import Notification Settings:
// import-notifications.php
function importNotificationSettings($jsonFile)
{
$data = json_decode(file_get_contents($jsonFile), true);
foreach ($data as $orgData) {
$org = Organization::where('name', $orgData['organization_name'])->first();
if (!$org) {
echo "Organization not found: {$orgData['organization_name']}\n";
continue;
}
$settings = NotificationSetting::getForOrganization($org->id);
$administrators = [];
foreach ($orgData['users'] as $userData) {
$user = User::where('email', $userData['email'])->first();
if ($user && $user->organization_id == $org->id) {
$administrators[] = [
'user_id' => $user->id,
'email_enabled' => $userData['email_enabled'],
'sms_enabled' => $userData['sms_enabled']
];
}
}
$settings->administrators = $administrators;
$settings->save();
echo "Imported notifications for {$org->name}: " . count($administrators) . " users\n";
}
}
Version Upgrade Procedures #
Pre-Upgrade Checklist #
Backup Requirements:
- [ ] Database backup - Full database dump
- [ ] File backup - Plugin files and configuration
- [ ] Environment backup -
.envfile copy - [ ] Queue status - Note current job counts
- [ ] Notification test - Verify current functionality
Environment Verification:
- [ ] PHP version - Check compatibility
- [ ] October CMS version - Verify plugin compatibility
- [ ] SaasBase version - Ensure dependency compatibility
- [ ] Database version - Check migration requirements
- [ ] Available disk space - Ensure sufficient space
Backup Script #
#!/bin/bash
# backup-before-upgrade.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups/servermonitor-upgrade-$DATE"
SITE_ROOT="/var/www/your-site"
echo "Creating backup directory: $BACKUP_DIR"
mkdir -p $BACKUP_DIR
# Database backup
echo "Backing up database..."
mysqldump your_database > "$BACKUP_DIR/database.sql"
# Files backup
echo "Backing up files..."
cp -r "$SITE_ROOT/plugins/albrightlabs/servermonitor" "$BACKUP_DIR/plugin-files"
cp "$SITE_ROOT/.env" "$BACKUP_DIR/env-backup"
# Configuration backup
echo "Backing up configuration..."
cp -r "$SITE_ROOT/config" "$BACKUP_DIR/config-backup"
# Supervisor configuration
cp /etc/supervisor/conf.d/servermonitor-workers.conf "$BACKUP_DIR/supervisor-backup.conf"
# Queue status
echo "Recording queue status..."
redis-cli llen queues:premium > "$BACKUP_DIR/queue-premium-count.txt"
redis-cli llen queues:free > "$BACKUP_DIR/queue-free-count.txt"
echo "Backup completed: $BACKUP_DIR"
Version-Specific Upgrades #
v1.1.x to v1.2.x Upgrade:
#!/bin/bash
# upgrade-v1.1-to-v1.2.sh
echo "Upgrading Server Monitor from v1.1.x to v1.2.x"
# 1. Stop workers
echo "Stopping queue workers..."
supervisorctl stop servermonitor-premium:*
supervisorctl stop servermonitor-free:*
supervisorctl stop servermonitor-calculations:*
# 2. Backup current installation
./backup-before-upgrade.sh
# 3. Update plugin files
echo "Updating plugin files..."
cd plugins/albrightlabs/servermonitor
git fetch origin
git checkout v1.2.1
# 4. Run migrations
echo "Running database migrations..."
cd ../../../
php artisan october:migrate
# 5. Clear caches
echo "Clearing caches..."
php artisan cache:clear
php artisan config:clear
# 6. Update configuration if needed
echo "Checking configuration..."
# Add any new required config variables
# 7. Restart workers
echo "Restarting queue workers..."
supervisorctl start servermonitor-premium:*
supervisorctl start servermonitor-free:*
supervisorctl start servermonitor-calculations:*
# 8. Verify functionality
echo "Verifying installation..."
php artisan servermonitor:check-capacity
echo "Upgrade completed!"
Post-Upgrade Verification #
Functionality Checklist:
- [ ] Dashboard loads - Admin interface accessible
- [ ] Servers visible - Existing servers display correctly
- [ ] Queue workers running - All workers operational
- [ ] API endpoints respond - Test API functionality
- [ ] Notifications work - Send test notification
- [ ] Uptime calculations - Verify calculations are updating
Verification Script:
#!/bin/bash
# verify-upgrade.sh
echo "Verifying Server Monitor upgrade..."
# Check database integrity
echo "Checking database..."
php artisan tinker --execute="echo \Albrightlabs\ServerMonitor\Models\Server::count() . ' servers found\n';"
# Check queue workers
echo "Checking queue workers..."
supervisorctl status | grep servermonitor
# Test API endpoints
echo "Testing API..."
curl -s "https://yoursite.com/api/servermonitor/worker-status/YOUR_API_KEY" | jq '.status'
# Check recent logs
echo "Checking recent activity..."
tail -5 storage/logs/laravel.log
# Test notification (if configured)
echo "Testing notification system..."
php artisan servermonitor:testalerts
echo "Verification completed!"
Data Migration Scripts #
Historical Data Migration #
Migrate Uptime History:
// migrate-uptime-history.php
function migrateUptimeHistory($csvFile)
{
$file = fopen($csvFile, 'r');
$headers = fgetcsv($file);
// Expected CSV: server_name, date, status, response_time
while (($row = fgetcsv($file)) !== FALSE) {
$data = array_combine($headers, $row);
$server = Server::where('title', $data['server_name'])->first();
if (!$server) {
echo "Server not found: {$data['server_name']}\n";
continue;
}
// Create historical log entry
$log = new \Albrightlabs\ServerMonitor\Models\Log();
$log->server_id = $server->id;
$log->original_status = null;
$log->updated_status = $data['status'];
$log->note = "Historical data import";
$log->created_at = $data['date'];
$log->save();
echo "Imported history for {$server->title}: {$data['date']} -> {$data['status']}\n";
}
fclose($file);
}
Organization Consolidation #
Merge Organizations:
// merge-organizations.php
function mergeOrganizations($sourceOrgId, $targetOrgId)
{
$sourceOrg = Organization::find($sourceOrgId);
$targetOrg = Organization::find($targetOrgId);
if (!$sourceOrg || !$targetOrg) {
throw new \Exception('Organizations not found');
}
echo "Merging {$sourceOrg->name} into {$targetOrg->name}\n";
// Move servers
$servers = Server::where('organization_id', $sourceOrgId)->get();
foreach ($servers as $server) {
$server->organization_id = $targetOrgId;
$server->save();
echo "Moved server: {$server->title}\n";
}
// Move alert logs
AlertLog::where('organization_id', $sourceOrgId)
->update(['organization_id' => $targetOrgId]);
// Move notification settings
$sourceSettings = NotificationSetting::where('organization_id', $sourceOrgId)->first();
$targetSettings = NotificationSetting::getForOrganization($targetOrgId);
if ($sourceSettings) {
// Merge administrators arrays
$mergedAdmins = array_merge(
$targetSettings->administrators ?? [],
$sourceSettings->administrators ?? []
);
// Remove duplicates based on user_id
$uniqueAdmins = [];
foreach ($mergedAdmins as $admin) {
$uniqueAdmins[$admin['user_id']] = $admin;
}
$targetSettings->administrators = array_values($uniqueAdmins);
$targetSettings->save();
$sourceSettings->delete();
}
echo "Merge completed. Review and delete source organization if appropriate.\n";
}
Rollback Procedures #
Emergency Rollback #
Quick Rollback Script:
#!/bin/bash
# emergency-rollback.sh
BACKUP_DIR="$1"
if [ -z "$BACKUP_DIR" ]; then
echo "Usage: $0 /path/to/backup/directory"
exit 1
fi
echo "EMERGENCY ROLLBACK - Restoring from $BACKUP_DIR"
# Stop workers
supervisorctl stop servermonitor-premium:*
supervisorctl stop servermonitor-free:*
supervisorctl stop servermonitor-calculations:*
# Restore database
echo "Restoring database..."
mysql your_database < "$BACKUP_DIR/database.sql"
# Restore plugin files
echo "Restoring plugin files..."
rm -rf plugins/albrightlabs/servermonitor
cp -r "$BACKUP_DIR/plugin-files" plugins/albrightlabs/servermonitor
# Restore configuration
echo "Restoring configuration..."
cp "$BACKUP_DIR/env-backup" .env
# Clear caches
php artisan cache:clear
php artisan config:clear
# Restart workers
supervisorctl start servermonitor-premium:*
supervisorctl start servermonitor-free:*
supervisorctl start servermonitor-calculations:*
echo "Rollback completed!"
Partial Rollback #
Configuration-Only Rollback:
# Rollback only configuration changes
cp "$BACKUP_DIR/env-backup" .env
cp -r "$BACKUP_DIR/config-backup/*" config/
php artisan config:clear
Database-Only Rollback:
# Rollback only database changes
mysql your_database < "$BACKUP_DIR/database.sql"
php artisan cache:clear
Testing Migration Success #
Automated Testing #
Migration Test Suite:
// tests/MigrationTest.php
class MigrationTest extends TestCase
{
public function testServerDataIntegrity()
{
$serverCount = Server::count();
$this->assertGreaterThan(0, $serverCount, 'Servers should exist after migration');
$serversWithOrganizations = Server::whereNotNull('organization_id')->count();
$this->assertEquals($serverCount, $serversWithOrganizations, 'All servers should have organizations');
}
public function testNotificationSettings()
{
$organizations = Organization::has('servers')->get();
foreach ($organizations as $org) {
$settings = NotificationSetting::where('organization_id', $org->id)->first();
$this->assertNotNull($settings, "Organization {$org->name} should have notification settings");
}
}
public function testQueueWorkers()
{
// Test that workers can process jobs
$server = Server::first();
CheckServerJob::dispatch($server->id, 'premium');
// Process job
Artisan::call('queue:work', ['--once' => true, '--queue' => 'premium']);
$server->refresh();
$this->assertNotNull($server->last_checked_at, 'Server should have been checked');
}
}
Manual Verification #
Step-by-Step Verification:
- Login to admin - Verify authentication works
- Check server list - Ensure all servers are visible
- View server details - Check uptime and logs
- Test notifications - Send test alert
- Monitor queue health - Verify workers are processing
- Check API endpoints - Test external integrations
Migration Best Practices #
Planning Phase #
Pre-Migration Assessment:
- Inventory current monitoring setup
- Identify critical vs. non-critical servers
- Plan migration in phases (test → staging → production)
- Communicate timeline to stakeholders
- Prepare rollback procedures
Execution Phase #
Migration Day Checklist:
- [ ] Maintenance window - Schedule appropriate downtime
- [ ] Team availability - Ensure technical team is available
- [ ] Backups verified - Test backup restoration
- [ ] Communication sent - Notify users of migration
- [ ] Monitoring disabled - Prevent false alerts during migration
Post-Migration #
Monitoring Period:
- Monitor system for 24-48 hours post-migration
- Check notification delivery
- Verify uptime calculations
- Review error logs
- Collect user feedback
Documentation Updates:
- Update system documentation
- Record lessons learned
- Update team procedures
- Update monitoring runbooks
Previous: ← Legal Pages | Next: Back to Documentation ↗