#!/usr/bin/php -q
<?PHP
/* Copyright 2005-2022, Lime Technology
 * Copyright 2012-2022, Bergware International.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 */
?>
<?
$opt = getopt('a',['all']);
$all = isset($opt['a']) || isset($opt['all']);
$zip = $all ? $argv[2] : $argv[1];
$cli = empty($zip);
$get = "/var/local/emhttp";
$var = (array)@parse_ini_file("$get/var.ini");

$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
$folders = ['/boot','/boot/config','/boot/config/plugins','/boot/syslinux','/var/log','/var/log/plugins','/boot/extra','/var/log/packages','/var/lib/pkgtools/packages','/tmp'];

function write(...$messages){
  $com = curl_init();
  curl_setopt_array($com,[
    CURLOPT_URL => 'http://localhost/pub/diagnostics?buffer_length=1',
    CURLOPT_UNIX_SOCKET_PATH => '/var/run/nginx.socket',
    CURLOPT_POST => 1,
    CURLOPT_RETURNTRANSFER => true
  ]);
  foreach ($messages as $message) {
    curl_setopt($com, CURLOPT_POSTFIELDS, $message);
    curl_exec($com);
  }
  curl_close($com);
}
function run($cmd, &$save=null) {
  global $cli,$diag;
  // output command for display
  write($cmd);
  // execute command with timeout of 30s
  exec("timeout -s9 30 $cmd", $save);
  return implode("\n",$save);
}
function shareDisks($share) {
  return exec("shopt -s dotglob; getfattr --no-dereference --absolute-names --only-values -n system.LOCATIONS ".escapeshellarg("/usr/local/emhttp/mnt/user/$share")." 2>/dev/null") ?: "";
}
function anonymize($text,$select) {
  global $all, $customShares,$unraid_vars;
  if ($all) return $text;
  switch ($select) {
  case 1:
    // remove any stray references to the GUID that may wind up in .ini files (notably Unassigned Devices)
    $text = str_replace(end(explode("-",$unraid_vars['regGUID'])),"...",$text);

    $rows = explode("\n", $text);
    $regex = "/\b((disk|cache|parity|cpu|eth|dev)[0-9]+)|(smart|flash|flashbackup|cache|parity|cpu$customShares)\b/";
    foreach ($rows as &$row) {
      if (!preg_match($regex, $row)) {
        $row = preg_replace("/^(\s*\[\S).*(\S\])( => Array)$/","$1..$2$3",$row);
        $row = preg_replace("/^(\s*\[(cachePool|name|nameOrig|comment|flashGUID|regGUID|regTo|readList|writeList|csrf_token|NGINX_DEFAULTURL|NGINX_CERTNAME|NGINX_LANFQDN|NGINX_WANFQDN|NGINX_WANIP)\] => \S).*(\S)$/","$1..$3",$row);
      }
    }
    return implode("\n", $rows);
  case 2:
    $name = basename($text,'.cfg');
    if ($name !== "cache") {
      $len = strlen($name);
      if ($len>2) {
        $dash = str_repeat('-',$len-2);
        $name = preg_replace("/^(\S).*(\S)/","$1$dash$2",$name);
        $i = 1;
        while (file_exists(dirname($text)."/$name.cfg")) {$name = substr($name,0,$len)." ($i)"; $i++;}
      }
    }
    return dirname($text)."/$name.cfg";
  }
}
function prefix($key) {
  return preg_replace('/\d+$/','',$key);
}
function cache_only($disk) {
  return $disk['type']=='Cache';
}
function cache_filter($disks) {
  return array_filter($disks,'cache_only');
}
function pools_filter($disks) {
  return array_unique(array_map('prefix',array_keys(cache_filter($disks))));
}
function download_url($url, $path="", $bg=false, $timeout=15) {
  $ch = curl_init();
  curl_setopt_array($ch,[
    CURLOPT_URL => $url,
    CURLOPT_FRESH_CONNECT => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => $timeout,
    CURLOPT_ENCODING => "",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true
  ]);
  $out = curl_exec($ch);
  curl_close($ch);
  if ($path) file_put_contents($path,$out);
  return $out ?: false;
}
function geturls_certdetails($file, $hostname, $ip="") {
  // called by the geturls() function
  // best to ensure the file exists before calling this function
  if (!file_exists($file)) return ['', '', ''];
  // read the cert
  $data = null;
  exec("/usr/bin/openssl x509 -noout -subject -nameopt multiline -in ".escapeshellarg($file), $data);
  $data = implode("\n", $data);
  // determine cn
  preg_match('/ *commonName *= (.*)/', $data, $matches);
  $cn = trim($matches[1]);
  if (strpos($cn, ".myunraid.net") !== false) {
    $type = 'myunraid.net';
    $iphost = str_replace('.','-',$ip);
    // anonymize ip portion of hostname if not a private ip
    $priv_iphost = (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) ? "ipaddress" : $iphost;
    // anonymize myunraid.net hash
    $cn_priv = preg_replace('/\*\.([a-f0-9]*)\.myunraid\.net/', "{$priv_iphost}.hash.myunraid.net", $cn);
    // replace wildcard with ip
    $cn = str_replace('*', $iphost, $cn);
  } elseif (strpos($cn, ".unraid.net") !== false) {
    $type = 'unraid.net';
    // anonymize unraid.net hash
    $cn_priv = preg_replace('/.*\.unraid\.net/', 'hash.unraid.net', $cn);
  } else {
    // replace wildcard with hostname
    $cn = str_replace('*', $hostname, $cn);
    $cn_priv = $cn;
    if (strpos($data, "Self-signed") !== false){
      $type = 'self-signed';
    } else {
      $type = 'user-provided';
    }
  }
  return [$cn, $cn_priv, $type];
}
function geturls_checkhost($host, $hostpriv, $expectedip, $dnsserver) {
  // called by the geturls() function
  // dns lookups will fail if there is no TLD or if it is ".local", so skip it
  if (strpos($host, '.') === false || strpos($host, '.local') !== false) return '';
  $result = @dns_get_record($host, DNS_A);
  $ip = ($result) ? $result[0]['ip'] : '';
  if ($ip == '') return "  ERROR: When using DNS server {$dnsserver}, the host {$hostpriv} does not resolve.\n";
  if ($ip != $expectedip) {
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) $ip = "[redacted]";
    if (filter_var($expectedip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) $expectedip = "[redacted]";
    return "  ERROR: When using DNS server {$dnsserver}, {$hostpriv} resolves to {$ip}. It should resolve to {$expectedip}\n";
  }
  return '';
}
function geturls() {
  $var = parse_ini_file('/var/local/emhttp/var.ini');
  extract(parse_ini_file('/var/local/emhttp/network.ini',true));
  $nginx = parse_ini_file('/var/local/emhttp/nginx.ini');
  $internalip = $internalip_priv = $internalip_msg = $eth0['IPADDR:0'];
  $dnsserver = $eth0['DNS_SERVER1'];
  if (filter_var($internalip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
    $internalip_priv = "ipaddress";
    $internalip_msg = "redacted (FYI - this system has a routable IP address, ensure it is behind a firewall)";
  }
  $host_tld_msg = $var['LOCAL_TLD'] ? '': '[blank] (FYI - a blank TLD can cause issues for Mac and Linux clients)';
  $isLegacyCert = preg_match('/.*\.unraid\.net$/', $nginx['NGINX_CERTNAME']);
  $rebindtesturl = $isLegacyCert ? "rebindtest.unraid.net" : "rebindtest.myunraid.net";
  $rebindtestdomain = $isLegacyCert ? "unraid.net" : "myunraid.net";
  $rebindip = "192.168.42.42";
  $rebindtest_ip = exec("host -4 $rebindtesturl 2>/dev/null|sed -n 's/.*has address //p'");
  $rebind_msg = ($rebindtest_ip != $rebindip) ? "is enabled, $rebindtestdomain urls will not work" : "is disabled, $rebindtestdomain urls will work";
  // show raw data from config files
  $urls = '';
  $urls .= "Server Name: {$var['NAME']}\n";
  $urls .= "Local TLD:   {$var['LOCAL_TLD']}{$host_tld_msg}\n";
  $urls .= "HTTP port:   {$var['PORT']}\n";
  $urls .= "HTTPS port:  {$var['PORTSSL']}\n";
  $urls .= "Internal IP: {$internalip_msg}\n";
  $urls .= "DNS 1:       {$dnsserver}\n";
  $urls .= "USE SSL:     {$var['USE_SSL']}\n";
  $urls .= "DNS Rebinding Protection {$rebind_msg} on this network\n\n";
  $urls .= "Available URLs:\n  (the URL marked with an asterisk is the primary url for this server)\n";
  // calculate variables
  $cert_path = "/boot/config/ssl/certs/";
  $host_name = $var['NAME'];
  $host_tld = $var['LOCAL_TLD'] ? ".{$var['LOCAL_TLD']}" : '';
  $expected_host = "{$host_name}{$host_tld}";
  $http_port = $var['PORT'] != 80 ? ":{$var['PORT']}" : '';
  $https_port = $var['PORTSSL'] != 443 ? ":{$var['PORTSSL']}" : '';
  $https_1_cert = "{$var['NAME']}_unraid_bundle.pem";
  $https_2_cert = 'certificate_bundle.pem';
  $http_primary = $https_1_primary = $https_2_primary = $http_msg = $https_1_msg = $https_2_msg = '';
  switch($var['USE_SSL']) {
  case "no":
    $http_primary = '*';
    break;
  case "yes":
    $https_1_primary = '*';
    $http_msg = "\n  (this will redirect to the primary url)";
    break;
  case "auto":
    $http_msg = "\n  (this will redirect to the primary url)";
    $https_1_msg = "\n  (this will redirect to the primary url)";
    $https_2_primary = '*';
    break;
  }
  // calculate http ip url
  $http_ip_url = "http://{$internalip_priv}{$http_port}";
  $urls .= "HTTP IP url: {$http_ip_url}{$http_msg}\n";
  // calculate http url
  $http_url = "http://{$expected_host}{$http_port}";
  $urls .= "{$http_primary}HTTP url: {$http_url}{$http_msg}\n";
  $urls .= geturls_checkhost($expected_host, $expected_host, $internalip, $dnsserver);
  // calculate https url - self-signed or user-provided in tower_unraid_bundle.pem
  // this is available when USE_SSL != no, and the certificate file exists
  if ($var['USE_SSL'] != "no" && file_exists("{$cert_path}{$https_1_cert}")) {
    [$https_1_host, $https_1_hostpriv, $https_1_type] = geturls_certdetails("{$cert_path}{$https_1_cert}", $var['NAME']);
    $https_1_url = "https://{$https_1_hostpriv}{$https_port}";
    $urls .= "{$https_1_primary}HTTPS url 1 ($https_1_type): {$https_1_url}{$https_1_msg}\n";
    $urls .= geturls_checkhost($https_1_host, $https_1_hostpriv, $internalip, $dnsserver);
    if (strtolower($https_1_host) != strtolower($expected_host)) {
      $urls .= "  ERROR: the certificate Subject CN in {$https_1_cert} should be {$expected_host}\n";
    }
  } else {
    // add a note that this url is not configured
    $urls .= "HTTPS url 1 (undefined): https://{$expected_host}{$https_port}\n  (this url is not configured, it will not work)\n";
    $urls .= geturls_checkhost($expected_host, $expected_host, $internalip, $dnsserver);
  }
  // calculate https url for certificate_bundle.pem
  // this is available if the certificate file exists, regardless of the USE_SSL setting
  // this is usually a (my)unraid.net LE cert, but it can also be a user-provided cert
  if (file_exists("{$cert_path}{$https_2_cert}")) {
    [$https_2_host, $https_2_hostpriv, $https_2_type] = geturls_certdetails("{$cert_path}{$https_2_cert}", $var['NAME'], $internalip);
    $https_2_url = "https://{$https_2_hostpriv}{$https_port}";
    $urls .= "{$https_2_primary}HTTPS url 2 ({$https_2_type}): {$https_2_url}{$https_2_msg}\n";
    $urls .= geturls_checkhost($https_2_host, $https_2_hostpriv, $internalip, $dnsserver);
    if (strpos($https_2_host, ".unraid.net") === false && strpos($https_2_host, ".myunraid.net") === false && strtolower($https_2_host) != strtolower($expected_host)) {
      $urls .= "  ERROR: the certificate Subject CN in {$https_2_cert} should be {$expected_host}\n";
    }
  }
  if ($var['USE_SSL'] != "no") {
    $telnet_disabled = ($var['USE_TELNET'] == "no") ? " (disabled)" : "";
    $ssh_disabled = ($var['USE_SSH'] == "no") ? " (disabled)" : "";
    $urls .= "\nTip: if DNS goes down and you lose access to the webgui, use telnet{$telnet_disabled}, ";
    $urls .= "ssh{$ssh_disabled}, or a local keyboard/monitor to run:\n";
    $urls .= "  use_ssl no\n";
    $urls .= "to enable 'HTTP IP url' and make 'HTTP url' the primary url for the system. ";
    if ($var['USE_SSL'] == "auto") {
      $urls .= "Or:\n";
      $urls .= "  use_ssl yes\n";
      $urls .= "to make 'HTTPS url 1' the primary.";
    }
    $urls .= "\nOnce DNS has been restored, navigate to Settings -> Management Access and set 'Use SSL' back to '{$var['USE_SSL']}'\n";
  }
  // get a list of the certificate files on the flash drive
  $dirlisting[0] = "{$cert_path}";
  if (file_exists($cert_path)) {
    exec("ls -l ".escapeshellarg($cert_path), $dirlisting);
  } else {
    $dirlisting[1] = "Directory not found";
  }
  $urls .= "\n\n".implode("\n", $dirlisting)."\n";
  $urls = str_replace("\n", "\r\n", $urls);
  return $urls;
}

// diagnostics start
run("mkdir -p /boot/logs");

if ($cli) {
  // script is called from CLI
  echo "Starting diagnostics collection... ";
  $server = isset($var['NAME']) ? str_replace(' ','_',strtolower($var['NAME'])) : 'tower';
  $date = date('Ymd-Hi');
  $diag = "$server-diagnostics-$date";
  $zip = "/boot/logs/$diag.zip";
} else {
  // script is called from GUI
  $diag = basename($zip, '.zip');
  $split = explode('-', $diag);
  $date = "{$split[2]}-{$split[3]}";
}

// don't anonymize system share names
$vardomain    = (array)@parse_ini_file('/boot/config/domain.cfg');
$vardocker    = (array)@parse_ini_file('/boot/config/docker.cfg');
$showshares   = [];
$showshares[] = current(array_slice(explode('/',$vardomain['IMAGE_FILE']), 3, 1)).'.cfg';
$showshares[] = current(array_slice(explode('/',$vardomain['DOMAINDIR']), 3, 1)).'.cfg';
$showshares[] = current(array_slice(explode('/',$vardomain['MEDIADIR']), 3, 1)).'.cfg';
$showshares[] = current(array_slice(explode('/',$vardomain['DISKDIR']), 3, 1)).'.cfg';
$showshares[] = current(array_slice(explode('/',$vardocker['DOCKER_IMAGE_FILE']), 3, 1)).'.cfg';
$showshares[] = current(array_slice(explode('/',$vardocker['DOCKER_APP_CONFIG_PATH']), 3, 1)).'.cfg';
$showshares[] = current(array_slice(explode('/',$vardocker['DOCKER_HOME']), 3, 1)).'.cfg';
foreach ($showshares as $show) {
  $showme = str_replace(".cfg","",$show);
  if ($showme) $customShares .= "|$showme";
}

// create folder structure
run("mkdir -p ".escapeshellarg("/$diag/system")." ".escapeshellarg("/$diag/config")." ".escapeshellarg("/$diag/logs")." ".escapeshellarg("/$diag/shares")." ".escapeshellarg("/$diag/smart")." ".escapeshellarg("/$diag/qemu")." ".escapeshellarg("/$diag/xml"));

// get utilization of running processes
run("top -bn1 -o%CPU 2>/dev/null|todos >".escapeshellarg("/$diag/system/top.txt"));

// make Unraid version reference
$unraid = parse_ini_file('/etc/unraid-version');
$unraid_vars = parse_ini_file('/var/local/emhttp/var.ini');
file_put_contents("/$diag/unraid-".$unraid['version'].".txt",$unraid['version']."\r\n");

// add bz*.sha256 values
run("tail /boot/bz*.sha256 >> ".escapeshellarg("/$diag/unraid-".$unraid['version'].".txt"));

// copy ini variables
foreach (glob("$get/*.ini") as $file) {
  $ini = basename($file,".ini");
  // skip users file in anonymized mode
  if ($all || $ini != "users") file_put_contents("/$diag/system/vars.txt",preg_replace(["/\n/","/^Array/"],["\r\n",$ini],anonymize(print_r(parse_ini_file($file,true),true),1)),FILE_APPEND);
}

// Create loads.txt
$cpuload = run("uptime")."  Cores: ".run("nproc")."\r\n".(string)@file_get_contents("$get/cpuload.ini")."\r\n";
$diskload = (array)@file("$get/diskload.ini");
$disks = (array)@parse_ini_file("$get/disks.ini",true);
$loadTxt = [];
foreach ($diskload as $loadLine) {
  $load = explode('=',$loadLine);
  foreach ($disks as $disk) {
    if ($load[0]==$disk['device']) {
      $loadTxt[] = "{$disk['device']} ({$disk['name']})=".trim($load[1]);
      break;
    }
  }
}
file_put_contents("/$diag/system/loads.txt",$cpuload.implode("\r\n",$loadTxt));

// individual commands execution (suppress errors)
run("lscpu 2>/dev/null|todos >".escapeshellarg("/$diag/system/lscpu.txt"));
run("lsscsi -vgl 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsscsi.txt"));
run("lspci -knn 2>/dev/null|todos >".escapeshellarg("/$diag/system/lspci.txt"));
run("lsusb 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsusb.txt"));
run("free -mth 2>/dev/null|todos >".escapeshellarg("/$diag/system/memory.txt"));
run("ps -auxf --sort=-pcpu 2>/dev/null|todos >".escapeshellarg("/$diag/system/ps.txt"));
run("lsof -Pni 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsof.txt"));
run("lsmod|sort 2>/dev/null|todos >".escapeshellarg("/$diag/system/lsmod.txt"));
run("df -h 2>/dev/null|todos >".escapeshellarg("/$diag/system/df.txt"));
run("ifconfig -a -s 2>/dev/null|grep -Po '^(eth|bond)[0-9]+'", $ports);
run("dmidecode -qt2|awk -F: '/^\tManufacturer:/{m=\$2};/^\tProduct Name:/{p=\$2} END{print m\" -\"p}' 2>/dev/null|todos >".escapeshellarg("/$diag/system/motherboard.txt"));
run("dmidecode -qt0 2>/dev/null|todos >>".escapeshellarg("/$diag/system/motherboard.txt"));
run("cat /proc/meminfo 2>/dev/null|todos >".escapeshellarg("/$diag/system/meminfo.txt"));
run("dmidecode --type 17 2>/dev/null|todos >>".escapeshellarg("/$diag/system/meminfo.txt"));

// create ethernet information information (suppress errors)
foreach ($ports as $port) {
  run("ethtool ".escapeshellarg($port)." 2>/dev/null|todos >>".escapeshellarg("/$diag/system/ethtool.txt"));
  file_put_contents("/$diag/system/ethtool.txt", "\r\n", FILE_APPEND);
  run("ethtool -i ".escapeshellarg($port)." 2>/dev/null|todos >>".escapeshellarg("/$diag/system/ethtool.txt"));
  file_put_contents("/$diag/system/ethtool.txt", "--------------------------------\r\n", FILE_APPEND);
}
run("ifconfig -a 2>/dev/null|todos >".escapeshellarg("/$diag/system/ifconfig.txt"));

// create system information (suppress errors)
run("find /sys/kernel/iommu_groups/ -type l 2>/dev/null|sort -V|todos >".escapeshellarg("/$diag/system/iommu_groups.txt"));
run("todos </proc/cmdline >".escapeshellarg("/$diag/system/cmdline.txt"));

// create folder structure listing
$dest = "/$diag/system/folders.txt";
foreach ($folders as $folder) {
  if (is_dir($folder)) run("echo -ne ".escapeshellarg("\r\n$folder\r\n")." >>".escapeshellarg($dest).";ls -l ".escapeshellarg($folder)."|todos >>".escapeshellarg("$dest")); else run("echo -ne ".escapeshellarg("\r\n$folder\r\nfolder does not exist\r\n")." >>".escapeshellarg("$dest"));
}

