Skip to content

Repository files navigation

Verseles\Flyclone

PHP 8.4+ wrapper for rclone, focused on safe, testable file operations across local disks, S3-compatible storage, SFTP, FTP, Google Drive, Dropbox, Mega, B2, and the rest of rclone's backends.

PHPUnitPHPLicense

Why Flyclone

  • Object-oriented providers around rclone remotes.
  • Single-remote and two-remote operations through the same Rclone class.
  • Transfer statistics and optional progress callbacks.
  • Typed exceptions for rclone exit codes.
  • Retry support with exponential backoff.
  • Fluent include/exclude filters.
  • Debug logging with secret redaction.
  • v5 safety hardening for long-lived workers: instance-scoped options, provider collision detection, safe temporary remotes, private temp directories, and inline SFTP key support.

Requirements

  • PHP >= 8.4
  • ext-json
  • rclone binary available in PATH, or configured with Rclone::setBIN()

Installation

composer require verseles/flyclone:^5.0

Quick Start

useVerseles\Flyclone\Providers\LocalProvider;
useVerseles\Flyclone\Providers\S3Provider;
useVerseles\Flyclone\Rclone;
$local = newLocalProvider('local_disk');
$s3 = newS3Provider('archive_bucket', [
'access_key_id' => 'YOUR_KEY',
'secret_access_key' => 'YOUR_SECRET',
'region' => 'us-east-1',
]);
$rclone = newRclone($local, $s3);
$result = $rclone->copy('/var/backups', 'my-bucket/backups');
if ($result->success) {
echo"Transferred {$result->stats->bytes} bytes at {$result->stats->speed_human}";
}

For one-remote operations, pass only one provider:

$files = (newRclone($local))->ls('/var/backups');

Providers

BackendClassNotes
Local filesystemLocalProviderLocal paths and temporary helper remotes
Amazon S3 / MinIO / R2S3ProviderS3-compatible flags map to rclone env config
SFTPSFtpProviderSupports password, key_file, and v5 inline key_pem/private_key
FTPFtpProviderFTP/FTPS
Google DriveGDriveProviderOAuth/config-driven rclone backend
DropboxDropboxProviderOAuth/config-driven rclone backend
Mega.nzMegaProviderMega backend
Backblaze B2B2ProviderB2 backend
Encrypted remoteCryptProviderWraps another provider
Union filesystemUnionProviderMerges upstream providers
Any other rclone backendProviderGeneric provider class

Provider names are normalized to rclone-safe uppercase remote names. In v5, names that normalize to an empty value are rejected, and conflicting provider environment variables fail fast instead of being silently overwritten.

Core Operations

$rclone = newRclone($sourceProvider, $destProvider);
$rclone->copy('/source/dir', '/dest/dir');
$rclone->copyto('/source/file.txt', '/dest/file.txt');
$rclone->move('/source/dir', '/dest/dir');
$rclone->moveto('/source/file.txt', '/dest/file.txt');
$rclone->sync('/source/dir', '/dest/dir');
$rclone->delete('/path/to/delete');

Upload and download helpers create unique temporary local remotes internally:

$rclone = newRclone($s3);
$rclone->upload_file('/tmp/report.pdf', 'bucket/reports/report.pdf');
$download = $rclone->download_to_local('bucket/reports/report.pdf');
echo$download->local_path;

SFTP Private Keys

Prefer inline PEM keys in v5. Flyclone passes the value to rclone as RCLONE_CONFIG_<REMOTE>_KEY_PEM; no key file is created by Flyclone.

useVerseles\Flyclone\Providers\SFtpProvider;
$sftp = newSFtpProvider('deploy', [
'host' => 'sftp.example.com',
'user' => 'deploy',
'private_key' => $privateKeyPem,
'key_use_agent' => false,
]);

private_key is a convenience alias for key_pem. key_pem and key_file are mutually exclusive.

Encryption

CryptProvider wraps another provider through the required remote flag.

useVerseles\Flyclone\Providers\CryptProvider;
useVerseles\Flyclone\Providers\S3Provider;
useVerseles\Flyclone\Rclone;
$s3 = newS3Provider('raw_archive', [
'access_key_id' => 'YOUR_KEY',
'secret_access_key' => 'YOUR_SECRET',
'region' => 'us-east-1',
]);
$encrypted = newCryptProvider('encrypted_archive', [
'remote' => $s3,
'remote_path' => 'encrypted-prefix',
'password' => Rclone::obscure('my-secret-password'),
'password2' => Rclone::obscure('my-salt'),
]);
(newRclone($encrypted))->copy('/local/sensitive-data', '/');

Union Filesystems

UnionProvider receives upstream providers through upstream_providers.

useVerseles\Flyclone\Providers\LocalProvider;
useVerseles\Flyclone\Providers\S3Provider;
useVerseles\Flyclone\Providers\UnionProvider;
useVerseles\Flyclone\Rclone;
$cache = newLocalProvider('cache', ['root' => '/tmp/cache']);
$archive = newS3Provider('archive', [/* config */]);
$union = newUnionProvider('combined', [
'upstream_providers' => [$cache, $archive],
'action_policy' => 'all',
'create_policy' => 'ff',
]);
$files = (newRclone($union))->ls('/');

Configuration

Static configuration is still available and is captured by new Rclone instances at construction time:

Rclone::setBIN('/custom/path/to/rclone');
Rclone::setFlags(['checksum' => true]);
Rclone::setEnvs(['RCLONE_BUFFER_SIZE' => '64M']);
Rclone::setTimeout(300);
Rclone::setIdleTimeout(120);

For long-lived workers, prefer instance-scoped options:

$rclone = (newRclone($source, $dest))
->withFlags(['checksum' => true])
->withEnvs(['RCLONE_BUFFER_SIZE' => '64M'])
->withTimeout(300)
->withIdleTimeout(120);

Filtering

useVerseles\Flyclone\FilterBuilder;
$filter = FilterBuilder::create()
->extensions(['jpg', 'png', 'gif'])
->minSize('100K')
->maxSize('50M')
->exclude('**/thumbnails/**');
$rclone->withFilter($filter)->copy('/source', '/dest');

Progress, Retry, And Dry Run

useVerseles\Flyclone\RetryHandler;
$handler = RetryHandler::create()
->maxAttempts(5)
->baseDelay(500)
->multiplier(2.0)
->maxDelay(30000)
->onRetry(fn (int$attempt, Throwable$e) => logger("Retry {$attempt}: {$e->getMessage()}"));
$progress = function (object$progress): void {
echo$progress->percentage . "%\n";
};
$rclone->withRetry($handler)
->dryRun(false)
->copy('/source', '/dest', onProgress: $progress);

Error Handling

useVerseles\Flyclone\Exception\DirectoryNotFoundException;
useVerseles\Flyclone\Exception\FileNotFoundException;
useVerseles\Flyclone\Exception\TemporaryErrorException;
try {
$rclone->copy('/source', '/dest');
} catch (FileNotFoundException|DirectoryNotFoundException$e) {
// Permanent user/input error.
} catch (TemporaryErrorException$e) {
if ($e->isRetryable()) {
// Retry may succeed.
}
$context = $e->getContext();
}

Debugging

useVerseles\Flyclone\Logger;
Logger::setDebugMode(true);
$rclone->copy('/source', '/dest');
echo$rclone->getLastCommand();
$envs = $rclone->getLastEnvs(); // Secrets are redacted.$logs = Logger::getLogs();

Utilities

$remotes = Rclone::listRemotes();
$config = Rclone::configDump();
$md5 = $rclone->md5sum('/path');
$sha1 = $rclone->sha1sum('/path');
$health = $rclone->healthCheck('/');

v5 Breaking Changes

  • Provider names that normalize to an empty rclone remote name now throw InvalidArgumentException.
  • Conflicting provider env vars now throw LogicException instead of overwriting values.
  • Two providers with the same normalized remote name and different config now fail fast.
  • Existing Rclone instances no longer observe later changes to static flags/envs/timeouts; use withFlags(), withEnvs(), withTimeout(), and withIdleTimeout() for per-instance updates.
  • SFtpProvider rejects ambiguous key_pem plus key_file configuration.

See MIGRATION.md for migration notes.

Testing

composer install
# Fast local/offline checks
composer test-local
make test# Full offline provider suite, when podman-compose is available
make test-offline
# Static analysis and formatting
composer analyse
composer run-script format-check

Provider-specific tests such as make test_dropbox and make test_gdrive require a configured .env.

Architecture

Flyclone v5 is organized around small components:

ComponentResponsibility
RclonePublic API and operation orchestration
Provider and subclassesrclone remote configuration
CommandBuilderCommand arguments and environment variables
ProcessManagerSymfony Process execution and binary detection
StatsParserTransfer statistics parsing
ProgressParserReal-time progress parsing
RetryHandlerRetry policy and backoff
FilterBuilderInclude/exclude filter construction
TemporaryPathPrivate temp directories and unique temporary remote names
SecretsRedactorSecret redaction for errors/logs/envs
LoggerStructured debug logging

Changelog

v5.0.0

  • Hardened provider configuration for long-lived workers.
  • Added instance-scoped flags, envs, and timeouts.
  • Added provider env collision detection.
  • Added remote-name collision detection.
  • Added safe temporary local provider names.
  • Added private temporary download directories.
  • Added SFTP inline private key support via key_pem/private_key.
  • Added configuration/security tests to the fast test path.

v4.x

  • Modularized Rclone internals into process, command, stats, progress, retry, filter, logger, and redaction components.
  • Added typed exception context, health checks, dry-run mode, command introspection, and static utilities.
  • Added CryptProvider and UnionProvider support.

v3.x

  • Added detailed transfer statistics and progress tracking improvements.

License

Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International

About

PHP wrapper for rclone. Supports Local disk, Dropbox, FTP, SFTP, Google Drive, MEGA, S3 (any compatible) and others. Progress support.

Topics

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages