Passed
Push — develop ( 48ed7a...f087ba )
by Nikolay
05:31
created

Storage::createAssetsSymlinks()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 10
rs 10
cc 1
nc 1
nop 0
1
<?php
2
/**
3
 * Copyright © MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Alexey Portnov, 8 2020
7
 */
8
9
namespace MikoPBX\Core\System;
10
11
use MikoPBX\Common\Models\PbxExtensionModules;
12
use MikoPBX\Core\System\Configs\PHPConf;
13
use MikoPBX\Core\Workers\WorkerRemoveOldRecords;
14
use MikoPBX\Common\Models\Storage as StorageModel;
15
use MikoPBX\Common\Providers\ConfigProvider;
16
use MikoPBX\Modules\PbxExtensionUtils;
17
use Phalcon\Di;
18
19
use function MikoPBX\Common\Config\appPath;
20
21
22
/**
23
 * Вспомогательные методы.
24
 */
25
class Storage extends Di\Injectable
26
{
27
    /**
28
     * @var \Phalcon\Config
29
     */
30
    private $config;
31
32
    /**
33
     * System constructor.
34
     */
35
    public function __construct()
36
    {
37
        $this->config = $this->di->getShared('config');
38
    }
39
40
    /**
41
     * Возвращает директорию для хранения файлов записей разговоров.
42
     *
43
     * @return string
44
     */
45
    public static function getMonitorDir(): string
46
    {
47
        $di = Di::getDefault();
48
        if ($di !== null) {
49
            return $di->getConfig()->path('asterisk.monitordir');
50
        }
51
52
        return '/tmp';
53
    }
54
55
    /**
56
     * Возвращает директорию для хранения media файлов.
57
     *
58
     * @return string
59
     */
60
    public static function getMediaDir(): string
61
    {
62
        $di = Di::getDefault();
63
        if ($di !== null) {
64
            return $di->getConfig()->path('core.mediaMountPoint');
65
        }
66
67
        return '/tmp';
68
    }
69
70
71
    /**
72
     * Прверяем является ли диск хранилищем.
73
     *
74
     * @param $device
75
     *
76
     * @return bool
77
     */
78
    public static function isStorageDisk($device): bool
79
    {
80
        $result = false;
81
        if ( ! file_exists("{$device}")) {
82
            return $result;
83
        }
84
85
        $tmp_dir = '/tmp/mnt_' . time();
86
        Util::mwMkdir($tmp_dir);
87
        $out = [];
88
89
        $storage  = new Storage();
90
        $uid_part = 'UUID=' . $storage->getUuid($device) . '';
91
        $format   = $storage->getFsType($device);
92
        if ($format === '') {
93
            return false;
94
        }
95
        $mountPath  = Util::which('mount');
96
        $umountPath = Util::which('umount');
97
        $rmPath     = Util::which('rm');
98
99
        Util::mwExec("{$mountPath} -t {$format} {$uid_part} {$tmp_dir}", $out);
100
        if (is_dir("{$tmp_dir}/mikopbx") && trim(implode('', $out)) === '') {
101
            // $out - пустая строка, ошибок нет
102
            // присутствует каталог mikopbx.
103
            $result = true;
104
        }
105
        if (self::isStorageDiskMounted($device)) {
106
            Util::mwExec("{$umountPath} {$device}");
107
        }
108
109
        if ( ! self::isStorageDiskMounted($device)) {
110
            Util::mwExec("{$rmPath} -rf '{$tmp_dir}'");
111
        }
112
113
        return $result;
114
    }
115
116
    /**
117
     * Получение идентификатора устройства.
118
     *
119
     * @param $device
120
     *
121
     * @return string
122
     */
123
    public function getUuid($device): string
124
    {
125
        if (empty($device)) {
126
            return '';
127
        }
128
        $blkidPath   = Util::which('blkid');
129
        $busyboxPath = Util::which('busybox');
130
        $sedPath     = Util::which('sed');
131
        $grepPath    = Util::which('grep');
132
        $awkPath     = Util::which('awk');
133
        $headPath    = Util::which('head');
134
135
        $res = Util::mwExec(
136
            "{$blkidPath} -ofull {$device} | {$busyboxPath} {$sedPath} -r 's/[[:alnum:]]+=/\\n&/g' | {$busyboxPath} {$grepPath} \"^UUID\" | {$busyboxPath} {$awkPath} -F \"\\\"\" '{print $2}' | {$headPath} -n 1",
137
            $output
138
        );
139
        if ($res === 0 && count($output) > 0) {
140
            $result = $output[0];
141
        } else {
142
            $result = '';
143
        }
144
145
        return $result;
146
    }
147
148
    /**
149
     * Возвращает тип файловой системы блочного устройства.
150
     *
151
     * @param $device
152
     *
153
     * @return string
154
     */
155
    public function getFsType($device): string
156
    {
157
        $blkidPath   = Util::which('blkid');
158
        $busyboxPath = Util::which('busybox');
159
        $sedPath     = Util::which('sed');
160
        $grepPath    = Util::which('grep');
161
        $awkPath     = Util::which('awk');
162
163
        $device = str_replace('/dev/', '', $device);
164
        $out    = [];
165
        Util::mwExec(
166
            "{$blkidPath} -ofull /dev/{$device} | {$busyboxPath} {$sedPath} -r 's/[[:alnum:]]+=/\\n&/g' | {$busyboxPath} {$grepPath} \"^TYPE=\" | {$busyboxPath} {$awkPath} -F \"\\\"\" '{print $2}'",
167
            $out
168
        );
169
        $format = implode('', $out);
170
        if ($format == 'msdosvfat') {
171
            $format = 'msdos';
172
        }
173
174
        return $format;
175
    }
176
177
    /**
178
     * Проверка, смонтирован ли диск - хранилище.
179
     *
180
     * @param string $filter
181
     * @param string $mount_dir
182
     *
183
     * @return bool
184
     */
185
    public static function isStorageDiskMounted($filter = '', &$mount_dir = ''): bool
186
    {
187
        if (Util::isSystemctl() && file_exists('/storage/usbdisk1/')) {
188
            $mount_dir = '/storage/usbdisk1/';
189
190
            return true;
191
        }
192
        if ('' === $filter) {
193
            $di = Di::getDefault();
194
            if ($di !== null) {
195
                $varEtcDir = $di->getConfig()->path('core.varEtcDir');
196
            } else {
197
                $varEtcDir = '/var/etc';
198
            }
199
200
            $filename = "{$varEtcDir}/storage_device";
201
            if (file_exists($filename)) {
202
                $filter = file_get_contents($filename);
203
            } else {
204
                $filter = 'usbdisk1';
205
            }
206
        }
207
        $filter = escapeshellarg($filter);
208
209
        $out       = [];
210
        $grepPath  = Util::which('grep');
211
        $mountPath = Util::which('mount');
212
        $awkPath   = Util::which('awk');
213
        Util::mwExec("{$mountPath} | {$grepPath} {$filter} | {$awkPath} '{print $3}'", $out);
214
        $mount_dir = trim(implode('', $out));
215
216
        return ($mount_dir !== '');
217
    }
218
219
    /**
220
     * Монитирование каталога с удаленного сервера SFTP.
221
     *
222
     * @param        $host
223
     * @param int    $port
224
     * @param string $user
225
     * @param string $pass
226
     * @param string $remout_dir
227
     * @param string $local_dir
228
     *
229
     * @return bool
230
     */
231
    public static function mountSftpDisk($host, $port, $user, $pass, $remout_dir, $local_dir): bool
232
    {
233
        Util::mwMkdir($local_dir);
234
235
        $out         = [];
236
        $timeoutPath = Util::which('timeout');
237
        $sshfsPath   = Util::which('sshfs');
238
239
        $command = "{$timeoutPath} -t 3 {$sshfsPath} -p {$port} -o nonempty -o password_stdin -o 'StrictHostKeyChecking=no' " .
240
            "{$user}@{$host}:{$remout_dir} {$local_dir} << EOF\n" .
241
            "{$pass}\n" .
242
            "EOF\n";
243
        // file_put_contents('/tmp/sshfs_'.$host, $command);
244
        Util::mwExec($command, $out);
245
        $response = trim(implode('', $out));
246
        if ('Terminated' == $response) {
247
            // Удаленный сервер не ответил / или не корректно указан пароль.
248
            unset($response);
249
        }
250
251
        return self::isStorageDiskMounted("$local_dir ");
252
    }
253
254
    /**
255
     * Монитирование каталога с удаленного сервера FTP.
256
     *
257
     * @param        $host
258
     * @param        $port
259
     * @param        $user
260
     * @param        $pass
261
     * @param string $remout_dir
262
     * @param        $local_dir
263
     *
264
     * @return bool
265
     */
266
    public static function mountFtp($host, $port, $user, $pass, $remout_dir, $local_dir): bool
267
    {
268
        Util::mwMkdir($local_dir);
269
        $out = [];
270
271
        // Собираем строку подключения к ftp.
272
        $auth_line = '';
273
        if ( ! empty($user)) {
274
            $auth_line .= 'user="' . $user;
275
            if ( ! empty($pass)) {
276
                $auth_line .= ":{$pass}";
277
            }
278
            $auth_line .= '",';
279
        }
280
281
        $connect_line = 'ftp://' . $host;
282
        if ( ! empty($port)) {
283
            $connect_line .= ":{$port}";
284
        }
285
        if ( ! empty($remout_dir)) {
286
            $connect_line .= "$remout_dir";
287
        }
288
289
        $timeoutPath   = Util::which('timeout');
290
        $curlftpfsPath = Util::which('curlftpfs');
291
        $command       = "{$timeoutPath} -t 3 {$curlftpfsPath}  -o allow_other -o {$auth_line}fsname={$host} {$connect_line} {$local_dir}";
292
        Util::mwExec($command, $out);
293
        $response = trim(implode('', $out));
294
        if ('Terminated' === $response) {
295
            // Удаленный сервер не ответил / или не корректно указан пароль.
296
            unset($response);
297
        }
298
299
        return self::isStorageDiskMounted("$local_dir ");
300
    }
301
302
    /**
303
     * Запускает процесс форматирования диска.
304
     *
305
     * @param $dev
306
     *
307
     * @return array|bool
308
     */
309
    public static function mkfs_disk($dev)
310
    {
311
        if ( ! file_exists($dev)) {
312
            $dev = "/dev/{$dev}";
313
        }
314
        if ( ! file_exists($dev)) {
315
            return false;
316
        }
317
        $dir = '';
318
        self::isStorageDiskMounted("$dev", $dir);
319
320
        if (empty($dir) || self::umountDisk($dir)) {
321
            // Диск размонтирован.
322
            $st = new Storage();
323
            // Будет запущен процесс:
324
            $st->formatDiskLocal($dev, true);
325
            sleep(1);
326
327
            return (self::statusMkfs($dev) == 'inprogress');
328
        } else {
329
            // Ошибка размонтирования диска.
330
            return false;
331
        }
332
    }
333
334
    /**
335
     * Размонтирует диск. Удаляет каталог в случае успеха.
336
     *
337
     * @param $dir
338
     *
339
     * @return bool
340
     */
341
    public static function umountDisk($dir): bool
342
    {
343
        $umountPath = Util::which('umount');
344
        $rmPath     = Util::which('rm');
345
        if (self::isStorageDiskMounted($dir)) {
346
            Util::mwExec("/sbin/shell_functions.sh 'killprocesses' '$dir' -TERM 0");
347
            Util::mwExec("{$umountPath} {$dir}");
348
        }
349
        $result = ! self::isStorageDiskMounted($dir);
350
        if ($result && file_exists($dir)) {
351
            // Если диск не смонтирован, то удаляем каталог.
352
            Util::mwExec("{$rmPath} -rf '{$dir}'");
353
        }
354
355
        return $result;
356
    }
357
358
    /**
359
     * Разметка диска.
360
     *
361
     * @param string $device
362
     * @param bool   $bg
363
     *
364
     * @return mixed
365
     */
366
    public function formatDiskLocal($device, $bg = false)
367
    {
368
        $partedPath = Util::which('parted');
369
        $retVal     = Util::mwExec(
370
            "{$partedPath} --script --align optimal '{$device}' 'mklabel msdos mkpart primary ext4 0% 100%'"
371
        );
372
        Util::sysLogMsg(__CLASS__, "{$partedPath} returned {$retVal}");
373
        if (false === $bg) {
374
            sleep(1);
375
        }
376
377
        return $this->formatDiskLocalPart2($device, $bg);
378
    }
379
380
    /**
381
     * Форматирование диска.
382
     *
383
     * @param string $device
384
     * @param bool   $bg
385
     *
386
     * @return mixed
387
     */
388
    private function formatDiskLocalPart2($device, $bg = false)
389
    {
390
        if (is_numeric(substr($device, -1))) {
391
            $device_id = "";
392
        } else {
393
            $device_id = "1";
394
        }
395
        $format   = 'ext4';
396
        $mkfsPath = Util::which("mkfs.{$format}");
397
        $cmd      = "{$mkfsPath} {$device}{$device_id}";
398
        if ($bg === false) {
399
            $retVal = Util::mwExec("{$cmd} 2>&1");
400
            Util::sysLogMsg(__CLASS__, "{$mkfsPath} returned {$retVal}");
401
        } else {
402
            usleep(200000);
403
            Util::mwExecBg($cmd);
404
            $retVal = true;
405
        }
406
407
        return $retVal;
408
    }
409
410
    /**
411
     * Возвращает текущий статус форматирования диска.
412
     *
413
     * @param $dev
414
     *
415
     * @return string
416
     */
417
    public static function statusMkfs($dev): string
418
    {
419
        if ( ! file_exists($dev)) {
420
            $dev = "/dev/{$dev}";
421
        }
422
        $out      = [];
423
        $psPath   = Util::which('ps');
424
        $grepPath = Util::which('grep');
425
        Util::mwExec("{$psPath} -A -f | {$grepPath} {$dev} | {$grepPath} mkfs | {$grepPath} -v grep", $out);
426
        $mount_dir = trim(implode('', $out));
427
428
        return empty($mount_dir) ? 'ended' : 'inprogress';
429
    }
430
431
    /**
432
     * Clear cache folders from PHP sessions files
433
     */
434
    public static function clearSessionsFiles()
435
    {
436
        $di = Di::getDefault();
437
        if ($di === null) {
438
            return;
439
        }
440
        $config        = $di->getShared('config');
441
        $phpSessionDir = $config->path('www.phpSessionDir');
442
        if ( ! empty($phpSessionDir)) {
443
            $rmPath = Util::which('rm');
444
            Util::mwExec("{$rmPath} -rf {$phpSessionDir}/*");
445
        }
446
    }
447
448
    /**
449
     * Проверка свободного места на дисках. Уведомление в случае проблем.
450
     */
451
    public function checkFreeSpace(): void
452
    {
453
        $util = new Util();
454
        $hdd  = $this->getAllHdd(true);
455
        // Создание больщого файла для тестов.
456
        // head -c 1500MB /dev/urandom > /storage/usbdisk1/big_file.mp3
457
        foreach ($hdd as $disk) {
458
            if ($disk['sys_disk'] === true && ! self::isStorageDiskMounted("{$disk['id']}4")) {
459
                // Это системный диск (4ый раздел). Он не смонтирован.
460
                continue;
461
            }
462
463
            $free       = ($disk['free_space'] / $disk['size'] * 100);
464
            $need_alert = false;
465
            $test_alert = '';
466
            if ($free < 5) {
467
                $need_alert = true;
468
                $test_alert = "The {$disk['id']} has less than 5% of free space available.";
469
            }
470
471
            if ($disk['free_space'] < 500) {
472
                $need_alert = true;
473
                $test_alert = "The {$disk['id']} has less than 500MB of free space available.";
474
            }
475
476
            if ($disk['free_space'] < 100) {
477
                $need_alert = true;
478
                $test_alert = "The {$disk['id']} has less than 100MB of free space available. Old call records will be deleted.";
479
                Util::restartPHPWorker(WorkerRemoveOldRecords::class);
480
            }
481
482
            if ( ! $need_alert) {
483
                continue;
484
            }
485
486
            Util::sysLogMsg("STORAGE", $test_alert);
487
            $data = [
488
                'Device     - ' => "/dev/{$disk['id']}",
489
                'Directoire - ' => "{$disk['mounted']}",
490
                'Desciption - ' => $test_alert,
491
            ];
492
            // Добавляем задачу на уведомление.
493
            $util->addJobToBeanstalk('WorkerNotifyError_storage', $data);
494
        }
495
    }
496
497
    /**
498
     * Возвращает все подключенные HDD.
499
     *
500
     * @param bool $mounted_only
501
     *
502
     * @return array
503
     */
504
    public function getAllHdd($mounted_only = false): array
505
    {
506
        $res_disks = [];
507
508
        if (Util::isSystemctl()) {
509
            $out      = [];
510
            $grepPath = Util::which('grep');
511
            $dfPath   = Util::which('df');
512
            $awkPath  = Util::which('awk');
513
            Util::mwExec(
514
                "{$dfPath} -k /storage/usbdisk1 | {$awkPath}  '{ print $1 \"|\" $3 \"|\" $4} ' | {$grepPath} -v 'Available'",
515
                $out
516
            );
517
            $disk_data = explode('|', implode(" ", $out));
518
            if (count($disk_data) === 3) {
519
                $m_size      = round(($disk_data[1] + $disk_data[2]) / 1024, 1);
520
                $res_disks[] = [
521
                    'id'         => $disk_data[0],
522
                    'size'       => "" . $m_size,
523
                    'size_text'  => "" . $m_size . " Mb",
524
                    'vendor'     => 'Debian',
525
                    'mounted'    => '/storage/usbdisk1',
526
                    'free_space' => round($disk_data[2] / 1024, 1),
527
                    'partitions' => [],
528
                    'sys_disk'   => true,
529
                ];
530
            }
531
532
            return $res_disks;
533
        }
534
535
        $cd_disks = $this->cdromGetDevices();
536
        $cd_disks = array_unique($cd_disks);
537
538
        // TODO Получение данных о дисках в формате JSON:
539
        // lsblk -J -b -o VENDOR,MODEL,SERIAL,LABEL,TYPE,FSTYPE,MOUNTPOINT,SUBSYSTEMS,NAME,UUID
540
        $disks = $this->diskGetDevices();
541
        $disks = array_unique($disks);
542
543
        $cf_disk   = '';
544
        $varEtcDir = $this->config->path('core.varEtcDir');
545
        if (file_exists($varEtcDir . '/cfdevice')) {
546
            $cf_disk = trim(file_get_contents($varEtcDir . '/cfdevice'));
547
        }
548
549
        foreach ($disks as $disk) {
550
            if (in_array($disk, $cd_disks)) {
551
                // Это CD-ROM.
552
                continue;
553
            }
554
            unset($temp_vendor, $temp_size, $original_size);
555
            $mounted = self::diskIsMounted("{$disk}");
556
            if ($mounted_only === true && $mounted === false) {
557
                continue;
558
            }
559
            $sys_disk = ($cf_disk == $disk);
560
561
            $mb_size = 0;
562
            if (is_file("/sys/block/" . $disk . "/size")) {
563
                $original_size = trim(file_get_contents("/sys/block/" . $disk . "/size"));
564
                $original_size = ($original_size * 512 / 1024 / 1024);
565
                $mb_size       = $original_size;
566
            }
567
            if ($mb_size > 100) {
568
                $temp_size   = sprintf("%.0f MB", $mb_size);
569
                $temp_vendor = $this->getVendorDisk($disk);
570
                $free_space  = $this->getFreeSpace($disk);
571
572
                $arr_disk_info = $this->determineFormatFs($disk);
573
                if (count($arr_disk_info) > 0) {
574
                    $used = 0;
575
                    foreach ($arr_disk_info as $disk_info) {
576
                        $used += $disk_info['used_space'];
577
                    }
578
                    if ($used > 0) {
579
                        $free_space = $mb_size - $used;
580
                    }
581
                }
582
583
                $res_disks[] = [
584
                    'id'         => $disk,
585
                    'size'       => $mb_size,
586
                    'size_text'  => $temp_size,
587
                    'vendor'     => $temp_vendor,
588
                    'mounted'    => $mounted,
589
                    'free_space' => $free_space,
590
                    'partitions' => $arr_disk_info,
591
                    'sys_disk'   => $sys_disk,
592
                ];
593
            }
594
        }
595
596
        return $res_disks;
597
    }
598
599
    /**
600
     * Получение массива подключенныйх cdrom.
601
     *
602
     * @return array
603
     */
604
    private function cdromGetDevices(): array
605
    {
606
        $grepPath    = Util::which('grep');
607
        $sysctlPath  = Util::which('sysctl');
608
        $busyboxPath = Util::which('busybox');
609
        $cutPath     = Util::which('cut');
610
611
        return explode(
612
            " ",
613
            trim(
614
                exec(
615
                    "{$sysctlPath} -n dev.cdrom.info | {$busyboxPath} {$grepPath} 'drive name' | {$busyboxPath} {$cutPath} -f 3"
616
                )
617
            )
618
        );
619
    }
620
621
    /**
622
     * Получение массива подключенныйх HDD.
623
     *
624
     * @return array
625
     */
626
    private function diskGetDevices(): array
627
    {
628
        //  TODO // Переписать через использование lsblk.
629
        $grepPath = Util::which('grep');
630
        $lsPath   = Util::which('ls');
631
        $trPath   = Util::which('tr');
632
633
        return explode(" ", trim(exec("{$lsPath} /dev | {$grepPath} '^[a-z]d[a-z]' | {$trPath} \"\n\" \" \"")));
634
    }
635
636
    /**
637
     * Проверка, смонтирован ли диск.
638
     *
639
     * @param $disk
640
     * @param $filter
641
     *
642
     * @return string|bool
643
     */
644
    public static function diskIsMounted($disk, $filter = '/dev/')
645
    {
646
        $out       = [];
647
        $grepPath  = Util::which('grep');
648
        $mountPath = Util::which('mount');
649
        Util::mwExec("{$mountPath} | {$grepPath} '{$filter}{$disk}'", $out);
650
        if (count($out) > 0) {
651
            $res_out = end($out);
652
        } else {
653
            $res_out = implode('', $out);
654
        }
655
        $data = explode(' ', trim($res_out));
656
657
        return (count($data) > 2) ? $data[2] : false;
658
    }
659
660
    /**
661
     * Получение сведений по диску.
662
     *
663
     * @param $disk
664
     *
665
     * @return string
666
     */
667
    private function getVendorDisk($disk): string
668
    {
669
        $temp_vendor = [];
670
        if (is_file("/sys/block/" . $disk . "/device/vendor")) {
671
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/vendor"));
672
            if ($data != '') {
673
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
674
            }
675
        }
676
        if (is_file("/sys/block/" . $disk . "/device/model")) {
677
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/model"));
678
            if ($data != '') {
679
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
680
            }
681
        }
682
        if (count($temp_vendor) == 0) {
683
            $temp_vendor[] = $disk;
684
        }
685
        if (is_file("/sys/block/" . $disk . "/device/type")) {
686
            $data = trim(file_get_contents("/sys/block/" . $disk . "/device/type"));
687
            if ($data != '') {
688
                $temp_vendor[] = trim(str_replace(',', ' ', $data));
689
            }
690
        }
691
692
        return implode(', ', $temp_vendor);
693
    }
694
695
    /**
696
     * Получаем свободное место на диске в Mb.
697
     *
698
     * @param $hdd
699
     *
700
     * @return mixed
701
     */
702
    public function getFreeSpace($hdd)
703
    {
704
        $out      = [];
705
        $hdd      = escapeshellarg($hdd);
706
        $grepPath = Util::which('grep');
707
        $awkPath  = Util::which('awk');
708
        $dfPath   = Util::which('df');
709
        Util::mwExec("{$dfPath} -m | {$grepPath} {$hdd} | {$awkPath} '{print $4}'", $out);
710
        $result = 0;
711
        foreach ($out as $res) {
712
            if ( ! is_numeric($res)) {
713
                continue;
714
            }
715
            $result += (1 * $res);
716
        }
717
718
        return $result;
719
    }
720
721
    /**
722
     * Определить формат файловой системы и размер дисков.
723
     *
724
     * @param $device
725
     *
726
     * @return array|bool
727
     */
728
    public function determineFormatFs($device)
729
    {
730
        $grepPath      = Util::which('grep');
731
        $lsPath        = Util::which('ls');
732
        $trPath        = Util::which('tr');
733
        $allow_formats = ['ext2', 'ext4', 'fat', 'ntfs', 'msdos'];
734
        $device        = str_replace('/dev/', '', $device);
735
        $devices       = explode(" ", trim(exec("{$lsPath} /dev | {$grepPath} '{$device}' | {$trPath} \"\n\" \" \"")));
736
737
        $result_data = [];
738
        foreach ($devices as $dev) {
739
            if (empty($dev) || (count($devices) > 1 && $device == $dev) || is_dir("/sys/block/{$dev}")) {
740
                continue;
741
            }
742
            $mb_size        = 0;
743
            $path_size_info = '';
744
            $tmp_path       = "/sys/block/{$device}/{$dev}/size";
745
            if (file_exists($tmp_path)) {
746
                $path_size_info = $tmp_path;
747
            }
748
            if (empty($path_size_info)) {
749
                $tmp_path = "/sys/block/" . substr($dev, 0, 3) . "/{$dev}/size";
750
                if (file_exists($tmp_path)) {
751
                    $path_size_info = $tmp_path;
752
                }
753
            }
754
755
            if ( ! empty($path_size_info)) {
756
                $original_size = trim(file_get_contents($path_size_info));
757
                $original_size = ($original_size * 512 / 1024 / 1024);
758
                $mb_size       = $original_size;
759
            }
760
761
            $tmp_dir = "/tmp/{$dev}_" . time();
762
            $out     = [];
763
764
            $fs           = null;
765
            $need_unmount = false;
766
            $mount_dir    = '';
767
            if (self::isStorageDiskMounted("/dev/{$dev} ", $mount_dir)) {
768
                $grepPath  = Util::which('grep');
769
                $awkPath   = Util::which('awk');
770
                $mountPath = Util::which('mount');
771
                Util::mwExec("{$mountPath} | {$grepPath} '/dev/{$dev}' | {$awkPath} '{print $5}'", $out);
772
                $fs         = trim(implode("", $out));
773
                $fs         = ($fs == 'fuseblk') ? 'ntfs' : $fs;
774
                $free_space = $this->getFreeSpace("/dev/{$dev} ");
775
                $used_space = $mb_size - $free_space;
776
            } else {
777
                $format = $this->getFsType($device);
778
                if (in_array($format, $allow_formats)) {
779
                    $fs = $format;
780
                }
781
                self::mountDisk($dev, $format, $tmp_dir);
782
783
                $need_unmount = true;
784
                $used_space   = Util::getSizeOfFile("$tmp_dir");
785
            }
786
            $result_data[] = [
787
                "dev"        => $dev,
788
                'size'       => round($mb_size, 2),
789
                "used_space" => round($used_space, 2),
790
                "free_space" => round($mb_size - $used_space, 2),
791
                "uuid"       => $this->getUuid("/dev/{$dev} "),
792
                "fs"         => $fs,
793
            ];
794
            if ($need_unmount) {
795
                self::umountDisk($tmp_dir);
796
            }
797
        }
798
799
        return $result_data;
800
    }
801
802
    /**
803
     * Монтирует диск в указанный каталог.
804
     *
805
     * @param $dev
806
     * @param $format
807
     * @param $dir
808
     *
809
     * @return bool
810
     */
811
    public static function mountDisk($dev, $format, $dir): bool
812
    {
813
        if (self::isStorageDiskMounted("/dev/{$dev} ")) {
814
            return true;
815
        }
816
        Util::mwMkdir($dir);
817
818
        if ( ! file_exists($dir)) {
819
            Util::sysLogMsg('Storage', "Unable mount $dev $format to $dir. Unable create dir.");
820
821
            return false;
822
        }
823
        $dev = str_replace('/dev/', '', $dev);
824
        if ('ntfs' == $format) {
825
            $mountNtfs3gPath = Util::which('mount.ntfs-3g');
826
            Util::mwExec("{$mountNtfs3gPath} /dev/{$dev} {$dir}", $out);
827
        } else {
828
            $storage   = new Storage();
829
            $uid_part  = 'UUID=' . $storage->getUuid("/dev/{$dev}") . '';
830
            $mountPath = Util::which('mount');
831
            Util::mwExec("{$mountPath} -t {$format} {$uid_part} {$dir}", $out);
832
        }
833
834
        return self::isStorageDiskMounted("/dev/{$dev} ");
835
    }
836
837
    /**
838
     * Монтирование разделов диска с базой данных настроек.
839
     */
840
    public function configure(): void
841
    {
842
        $cf_disk          = '';
843
        $varEtcDir        = $this->config->path('core.varEtcDir');
844
        $storage_dev_file = "{$varEtcDir}/storage_device";
845
        if (file_exists($storage_dev_file)) {
846
            unlink($storage_dev_file);
847
        }
848
849
        if (file_exists($varEtcDir . '/cfdevice')) {
850
            $cf_disk = trim(file_get_contents($varEtcDir . '/cfdevice'));
851
        }
852
853
        $disks = $this->getDiskSettings();
854
        $conf  = '';
855
        foreach ($disks as $disk) {
856
            clearstatcache();
857
            if ($disk['device'] !== "/dev/{$cf_disk}") {
858
                // Если это обычный диск, то раздел 1.
859
                $dev = "{$disk['device']}1";
860
            } else {
861
                // Если это системный диск, то пытаемся подключить раздел 4.
862
                $dev = "{$disk['device']}4";
863
            }
864
            if ( ! $this->hddExists($dev)) {
865
                // Диск не существует.
866
                continue;
867
            }
868
            if ($disk['media'] === '1' || ! file_exists($storage_dev_file)) {
869
                file_put_contents($storage_dev_file, "/storage/usbdisk{$disk['id']}");
870
                $this->updateConfigWithNewMountPoint("/storage/usbdisk{$disk['id']}");
871
            }
872
873
            $str_uid     = 'UUID=' . $this->getUuid($dev) . '';
874
            $format_p4   = $this->getFsType($dev);
875
            $conf        .= "{$str_uid} /storage/usbdisk{$disk['id']} {$format_p4} async,rw 0 0\n";
876
            $mount_point = "/storage/usbdisk{$disk['id']}";
877
            Util::mwMkdir($mount_point);
878
        }
879
        $this->saveFstab($conf);
880
        $this->createWorkDirs();
881
        PHPConf::setupLog();
882
    }
883
884
    /**
885
     * Получаем настройки диска из базы данных.
886
     *
887
     * @param string $id
888
     *
889
     * @return array
890
     */
891
    public function getDiskSettings($id = ''): array
892
    {
893
        $data = [];
894
        if ('' === $id) {
895
            $pbxSettings = StorageModel::find();
896
            if ($pbxSettings) {
0 ignored issues
show
introduced by
$pbxSettings is of type Phalcon\Mvc\Model\ResultsetInterface, thus it always evaluated to true.
Loading history...
897
                // Возвращаем данные до модификации.
898
                $data = $pbxSettings->toArray();
899
            }
900
        } else {
901
            $pbxSettings = StorageModel::findFirst("id='$id'");
902
            if ($pbxSettings !== null) {
903
                $data = $pbxSettings->toArray();
904
            }
905
        }
906
907
        return $data;
908
    }
909
910
    /**
911
     * Проверяет, существует ли диск в массиве.
912
     *
913
     * @param $disk
914
     *
915
     * @return bool
916
     */
917
    private function hddExists($disk): bool
918
    {
919
        $result = false;
920
        $uid    = $this->getUuid("{$disk}");
921
        if (file_exists("{$disk}") && $uid !== false) {
922
            $result = true;
923
        }
924
925
        return $result;
926
    }
927
928
    /**
929
     * After mount storage we will change /mountpoint/ to new $mount_point value
930
     *
931
     * @param string $mount_point
932
     *
933
     * @throws \JsonException
934
     */
935
    private function updateConfigWithNewMountPoint(string $mount_point): void
936
    {
937
        $staticSettingsFile     = '/etc/inc/mikopbx-settings.json';
938
        $staticSettingsFileOrig = appPath('config/mikopbx-settings.json');
939
940
        $jsonString = file_get_contents($staticSettingsFileOrig);
941
        $data       = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR);
942
        foreach ($data as $rootKey => $rootEntry) {
943
            foreach ($rootEntry as $nestedKey => $entry) {
944
                if (stripos($entry, '/mountpoint') !== false) {
945
                    $data[$rootKey][$nestedKey] = str_ireplace('/mountpoint', $mount_point, $entry);
946
                }
947
            }
948
        }
949
        $newJsonString = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
950
        file_put_contents($staticSettingsFile, $newJsonString);
951
952
        // Update config variable
953
        $this->di->remove('config');
954
        $this->di->register(new ConfigProvider());
955
        $this->config = $this->di->getShared('config');
956
957
        // Delete boot cache folders
958
        if (str_contains($mount_point, '/mountpoint') === false
959
             && is_dir('/mountpoint')) {
960
            $rmPath = Util::which('rm');
961
            Util::mwExec("{$rmPath} -rf /mountpoint");
962
        }
963
    }
