Nextcloud update extra missing files script remover php

Nextcloud Server version (from 31 and above):

Linux, Freebsd

That is not a real support request, but it seems some ppl need to remove some extra files from nextcloud folder after update. Somewhere here were another example, but this version is

  • fresh
  • vibecoded with some free AI
  • can remove all EXTRA files from occ integrity:check-core and occ integrity:check-app --all
  • can restore MISSING files from occ integrity:check-core and occ integrity:check-app --all (if you have a “backup” or “updated extra folder”
  • it was tested and works (i can confirm)
  • run with “php /your/way/to/a/script.php”
  • try to use it at your own risk as any script from web
  • regards
  • with hope to save some time for goods
#!/usr/bin/env php
<?php

declare(strict_types=1);

/* ================= CONFIG ================= */

$php_user = 'www';
$occ_path = '/usr/local/www/nextcloud/occ';

$core_path = '/usr/local/www/nextcloud';
$apps_path = $core_path . '/apps';
$backup    = '/mnt/share/nextcloud';

$dryRun = in_array('--dry-run', $argv, true);

$restored = [];
$deleted  = [];

/* ================= OCC ================= */

function run_occ_json(string $cmd): string {
    global $occ_path, $php_user;

    $command = "php $occ_path $cmd --output=json";

    $shell = sprintf(
        "su -m %s -c %s 2>&1",
        escapeshellarg($php_user),
        escapeshellarg($command)
    );

    return trim((string)shell_exec($shell));
}

/* ================= PARSER ================= */

function parse_app_json(string $output): array {
    $apps = [];
    $lines = explode("\n", $output);

    for ($i = 0; $i < count($lines); $i++) {
        if (preg_match('/^([a-zA-Z0-9_\-]+): \d+ errors found:/', $lines[$i], $m)) {
            $app = $m[1];
            $json = $lines[$i + 1] ?? '';
            $data = json_decode($json, true);

            if (is_array($data)) {
                $apps[$app] = $data;
            }
            $i++;
        }
    }
    return $apps;
}

/* ================= SAFE DELETE ================= */

function safe_delete_app(string $app, string $file): ?string {
    global $apps_path, $dryRun;

    $base = realpath("$apps_path/$app");
    $path = realpath("$apps_path/$app/$file");

    if (!$base || !$path) return null;
    if (!str_starts_with($path, $base)) return null;
    if (!is_file($path)) return null;

    if ($dryRun) return $path;

    return unlink($path) ? $path : null;
}

/* ================= SAFE RESTORE ================= */

function safe_restore_core(string $file): ?string {
    global $backup, $core_path, $dryRun;

    $src = preg_replace('#/+#', '/', "$backup/$file");
    $dst = preg_replace('#/+#', '/', "$core_path/$file");

    if (!file_exists($src)) return null;

    if (!is_dir(dirname($dst))) {
        mkdir(dirname($dst), 0755, true);
    }

    if ($dryRun) return $dst;

    return copy($src, $dst) ? $dst : null;
}

function safe_restore_app(string $app, string $file): ?string {
    global $backup, $apps_path, $dryRun;

    $src = preg_replace('#/+#', '/', "$backup/apps/$app/$file");
    $dst = preg_replace('#/+#', '/', "$apps_path/$app/$file");

    if (!file_exists($src)) return null;

    if (!is_dir(dirname($dst))) {
        mkdir(dirname($dst), 0755, true);
    }

    if ($dryRun) return $dst;

    return copy($src, $dst) ? $dst : null;
}

/* ================= CORE LOOP ================= */

function process_core(): void {
    global $deleted, $restored;

    echo "\n[CORE]\n";

    for ($i = 1; $i <= 10; $i++) {
        echo "\n--- Core iteration $i ---\n";

        $out = run_occ_json('integrity:check-core');
        if ($out === '') {
            echo "No output from OCC\n";
            return;
        }

        $data = json_decode($out, true);
        if (!$data || empty($data)) {
            echo "Core clean\n";
            return;
        }

        $changes = 0;

if (!empty($data['EXTRA_FILE'])) {
    $base = realpath($GLOBALS['core_path']);

    if ($base) {
        $base .= DIRECTORY_SEPARATOR;

        foreach (array_keys($data['EXTRA_FILE']) as $file) {

            $path = realpath($GLOBALS['core_path'] . '/' . $file);

            if ($path &&
                str_starts_with($path, $base) &&
                is_file($path)
            ) {
                if ($GLOBALS['dryRun'] || unlink($path)) {
                    echo "DELETE $path\n";
                    $deleted[] = $path;
                    $changes++;
                }
            }
        }
    }
}


        if (!empty($data['FILE_MISSING'])) {
            foreach (array_keys($data['FILE_MISSING']) as $file) {
                $r = safe_restore_core($file);
                if ($r) {
                    echo "RESTORE $r\n";
                    $restored[] = $r;
                    $changes++;
                }
            }
        }

        if ($changes === 0) {
            echo "No progress. Stopping.\n";
            return;
        }
    }

    echo "Max core iterations reached.\n";
}

/* ================= APPS LOOP ================= */

function process_apps(): void {
    global $deleted, $restored;

    echo "\n[APPS]\n";

    for ($i = 1; $i <= 20; $i++) {
        echo "\n--- Apps iteration $i ---\n";

        $out = run_occ_json('integrity:check-app --all');
        if ($out === '') {
            echo "No output from OCC\n";
            return;
        }

        $apps = parse_app_json($out);
        $changes = 0;

        foreach ($apps as $app => $data) {

            if (!empty($data['EXTRA_FILE'])) {
                foreach (array_keys($data['EXTRA_FILE']) as $file) {
                    $d = safe_delete_app($app, $file);
                    if ($d) {
                        echo "DELETE $d\n";
                        $deleted[] = $d;
                        $changes++;
                    }
                }
            }

            if (!empty($data['FILE_MISSING'])) {
                foreach (array_keys($data['FILE_MISSING']) as $file) {
                    $r = safe_restore_app($app, $file);
                    if ($r) {
                        echo "RESTORE $r\n";
                        $restored[] = $r;
                        $changes++;
                    }
                }
            }
        }

        if ($changes === 0) {
            echo "Apps clean or no progress. Stopping.\n";
            return;
        }
    }

    echo "Max app iterations reached.\n";
}

/* ================= RUN ================= */

echo "=== Nextcloud Integrity Auto Fix (FINAL) ===\n";
echo $dryRun ? "Mode: DRY RUN\n" : "Mode: LIVE\n";

process_core();
process_apps();

echo "\n=== REPORT ===\n";
echo "Deleted:  " . count($deleted) . "\n";
echo "Restored: " . count($restored) . "\n";

foreach ($deleted as $f)  echo " - DEL $f\n";
foreach ($restored as $f) echo " - RES $f\n";

exit(0);