// copy configuration files (suppress errors)
run("cp /boot/config/*.{cfg,conf,dat} ".escapeshellarg("/$diag/config")." 2>/dev/null");
run("cp /boot/config/go ".escapeshellarg("/$diag/config/go.txt")." 2>/dev/null");
if (!$all)
  run("sed -i -e '/password/c ***line removed***' -e '/user/c ***line removed***' -e '/pass/c ***line removed***' ".escapeshellarg("/$diag/config/go.txt"));

// anonymize configuration files
if (!$all)
  run("sed -ri 's/^((disk|flash)(Read|Write)List.*=\")[^\"]+/\\1.../' ".escapeshellarg("/$diag/config/*.cfg")." 2>/dev/null");

// copy share information (anonymize if applicable)
$files = glob("/boot/config/shares/*.cfg");
foreach ($files as $file) {
  $dest = "/$diag/shares/".basename($file);
  if (!in_array(basename($file),$showshares)) {
    $dest = anonymize($dest,2);
  }
  @copy($file, $dest);
  if (!$all) run("sed -ri 's/^(share(Comment|ReadList|WriteList)=\")[^\"]+/\\1.../' ".escapeshellarg($dest)." 2>/dev/null");
  $share = pathinfo(basename($file),PATHINFO_FILENAME);
  $shareExists = explode(",",shareDisks($share));
  unset($exists);
  foreach ($shareExists as $anon) {
    if (!$all) {
      if (!preg_match("/\b((disk)[0-9]+)|(cache)\b/", $anon)) {
        $len = strlen($anon);
        if ($len>2) {
          $dash = str_repeat('-',$len-2);
          $exists[] = substr($anon,0,1).$dash.substr($anon,-1);
        }
      }
    } else {
      $exists[] = $anon;
    }
  }
  $shareDisk .= $exists ? str_pad(pathinfo($dest,PATHINFO_FILENAME),34).str_pad(exec("cat ".escapeshellarg($file)." | grep shareUseCache"),22)."  Exists on ".implode(", ",$exists)."\r\n" : "";
  $exists = $exists ?: ["no drives"];
  file_put_contents($dest,"# Share exists on ".implode(", ",$exists)."\r\n",FILE_APPEND);
}
file_put_contents("/$diag/shares/shareDisks.txt",$shareDisk);

// create default user shares information
$shares = (array)@parse_ini_file("$get/shares.ini", true);
foreach ($shares as $share) {
  $name = $share['name'];
  if (!in_array("/boot/config/shares/$name.cfg",$files)) file_put_contents(anonymize("/$diag/shares/$name.cfg",2),"# This share has default settings.\r\n# Share exists on ".shareDisks($name)."\r\n");
}

// copy pools information (anonymize)
$files = glob("/boot/config/pools/*.cfg");
@mkdir("/$diag/config/pools");
foreach ($files as $file) {
  $dest = anonymize("/$diag/config/pools/".basename($file),2);
  @copy($file,$dest);
}

// copy modprobe information
$files = glob("/boot/config/modprobe.d/*.conf");
if ($files) {
  @mkdir("/$diag/config/modprobe.d");
  foreach ($files as $file) {
    $dest = "/$diag/config/modprobe.d/".basename($file);
    @copy($file,$dest);
  }
}

// copy docker information (if existing)
$max = 1*1024*1024; //=1MB
$docker = "/var/log/docker.log";
if (file_exists($docker)) {
  $log = "/$diag/logs/docker";
  run("todos <$docker >".escapeshellarg("$log.txt"));
  if (filesize($docker)>=$max) {
    run("tail -n 200 ".escapeshellarg("$log.txt")." >".escapeshellarg("$log.last200.txt"));
    run("truncate -s '<$max' ".escapeshellarg("$log.txt"));
  }
}

// create SMART reports (suppress errors)
$disks = (array)@parse_ini_file("$get/disks.ini", true);
include_once "$docroot/webGui/include/CustomMerge.php";
include_once "$docroot/webGui/include/Wrappers.php";
run("ls -l /dev/disk/by-id/[asun]* 2>/dev/null|sed '/-part/d;s|^.*/by-id/[^-]*-||;s|-> ../../||;s|:|-|'", $devices);
foreach ($devices as $device) {
  list($name,$port) = explode(' ',$device);
  $diskName = ''; $type = '';
  foreach ($disks as $find) {
    if ($find['device']==$port) {
      $diskName = $find['name'];
      $type = get_value($find,'smType','');
      get_ctlr_options($type, $find);
      $port = $find['smDevice'] ?? $port;
      break;
    }
  }
  $port = port_name($port);
  $status = $find['status'] == "DISK_OK" ? "" : " - {$find['status']}";
  run("smartctl -x $type ".escapeshellarg("/dev/$port")." 2>/dev/null|todos >".escapeshellarg("/$diag/smart/$name-$date $diskName ($port)$status.txt"));
}

// create pool btrfs information
foreach (pools_filter($disks) as $pool) {
  if (strpos($disks[$pool]['fsType'],'btrfs')!==false) {
    run("echo 'Pool: $pool'|todos >>".escapeshellarg("/$diag/system/btrfs-usage.txt"));
    run("/sbin/btrfs filesystem usage -T /mnt/$pool 2>/dev/null|todos >>".escapeshellarg("/$diag/system/btrfs-usage.txt"));
  }
}

// create installed plugin information
$pluginList = json_decode(download_url("https://raw.githubusercontent.com/Squidly271/AppFeed/master/pluginList.json"),true);
if (!$pluginList) $installedPlugins = "Could not download current plugin versions\r\n\r\n";
$plugins = glob("/var/log/plugins/*.plg");
foreach ($plugins as $plugin) {
  $plgVer = run("plugin version ".escapeshellarg($plugin));
  $plgURL = run("plugin pluginURL ".escapeshellarg($plugin));
  $installedPlugins .= basename($plugin)." - $plgVer";
  if ($pluginList && ! $pluginList[$plgURL] && basename($plugin) !== "unRAIDServer.plg")
    $installedPlugins .= "  (Unknown to Community Applications)";
  if ($pluginList[$plgURL]['blacklist'])
    $installedPlugins .= "  (Blacklisted)";
  if ($pluginList[$plgURL]['deprecated'] || ($pluginList[$plgURL]['dmax'] && version_compare($pluginList[$plgURL]['dmax'],$unraid['version'],"<")))
    $installedPlugins .= "  (Deprecated)";
  if ($pluginList[$plgURL]['version'] > $plgVer)
    $installedPlugins .= "  (Update available: {$pluginList[$plgURL]['version']})";
  elseif ($pluginList[$plgURL])
    $installedPlugins .= "  (Up to date)";
  if ($pluginList[$plgURL]['max'] && version_compare($pluginList[$plgURL]['max'],$unraid['version'],"<"))
    $installedPlugins .= "  (Incompatible)";
  if ($pluginList[$plgURL]['min'] && version_compare($pluginList[$plgURL]['min'],$unraid['version'],">"))
    $installedPlugins .= "  (Incompatible)";
  $installedPlugins .= "\r\n";
}
$installedPlugins = $installedPlugins ?: "No additional Plugins Installed";
file_put_contents("/$diag/system/plugins.txt",$installedPlugins);

// determine urls
file_put_contents("/$diag/system/urls.txt",geturls());

// copy libvirt information (if existing)
$libvirtd = "/var/log/libvirt/libvirtd.log";
if (file_exists($libvirtd)) {
  $log = "/$diag/logs/libvirt";
  run("todos <$libvirtd >".escapeshellarg("$log.txt"));
  if (filesize($libvirtd)>=$max) {
    run("tail -n 200 ".escapeshellarg("$log.txt")." >".escapeshellarg("$log.last200.txt"));
    run("truncate -s '<$max' ".escapeshellarg("$log.txt"));
  }
}

// copy VMs information (if existing)
$qemu = glob("/var/log/libvirt/qemu/*.log*");
if ($qemu) {
  foreach ($qemu as $file) {
    $log = "/$diag/qemu/".basename($file,'.log');
    run("todos <".escapeshellarg($file)." >".escapeshellarg("$log.txt"));
    if (filesize($file)>=$max) {
      run("tail -n 200 ".escapeshellarg("$log.txt")." >".escapeshellarg("$log.last200.txt"));
      run("truncate -s '<$max' ".escapeshellarg("$log.txt"));
    }
  }
} else {
  file_put_contents("/$diag/qemu/no qemu log files","");
}

// copy VM XML config files
run("cp /etc/libvirt/qemu/*.xml ".escapeshellarg("/$diag/xml")." 2>/dev/null");

// anonymize MAC OSK info
$all_xml = glob("/$diag/xml/*.xml");
foreach ($all_xml as $xml) {
  run("sed -ri 's/(,osk=).+/\\1.../' ".escapeshellarg("$xml")." 2>/dev/null");
  run("sed -ri 's/(passwd=).+/\\1.../' ".escapeshellarg("$xml")." 2>/dev/null");
}

// copy syslog information (anonymize if applicable)
$max = 2*1024*1024; //=2MB
foreach (glob("/var/log/syslog*") as $file) {
  $log = "/$diag/logs/".basename($file);
  run("todos <".escapeshellarg($file)." >".escapeshellarg("$log.txt"));
  if (!$all) {
    unset($titles,$rows);
    run("grep -Po 'file: \K[^\"]+' ".escapeshellarg("$log.txt")." 2>/dev/null|sort|uniq", $titles);
    run("sed -ri 's|\b\S+@\S+\.\S+\b|email@removed.com|;s|\b(username\|password)([=:])\S+\b|\\1\\2xxx|;s|(GUID: \S)\S+(\S) |\\1..\\2 |;s|(moving \"\S\|\"/mnt/user/\S).*(\S)\"|\\1..\\2\"|' ".escapeshellarg("$log.txt"));
    run("sed -ri 's|(server: ).+(\.(my)?unraid\.net(:[0-9]+)?,)|\\1hash\\2|;s|(host: \").+(\.(my)?unraid\.net(:[0-9]+)?\")|\\1hash\\2|;s|(referrer: \"https?://).+(\.(my)?unraid\.net)|\\1hash\\2|' ".escapeshellarg("$log.txt"));
    foreach ($titles as $mover) {
      $title = "/{$mover[0]}..".substr($mover,-1)."/...";
      run("sed -i 's/".str_replace("/","\/",$mover)."/".str_replace("/","\/",$title)."/g' ".escapeshellarg("$log.txt")." 2>/dev/null");
      //run("sed -ri 's|(file: [.>cr].*)[ /]$mover/.*$|\\1 file: $title|' ".escapeshellarg("$log.txt")." 2>/dev/null");
    }
    run("grep -n ' cache_dirs: -' ".escapeshellarg("$log.txt")." 2>/dev/null|cut -d: -f1", $rows);
    for ($i = 0; $i < count($rows); $i += 2) for ($row = $rows[$i]+1; $row < $rows[$i+1]; $row++) run("sed -ri '$row s|(cache_dirs: \S).*(\S)|\\1..\\2|' ".escapeshellarg("$log.txt")." 2>/dev/null");
  }
  // replace consecutive repeated lines in syslog
  run("awk -i inplace '{if(s!=substr(\$0,17)){if(x>0)print\"### [PREVIOUS LINE REPEATED \"x\" TIMES] ###\\r\";print;x=0}else{x++}s=substr(\$0,17)}END{if(x>0)print\"### [PREVIOUS LINE REPEATED \"x\" TIMES] ###\\r\"}' ".escapeshellarg("$log.txt"));
  // remove SHA256 hashes
  run("sed -ri 's/(SHA256:).+[^\s\b]/SHA256:***REMOVED***/gm' $log.txt");
  // truncate syslog if too big
  if (basename($file)=='syslog' && filesize($file)>=$max) run("tail -n 200 ".escapeshellarg("$log.txt")." >".escapeshellarg("$log.last200.txt"));
  run("truncate -s '<$max' ".escapeshellarg("$log.txt"));
}

// copy dhcplog
$dhcplog = "/var/log/dhcplog";
if (file_exists($dhcplog)) {
  $log = "/$diag/logs/dhcplog.txt";
  run("todos <$dhcplog >".escapeshellarg($log));
}

// copy graphql-api.log
$graphql = "/var/log/graphql-api.log";
if (file_exists($graphql)) {
  $log = "/$diag/logs/graphql-api.txt";
  run("todos <$graphql >".escapeshellarg($log));
}

// copy vfio-pci log
$vfiopci = "/var/log/vfio-pci";
if (file_exists($vfiopci)) {
  $log = "/$diag/logs/vfio-pci.txt";
  run("todos <$vfiopci >".escapeshellarg($log));
}

// copy wg-quick log
$wgquick = '/var/log/wg-quick.log';
if (file_exists($wgquick)) {
  $log = "/$diag/logs/wg-quick.txt";
  run("todos <$wgquick >".escapeshellarg($log));
}

// generate unraid-api.txt
if (file_exists("/usr/local/sbin/unraid-api")) {
  $log = "/$diag/system/unraid-api.txt";
  run("unraid-api report | todos >".escapeshellarg($log));
}

// generate thrid party system diagnostics
run("ls -lA /boot/config/plugins/*/packages/*/ 2>/dev/null|todos >>".escapeshellarg("/$diag/system/thirdparty_packages.txt"));
if (is_dir("/dev/dri/")) run("ls -lA /sys/class/drm/*/device/driver 2>/dev/null|todos >>".escapeshellarg("/$diag/system/drm.txt")); else null;
if (is_dir("/dev/dvb/")) run("ls -lA /sys/class/dvb/*/device/driver 2>/dev/null|todos >>".escapeshellarg("/$diag/system/dvb.txt")); else null;
if (is_file("/usr/bin/nvidia-smi")) run("/usr/bin/nvidia-smi --query 2>/dev/null|todos >>".escapeshellarg("/$diag/system/nvidia-smi.txt")); else null;

// create resulting zip file and remove temp folder
run("zip -qmr ".escapeshellarg($zip)." ".escapeshellarg("/$diag"));
if ($cli) {
  echo "done.\nZIP file '$zip' created.\n";
} else {
  copy($zip,"/boot/logs/".basename($zip));
}

// signal we are DONE
write('_DONE_','');
?>