964
965
    /**
966
     * Генерация файла fstab. Монтирование разделов.
967
     *
968
     * @param string $conf
969
     */
970
    public function saveFstab($conf = ''): void
971
    {
972
        $varEtcDir = $this->config->path('core.varEtcDir');
973
        // Точка монтирования доп. дисков.
974
        Util::mwMkdir('/storage');
975
        $chmodPath = Util::which('chmod');
976
        Util::mwExec("{$chmodPath} 755 /storage");
977
        if ( ! file_exists($varEtcDir . '/cfdevice')) {
978
            return;
979
        }
980
        $fstab     = '';
981
        $file_data = file_get_contents($varEtcDir . '/cfdevice');
982
        $cf_disk   = trim($file_data);
983
        if ('' == $cf_disk) {
984
            return;
985
        }
986
        // $part1 	 = (strpos($cf_disk, "mmcblk") !== false)?"{$cf_disk}p1":"{$cf_disk}1"; // Boot
987
        $part2 = (strpos($cf_disk, 'mmcblk') !== false) ? "{$cf_disk}p2" : "{$cf_disk}2"; // Offload
988
        $part3 = (strpos($cf_disk, 'mmcblk') !== false) ? "{$cf_disk}p3" : "{$cf_disk}3"; // Conf
989
990
991
        $uid_part2 = 'UUID=' . $this->getUuid("/dev/{$part2}") . '';
992
        $format_p2 = $this->getFsType($part2);
993
        $uid_part3 = 'UUID=' . $this->getUuid("/dev/{$part3}") . '';
994
        $format_p3 = $this->getFsType($part3);
995
996
        // $fstab .= "/dev/{$part1} /cf msdos ro 1 1\n"; // НЕ МОНТИРУЕМ!
997
        $fstab .= "{$uid_part2} /offload {$format_p2} ro 0 0\n";
998
        $fstab .= "{$uid_part3} /cf {$format_p3} rw 1 1\n";
999
        $fstab .= $conf;
1000
1001
        file_put_contents("/etc/fstab", $fstab);
1002
        // Дублируем для работы vmtoolsd.
1003
        file_put_contents("/etc/mtab", $fstab);
1004
        $mountPath = Util::which('mount');
1005
        Util::mwExec("{$mountPath} -a 2> /dev/null");
1006
        Util::addRegularWWWRights('/cf');
1007
    }
