Skip to content

Instantly share code, notes, and snippets.

@obiPlabon
Created March 11, 2026 11:33
Show Gist options
  • Select an option

  • Save obiPlabon/47e008c050077c98016e8e2a8c12dcec to your computer and use it in GitHub Desktop.

Select an option

Save obiPlabon/47e008c050077c98016e8e2a8c12dcec to your computer and use it in GitHub Desktop.
Pull migration backup from Hosting A to Hosting B through HTTP pull request. WORKS really fast πŸš€
<?php
declare(strict_types=1);
/**
* Chunked, resumable HTTP downloader for large .wpress backups.
*
* Usage:
* 1. Upload this file to the destination hosting.
* 2. Edit $config below.
* 3. Open in browser:
* https://newsite.com/pull-ai1wm.php?token=CHANGE_ME
*
* Notes:
* - Downloads in chunks to avoid request timeout.
* - Resumes automatically from where it stopped.
* - Requires source server to allow direct HTTP/HTTPS download.
* - Works best if source supports Accept-Ranges, but can still work without it
* if the connection is stable enough for each chunk.
*/
@ini_set('max_execution_time', '120');
@set_time_limit(120);
@ini_set('memory_limit', '256M');
$config = [
'token' => 'CHANGE_ME_TO_A_LONG_RANDOM_SECRET',
'source_url' => 'https://oldsite.com/wp-content/ai1wm-backups/site-backup.wpress',
'dest_dir' => __DIR__ . '/wp-content/ai1wm-backups',
'dest_file' => 'site-backup.wpress',
'state_file' => __DIR__ . '/pull-ai1wm-state.json',
'chunk_size' => 10 * 1024 * 1024, // 10 MB per request
'connect_timeout' => 15,
'timeout' => 90,
'user_agent' => 'AI1WM-Chunk-Puller/1.0',
];
function fail(string $message, int $code = 400): void {
http_response_code($code);
echo '<pre style="font:14px/1.4 monospace;color:#b00020;">' . htmlspecialchars($message) . '</pre>';
exit;
}
function out(string $message): void {
echo htmlspecialchars($message) . "\n";
}
function read_state(string $file): array {
if (!file_exists($file)) {
return [
'offset' => 0,
'size' => null,
'done' => false,
'etag' => null,
'last_modified' => null,
];
}
$json = file_get_contents($file);
$data = json_decode((string)$json, true);
if (!is_array($data)) {
return [
'offset' => 0,
'size' => null,
'done' => false,
'etag' => null,
'last_modified' => null,
];
}
return array_merge([
'offset' => 0,
'size' => null,
'done' => false,
'etag' => null,
'last_modified' => null,
], $data);
}
function write_state(string $file, array $state): void {
file_put_contents($file, json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
function format_bytes(?int $bytes): string {
if ($bytes === null) {
return 'unknown';
}
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
$value = (float)$bytes;
while ($value >= 1024 && $i < count($units) - 1) {
$value /= 1024;
$i++;
}
return round($value, 2) . ' ' . $units[$i];
}
function get_remote_meta(array $config): array {
$headers = [];
$ch = curl_init($config['source_url']);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => $config['connect_timeout'],
CURLOPT_TIMEOUT => $config['timeout'],
CURLOPT_USERAGENT => $config['user_agent'],
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
curl_close($ch);
fail('HEAD request failed: ' . $error, 500);
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
foreach (explode("\n", (string)$response) as $line) {
$line = trim($line);
if ($line === '' || strpos($line, ':') === false) {
continue;
}
[$key, $value] = explode(':', $line, 2);
$headers[strtolower(trim($key))] = trim($value);
}
if ($status >= 400) {
fail('Source server returned HTTP ' . $status . ' on HEAD request.', 500);
}
return [
'size' => isset($headers['content-length']) ? (int)$headers['content-length'] : null,
'etag' => $headers['etag'] ?? null,
'last_modified' => $headers['last-modified'] ?? null,
'accept_ranges' => $headers['accept-ranges'] ?? null,
];
}
function download_chunk(array $config, int $start, int $end, string $destPath): array {
$fp = fopen($destPath, 'c+b');
if (!$fp) {
fail('Could not open destination file for writing: ' . $destPath, 500);
}
if (fseek($fp, $start) !== 0) {
fclose($fp);
fail('Could not seek to destination offset.', 500);
}
$responseHeaders = [];
$ch = curl_init($config['source_url']);
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_CONNECTTIMEOUT => $config['connect_timeout'],
CURLOPT_TIMEOUT => $config['timeout'],
CURLOPT_USERAGENT => $config['user_agent'],
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_RANGE => $start . '-' . $end,
CURLOPT_HEADERFUNCTION => function ($ch, $headerLine) use (&$responseHeaders) {
$len = strlen($headerLine);
$headerLine = trim($headerLine);
if ($headerLine !== '' && strpos($headerLine, ':') !== false) {
[$key, $value] = explode(':', $headerLine, 2);
$responseHeaders[strtolower(trim($key))] = trim($value);
}
return $len;
},
]);
$ok = curl_exec($ch);
$error = curl_error($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
fclose($fp);
if ($ok === false) {
fail('Chunk download failed: ' . $error, 500);
}
if (!in_array($status, [200, 206], true)) {
fail('Unexpected HTTP status during chunk download: ' . $status, 500);
}
clearstatcache(true, $destPath);
return [
'status' => $status,
'headers' => $responseHeaders,
'local_size' => file_exists($destPath) ? filesize($destPath) : 0,
];
}
$token = $_GET['token'] ?? '';
if (!hash_equals($config['token'], (string)$token)) {
fail('Unauthorized.', 403);
}
if (!is_dir($config['dest_dir']) && !mkdir($config['dest_dir'], 0755, true) && !is_dir($config['dest_dir'])) {
fail('Could not create destination directory: ' . $config['dest_dir'], 500);
}
$destPath = rtrim($config['dest_dir'], '/\\') . DIRECTORY_SEPARATOR . $config['dest_file'];
$state = read_state($config['state_file']);
if (isset($_GET['reset']) && $_GET['reset'] === '1') {
@unlink($destPath);
@unlink($config['state_file']);
$state = [
'offset' => 0,
'size' => null,
'done' => false,
'etag' => null,
'last_modified' => null,
];
}
$meta = get_remote_meta($config);
if ($state['etag'] && $meta['etag'] && $state['etag'] !== $meta['etag']) {
fail('Remote file ETag changed. Use ?reset=1 to restart safely.', 409);
}
if ($state['last_modified'] && $meta['last_modified'] && $state['last_modified'] !== $meta['last_modified']) {
fail('Remote file Last-Modified changed. Use ?reset=1 to restart safely.', 409);
}
$state['size'] = $meta['size'];
$state['etag'] = $meta['etag'];
$state['last_modified'] = $meta['last_modified'];
if (file_exists($destPath)) {
$actualLocalSize = filesize($destPath);
if ($actualLocalSize > (int)$state['offset']) {
$state['offset'] = $actualLocalSize;
}
}
if ($state['done'] === true) {
header('Content-Type: text/html; charset=utf-8');
echo '<pre>';
out('Download already completed.');
out('File: ' . $destPath);
out('Size: ' . format_bytes($state['size']));
out('');
out('Now go to WordPress admin β†’ All-in-One WP Migration β†’ Backups');
out('Then restore the backup.');
echo '</pre>';
exit;
}
$start = (int)$state['offset'];
if ($state['size'] !== null && $start >= (int)$state['size']) {
$state['done'] = true;
write_state($config['state_file'], $state);
header('Content-Type: text/html; charset=utf-8');
echo '<pre>';
out('Download completed.');
out('File: ' . $destPath);
out('Size: ' . format_bytes($state['size']));
echo '</pre>';
exit;
}
$end = $start + $config['chunk_size'] - 1;
if ($state['size'] !== null) {
$end = min($end, (int)$state['size'] - 1);
}
$result = download_chunk($config, $start, $end, $destPath);
$downloaded = ($end - $start) + 1;
$state['offset'] = $end + 1;
if ($state['size'] !== null && $state['offset'] >= (int)$state['size']) {
$state['offset'] = (int)$state['size'];
$state['done'] = true;
}
write_state($config['state_file'], $state);
$percent = $state['size']
? round(($state['offset'] / $state['size']) * 100, 2)
: null;
header('Content-Type: text/html; charset=utf-8');
echo '<!doctype html><html><head><meta charset="utf-8">';
if (!$state['done']) {
echo '<meta http-equiv="refresh" content="1">';
}
echo '<title>Chunked Pull</title></head><body><pre>';
out('Chunk downloaded successfully.');
out('Source: ' . $config['source_url']);
out('Destination: ' . $destPath);
out('Accept-Ranges: ' . ($meta['accept_ranges'] ?? 'unknown'));
out('');
out('Downloaded this request: ' . format_bytes($downloaded));
out('Current offset: ' . format_bytes($state['offset']));
out('Total size: ' . format_bytes($state['size']));
out('Progress: ' . ($percent !== null ? $percent . '%' : 'unknown'));
out('');
if ($state['done']) {
out('Completed.');
out('Now restore from All-in-One WP Migration β†’ Backups');
} else {
out('Continuing automatically...');
out('Do not close this page until it finishes.');
out('');
out('If it stops unexpectedly, just reopen this URL.');
}
echo '</pre></body></html>';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment