├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── config.sample.php ├── ftp_upload_s3.php ├── package.json └── upload-script.sh /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # phpstorm project files 3 | .idea 4 | 5 | # netbeans project files 6 | nbproject 7 | 8 | # zend studio for eclipse project files 9 | .buildpath 10 | .project 11 | .settings 12 | 13 | # windows thumbnail cache 14 | Thumbs.db 15 | 16 | # composer vendor dir 17 | /vendor 18 | 19 | # composer itself is not needed 20 | composer.phar 21 | 22 | # Mac DS_Store Files 23 | .DS_Store 24 | 25 | # phpunit itself is not needed 26 | phpunit.phar 27 | # local phpunit config 28 | /phpunit.xml 29 | 30 | # vagrant runtime 31 | /.vagrant 32 | 33 | # sensitive information 34 | config.php -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## 1.0.0 (2020-10-25) 6 | 7 | 8 | ### Features 9 | 10 | * add upload script ([91fd808](https://github.com/powerkernel/directadmin-s3-backup/commit/91fd80842422e309c93bfa408a4b170fdae73bff)) 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Power Kernel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # directadmin-s3-backup 2 | 3 | DirectAdmin S3 Backup 4 | 5 | ## Features 6 | 7 | - Auto backup upload to AWS S3 8 | - Auto Create S3 Bucket 9 | - Auto delete old backups after X day(s) 10 | 11 | ## Installation 12 | 13 | ```bash 14 | mkdir -p /home/admin/tools/ 15 | wget -O /home/admin/tools/s3backup.zip https://github.com/powerkernel/directadmin-s3-backup/archive/master.zip 16 | cd /home/admin/tools 17 | unzip s3backup.zip 18 | mv directadmin-s3-backup-master directadmin-s3-backup 19 | cd directadmin-s3-backup 20 | php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" 21 | php composer-setup.php 22 | php -r "unlink('composer-setup.php');" 23 | php composer.phar update 24 | mkdir -p /usr/local/directadmin/scripts/custom 25 | wget -O /usr/local/directadmin/scripts/custom/ftp_upload.php https://raw.githubusercontent.com/powerkernel/directadmin-s3-backup/master/upload-script.sh 26 | chmod +x /usr/local/directadmin/scripts/custom/ftp_upload.php 27 | cp config.sample.php config.php 28 | ``` 29 | 30 | Update `config.php` with your AWS access keys, region and bucket name 31 | 32 | Finally, go to `DirectAdmin \ Admin Backup/Transfer` to create Cron Schedule backup, select FTP for the backup location. 33 | 34 | NOTE: The FTP user/pass is your DirectAdmin admin account & password, FTP IP is `127.0.0.1`. 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "powerkernel/directadmin-s3-backup", 3 | "description": "DirectAdmin S3 Backup", 4 | "keywords": [ 5 | "s3", 6 | "aws", 7 | "direct admin", 8 | "backup" 9 | ], 10 | "homepage": "https://powerkernel.com/", 11 | "type": "project", 12 | "license": "MIT", 13 | "support": { 14 | "issues": "https://github.com/powerkernel/directadmin-s3-backup/issues?state=open", 15 | "source": "https://github.com/powerkernel/directadmin-s3-backup" 16 | }, 17 | "require": { 18 | "aws/aws-sdk-php": "*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "85325c0f13ef626582fea9d1df635a9d", 8 | "packages": [ 9 | { 10 | "name": "aws/aws-sdk-php", 11 | "version": "3.64.10", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/aws/aws-sdk-php.git", 15 | "reference": "4aa66872c4428d0db08c861a34559b0923eaae23" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4aa66872c4428d0db08c861a34559b0923eaae23", 20 | "reference": "4aa66872c4428d0db08c861a34559b0923eaae23", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "ext-json": "*", 25 | "ext-pcre": "*", 26 | "ext-simplexml": "*", 27 | "ext-spl": "*", 28 | "guzzlehttp/guzzle": "^5.3.1|^6.2.1", 29 | "guzzlehttp/promises": "~1.0", 30 | "guzzlehttp/psr7": "^1.4.1", 31 | "mtdowling/jmespath.php": "~2.2", 32 | "php": ">=5.5" 33 | }, 34 | "require-dev": { 35 | "andrewsville/php-token-reflection": "^1.4", 36 | "aws/aws-php-sns-message-validator": "~1.0", 37 | "behat/behat": "~3.0", 38 | "doctrine/cache": "~1.4", 39 | "ext-dom": "*", 40 | "ext-openssl": "*", 41 | "nette/neon": "^2.3", 42 | "phpunit/phpunit": "^4.8.35|^5.4.3", 43 | "psr/cache": "^1.0" 44 | }, 45 | "suggest": { 46 | "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", 47 | "doctrine/cache": "To use the DoctrineCacheAdapter", 48 | "ext-curl": "To send requests using cURL", 49 | "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages" 50 | }, 51 | "type": "library", 52 | "extra": { 53 | "branch-alias": { 54 | "dev-master": "3.0-dev" 55 | } 56 | }, 57 | "autoload": { 58 | "psr-4": { 59 | "Aws\\": "src/" 60 | }, 61 | "files": [ 62 | "src/functions.php" 63 | ] 64 | }, 65 | "notification-url": "https://packagist.org/downloads/", 66 | "license": [ 67 | "Apache-2.0" 68 | ], 69 | "authors": [ 70 | { 71 | "name": "Amazon Web Services", 72 | "homepage": "http://aws.amazon.com" 73 | } 74 | ], 75 | "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", 76 | "homepage": "http://aws.amazon.com/sdkforphp", 77 | "keywords": [ 78 | "amazon", 79 | "aws", 80 | "cloud", 81 | "dynamodb", 82 | "ec2", 83 | "glacier", 84 | "s3", 85 | "sdk" 86 | ], 87 | "time": "2018-08-10T21:49:00+00:00" 88 | }, 89 | { 90 | "name": "guzzlehttp/guzzle", 91 | "version": "6.3.3", 92 | "source": { 93 | "type": "git", 94 | "url": "https://github.com/guzzle/guzzle.git", 95 | "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" 96 | }, 97 | "dist": { 98 | "type": "zip", 99 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", 100 | "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", 101 | "shasum": "" 102 | }, 103 | "require": { 104 | "guzzlehttp/promises": "^1.0", 105 | "guzzlehttp/psr7": "^1.4", 106 | "php": ">=5.5" 107 | }, 108 | "require-dev": { 109 | "ext-curl": "*", 110 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", 111 | "psr/log": "^1.0" 112 | }, 113 | "suggest": { 114 | "psr/log": "Required for using the Log middleware" 115 | }, 116 | "type": "library", 117 | "extra": { 118 | "branch-alias": { 119 | "dev-master": "6.3-dev" 120 | } 121 | }, 122 | "autoload": { 123 | "files": [ 124 | "src/functions_include.php" 125 | ], 126 | "psr-4": { 127 | "GuzzleHttp\\": "src/" 128 | } 129 | }, 130 | "notification-url": "https://packagist.org/downloads/", 131 | "license": [ 132 | "MIT" 133 | ], 134 | "authors": [ 135 | { 136 | "name": "Michael Dowling", 137 | "email": "mtdowling@gmail.com", 138 | "homepage": "https://github.com/mtdowling" 139 | } 140 | ], 141 | "description": "Guzzle is a PHP HTTP client library", 142 | "homepage": "http://guzzlephp.org/", 143 | "keywords": [ 144 | "client", 145 | "curl", 146 | "framework", 147 | "http", 148 | "http client", 149 | "rest", 150 | "web service" 151 | ], 152 | "time": "2018-04-22T15:46:56+00:00" 153 | }, 154 | { 155 | "name": "guzzlehttp/promises", 156 | "version": "v1.3.1", 157 | "source": { 158 | "type": "git", 159 | "url": "https://github.com/guzzle/promises.git", 160 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" 161 | }, 162 | "dist": { 163 | "type": "zip", 164 | "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", 165 | "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", 166 | "shasum": "" 167 | }, 168 | "require": { 169 | "php": ">=5.5.0" 170 | }, 171 | "require-dev": { 172 | "phpunit/phpunit": "^4.0" 173 | }, 174 | "type": "library", 175 | "extra": { 176 | "branch-alias": { 177 | "dev-master": "1.4-dev" 178 | } 179 | }, 180 | "autoload": { 181 | "psr-4": { 182 | "GuzzleHttp\\Promise\\": "src/" 183 | }, 184 | "files": [ 185 | "src/functions_include.php" 186 | ] 187 | }, 188 | "notification-url": "https://packagist.org/downloads/", 189 | "license": [ 190 | "MIT" 191 | ], 192 | "authors": [ 193 | { 194 | "name": "Michael Dowling", 195 | "email": "mtdowling@gmail.com", 196 | "homepage": "https://github.com/mtdowling" 197 | } 198 | ], 199 | "description": "Guzzle promises library", 200 | "keywords": [ 201 | "promise" 202 | ], 203 | "time": "2016-12-20T10:07:11+00:00" 204 | }, 205 | { 206 | "name": "guzzlehttp/psr7", 207 | "version": "1.4.2", 208 | "source": { 209 | "type": "git", 210 | "url": "https://github.com/guzzle/psr7.git", 211 | "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" 212 | }, 213 | "dist": { 214 | "type": "zip", 215 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", 216 | "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", 217 | "shasum": "" 218 | }, 219 | "require": { 220 | "php": ">=5.4.0", 221 | "psr/http-message": "~1.0" 222 | }, 223 | "provide": { 224 | "psr/http-message-implementation": "1.0" 225 | }, 226 | "require-dev": { 227 | "phpunit/phpunit": "~4.0" 228 | }, 229 | "type": "library", 230 | "extra": { 231 | "branch-alias": { 232 | "dev-master": "1.4-dev" 233 | } 234 | }, 235 | "autoload": { 236 | "psr-4": { 237 | "GuzzleHttp\\Psr7\\": "src/" 238 | }, 239 | "files": [ 240 | "src/functions_include.php" 241 | ] 242 | }, 243 | "notification-url": "https://packagist.org/downloads/", 244 | "license": [ 245 | "MIT" 246 | ], 247 | "authors": [ 248 | { 249 | "name": "Michael Dowling", 250 | "email": "mtdowling@gmail.com", 251 | "homepage": "https://github.com/mtdowling" 252 | }, 253 | { 254 | "name": "Tobias Schultze", 255 | "homepage": "https://github.com/Tobion" 256 | } 257 | ], 258 | "description": "PSR-7 message implementation that also provides common utility methods", 259 | "keywords": [ 260 | "http", 261 | "message", 262 | "request", 263 | "response", 264 | "stream", 265 | "uri", 266 | "url" 267 | ], 268 | "time": "2017-03-20T17:10:46+00:00" 269 | }, 270 | { 271 | "name": "mtdowling/jmespath.php", 272 | "version": "2.4.0", 273 | "source": { 274 | "type": "git", 275 | "url": "https://github.com/jmespath/jmespath.php.git", 276 | "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac" 277 | }, 278 | "dist": { 279 | "type": "zip", 280 | "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/adcc9531682cf87dfda21e1fd5d0e7a41d292fac", 281 | "reference": "adcc9531682cf87dfda21e1fd5d0e7a41d292fac", 282 | "shasum": "" 283 | }, 284 | "require": { 285 | "php": ">=5.4.0" 286 | }, 287 | "require-dev": { 288 | "phpunit/phpunit": "~4.0" 289 | }, 290 | "bin": [ 291 | "bin/jp.php" 292 | ], 293 | "type": "library", 294 | "extra": { 295 | "branch-alias": { 296 | "dev-master": "2.0-dev" 297 | } 298 | }, 299 | "autoload": { 300 | "psr-4": { 301 | "JmesPath\\": "src/" 302 | }, 303 | "files": [ 304 | "src/JmesPath.php" 305 | ] 306 | }, 307 | "notification-url": "https://packagist.org/downloads/", 308 | "license": [ 309 | "MIT" 310 | ], 311 | "authors": [ 312 | { 313 | "name": "Michael Dowling", 314 | "email": "mtdowling@gmail.com", 315 | "homepage": "https://github.com/mtdowling" 316 | } 317 | ], 318 | "description": "Declaratively specify how to extract elements from a JSON document", 319 | "keywords": [ 320 | "json", 321 | "jsonpath" 322 | ], 323 | "time": "2016-12-03T22:08:25+00:00" 324 | }, 325 | { 326 | "name": "psr/http-message", 327 | "version": "1.0.1", 328 | "source": { 329 | "type": "git", 330 | "url": "https://github.com/php-fig/http-message.git", 331 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 332 | }, 333 | "dist": { 334 | "type": "zip", 335 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 336 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 337 | "shasum": "" 338 | }, 339 | "require": { 340 | "php": ">=5.3.0" 341 | }, 342 | "type": "library", 343 | "extra": { 344 | "branch-alias": { 345 | "dev-master": "1.0.x-dev" 346 | } 347 | }, 348 | "autoload": { 349 | "psr-4": { 350 | "Psr\\Http\\Message\\": "src/" 351 | } 352 | }, 353 | "notification-url": "https://packagist.org/downloads/", 354 | "license": [ 355 | "MIT" 356 | ], 357 | "authors": [ 358 | { 359 | "name": "PHP-FIG", 360 | "homepage": "http://www.php-fig.org/" 361 | } 362 | ], 363 | "description": "Common interface for HTTP messages", 364 | "homepage": "https://github.com/php-fig/http-message", 365 | "keywords": [ 366 | "http", 367 | "http-message", 368 | "psr", 369 | "psr-7", 370 | "request", 371 | "response" 372 | ], 373 | "time": "2016-08-06T14:39:51+00:00" 374 | } 375 | ], 376 | "packages-dev": [], 377 | "aliases": [], 378 | "minimum-stability": "stable", 379 | "stability-flags": [], 380 | "prefer-stable": false, 381 | "prefer-lowest": false, 382 | "platform": [], 383 | "platform-dev": [] 384 | } 385 | -------------------------------------------------------------------------------- /config.sample.php: -------------------------------------------------------------------------------- 1 | 4 | * @link https://powerkernel.com 5 | * @copyright Copyright (c) 2018 Power Kernel 6 | */ 7 | 8 | return [ 9 | 'credentials' => [ 10 | 'key' => 'YOUR_AWS_KEY', 11 | 'secret' => 'YOUR_AWS_SECRET', 12 | ], 13 | 'region' => 'us-east-1', 14 | 'bucket'=>'you-bucket-name', 15 | 'expiration'=>3, 16 | //'endpoint' => 'https://sfo2.digitaloceanspaces.com', //enable this for digitalocean space 17 | ]; -------------------------------------------------------------------------------- /ftp_upload_s3.php: -------------------------------------------------------------------------------- 1 | 4 | * @link https://powerkernel.com 5 | * @copyright Copyright (c) 2018 Power Kernel 6 | */ 7 | 8 | /* @var $ftp_local_file string $argv[1] */ 9 | 10 | /* @var $ftp_remote_file string $argv[2] */ 11 | 12 | use Aws\Exception\MultipartUploadException; 13 | use Aws\S3\MultipartUploader; 14 | 15 | require __DIR__ . '/vendor/autoload.php'; 16 | $conf = require __DIR__ . '/config.php'; 17 | 18 | //$date = date('Ymd'); 19 | $bucket = $conf['bucket']; 20 | $ftp_local_file=$argv[1]; 21 | $ftp_remote_file=$argv[2]; 22 | 23 | // S3 Client 24 | $opts = [ 25 | //'profile' => 'default', 26 | 'version' => 'latest', 27 | 'region' => $conf['region'], 28 | 'credentials' => $conf['credentials'], 29 | ]; 30 | if (!empty($conf['endpoint'])) { 31 | $opts['endpoint'] = $conf['endpoint']; 32 | } 33 | 34 | $client = new Aws\S3\S3Client($opts); 35 | 36 | // check bucket exist 37 | $exist = false; 38 | $buckets = $client->listBuckets(); 39 | if ($buckets) { 40 | foreach ($buckets['Buckets'] as $i => $b) { 41 | if ($b['Name'] == $bucket) { 42 | $exist = true; 43 | } 44 | } 45 | } 46 | 47 | 48 | // Create Bucket 49 | if (!$exist) { 50 | $client->createBucket([ 51 | 'Bucket' => $bucket, 52 | 'LocationConstraint' => $conf['region'], 53 | ]); 54 | 55 | // Poll the bucket until it is accessible 56 | $client->waitUntil('BucketExists', [ 57 | 'Bucket' => $bucket 58 | ]); 59 | 60 | // add Lifecycle 61 | $client->putBucketLifecycleConfiguration( 62 | [ 63 | 'Bucket' => $bucket, // REQUIRED 64 | 'LifecycleConfiguration' => [ 65 | 'Rules' => [ // REQUIRED 66 | [ 67 | 'ID' => 'AutoDelete', 68 | 'AbortIncompleteMultipartUpload' => [ 69 | 'DaysAfterInitiation' => 1, 70 | ], 71 | 'Expiration' => [ 72 | 'Days' => $conf['expiration'], 73 | ], 74 | 'Status' => 'Enabled', 75 | 'Prefix' => '' 76 | ], 77 | ], 78 | ], 79 | ] 80 | ); 81 | } 82 | 83 | // check and apply lifecycle 84 | $hasLifeCycle = false; 85 | try { 86 | $r = $client->getBucketLifecycle(['Bucket' => $bucket]); 87 | $rules = $r->get('Rules'); 88 | foreach ($rules as $rule) { 89 | if ($rule['ID'] == 'AutoDelete') { 90 | $hasLifeCycle = true; 91 | } 92 | } 93 | } catch (\Aws\S3\Exception\S3Exception $e) { 94 | 95 | } 96 | if (!$hasLifeCycle) { 97 | // Config auto delete 98 | $client->putBucketLifecycleConfiguration( 99 | [ 100 | 'Bucket' => $bucket, // REQUIRED 101 | 'LifecycleConfiguration' => [ 102 | 'Rules' => [ // REQUIRED 103 | [ 104 | 'ID' => 'AutoDelete', 105 | 'AbortIncompleteMultipartUpload' => [ 106 | 'DaysAfterInitiation' => 1, 107 | ], 108 | 'Expiration' => [ 109 | 'Days' => $conf['expiration'], 110 | ], 111 | 'Status' => 'Enabled', 112 | 'Prefix' => '' 113 | ], 114 | ], 115 | ], 116 | ] 117 | ); 118 | echo "adding lifecycle"; 119 | } else { 120 | echo "lifecycle already added."; 121 | } 122 | 123 | // Upload 124 | $uploader = new MultipartUploader($client, $ftp_local_file, [ 125 | 'bucket' => $bucket, 126 | 'key' => date('Y-m-d') . '/' . $ftp_remote_file, 127 | ]); 128 | 129 | try { 130 | $result = $uploader->upload(); 131 | echo "Upload complete: {$result['ObjectURL']}\n"; 132 | } catch (MultipartUploadException $e) { 133 | echo $e->getMessage() . "\n"; 134 | } 135 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "directadmin-s3-backup", 3 | "version": "1.0.0", 4 | "description": "DirectAdmin S3 Backup", 5 | "main": "index.js", 6 | "scripts": { 7 | "release": "npx standard-version && git push --follow-tags origin main" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/powerkernel/directadmin-s3-backup.git" 12 | }, 13 | "keywords": [], 14 | "author": { 15 | "name": "Harry Tang", 16 | "email": "harry@powerkernel.com", 17 | "url": "https://powerkernel.com" 18 | }, 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/powerkernel/directadmin-s3-backup/issues" 22 | }, 23 | "homepage": "https://github.com/powerkernel/directadmin-s3-backup#readme" 24 | } 25 | -------------------------------------------------------------------------------- /upload-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ ${ftp_ip} == "127.0.0.1" ] && [ ${ftp_username} == "admin" ]; then 3 | RET=0; 4 | php /home/admin/tools/directadmin-s3-backup/ftp_upload_s3.php $ftp_local_file $ftp_remote_file 2>&1 5 | RET=$? 6 | else 7 | FTPPUT=/usr/bin/ncftpput 8 | CURL=/usr/local/bin/curl 9 | OS=`uname`; 10 | DU=/usr/bin/du 11 | BC=/usr/bin/bc 12 | EXPR=/usr/bin/expr 13 | TOUCH=/bin/touch 14 | PORT=${ftp_port} 15 | FTPS=0 16 | MD5=${ftp_md5} 17 | 18 | if [ "${ftp_secure}" = "ftps" ]; then 19 | FTPS=1 20 | fi 21 | 22 | ####################################################### 23 | # SETUP 24 | 25 | if [ ! -e $TOUCH ] && [ -e /usr/bin/touch ]; then 26 | TOUCH=/usr/bin/touch 27 | fi 28 | if [ ! -x ${EXPR} ] && [ -x /bin/expr ]; then 29 | EXPR=/bin/expr 30 | fi 31 | 32 | if [ ! -e "${ftp_local_file}" ]; then 33 | echo "Cannot find backup file ${ftp_local_file} to upload"; 34 | 35 | /bin/ls -la ${ftp_local_path} 36 | 37 | /bin/df -h 38 | 39 | exit 11; 40 | fi 41 | 42 | get_md5() { 43 | MF=$1 44 | 45 | if [ ${OS} = "FreeBSD" ]; then 46 | MD5SUM=/sbin/md5 47 | else 48 | MD5SUM=/usr/bin/md5sum 49 | fi 50 | if [ ! -x ${MD5SUM} ]; then 51 | return 52 | fi 53 | 54 | if [ ! -e ${MF} ]; then 55 | return 56 | fi 57 | 58 | if [ ${OS} = "FreeBSD" ]; then 59 | FMD5=`$MD5SUM -q $MF` 60 | else 61 | FMD5=`$MD5SUM $MF | cut -d\ -f1` 62 | fi 63 | 64 | echo "${FMD5}" 65 | } 66 | 67 | ####################################################### 68 | 69 | CFG=${ftp_local_file}.cfg 70 | /bin/rm -f $CFG 71 | $TOUCH $CFG 72 | /bin/chmod 600 $CFG 73 | 74 | RET=0; 75 | 76 | 77 | ####################################################### 78 | TIMEOUT=120 79 | 80 | #dynamic timeout for nctpput. 81 | #Curl kicks the control connection with keep-alive pings by default. 82 | SIZE_GIG=0 83 | SECONDS_PER_GIG=120 84 | if [ -x ${DU} ]; then 85 | if [ "${OS}" = "FreeBSD" ]; then 86 | SIZE_GIG=`BLOCKSIZE=G ${DU} -A ${ftp_local_file} | cut -f1` 87 | else 88 | SIZE_GIG=`${DU} --apparent-size --block-size=1G ${ftp_local_file} | cut -f1` 89 | fi 90 | 91 | if [ "${SIZE_GIG}" -gt 1 ]; then 92 | NEW_TIMEOUT=$TIMEOUT 93 | 94 | if [ -x ${BC} ]; then 95 | NEW_TIMEOUT=`echo "${SIZE_GIG} * ${SECONDS_PER_GIG}" | ${BC}` 96 | elif [ -x ${EXPR} ]; then 97 | NEW_TIMEOUT=`${EXPR} ${SIZE_GIG} \* ${SECONDS_PER_GIG}` 98 | else 99 | echo "Cannot find ${BC} nor ${EXPR} for ftp upload timeout change on large file: ${SIZE_GIG} Gig."; 100 | fi 101 | 102 | #make sure it's a useful number 103 | if [ "${NEW_TIMEOUT}" -gt "${TIMEOUT}" ]; then 104 | TIMEOUT=${NEW_TIMEOUT}; 105 | fi 106 | fi 107 | fi 108 | 109 | ####################################################### 110 | # FTP 111 | upload_file() 112 | { 113 | if [ ! -e $FTPPUT ]; then 114 | echo ""; 115 | echo "*** Backup not uploaded ***"; 116 | echo "Please install $FTPPUT by running:"; 117 | echo ""; 118 | echo "cd /usr/local/directadmin/scripts"; 119 | echo "./ncftp.sh"; 120 | echo ""; 121 | exit 10; 122 | fi 123 | 124 | /bin/echo "host $ftp_ip" >> $CFG 125 | /bin/echo "user $ftp_username" >> $CFG 126 | /bin/echo "pass $ftp_password" >> $CFG 127 | 128 | if [ ! -s ${CFG} ]; then 129 | echo "${CFG} is empty. ncftpput is not going to be happy about it."; 130 | ls -la ${CFG} 131 | ls -la ${ftp_local_file} 132 | df -h 133 | fi 134 | 135 | $FTPPUT -f $CFG -V -t ${TIMEOUT} -P $PORT -m "$ftp_path" "$ftp_local_file" 2>&1 136 | RET=$? 137 | 138 | if [ "${RET}" -ne 0 ]; then 139 | echo "ncftpput return code: $RET"; 140 | fi 141 | } 142 | 143 | ####################################################### 144 | # FTPS 145 | upload_file_ftps() 146 | { 147 | if [ ! -e ${CURL} ]; then 148 | CURL=/usr/bin/curl 149 | fi 150 | 151 | if [ ! -e ${CURL} ]; then 152 | echo ""; 153 | echo "*** Backup not uploaded ***"; 154 | echo "Please install curl by running:"; 155 | echo ""; 156 | echo "cd /usr/local/directadmin/custombuild"; 157 | echo "./build curl"; 158 | echo ""; 159 | exit 10; 160 | fi 161 | 162 | /bin/echo "user = \"$ftp_username:$ftp_password\"" >> $CFG 163 | 164 | if [ ! -s ${CFG} ]; then 165 | echo "${CFG} is empty. curl is not going to be happy about it."; 166 | ls -la ${CFG} 167 | ls -la ${ftp_local_file} 168 | df -h 169 | fi 170 | 171 | #ensure ftp_path ends with / 172 | ENDS_WITH_SLASH=`echo "$ftp_path" | grep -c '/$'` 173 | if [ "${ENDS_WITH_SLASH}" -eq 0 ]; then 174 | ftp_path=${ftp_path}/ 175 | fi 176 | 177 | ${CURL} --config ${CFG} --ftp-ssl -k --silent --show-error --ftp-create-dirs --upload-file $ftp_local_file ftp://$ftp_ip:${PORT}/$ftp_path$ftp_remote_file 2>&1 178 | RET=$? 179 | 180 | if [ "${RET}" -ne 0 ]; then 181 | echo "curl return code: $RET"; 182 | fi 183 | } 184 | 185 | ####################################################### 186 | # Start 187 | 188 | if [ "${FTPS}" = "1" ]; then 189 | upload_file_ftps 190 | else 191 | upload_file 192 | fi 193 | 194 | if [ "${RET}" = "0" ] && [ "${MD5}" = "1" ]; then 195 | MD5_FILE=${ftp_local_file}.md5 196 | M=`get_md5 ${ftp_local_file}` 197 | if [ "${M}" != "" ]; then 198 | echo "${M}" > ${MD5_FILE} 199 | 200 | ftp_local_file=${MD5_FILE} 201 | ftp_remote_file=${ftp_remote_file}.md5 202 | 203 | if [ "${FTPS}" = "1" ]; then 204 | upload_file_ftps 205 | else 206 | upload_file 207 | fi 208 | fi 209 | fi 210 | 211 | /bin/rm -f $CFG 212 | 213 | exit $RET 214 | fi --------------------------------------------------------------------------------