2019-05-23 17:29:22 +02:00
|
|
|
<?php
|
2020-10-15 00:19:31 +02:00
|
|
|
|
2021-07-19 07:53:45 +02:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-09-21 16:06:24 +02:00
|
|
|
namespace App\Console\Command\Backup;
|
2019-05-23 17:29:22 +02:00
|
|
|
|
|
|
|
use App\Entity;
|
2022-04-20 14:14:39 +02:00
|
|
|
use App\Environment;
|
2020-06-26 22:22:53 +02:00
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
2021-12-23 02:32:40 +01:00
|
|
|
use Symfony\Component\Console\Attribute\AsCommand;
|
|
|
|
use Symfony\Component\Console\Input\InputArgument;
|
|
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
2019-05-23 17:29:22 +02:00
|
|
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
2022-04-13 04:30:19 +02:00
|
|
|
use Symfony\Component\Filesystem\Filesystem;
|
2022-04-17 14:25:31 +02:00
|
|
|
use Symfony\Component\Filesystem\Path;
|
2022-05-08 20:05:02 +02:00
|
|
|
use Throwable;
|
2020-10-15 00:19:31 +02:00
|
|
|
|
2019-09-04 20:00:51 +02:00
|
|
|
use const PATHINFO_EXTENSION;
|
2019-05-23 17:29:22 +02:00
|
|
|
|
2021-12-23 02:32:40 +01:00
|
|
|
#[AsCommand(
|
|
|
|
name: 'azuracast:backup',
|
|
|
|
description: 'Back up the AzuraCast database and statistics (and optionally media).',
|
|
|
|
)]
|
2022-05-07 15:50:48 +02:00
|
|
|
class BackupCommand extends AbstractBackupCommand
|
2019-05-23 17:29:22 +02:00
|
|
|
{
|
2021-12-23 02:32:40 +01:00
|
|
|
public function __construct(
|
2022-05-07 15:50:48 +02:00
|
|
|
Environment $environment,
|
|
|
|
EntityManagerInterface $em,
|
2021-12-23 02:32:40 +01:00
|
|
|
protected Entity\Repository\StorageLocationRepository $storageLocationRepo,
|
|
|
|
) {
|
2022-05-07 15:50:48 +02:00
|
|
|
parent::__construct($environment, $em);
|
2021-12-23 02:32:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
protected function configure(): void
|
|
|
|
{
|
|
|
|
$this->addArgument('path', InputArgument::REQUIRED)
|
2022-05-06 17:20:33 +02:00
|
|
|
->addOption('storage-location-id', null, InputOption::VALUE_OPTIONAL)
|
2021-12-23 02:32:40 +01:00
|
|
|
->addOption('exclude-media', null, InputOption::VALUE_NONE);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
|
|
|
{
|
|
|
|
$io = new SymfonyStyle($input, $output);
|
2022-04-13 04:30:19 +02:00
|
|
|
$fsUtils = new Filesystem();
|
2021-12-23 02:32:40 +01:00
|
|
|
|
|
|
|
$path = $input->getArgument('path');
|
|
|
|
$excludeMedia = (bool)$input->getOption('exclude-media');
|
|
|
|
$storageLocationId = $input->getOption('storage-location-id');
|
|
|
|
|
2019-05-25 00:23:27 +02:00
|
|
|
$start_time = microtime(true);
|
|
|
|
|
2019-09-11 01:10:57 +02:00
|
|
|
if (empty($path)) {
|
|
|
|
$path = 'manual_backup_' . gmdate('Ymd_Hi') . '.zip';
|
2019-05-23 17:29:22 +02:00
|
|
|
}
|
2020-11-10 04:06:48 +01:00
|
|
|
|
|
|
|
$file_ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
|
|
|
|
2022-04-17 14:25:31 +02:00
|
|
|
if (Path::isAbsolute($path)) {
|
2020-11-10 04:06:48 +01:00
|
|
|
$tmpPath = $path;
|
|
|
|
$storageLocation = null;
|
|
|
|
} else {
|
2022-04-13 04:30:19 +02:00
|
|
|
$tmpPath = $fsUtils->tempnam(
|
|
|
|
sys_get_temp_dir(),
|
|
|
|
'backup_',
|
|
|
|
'.' . $file_ext
|
2022-02-01 03:44:01 +01:00
|
|
|
);
|
2020-11-10 04:06:48 +01:00
|
|
|
|
2022-04-17 14:25:31 +02:00
|
|
|
// Zip command cannot handle an existing file (even an empty one)
|
|
|
|
@unlink($tmpPath);
|
|
|
|
|
2020-11-10 04:06:48 +01:00
|
|
|
if (null === $storageLocationId) {
|
|
|
|
$io->error('You must specify a storage location when providing a relative path.');
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2021-12-23 02:32:40 +01:00
|
|
|
$storageLocation = $this->storageLocationRepo->findByType(
|
2022-01-07 09:26:40 +01:00
|
|
|
Entity\Enums\StorageLocationTypes::Backup,
|
2020-11-10 04:06:48 +01:00
|
|
|
$storageLocationId
|
|
|
|
);
|
|
|
|
if (!($storageLocation instanceof Entity\StorageLocation)) {
|
|
|
|
$io->error('Invalid storage location specified.');
|
|
|
|
return 1;
|
|
|
|
}
|
2021-11-23 16:44:41 +01:00
|
|
|
|
|
|
|
if ($storageLocation->isStorageFull()) {
|
|
|
|
$io->error('Storage location is full.');
|
|
|
|
return 1;
|
|
|
|
}
|
2019-05-23 17:29:22 +02:00
|
|
|
}
|
|
|
|
|
2019-09-11 01:10:57 +02:00
|
|
|
$includeMedia = !$excludeMedia;
|
2019-05-23 17:29:22 +02:00
|
|
|
$files_to_backup = [];
|
|
|
|
|
2019-08-22 00:33:47 +02:00
|
|
|
$io->title(__('AzuraCast Backup'));
|
|
|
|
$io->writeln(__('Please wait while a backup is generated...'));
|
2019-05-23 17:29:22 +02:00
|
|
|
|
|
|
|
// Create temp directories
|
2019-08-22 00:33:47 +02:00
|
|
|
$io->section(__('Creating temporary directories...'));
|
2019-05-23 17:29:22 +02:00
|
|
|
|
|
|
|
$tmp_dir_mariadb = '/tmp/azuracast_backup_mariadb';
|
2022-04-11 04:18:15 +02:00
|
|
|
try {
|
2022-04-13 04:30:19 +02:00
|
|
|
$fsUtils->mkdir($tmp_dir_mariadb);
|
2022-05-08 20:05:02 +02:00
|
|
|
} catch (Throwable $e) {
|
2022-04-11 04:18:15 +02:00
|
|
|
$io->error($e->getMessage());
|
2019-05-23 17:29:22 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
$io->newLine();
|
|
|
|
|
|
|
|
// Back up MariaDB
|
2019-08-22 00:33:47 +02:00
|
|
|
$io->section(__('Backing up MariaDB...'));
|
2019-05-23 17:29:22 +02:00
|
|
|
|
2019-09-04 20:00:51 +02:00
|
|
|
$path_db_dump = $tmp_dir_mariadb . '/db.sql';
|
2019-05-23 17:29:22 +02:00
|
|
|
|
2022-05-07 15:50:48 +02:00
|
|
|
[$commandFlags, $commandEnvVars] = $this->getDatabaseSettingsAsCliFlags();
|
|
|
|
|
|
|
|
$commandFlags[] = '--add-drop-table';
|
|
|
|
$commandFlags[] = '--default-character-set=UTF8MB4';
|
|
|
|
|
|
|
|
$commandEnvVars['DB_DEST'] = $path_db_dump;
|
2019-05-23 17:29:22 +02:00
|
|
|
|
2020-07-08 09:03:50 +02:00
|
|
|
$this->passThruProcess(
|
2019-05-23 17:29:22 +02:00
|
|
|
$io,
|
2022-05-07 15:50:48 +02:00
|
|
|
'mysqldump ' . implode(' ', $commandFlags) . ' $DB_DATABASE > $DB_DEST',
|
2019-05-23 17:29:22 +02:00
|
|
|
$tmp_dir_mariadb,
|
2022-05-07 15:50:48 +02:00
|
|
|
$commandEnvVars
|
2019-05-23 17:29:22 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
$files_to_backup[] = $path_db_dump;
|
|
|
|
$io->newLine();
|
|
|
|
|
|
|
|
// Include station media if specified.
|
2019-09-11 01:10:57 +02:00
|
|
|
if ($includeMedia) {
|
2021-12-23 02:32:40 +01:00
|
|
|
$stations = $this->em->createQuery(
|
2020-12-02 01:36:09 +01:00
|
|
|
<<<'DQL'
|
|
|
|
SELECT s FROM App\Entity\Station s
|
|
|
|
DQL
|
|
|
|
)->execute();
|
2019-05-23 17:29:22 +02:00
|
|
|
|
2019-09-04 20:00:51 +02:00
|
|
|
foreach ($stations as $station) {
|
2019-05-23 17:29:22 +02:00
|
|
|
/** @var Entity\Station $station */
|
|
|
|
|
2020-11-10 04:06:48 +01:00
|
|
|
$mediaAdapter = $station->getMediaStorageLocation();
|
|
|
|
if ($mediaAdapter->isLocal()) {
|
|
|
|
$files_to_backup[] = $mediaAdapter->getPath();
|
2019-05-23 17:29:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Compress backup files.
|
2019-08-22 00:33:47 +02:00
|
|
|
$io->section(__('Creating backup archive...'));
|
2019-05-23 17:29:22 +02:00
|
|
|
|
|
|
|
// Strip leading slashes from backup paths.
|
2020-12-11 03:43:58 +01:00
|
|
|
$files_to_backup = array_map(
|
2021-07-19 07:53:45 +02:00
|
|
|
static function (string $val) {
|
2021-04-24 00:12:47 +02:00
|
|
|
if (str_starts_with($val, '/')) {
|
2020-12-11 03:43:58 +01:00
|
|
|
return substr($val, 1);
|
|
|
|
}
|
|
|
|
return $val;
|
|
|
|
},
|
|
|
|
$files_to_backup
|
|
|
|
);
|
2019-05-23 17:29:22 +02:00
|
|
|
|
2019-09-04 20:00:51 +02:00
|
|
|
switch ($file_ext) {
|
2021-12-13 04:37:13 +01:00
|
|
|
case 'tzst':
|
|
|
|
$this->passThruProcess(
|
|
|
|
$io,
|
|
|
|
array_merge(
|
|
|
|
[
|
|
|
|
'tar',
|
|
|
|
'-I',
|
|
|
|
'zstd',
|
2021-12-14 17:35:47 +01:00
|
|
|
'-cvf',
|
2021-12-13 04:37:13 +01:00
|
|
|
$tmpPath,
|
|
|
|
],
|
|
|
|
$files_to_backup
|
|
|
|
),
|
|
|
|
'/'
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
|
2019-05-24 16:58:33 +02:00
|
|
|
case 'gz':
|
|
|
|
case 'tgz':
|
2020-12-11 03:43:58 +01:00
|
|
|
$this->passThruProcess(
|
|
|
|
$io,
|
|
|
|
array_merge(
|
|
|
|
[
|
|
|
|
'tar',
|
|
|
|
'zcvf',
|
|
|
|
$tmpPath,
|
|
|
|
],
|
|
|
|
$files_to_backup
|
|
|
|
),
|
|
|
|
'/'
|
|
|
|
);
|
2019-05-24 16:58:33 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'zip':
|
|
|
|
default:
|
2019-05-25 00:23:27 +02:00
|
|
|
$dont_compress = ['.tar.gz', '.zip', '.jpg', '.mp3', '.ogg', '.flac', '.aac', '.wav'];
|
2019-05-24 16:58:33 +02:00
|
|
|
|
2020-12-11 03:43:58 +01:00
|
|
|
$this->passThruProcess(
|
|
|
|
$io,
|
|
|
|
array_merge(
|
|
|
|
[
|
|
|
|
'zip',
|
|
|
|
'-r',
|
|
|
|
'-n',
|
|
|
|
implode(':', $dont_compress),
|
|
|
|
$tmpPath,
|
|
|
|
],
|
|
|
|
$files_to_backup
|
|
|
|
),
|
|
|
|
'/'
|
|
|
|
);
|
2019-05-24 16:58:33 +02:00
|
|
|
break;
|
|
|
|
}
|
2019-05-23 17:29:22 +02:00
|
|
|
|
2020-11-10 04:06:48 +01:00
|
|
|
if (null !== $storageLocation) {
|
|
|
|
$fs = $storageLocation->getFilesystem();
|
2021-03-31 18:42:24 +02:00
|
|
|
$fs->uploadAndDeleteOriginal($tmpPath, $path);
|
2020-11-10 04:06:48 +01:00
|
|
|
}
|
2020-12-01 14:45:01 +01:00
|
|
|
|
2019-05-24 16:58:33 +02:00
|
|
|
$io->newLine();
|
|
|
|
|
|
|
|
// Cleanup
|
2019-08-22 00:33:47 +02:00
|
|
|
$io->section(__('Cleaning up temporary files...'));
|
2019-05-24 16:58:33 +02:00
|
|
|
|
2022-04-13 04:30:19 +02:00
|
|
|
$fsUtils->remove($tmp_dir_mariadb);
|
2019-05-24 16:58:33 +02:00
|
|
|
|
|
|
|
$io->newLine();
|
|
|
|
|
2019-05-25 00:23:27 +02:00
|
|
|
$end_time = microtime(true);
|
|
|
|
$time_diff = $end_time - $start_time;
|
|
|
|
|
2020-12-11 03:43:58 +01:00
|
|
|
$io->success(
|
|
|
|
[
|
2022-05-07 18:44:14 +02:00
|
|
|
sprintf(
|
|
|
|
__('Backup complete in %.2f seconds.'),
|
|
|
|
$time_diff
|
|
|
|
),
|
2020-12-11 03:43:58 +01:00
|
|
|
]
|
|
|
|
);
|
2019-05-23 17:29:22 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|