1008
1009
    /**
1010
     * Create system folders
1011
     *
1012
     * @return void
1013
     */
1014
    private function createWorkDirs(): void
1015
    {
1016
        $path      = '';
1017
        $mountPath = Util::which('mount');
1018
        Util::mwExec("{$mountPath} -o remount,rw /offload 2> /dev/null");
1019
1020
        $isLiveCd = file_exists('/offload/livecd');
1021
        // Create dirs
1022
        $arrConfig = $this->config->toArray();
1023
        foreach ($arrConfig as $rootEntry) {
1024
            foreach ($rootEntry as $key => $entry) {
1025
                if (stripos($key, 'path') === false && stripos($key, 'dir') === false) {
1026
                    continue;
1027
                }
1028
                if (file_exists($entry)) {
1029
                    continue;
1030
                }
1031
                if ($isLiveCd && strpos($entry, '/offload/') === 0) {
1032
                    continue;
1033
                }
1034
                $path .= " $entry";
1035
            }
1036
        }
1037
1038
        if ( ! empty($path)) {
1039
            Util::mwMkdir($path);
1040
        }
1041
1042
        $downloadCacheDir = appPath('sites/pbxcore/files/cache');
1043
        if ( ! $isLiveCd) {
1044
            Util::mwMkdir($downloadCacheDir);
1045
            Util::createUpdateSymlink($this->config->path('www.downloadCacheDir'), $downloadCacheDir);
1046
        }
1047
1048
        $this->createAssetsSymlinks();
1049
1050
        Util::createUpdateSymlink($this->config->path('www.phpSessionDir'), '/var/lib/php/session');
1051
        Util::createUpdateSymlink($this->config->path('www.uploadDir'), '/ultmp');
1052
1053
        $filePath = appPath('src/Core/Asterisk/Configs/lua/extensions.lua');
1054
        Util::createUpdateSymlink($filePath, '/etc/asterisk/extensions.lua');
1055
1056
        // Create symlinks to AGI-BIN
1057
        $agiBinDir = $this->config->path('asterisk.astagidir');
1058
        if ($isLiveCd && strpos($agiBinDir, '/offload/') !== 0) {
1059
            Util::mwMkdir($agiBinDir);
1060
        }
1061
1062
        $roAgiBinFolder = appPath('src/Core/Asterisk/agi-bin');
1063
        $files          = glob("{$roAgiBinFolder}/*.{php}", GLOB_BRACE);
1064
        foreach ($files as $file) {
1065
            $fileInfo    = pathinfo($file);
1066
            $newFilename = "{$agiBinDir}/{$fileInfo['filename']}.{$fileInfo['extension']}";
1067
            Util::createUpdateSymlink($file, $newFilename);
1068
        }
1069
        $this->clearCacheFiles();
1070
        $this->createModulesCacheSymlinks();
1071
        $this->applyFolderRights();
1072
    }
