├── .gitattributes ├── .github └── workflows │ └── run-tests.yml ├── .gitignore ├── composer.json ├── composer.lock ├── phpunit.xml ├── readme.md └── src ├── BackupTelegramServiceProvider.php ├── ServiceProvider.php ├── Sword.php └── Transporter.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /tests export-ignore 2 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: true 14 | matrix: 15 | php: [8.0, 8.1, 8.2, 8.3] 16 | laravel: [9.*, 10.*, 11.*, 12.*] 17 | exclude: 18 | # PHP 8.0 is not compatible with Laravel 10/11/12 19 | - php: 8.0 20 | laravel: 10.* 21 | - php: 8.0 22 | laravel: 11.* 23 | - php: 8.0 24 | laravel: 12.* 25 | # PHP 8.1 is not compatible with Laravel 11/12 26 | - php: 8.1 27 | laravel: 11.* 28 | - php: 8.1 29 | laravel: 12.* 30 | # Laravel 9 requires at most PHP 8.2 31 | - php: 8.3 32 | laravel: 9.* 33 | 34 | name: P${{ matrix.php }} - L${{ matrix.laravel }} 35 | 36 | steps: 37 | - name: Checkout code 38 | uses: actions/checkout@v4 39 | 40 | - name: Setup PHP 41 | uses: shivammathur/setup-php@v2 42 | with: 43 | php-version: ${{ matrix.php }} 44 | extensions: dom, curl, libxml, mbstring, zip 45 | coverage: none 46 | 47 | - name: Install dependencies 48 | run: | 49 | composer require "illuminate/support:${{ matrix.laravel }}" --no-interaction --no-update 50 | composer update --prefer-dist --no-interaction 51 | 52 | - name: Execute tests 53 | run: vendor/bin/phpunit 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor 3 | /.phpunit.result.cache 4 | /tests/dummy/test.zip.* 5 | 6 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bangnokia/laravel-backup-telegram", 3 | "description": "Upload your backup file to telegram channel for spatie/laravel-backup", 4 | "type": "library", 5 | "license": "MIT", 6 | "require": { 7 | "php": "^8.0", 8 | "illuminate/support": "^9.0|^10.0|^11.0|^12.0", 9 | "spatie/laravel-backup": "^8.0|^9.0" 10 | }, 11 | "require-dev": { 12 | "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "Bangnokia\\LaravelBackupTelegram\\": "src/" 17 | } 18 | }, 19 | "autoload-dev": { 20 | "psr-4": { 21 | "Bangnokia\\LaravelBackupTelegram\\Tests\\": "tests/" 22 | } 23 | }, 24 | "authors": [ 25 | { 26 | "name": "Nguyen Viet", 27 | "email": "bangnokia@gmail.com" 28 | } 29 | ], 30 | "extra": { 31 | "laravel": { 32 | "providers": [ 33 | "Bangnokia\\LaravelBackupTelegram\\ServiceProvider" 34 | ] 35 | } 36 | }, 37 | "scripts": { 38 | "post-autoload-dump": [ 39 | "@php ./vendor/bin/testbench package:discover --ansi" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Laravel backup Telegram 2 | 3 | This package requires [spatie/laravel-backup](https://github.com/spatie/laravel-backup) to work, it supports for uploading the backup file to a telegram channel. 4 | 5 | ~~Because this package use [Telegram API](https://core.telegram.org/bots/api#senddocument), the file size must be limited to 50MB. So if your database is big, this solution isn't for your business.~~ 6 | If the file size is bigger than 50MB, the package will split the file into multiple files, using the `split` command. 7 | 8 | ## Compatibility 9 | 10 | This package supports Laravel 9, 10, 11 and 12. 11 | 12 | ## Setup 13 | 14 | Install this package 15 | ``` 16 | composer require bangnokia/laravel-backup-telegram 17 | ``` 18 | 19 | Edit your `config/services.php` file, then add these lines. I tried to match config with the package [Telegram notification channel](https://github.com/laravel-notification-channels/telegram) 20 | ``` 21 | 'telegram-bot-api' => [ 22 | 'token' => env('TELEGRAM_BOT_TOKEN'), 23 | 'chat_id' => env('TELEGRAM_CHAT_ID') 24 | ] 25 | ``` 26 | 27 | ## Usage 28 | 29 | This package automatically add a event listener when a backup created successfully. So you just simply run `php artisan backup:run`, your backup file will be uploaded to Telegram channel. 30 | 31 | **If you don't use the email notification, please publish the `backup.php` config file and change `mail.to` value to an empty string, refer to this issue [Does not work in Laravel 9](https://github.com/bangnokia/laravel-backup-telegram/issues/1)* 32 | -------------------------------------------------------------------------------- /src/BackupTelegramServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 20 | $this->publishes([ 21 | __DIR__.'/../config/backup-telegram.php' => config_path('backup-telegram.php'), 22 | ], 'config'); 23 | } 24 | 25 | // Register event listeners 26 | Event::listen(BackupWasSuccessful::class, BackupSuccessfulListener::class); 27 | Event::listen(BackupHasFailed::class, BackupFailedListener::class); 28 | } 29 | 30 | // ...existing code... 31 | } 32 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | run(); 13 | 14 | if ($result !== 0) { 15 | throw new \RuntimeException('Can not split the file'); 16 | } 17 | 18 | // get the list of parts 19 | $parts = glob("{$filePath}.part*"); 20 | 21 | return $parts; 22 | } 23 | 24 | public function cleanup(array $parts): void 25 | { 26 | foreach ($parts as $part) { 27 | unlink($part); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Transporter.php: -------------------------------------------------------------------------------- 1 | token = $token ?? config('services.telegram-bot-api.token'); 19 | $this->chatId = $chatId ?? config('services.telegram-bot-api.chat_id'); 20 | } 21 | 22 | public function handle(BackupWasSuccessful $event): void 23 | { 24 | $backup = $event->backupDestination->newestBackup(); 25 | 26 | $threshold = 40; // in MB 27 | consoleOutput()->info("File size is {$backup->sizeInBytes()} bytes"); 28 | if ($backup->sizeInBytes() > $threshold * 1024 * 1024) { 29 | consoleOutput()->info("File size is bigger than {$threshold}MB, chunking the file into multiple parts"); 30 | $response = $this->splitAndUpload($backup, $threshold); 31 | } else { 32 | $response = $this->singleUpload($backup); 33 | } 34 | 35 | if ($response['ok']) { 36 | consoleOutput()->info('Uploaded to Telegram successful!'); 37 | } else { 38 | consoleOutput()->error('Can not upload to Telegram'); 39 | } 40 | } 41 | 42 | public function singleUpload(Backup $backup): array 43 | { 44 | $response = Http::attach('document', $backup->disk()->get($backup->path()), $backup->path()) 45 | ->timeout(300) 46 | ->post( 47 | "https://api.telegram.org/bot{$this->token}/sendDocument", 48 | ['chat_id' => $this->chatId, 'caption' => config('app.name')] 49 | )->throw(); 50 | 51 | return $response->json(); 52 | } 53 | 54 | /** 55 | * @throws RequestException 56 | */ 57 | public function splitAndUpload(?Backup $backup, int $threshold): array 58 | { 59 | $sword = new Sword(); 60 | 61 | $parts = $sword->slash($backup->disk()->path($backup->path()), $threshold); 62 | 63 | foreach ($parts as $part) { 64 | consoleOutput()->info("Uploading part {$part}"); 65 | $stream = fopen($part, 'r'); 66 | 67 | $response = Http::attach('document', $stream, basename($part)) 68 | ->timeout(300) // is this enough? 69 | ->post( 70 | "https://api.telegram.org/bot{$this->token}/sendDocument", 71 | ['chat_id' => $this->chatId, 'caption' => config('app.name')] 72 | )->throw(); 73 | 74 | fclose($stream); 75 | } 76 | 77 | // delete the parts 78 | $sword->cleanup($parts); 79 | 80 | return $response->json(); // return the last response 81 | } 82 | } 83 | --------------------------------------------------------------------------------