diff --git a/composer.json b/composer.json index ecaf1ca9e..6f5d4788e 100755 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "doctrine/annotations": "^1.10", "lavary/crunz": "^2.2", "symfony/routing": "^5.1", - "robmorgan/phinx": "^0.12.4" + "robmorgan/phinx": "^0.12.4", + "symfony/yaml": "^5.3" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index d246f254e..3e8e4ef38 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0794a5a9aef9cea2de69788c63a07b37", + "content-hash": "b527b2c61f83b92047d2263dcce6d018", "packages": [ { "name": "cakephp/core", @@ -3518,16 +3518,16 @@ }, { "name": "symfony/yaml", - "version": "v5.1.2", + "version": "v5.3.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "ea342353a3ef4f453809acc4ebc55382231d4d23" + "reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/ea342353a3ef4f453809acc4ebc55382231d4d23", - "reference": "ea342353a3ef4f453809acc4ebc55382231d4d23", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7", + "reference": "4500fe63dc9c6ffc32d3b1cb0448c329f9c814b7", "shasum": "" }, "require": { @@ -3548,11 +3548,6 @@ "Resources/bin/yaml-lint" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.1-dev" - } - }, "autoload": { "psr-4": { "Symfony\\Component\\Yaml\\": "" @@ -3575,9 +3570,23 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Yaml Component", + "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", - "time": "2020-05-20T17:43:50+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-29T06:20:01+00:00" }, { "name": "vlucas/phpdotenv", diff --git a/db/migrations/20210819182053_profile_backup.php b/db/migrations/20210819182053_profile_backup.php new file mode 100644 index 000000000..3f4c6c6f0 --- /dev/null +++ b/db/migrations/20210819182053_profile_backup.php @@ -0,0 +1,44 @@ +table('Profile_Backup_Strategies', ['id' => "PBS_ID", 'primary_key' => ["PBS_ID"]]); + $table->addColumn('PBS_Date_Created', 'datetime', ['default' => 'CURRENT_TIMESTAMP']) + ->addColumn('PBS_Name', 'string') + ->create(); + + $this->execute("INSERT INTO `Profile_Backup_Strategies`(`PBS_Name`) VALUES ('Download & Archive')"); + + $table = $this->table('Profile_Backup_Schedule', ['id' => "PBS_ID", 'primary_key' => ["PBS_ID"]]); + $table->addColumn('PBS_Date_Created', 'datetime', ['default' => 'CURRENT_TIMESTAMP']) + ->addColumn('PBS_User_ID', 'integer') + ->addColumn('PBS_Schedule_String', 'string') + ->addColumn('PBS_Retention', 'integer') + ->addColumn('PBS_Host_ID', 'integer') + ->addColumn('PBS_Project', 'string') + ->addColumn('PBS_Stratergy_ID', 'integer') + ->addColumn('PBS_Disabled', 'boolean', ['null'=>false, "default"=>0]) + ->addColumn('PBS_Disabled_Date', 'datetime', ['null'=>true]) + ->addColumn('PBS_Disabled_By', 'integer', ['null'=>true]) + ->addForeignKey('PBS_User_ID', 'Users', 'User_ID', ['delete'=> 'RESTRICT', 'update'=> 'RESTRICT']) + ->addForeignKey('PBS_Stratergy_ID', 'Profile_Backup_Strategies', 'PBS_ID', ['delete'=> 'RESTRICT', 'update'=> 'RESTRICT']) + ->addForeignKey('PBS_Disabled_By', 'Users', 'User_ID', ['delete'=> 'RESTRICT', 'update'=> 'RESTRICT']) + ->addForeignKey('PBS_Host_ID', 'Hosts', 'Host_ID', ['delete'=> 'CASCADE', 'update'=> 'RESTRICT']) + ->create(); + + $table = $this->table('Profile_Backups', ['id' => "PB_ID", 'primary_key' => ["PB_ID"]]); + $table->addColumn('PB_Date_Created', 'datetime', ['default' => 'CURRENT_TIMESTAMP']) + ->addColumn('PB_Host_ID', 'integer') + ->addColumn('PB_Local_Path', 'string') + ->addColumn('PB_Project', 'string') + ->addColumn('PB_Filesize', 'biginteger') + ->addColumn('PB_Deleted', 'datetime', ['null'=>true]) + ->addForeignKey('PB_Host_ID', 'Hosts', 'Host_ID', ['delete'=> 'RESTRICT', 'update'=> 'RESTRICT']) + ->create(); + } +} diff --git a/src/classes/Constants/ProfileBackupStrategies.php b/src/classes/Constants/ProfileBackupStrategies.php new file mode 100644 index 000000000..217f7dc4e --- /dev/null +++ b/src/classes/Constants/ProfileBackupStrategies.php @@ -0,0 +1,7 @@ +getProfilesBackupsOverview = $getProfilesBackupsOverview; + } + + public function get($userId) + { + return $this->getProfilesBackupsOverview->get($userId); + } +} diff --git a/src/classes/Model/Backups/Profiles/FetchProfileBackups.php b/src/classes/Model/Backups/Profiles/FetchProfileBackups.php new file mode 100644 index 000000000..c196f2426 --- /dev/null +++ b/src/classes/Model/Backups/Profiles/FetchProfileBackups.php @@ -0,0 +1,67 @@ +database = $database->dbObject; + } + + public function fetchAll() + { + $sql = "SELECT + `PB_ID` as `id`, + `PB_Date_Created` as `storedDateCreated`, + `PB_Host_ID` as `hostId`, + `PB_Local_Path` as `localPath`, + `PB_Project` as `project`, + `PB_Deleted` as `deletedDate`, + `PB_Filesize` as `filesize` + FROM + `Profile_Backups` + ORDER BY + `storedDateCreated` DESC + "; + $do = $this->database->query($sql); + return $do->fetchAll(\PDO::FETCH_ASSOC); + } + + public function fetchBackupsUserCanAccess(int $userId) + { + $sql = "SELECT + `PB_ID` as `id`, + `PB_Date_Created` as `storedDateCreated`, + `PB_Host_ID` as `hostId`, + `PB_Local_Path` as `localPath`, + `PB_Project` as `project`, + `PB_Deleted` as `deletedDate`, + `PB_Filesize` as `filesize` + FROM + `Profile_Backups` + WHERE + `PB_Project` IN( + SELECT + `UAP_Project` + FROM + `User_Allowed_Projects` + WHERE + `UAP_User_ID` = :userId + ) AND `PB_Host_ID` IN( + SELECT + `UAP_Host_ID` + FROM + `User_Allowed_Projects` + WHERE + `UAP_User_ID` = :userId + )"; + $do = $this->database->prepare($sql); + $do->execute([ + ":userId"=>$userId + ]); + return $do->fetchAll(\PDO::FETCH_ASSOC); + } +} diff --git a/src/classes/Model/Hosts/Backups/Profiles/InsertProfilesBackup.php b/src/classes/Model/Hosts/Backups/Profiles/InsertProfilesBackup.php new file mode 100644 index 000000000..6f2f4204c --- /dev/null +++ b/src/classes/Model/Hosts/Backups/Profiles/InsertProfilesBackup.php @@ -0,0 +1,40 @@ +database = $database->dbObject; + } + + public function insert( + int $hostId, + string $project, + string $localPath, + int $filesize + ) { + $sql = "INSERT INTO `Profile_Backups`( + `PB_Host_ID`, + `PB_Local_Path`, + `PB_Project`, + `PB_Filesize` + ) VALUES( + :hostId, + :localPath, + :project, + :filesize + )"; + $do = $this->database->prepare($sql); + $do->execute([ + ":hostId"=>$hostId, + ":localPath"=>$localPath, + ":project"=>$project, + ":filesize"=>$filesize + ]); + return $this->database->lastInsertId(); + } +} diff --git a/src/classes/Model/Hosts/Backups/Profiles/Schedules/FetchProfilesBackupSchedules.php b/src/classes/Model/Hosts/Backups/Profiles/Schedules/FetchProfilesBackupSchedules.php new file mode 100644 index 000000000..0f4016dad --- /dev/null +++ b/src/classes/Model/Hosts/Backups/Profiles/Schedules/FetchProfilesBackupSchedules.php @@ -0,0 +1,63 @@ +database = $database->dbObject; + } + + public function fetchActiveSchedsGroupedByHostId() + { + $sql = "SELECT + `PBS_Host_ID` as `hostId`, + `PBS_Project` as `project`, + `PBS_Schedule_String` as `scheduleString`, + `PBS_Stratergy_ID` as `strategyId`, + `PBS_Retention` as `scheduleRetention`, + `Profile_Backup_Strategies`.`PBS_Name` as `strategyName` + FROM + `Profile_Backup_Schedule` + LEFT JOIN `Profile_Backup_Strategies` ON + `PBS_Stratergy_ID` = `Profile_Backup_Strategies`.`PBS_ID` + WHERE + `PBS_Disabled` = 0 + ORDER BY + `Profile_Backup_Schedule`.`PBS_ID` ASC + "; + $do = $this->database->query($sql); + return $do->fetchAll(\PDO::FETCH_GROUP|\PDO::FETCH_ASSOC); + } + + public function fetchActive(int $hostId) + { + $sql = "SELECT + `IBS_Host_ID` as `hostId`, + `IBS_Instance` as `instance`, + `IBS_Project` as `project`, + `IBS_Schedule_String` as `scheduleString`, + `IBS_BS_ID` as `strategyId`, + `IBS_Retention` as `scheduleRetention`, + `Backup_Strategies`.`BS_Name` as `strategyName` + FROM + `Instance_Backup_Schedule` + LEFT JOIN `Backup_Strategies` ON + `Instance_Backup_Schedule`.`IBS_BS_ID` = `Backup_Strategies`.`BS_ID` + WHERE + `IBS_Host_ID` = :hostId + AND + `IBS_Disabled` = 0 + ORDER BY + `IBS_ID` ASC + "; + $do = $this->database->prepare($sql); + $do->execute([ + ":hostId"=>$hostId + ]); + return $do->fetchAll(\PDO::FETCH_ASSOC); + } +} diff --git a/src/classes/Model/Hosts/Backups/Profiles/Schedules/InsertProfileBackupSchedule.php b/src/classes/Model/Hosts/Backups/Profiles/Schedules/InsertProfileBackupSchedule.php new file mode 100644 index 000000000..4e0eebbe8 --- /dev/null +++ b/src/classes/Model/Hosts/Backups/Profiles/Schedules/InsertProfileBackupSchedule.php @@ -0,0 +1,49 @@ +database = $database->dbObject; + } + + public function insert( + int $userId, + int $hostId, + string $project, + string $schedule, + int $strategyId, + int $retention + ) { + $sql = "INSERT INTO `Profile_Backup_Schedule` + ( + `PBS_User_ID`, + `PBS_Host_ID`, + `PBS_Project`, + `PBS_Schedule_String`, + `PBS_Stratergy_ID`, + `PBS_Retention` + ) VALUES ( + :userId, + :hostId, + :project, + :schedule, + :strategyId, + :retention + );"; + $do = $this->database->prepare($sql); + $do->execute([ + ":userId"=>$userId, + ":hostId"=>$hostId, + ":project"=>$project, + ":schedule"=>$schedule, + ":strategyId"=>$strategyId, + ":retention"=>$retention + ]); + return $do->rowCount() ? true : false; + } +} diff --git a/src/classes/Model/Hosts/Backups/Profiles/Schedules/UpdateProfileBackupSchedules.php b/src/classes/Model/Hosts/Backups/Profiles/Schedules/UpdateProfileBackupSchedules.php new file mode 100644 index 000000000..8f726ad6f --- /dev/null +++ b/src/classes/Model/Hosts/Backups/Profiles/Schedules/UpdateProfileBackupSchedules.php @@ -0,0 +1,38 @@ +database = $database->dbObject; + } + + public function disableActiveScheds( + int $userId, + int $hostId, + string $project + ) { + $sql = "UPDATE `Profile_Backup_Schedule` + SET + `PBS_Disabled` = 1, + `PBS_Disabled_By` = :userId, + `PBS_Disabled_Date` = CURRENT_TIMESTAMP + WHERE + `PBS_Host_ID` = :hostId + AND + `PBS_Project` = :project + AND + `PBS_Disabled` = 0;"; + $do = $this->database->prepare($sql); + $do->execute([ + ":userId"=>$userId, + ":hostId"=>$hostId, + ":project"=>$project + ]); + return $do->rowCount() ? true : false; + } +} diff --git a/src/classes/Tools/Backups/Profiles/GetHostProfileStatusForBackupSet.php b/src/classes/Tools/Backups/Profiles/GetHostProfileStatusForBackupSet.php new file mode 100644 index 000000000..710fa9a55 --- /dev/null +++ b/src/classes/Tools/Backups/Profiles/GetHostProfileStatusForBackupSet.php @@ -0,0 +1,200 @@ +getHostsInstances = $getHostsInstances; + $this->lxdClient = $lxdClient; + $this->fetchProfilesBackupSchedules = $fetchProfilesBackupSchedules; + $this->universe = $universe; + $this->hasExtension = $hasExtension; + } + + public function get(int $userId, array $backups) + { + $clustersAndStandalone = $this->universe->getEntitiesUserHasAccesTo($userId, "projects"); + + $backupsByHostId = $this->createBackupsByHostIdStruct($backups); + + $groupedSchedule = $this->groupSchedules($this->fetchProfilesBackupSchedules->fetchActiveSchedsGroupedByHostId()); + + $missingBackups = []; + + foreach ($clustersAndStandalone["clusters"] as $cluster) { + foreach ($cluster["members"] as $host) { + if ($host->hostOnline()) { + $missingBackups[$host->getAlias()] = $this->addDetailsToHost($host, $backupsByHostId, $groupedSchedule); + } + } + } + + foreach ($clustersAndStandalone["standalone"]["members"] as $host) { + if ($host->hostOnline()) { + $missingBackups[$host->getAlias()] = $this->addDetailsToHost($host, $backupsByHostId, $groupedSchedule); + } + } + + return $missingBackups; + } + + private function addDetailsToHost($host, $backupsByHostId, $groupedSchedule) + { + $backupsToSearch = []; + + if (isset($backupsByHostId[$host->getHostId()])) { + $backupsToSearch = $backupsByHostId[$host->getHostId()]; + } + + $projects = []; + $seenProjectNames = []; + + foreach ($host->getCustomProp("projects") as $name) { + $lastBackup = $this->findLastBackup($name, $backupsToSearch); + $project = ["name"=>$name]; + $allBackups = $this->findAllBackupsandTotalSize($name, $backupsToSearch); + + $scheduleString = ""; + $scheduleRetention = ""; + $strategyName = ""; + + if (isset($groupedSchedule[$host->getHostId()][$name])) { + $scheduleString = $groupedSchedule[$host->getHostId()][$name]["scheduleString"]; + $scheduleRetention = $groupedSchedule[$host->getHostId()][$name]["scheduleRetention"]; + $strategyName = $groupedSchedule[$host->getHostId()][$name]["strategyName"]; + } + + $project["projectExists"] = true; + $project["lastBackup"] = $lastBackup; + $project["totalSize"] = $allBackups["size"]; + $project["allBackups"] = $allBackups["backups"]; + $project["scheduleString"] = $scheduleString; + $project["scheduleRetention"] = $scheduleRetention; + $project["strategyName"] = $strategyName; + + $projects[] = $project; + + if (!isset($seenProjectNames[$name])) { + $seenProjectNames[$name] = []; + } + + $seenProjectNames[$name][] = $name; + } + + foreach ($backupsToSearch as $backup) { + if (!isset($seenProjectNames[$backup["project"]])) { + continue; + } + + if (!isset($seenProjectNames[$backup["project"]])) { + $seenProjectNames[$backup["project"]] = []; + } + + if (!in_array($backup["project"], $seenProjectNames[$backup["project"]])) { + $allBackups = $this->findAllBackupsandTotalSize($backup["project"], $backupsToSearch); + + $projects[] = [ + "name"=>$backup["project"], + "projectExists"=>false, + "scheduleString"=>"N/A", + "lastBackup"=>$this->findLastBackup($backup["project"], $backupsToSearch), + "allBackups"=>$allBackups["backups"], + "totalSize"=>$allBackups["size"], + "scheduleRetention"=>"", + "strategyName"=>"" + ]; + $seenProjectNames[$backup["project"]][] = $backup["project"]; + } + } + + + $supportsBackups = $this->hasExtension->checkWithHost($host, LxdApiExtensions::CONTAINER_BACKUP); + $host->setCustomProp("supportsBackups", $supportsBackups); + $host->setCustomProp("projects", $projects); + $host->removeCustomProp("hostInfo"); + $host->removeCustomProp("instances"); + + return $host; + } + + private function createBackupsByHostIdStruct(array $backups) + { + $backupsByHostId = []; + foreach ($backups as $backup) { + if (!isset($backupsByHostId[$backup["hostId"]])) { + $backupsByHostId[$backup["hostId"]] = []; + } + $backupsByHostId[$backup["hostId"]][] = $backup; + } + return $backupsByHostId; + } + + private function findAllBackupsandTotalSize(string $project, array $hostBackups) + { + $output = []; + $totalSize = 0; + foreach ($hostBackups as $backup) { + if ($backup["project"] == $project) { + $totalSize += $backup["filesize"]; + $output[] = $backup; + } + } + return [ + "backups"=>$output, + "size"=>$totalSize + ]; + } + + private function findLastBackup(string $project, ?array $hostBackups) + { + $x = [ + "neverBackedUp"=>true + ]; + + if (empty($hostBackups)) { + return $x; + } + + $latestDate = null; + + foreach ($hostBackups as $backup) { + if ($project == $backup["project"]) { + $x["neverBackedUp"] = false; + if ((new \DateTime($backup["storedDateCreated"])) > $latestDate) { + $latestDate = (new \DateTime($backup["storedDateCreated"])); + $x = array_merge($x, $backup); + } + } + } + return $x; + } + + private function groupSchedules(array $schedules) + { + foreach ($schedules as $hostId => $instances) { + foreach ($instances as $index => $instance) { + $schedules[$hostId][$instance["project"]] = $instance; + unset($schedules[$hostId][$index]); + } + } + return $schedules; + } +} diff --git a/src/classes/Tools/Backups/Profiles/GetProfilesBackupsOverview.php b/src/classes/Tools/Backups/Profiles/GetProfilesBackupsOverview.php new file mode 100644 index 000000000..cdc7b7bb9 --- /dev/null +++ b/src/classes/Tools/Backups/Profiles/GetProfilesBackupsOverview.php @@ -0,0 +1,86 @@ +fetchProfileBackups = $fetchProfileBackups; + $this->getHostProfileStatusForBackupSet = $getHostProfileStatusForBackupSet; + $this->fetchUserDetails = $fetchUserDetails; + } + + public function get($userId) + { + if ($this->fetchUserDetails->isAdmin($userId)) { + $allBackups = $this->fetchProfileBackups->fetchAll(); + } else { + $allBackups = $this->fetchProfileBackups->fetchProfileBackupsUserCanAccess($userId); + } + + $properties = $this->getProperties($allBackups); + + $allBackups = $this->getHostProfileStatusForBackupSet->get($userId, $properties["localBackups"]); + + return [ + "sizeByMonthYear"=>$properties["sizeByMonthYear"], + "filesByMonthYear"=>$properties["filesByMonthYear"], + "allBackups"=>$allBackups + ]; + } + + private function getProperties(array $backups) + { + $sizeByMonthYear = []; + $filesByMonthYear = []; + + foreach ($backups as $index => $backup) { + $date = new \DateTime($backup["storedDateCreated"]); + $month = $date->format("n"); + $year = $date->format("Y"); + + if (!isset($sizeByMonthYear[$year])) { + $this->createYearArray($sizeByMonthYear, $year); + $this->createYearArray($filesByMonthYear, $year); + } + + + $filesize = $backup["filesize"]; + // After 6 months or so this can be removed, most backup should have + // thier size stored in the database then. + if ($filesize == 0 && file_exists($backup["localPath"])) { + $filesize = filesize($backup["localPath"]); + } + + $backup["filesize"] = $filesize; + + if ($backup["deletedDate"] != null) { + unset($backups[$index]); + } + + $sizeByMonthYear[$year][$month] += $filesize; + $filesByMonthYear[$year][$month] ++; + } + + return [ + "sizeByMonthYear"=>$sizeByMonthYear, + "filesByMonthYear"=>$filesByMonthYear, + "localBackups"=>$backups + ]; + } + + private function createYearArray(&$array, $year) + { + $array[$year] = array_fill(1, 12, null); + } +} diff --git a/src/classes/Tools/Hosts/Backups/Profiles/AddBackupSchedule.php b/src/classes/Tools/Hosts/Backups/Profiles/AddBackupSchedule.php new file mode 100644 index 000000000..e50afadc5 --- /dev/null +++ b/src/classes/Tools/Hosts/Backups/Profiles/AddBackupSchedule.php @@ -0,0 +1,59 @@ +insertProfileBackupSchedule = $insertProfileBackupSchedule; + $this->updateProfileBackupSchedules = $updateProfileBackupSchedules; + } + + public function add( + string $userId, + Host $host, + string $project, + string $frequency, + string $time, + int $strategyId, + int $retention, + array $daysOfWeek = [], + int $dayOfMonth = 0 + ) { + $this->validateBakupSchedule($frequency, $time); + + $this->updateProfileBackupSchedules->disableActiveScheds( + $userId, + $host->getHostId(), + $host->callClientMethod("getProject") + ); + + $this->insertProfileBackupSchedule->insert( + $userId, + $host->getHostId(), + $host->callClientMethod("getProject"), + $frequency . "~ " . $time . "~ [" . implode(",", $daysOfWeek) . "]~ $dayOfMonth~", + $strategyId, + $retention + ); + } + + private function validateBakupSchedule(string $frequency, string $time) + { + $allowedSchedules = ["daily", "weekly", "monthly"]; + if (!in_array($frequency, $allowedSchedules)) { + throw new \Exception("Only supporting daily & weekly backups for the time being", 1); + } + // https://stackoverflow.com/a/3964994/4008082 + if (preg_match("/^(?:2[0-3]|[01][0-9]):[0-5][0-9]$/", $time) == false) { + throw new \Exception("Time not correct format HH:MM 24 hour format", 1); + } + } +} diff --git a/src/classes/Tools/Hosts/Backups/Profiles/BackupProfiles.php b/src/classes/Tools/Hosts/Backups/Profiles/BackupProfiles.php new file mode 100644 index 000000000..2c4ddbf3b --- /dev/null +++ b/src/classes/Tools/Hosts/Backups/Profiles/BackupProfiles.php @@ -0,0 +1,58 @@ +getSetting = $getSetting; + $this->insertProfilesBackup = $insertProfilesBackup; + } + + public function create( + Host $host, + string $project + ) { + $profiles = $host->profiles->all(LxdRecursionLevels::INSTANCE_FULL_RECURSION); + $zip = new \ZipArchive; + + $backupPath = $this->getSetting->getSettingLatestValue(InstanceSettingsKeys::BACKUP_DIRECTORY); + $timezone = $this->getSetting->getSettingLatestValue(InstanceSettingsKeys::TIMEZONE); + + $name = (new \DateTime())->setTimezone(new \DateTimeZone($timezone))->format("Y-m-d_H:i:s"); + + $backupPath .= "/{$host->getHostId()}/$project/profiles/"; + + if (!is_dir($backupPath)) { + mkdir($backupPath, 0777, true); + } + + $backupPath .= "$name.zip"; + + $res = $zip->open($backupPath, \ZipArchive::CREATE); + + foreach ($profiles as $name => $profile) { + $yaml = Yaml::dump($profile, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $zip->addFromString("{$profile["name"]}.yaml", (string) $yaml); + } + + $zip->close(); + + $this->insertProfilesBackup->insert( + $host->getHostId(), + $project, + $backupPath, + filesize($backupPath) + ); + } +} diff --git a/src/cronJobs/backupProfilesTask.php b/src/cronJobs/backupProfilesTask.php new file mode 100644 index 000000000..81f8452b6 --- /dev/null +++ b/src/cronJobs/backupProfilesTask.php @@ -0,0 +1,66 @@ +build(); + +$env = new Dotenv\Dotenv(__DIR__ . "/../../"); +$env->load(); + +$fetchBackupSchedules = $container->make("dhope0000\LXDClient\Model\Hosts\Backups\Profiles\Schedules\FetchProfilesBackupSchedules"); +$schedules = $fetchBackupSchedules->fetchActiveSchedsGroupedByHostId(); +var_dump($schedules); + +$executeString = PHP_BINARY . ' ' . __DIR__ . '/scripts/backupProfiles.php'; + +$dayOfWeekList = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + +$schedule = new Schedule(); + +$getInstanceSetting = $container->make("dhope0000\LXDClient\Model\InstanceSettings\GetSetting"); +$timezone = $getInstanceSetting->getSettingLatestValue(dhope0000\LXDClient\Constants\InstanceSettingsKeys::TIMEZONE); + +foreach ($schedules as $hostId => $hostSchedules) { + foreach ($hostSchedules as $item) { + $argString = "{$hostId} {$item["project"]} {$item["strategyId"]}"; + $name = "Backing profiles for $argString"; + + // Support the original backup format that was "daily hour:minute" + if (preg_match('/daily [0-9]{2}:[0-9]{2}/m', $item["scheduleString"])) { + $tSchedule = new BackupSchedule("daily", [explode(" ", $item["scheduleString"])[1]]); + } else { + $tSchedule = $createBackupSchedule->parseString($item["scheduleString"]); + } + + if ($tSchedule->getRange() == "daily") { + foreach ($tSchedule->getTimes() as $time) { + $task = $schedule->run("$executeString $argString")->description($name . " daily@" . $time); + $task->timezone($timezone); + $task->daily()->at($time); + } + } elseif ($tSchedule->getRange() == "weekly") { + foreach ($tSchedule->getDaysOfWeek() as $dayOfWeek) { + foreach ($tSchedule->getTimes() as $time) { + $task = $schedule->run("$executeString $argString")->description($name . " " . $dayOfWeekList[$dayOfWeek] . "@" . $time); + $task->timezone($timezone); + $task->weeklyOn($dayOfWeek, $time); + } + } + } elseif ($tSchedule->getRange() == "monthly") { + foreach ($tSchedule->getTimes() as $time) { + $tParts = explode(":", $time); + $task = $schedule->run("$executeString $argString")->description($name . " monthly@" . $time); + $task->timezone($timezone); + $task->cron("{$tParts[1]} {$tParts[0]} {$tSchedule->getDayOfMonth()} * *"); + } + } else { + throw new \Exception("Unsupported backup schedule", 1); + } + } +} + +return $schedule; diff --git a/src/cronJobs/scripts/backupProfiles.php b/src/cronJobs/scripts/backupProfiles.php new file mode 100644 index 000000000..8c1e06d42 --- /dev/null +++ b/src/cronJobs/scripts/backupProfiles.php @@ -0,0 +1,37 @@ +build(); + +if (count($argv) !== 4) { + throw new \Exception("script should be called backupProfiles.php hostId project strategyId", 1); +} + +$hostId = $argv[1]; + +if (!is_numeric($hostId)) { + throw new \Exception("host must be numeric id", 1); +} + +$project = $argv[2]; +$strategy = $argv[3]; + +if (!is_numeric($strategy)) { + throw new \Exception("Please provide strategy as numeric id", 1); +} + +$getHost = $container->make("dhope0000\LXDClient\Model\Hosts\GetDetails"); +$backupProfiles = $container->make("dhope0000\LXDClient\Tools\Hosts\Backups\Profiles\BackupProfiles"); + +$host = $getHost->fetchHost($hostId); + +$backupProfiles->create( + $host, + $project +); diff --git a/src/views/boxes/backups.php b/src/views/boxes/backups.php index 67f1f0839..259613874 100644 --- a/src/views/boxes/backups.php +++ b/src/views/boxes/backups.php @@ -1,441 +1,21 @@
-
-
-
-
-
-

Instance Backups

-
-
- - - - - - - - - - - - -
InstanceScheduleLast Backed Up# BackupsSize On Disk
-
-
-
-
-
-
-

Disk Usage

-
-
- -
-
-
-
-

# Backup Files

-
-
- -
-
-
-
-
-
-

- - Backups -

-
-
- - - -
-
- -
-
-
- This instance has been deleted, this backup is all that remains! -
- - - - - - - - - - - - -
NameDate CreatedSize On DiskRestoreDelete
-
-
-
-
-
-
-
- -
+
+
+
diff --git a/src/views/boxes/backups/instaces.php b/src/views/boxes/backups/instaces.php new file mode 100644 index 000000000..93d078be2 --- /dev/null +++ b/src/views/boxes/backups/instaces.php @@ -0,0 +1,546 @@ +
+
+
+
+
+

Instance Backups

+
+
+ + + + + + + + + + + + +
InstanceScheduleLast Backed Up# BackupsSize On Disk
+
+
+
+
+
+
+

Disk Usage

+
+
+ +
+
+
+
+

# Backup Files

+
+
+ +
+
+
+
+
+
+

+ + Backups +

+
+
+ + + +
+
+ +
+
+
+ This instance has been deleted, this backup is all that remains! +
+ + + + + + + + + + + + +
NameDate CreatedSize On DiskRestoreDelete
+
+
+
+
+
+
+
+ +
+
+ diff --git a/src/views/boxes/backups/profiles.php b/src/views/boxes/backups/profiles.php new file mode 100644 index 000000000..c74a0321f --- /dev/null +++ b/src/views/boxes/backups/profiles.php @@ -0,0 +1,245 @@ + + + + diff --git a/src/views/index.php b/src/views/index.php index dc5dac612..2b23fe880 100755 --- a/src/views/index.php +++ b/src/views/index.php @@ -165,6 +165,10 @@ getAllData: '/api/AnalyticData/DownloadHistoryController/download' }, backups: { + profiles: { + getOverview: '/api/Backups/Profiles/GetProfilesBackupsOverviewController/get', + }, + //TODO These should all be under an "instance" key strategies: { getAll: "/api/Backups/Strategies/GetStrategiesController/get", },