First version
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/vendor/
|
||||||
|
config.yml
|
32
backup
Executable file
32
backup
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
$autoload = null;
|
||||||
|
|
||||||
|
$autoloadFiles = [
|
||||||
|
__DIR__ . '/../vendor/autoload.php',
|
||||||
|
__DIR__ . '/../../../autoload.php',
|
||||||
|
__DIR__ . '/vendor/autoload.php'
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($autoloadFiles as $autoloadFile) {
|
||||||
|
if (file_exists($autoloadFile)) {
|
||||||
|
$autoload = $autoloadFile;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $autoload) {
|
||||||
|
echo "Autoload file not found; try 'composer dump-autoload' first." . PHP_EOL;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require $autoload;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
|
||||||
|
$application = new Application();
|
||||||
|
|
||||||
|
$application->add(new App\CommandBackup());
|
||||||
|
$application->add(new App\CommandShow());
|
||||||
|
|
||||||
|
$application->run();
|
13
composer.json
Normal file
13
composer.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"require": {
|
||||||
|
"symfony/console": "^5.4",
|
||||||
|
"symfony/yaml": "^5.4",
|
||||||
|
"twig/twig": "^3.6",
|
||||||
|
"symfony/process": "^5.4"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"App\\": "src/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1102
composer.lock
generated
Normal file
1102
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
config.example.yml
Normal file
18
config.example.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
notification:
|
||||||
|
domain: ntfy.jcktrue.dk
|
||||||
|
topic: backup
|
||||||
|
backup:
|
||||||
|
- title: Example
|
||||||
|
source: test/srcdasasda
|
||||||
|
destination: test/dest
|
||||||
|
templates:
|
||||||
|
notify: |
|
||||||
|
{{ config.title }}
|
||||||
|
From {{ config.source }} to {{ config.destination }}
|
||||||
|
Backup started: {{ start | date }}
|
||||||
|
Source size: {{ source_size | formatBytes}}
|
||||||
|
Destination before: {{ destination_size_before | formatBytes}}
|
||||||
|
Destination after: {{ destination_size_after | formatBytes}}
|
||||||
|
Destination change : {{ (destination_size_after - destination_size_before) | formatBytes}}
|
||||||
|
Backup completed: {{ end | date }}
|
||||||
|
|
65
src/CommandBackup.php
Normal file
65
src/CommandBackup.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Symfony\Component\Yaml\Exception\ParseException;
|
||||||
|
|
||||||
|
class CommandBackup extends Command
|
||||||
|
{
|
||||||
|
static $defaultName = "backup";
|
||||||
|
static $defaultDescription = "Start backup to assigned buckets";
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->addArgument('config', InputArgument::OPTIONAL, 'Configuration file', "config.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$output->writeln('Start backup!');
|
||||||
|
|
||||||
|
$output->writeln('Opening: '.$input->getArgument('config'));
|
||||||
|
try {
|
||||||
|
$config = Yaml::parseFile($input->getArgument('config'));
|
||||||
|
} catch (ParseException $exception) {
|
||||||
|
$output->writeln('Unable to parse the YAML string: '. $exception->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$rclone = new Rclone\Rclone();
|
||||||
|
$output->writeln("Rclone version: ". $rclone->getVersion());
|
||||||
|
$ntfy = new Ntfy\Ntfy($config['notification']['domain']);
|
||||||
|
|
||||||
|
$loader = new \Twig\Loader\ArrayLoader($config['templates']);
|
||||||
|
$twig = new \Twig\Environment($loader);
|
||||||
|
$twig->addExtension(new Twig\AppExtension());
|
||||||
|
|
||||||
|
foreach($config['backup'] as $conf)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$template['config'] = $conf;
|
||||||
|
$template['start'] = new \DateTime();
|
||||||
|
$template['source_size'] = $rclone->getSize($conf['source']);
|
||||||
|
$template['destination_size_before'] = $rclone->getSize($conf['destination']);
|
||||||
|
|
||||||
|
$rclone->copy($conf['source'], $conf['destination'], "6M");
|
||||||
|
|
||||||
|
$template['destination_size_after'] = $rclone->getSize($conf['destination']);
|
||||||
|
$template['end'] = new \DateTime();
|
||||||
|
|
||||||
|
$message = $twig->render('notify', $template);
|
||||||
|
echo $message;
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$message = $e->getMessage();
|
||||||
|
}
|
||||||
|
$ntfy->send($config['notification']['topic'], $conf['title'], $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
40
src/CommandShow.php
Normal file
40
src/CommandShow.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
use Symfony\Component\Yaml\Exception\ParseException;
|
||||||
|
|
||||||
|
class CommandShow extends Command
|
||||||
|
{
|
||||||
|
static $defaultName = "show";
|
||||||
|
static $defaultDescription = "Show all backup entries";
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->addArgument('config', InputArgument::OPTIONAL, 'Configuration file', "config.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$output->writeln('Reading from: '.$input->getArgument('config'));
|
||||||
|
try {
|
||||||
|
$config = Yaml::parseFile($input->getArgument('config'));
|
||||||
|
} catch (ParseException $exception) {
|
||||||
|
$output->writeln('Unable to parse the YAML string: '. $exception->getMessage());
|
||||||
|
}
|
||||||
|
$table = new Table($output);
|
||||||
|
$table
|
||||||
|
->setHeaders(['Description', 'Source', 'Destination'])
|
||||||
|
->setRows($config['backup']);
|
||||||
|
;
|
||||||
|
$table->render();
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
25
src/Ntfy/Ntfy.php
Normal file
25
src/Ntfy/Ntfy.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Ntfy;
|
||||||
|
|
||||||
|
class Ntfy {
|
||||||
|
private $domain;
|
||||||
|
function __construct($domain)
|
||||||
|
{
|
||||||
|
$this->domain = $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
function send($topic,$title, $message)
|
||||||
|
{
|
||||||
|
file_get_contents('https://'.$this->domain.'/'.$topic, false, stream_context_create([
|
||||||
|
'http' => [
|
||||||
|
'method' => 'POST',
|
||||||
|
'header' => 'Content-Type: text/plain',
|
||||||
|
'header' =>
|
||||||
|
"Content-Type: text/plain\r\n" .
|
||||||
|
"Title: $title\r\n",
|
||||||
|
'content' => $message
|
||||||
|
]
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
src/Rclone/Rclone.php
Normal file
56
src/Rclone/Rclone.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
namespace App\Rclone;
|
||||||
|
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
use Symfony\Component\Process\Exception\ProcessFailedException;
|
||||||
|
|
||||||
|
class Rclone {
|
||||||
|
private $rclone_path;
|
||||||
|
private $global_options = [];
|
||||||
|
|
||||||
|
function __construct($rclone_path = "rclone")
|
||||||
|
{
|
||||||
|
$this->rclone_path = $rclone_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersion()
|
||||||
|
{
|
||||||
|
return $this->exec('--version');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSize($path)
|
||||||
|
{
|
||||||
|
$output = $this->exec('size', ['--json', $path]);
|
||||||
|
|
||||||
|
return (int)json_decode($output[0])->bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copy($src, $dest, $bandwidth = null)
|
||||||
|
{
|
||||||
|
$options = array();
|
||||||
|
|
||||||
|
$options[] = $src;
|
||||||
|
$options[] = $dest;
|
||||||
|
if($bandwidth)
|
||||||
|
{
|
||||||
|
$options[] = "--bwlimit";
|
||||||
|
$options[] = $bandwidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->exec('copy', $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function exec(string $command, array $options = array())
|
||||||
|
{
|
||||||
|
$process = new Process(array_merge([$this->rclone_path], $this->global_options, [$command], $options));
|
||||||
|
$process->setTimeout(4*3600);
|
||||||
|
$process->run();
|
||||||
|
|
||||||
|
// executes after the command finishes
|
||||||
|
if (!$process->isSuccessful()) {
|
||||||
|
throw new ProcessFailedException($process);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $process->getOutput();
|
||||||
|
}
|
||||||
|
}
|
30
src/Twig/AppExtension.php
Normal file
30
src/Twig/AppExtension.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Twig;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use Twig\Extension\AbstractExtension;
|
||||||
|
use Twig\TwigFilter;
|
||||||
|
|
||||||
|
class AppExtension extends AbstractExtension
|
||||||
|
{
|
||||||
|
public function getFilters()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
new TwigFilter('formatBytes', array($this, 'formatBytes')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $bytes
|
||||||
|
* @param int $precision
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function formatBytes($bytes, $precision = 2)
|
||||||
|
{
|
||||||
|
$size = ['B','kB','MB','GB','TB','PB','EB','ZB','YB'];
|
||||||
|
$factor = floor((strlen($bytes) - 1) / 3);
|
||||||
|
return sprintf("%.{$precision}f", $bytes / pow(1024, $factor)) . @$size[$factor];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user