1073
1074
1075
    /**
1076
     * Creates JS, CSS, IMG cache folders and links
1077
     */
1078
    public function createAssetsSymlinks(): void
1079
    {
1080
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
1081
        Util::createUpdateSymlink($this->config->path('adminApplication.assetsCacheDir') . '/js', $jsCacheDir);
1082
1083
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
1084
        Util::createUpdateSymlink($this->config->path('adminApplication.assetsCacheDir') . '/css', $cssCacheDir);
1085
1086
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
1087
        Util::createUpdateSymlink($this->config->path('adminApplication.assetsCacheDir') . '/img', $imgCacheDir);
1088
    }
1089
1090
    /**
1091
     * Clear cache folders from old and orphaned files
1092
     */
1093
    public function clearCacheFiles()
1094
    {
1095
        $cacheDirs   = [];
1096
        $cacheDirs[] = $this->config->path('www.uploadDir');
1097
        $cacheDirs[] = $this->config->path('www.downloadCacheDir');
1098
        $cacheDirs[] = $this->config->path('www.managedCacheDir');
1099
        $cacheDirs[] = $this->config->path('www.modelsCacheDir');
1100
        $cacheDirs[] = $this->config->path('adminApplication.assetsCacheDir') . '/js';
1101
        $cacheDirs[] = $this->config->path('adminApplication.assetsCacheDir') . '/css';
1102
        $cacheDirs[] = $this->config->path('adminApplication.assetsCacheDir') . '/img';
1103
        $cacheDirs[] = $this->config->path('adminApplication.voltCacheDir');
1104
        $rmPath      = Util::which('rm');
1105
        foreach ($cacheDirs as $cacheDir) {
1106
            if ( ! empty($cacheDir)) {
1107
                Util::mwExec("{$rmPath} -rf {$cacheDir}/*");
1108
            }
1109
        }
1110
    }
