api/update.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/../includes/core.php';
$versionFile = __DIR__ . '/../includes/version.php';
$moduleUpdateFile = __DIR__ . '/../includes/modules/update.php';
if (is_file($versionFile)) {
require_once $versionFile;
}
require_once $moduleUpdateFile;
header('Content-Type: application/json; charset=utf-8');
brivaciaLog('update/ping.log', 'api/update.php reached: ' . ($_SERVER['REQUEST_METHOD'] ?? ''));
function update_blocked_dirs(): array {
return ['archives', 'backup', 'corrupt', 'data', 'logs', 'static', 'update'];
}
function update_blocked_files(): array {
return ['.htaccess', '.htpasswd'];
}
function update_progress(string $step): void {
brivaciaLog('update/info.log', 'Step: ' . $step);
file_put_contents(
updateDir() . '/progress.json',
json_encode([
'step' => $step,
'time' => time(),
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
LOCK_EX
);
}
function update_cleanup(string $dir, string $zipFile, string $extractDir): void {
brivaciaLog('update/info.log', 'Cleanup update files');
@unlink($dir . '/progress.json');
update_rrmdir($extractDir);
@unlink($zipFile);
}
function update_json(array $data, int $code = 200): never {
if ($code >= 400) {
brivaciaLog('update/failed.log', ($data['error'] ?? 'Unknown update error') . ' HTTP ' . $code);
}
http_response_code($code);
echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
if (($_GET['progress'] ?? '') === '1') {
$file = updateDir() . '/progress.json';
if (!is_file($file)) {
update_json(['ok' => true, 'running' => false]);
}
$data = json_decode((string)file_get_contents($file), true);
update_json([
'ok' => true,
'running' => true,
'step' => is_array($data) ? (string)($data['step'] ?? '') : '',
'time' => is_array($data) ? (int)($data['time'] ?? 0) : 0,
]);
}
function update_rrmdir(string $dir): void {
if (!is_dir($dir)) return;
foreach (scandir($dir) ?: [] as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dir . '/' . $item;
if (is_dir($path) && !is_link($path)) {
update_rrmdir($path);
} else {
@unlink($path);
}
}
@rmdir($dir);
}
function update_copy_dir(string $from, string $to): void {
foreach (scandir($from) ?: [] as $item) {
if ($item === '.' || $item === '..') continue;
if (in_array($item, update_blocked_dirs(), true)) continue;
if (in_array($item, update_blocked_files(), true)) continue;
$src = $from . '/' . $item;
$dst = $to . '/' . $item;
if (is_dir($src) && !is_link($src)) {
if (!is_dir($dst)) {
brivaciaLog('update/info.log', 'Create directory: ' . $dst);
mkdir($dst, 0755, true);
}
update_copy_dir($src, $dst);
continue;
}
if (!copy($src, $dst)) {
throw new RuntimeException('Unable to copy ' . $src);
}
}
}
function update_verify_package(string $source): void {
$required = [
'api',
'assets',
'includes',
'index.php',
];
foreach ($required as $path) {
$full = $source . '/' . $path;
if (!file_exists($full)) {
throw new RuntimeException('Invalid update package: missing ' . $path);
}
}
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
update_json(['ok' => false, 'error' => 'Method not allowed'], 405);
}
brivaciaLog('update/info.log', 'Starting update');
$info = brivaciaUpdateInfo();
if (empty($info['available']) && !brivaciaTestUpdate()) {
update_json(['ok' => false, 'error' => 'No update available'], 400);
}
$download = (string)($info['download'] ?? '');
if ($download === '' || !str_starts_with($download, 'https://')) {
update_json(['ok' => false, 'error' => 'Invalid download URL'], 400);
}
$dir = updateDir();
$zipFile = $dir . '/brivacia.zip';
$extractDir = $dir . '/extracted';
@unlink($dir . '/progress.json');
update_rrmdir($extractDir);
@unlink($zipFile);
update_progress('download');
brivaciaLog('update/info.log', 'Download URL: ' . $download);
$zipData = @file_get_contents($download);
if (!is_string($zipData) || $zipData === '') {
update_json(['ok' => false, 'error' => 'Unable to download update'], 500);
}
brivaciaLog('update/info.log', 'Downloaded bytes: ' . strlen($zipData));
if (file_put_contents($zipFile, $zipData, LOCK_EX) === false) {
update_json(['ok' => false, 'error' => 'Unable to save update zip'], 500);
}
update_progress('extract');
$zip = new ZipArchive();
if ($zip->open($zipFile) !== true) {
update_cleanup($dir, $zipFile, $extractDir);
update_json(['ok' => false, 'error' => 'Unable to open update zip'], 500);
}
mkdir($extractDir, 0755, true);
if (!$zip->extractTo($extractDir)) {
$zip->close();
update_cleanup($dir, $zipFile, $extractDir);
update_json(['ok' => false, 'error' => 'Unable to extract update zip'], 500);
}
$zip->close();
$source = $extractDir;
if (is_dir($extractDir . '/brivacia')) {
$source = $extractDir . '/brivacia';
}
brivaciaLog('update/info.log', 'Extracted source: ' . $source);
update_progress('verify');
try {
update_verify_package($source);
} catch (Throwable $e) {
update_cleanup($dir, $zipFile, $extractDir);
update_json([
'ok' => false,
'error' => $e->getMessage(),
], 500);
}
update_progress('install');
try {
update_copy_dir($source, brivacia_root_path());
} catch (Throwable $e) {
update_cleanup($dir, $zipFile, $extractDir);
update_json([
'ok' => false,
'error' => $e->getMessage(),
], 500);
}
update_progress('cleanup');
update_rrmdir($extractDir);
@unlink($zipFile);
@unlink($dir . '/progress.json');
brivaciaLog('update/info.log', 'Update done: ' . (string)($info['latest'] ?? ''));
update_json([
'ok' => true,
'version' => (string)($info['latest'] ?? ''),
]);