1111
1112
    /**
1113
     * Restore modules cache folders and symlinks
1114
     */
1115
    public function createModulesCacheSymlinks(): void
1116
    {
1117
        $modules = PbxExtensionModules::find()->toArray();
1118
        foreach ($modules as $module) {
1119
            PbxExtensionUtils::createAssetsSymlinks($module['uniqid']);
1120
            PbxExtensionUtils::createAgiBinSymlinks($module['uniqid']);
1121
        }
1122
    }
1123
1124
    /**
1125
     * Fix permissions for Folder and Files
1126
     */
1127
    private function applyFolderRights(): void
1128
    {
1129
        // Add Rights to the WWW dirs plus some core dirs
1130
        $www_dirs  = [];
1131
        $exec_dirs = [];
1132
        $arrConfig = $this->config->adminApplication->toArray();
1133
        foreach ($arrConfig as $key => $entry) {
1134
            if (stripos($key, 'path') === false
1135
                && stripos($key, 'dir') === false
1136
            ) {
1137
                continue;
1138
            }
1139
            $www_dirs[] = $entry;
1140
        }
1141
1142
        $arrConfig = $this->config->www->toArray();
1143
        foreach ($arrConfig as $key => $entry) {
1144
            if (stripos($key, 'path') === false
1145
                && stripos($key, 'dir') === false
1146
            ) {
1147
                continue;
1148
            }
1149
            $www_dirs[] = $entry;
1150
        }
1151
1152
        $www_dirs[] = $this->config->path('core.tempDir');
1153
        $www_dirs[] = $this->config->path('database.logsDir');
1154
        $www_dirs[] = '/etc/version';
1155
        $www_dirs[] = appPath('/');
1156
1157
        // Add read rights
1158
        Util::addRegularWWWRights(implode(' ', $www_dirs));
1159
1160
        // Add executable rights
1161
        $exec_dirs[] = appPath('src/Core/Asterisk/agi-bin');
1162
        $exec_dirs[] = appPath('src/Core/Rc');
1163
        Util::addExecutableRights(implode(' ', $exec_dirs));
1164
1165
        $mountPath = Util::which('mount');
1166
        Util::mwExec("{$mountPath} -o remount,ro /offload 2> /dev/null");
1167
    }
1168
1169
    /**
1170
     * Сохраняем новые данные диска.
1171
     *
1172
     * @param        $data
1173
     * @param string $id
1174
     */
1175
    public function saveDiskSettings($data, $id = '1'): void
1176
    {
1177
        if ( ! is_array($data)) {
1178
            return;
1179
        }
1180
        $disk_data = $this->getDiskSettings($id);
1181
        if (count($disk_data) === 0) {
1182
            $uniqid           = strtoupper('STORAGE-DISK-' . md5(time()));
1183
            $storage_settings = new StorageModel();
1184
            foreach ($data as $key => $val) {
1185
                $storage_settings->writeAttribute($key, $val);
1186
            }
1187
            $storage_settings->writeAttribute('uniqid', $uniqid);
1188
            $storage_settings->save();
1189
        } else {
1190
            $storage_settings = StorageModel::findFirst("id = '$id'");
1191
            if ($storage_settings === null) {
1192
                return;
1193
            }
1194
            foreach ($data as $key => $value) {
1195
                $storage_settings->writeAttribute($key, $value);
1196
            }
1197
            $storage_settings->save();
1198
        }
1199
    }
1200
}