Passed
Push — develop ( 195eeb...314c02 )
by Портнов
05:03
created

Storage   F

Complexity

Total Complexity 216

Size/Duplication

Total Lines 1445
Duplicated Lines 0 %

Importance

Changes 18
Bugs 0 Features 0
Metric Value
wmc 216
eloc 714
c 18
b 0
f 0
dl 0
loc 1445
rs 1.886

46 Methods

Rating   Name   Duplication   Size   Complexity  
A getMonitorDir() 0 8 2
A getMediaDir() 0 8 2
A getFsType() 0 20 2
A getUuid() 0 17 4
B isStorageDisk() 0 36 7
B moveReadOnlySoundsToStorage() 0 27 8
B copyMohFilesToStorage() 0 26 8
A getLsBlkDiskInfo() 0 14 2
A mountWebDav() 0 27 4
A diskIsMounted() 0 14 3
A getFreeSpace() 0 17 3
A mountSftpDisk() 0 17 2
A statusMkfs() 0 12 3
A mkfs_disk() 0 23 5
A formatDiskLocalPart2() 0 20 3
A formatDiskLocal() 0 12 2
B diskGetDevices() 0 26 7
B mountFtp() 0 34 6
A umountDisk() 0 15 4
A getVendorDisk() 0 14 4
A cdromGetDevices() 0 13 4
A clearSessionsFiles() 0 11 3
A getDiskParted() 0 21 4
C determineFormatFs() 0 69 14
A mountDisk() 0 24 4
C getAllHdd() 0 93 14
B isStorageDiskMounted() 0 29 6
A createWorkDirsAfterDBUpgrade() 0 7 1
B configure() 0 45 11
C createWorkDirs() 0 58 13
A createModulesCacheSymlinks() 0 6 2
A clearCacheFiles() 0 19 5
A saveFstab() 0 39 4
A saveDiskSettings() 0 23 6
A createAssetsSymlinks() 0 10 1
A getStorageDev() 0 22 4
A getDiskSettings() 0 14 3
A getStorageFreeSpaceMb() 0 16 4
A getDevPartName() 0 13 1
A updateEnvironmentAfterChangeMountPoint() 0 11 1
B applyFolderRights() 0 49 8
A hddExists() 0 8 3
A mountSwap() 0 19 3
A updateConfigWithNewMountPoint() 0 22 5
A makeSwapFile() 0 29 5
A getRecoverDiskName() 0 19 6

How to fix   Complexity   

Complex Class

Complex classes like Storage often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Storage, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\Core\System;
21
22
use Error;
23
use JsonException;
24
use MikoPBX\Common\Config\ClassLoader;
25
use MikoPBX\Common\Models\PbxExtensionModules;
26
use MikoPBX\Common\Models\SoundFiles;
27
use MikoPBX\Core\Config\RegisterDIServices;
28
use MikoPBX\Core\System\Configs\PHPConf;
29
use MikoPBX\Common\Models\Storage as StorageModel;
30
use MikoPBX\Common\Providers\ConfigProvider;
31
use MikoPBX\Modules\PbxExtensionUtils;
32
use MikoPBX\PBXCoreREST\Lib\SystemManagementProcessor;
33
use Phalcon\Di;
34
35
use function MikoPBX\Common\Config\appPath;
36
37
38
/**
39
 * Class Storage
40
 *
41
 * @package MikoPBX\Core\System
42
 * @property \Phalcon\Config config
43
 */
44
class Storage extends Di\Injectable
45
{
46
    /**
47
     * Возвращает директорию для хранения файлов записей разговоров.
48
     *
49
     * @return string
50
     */
51
    public static function getMonitorDir(): string
52
    {
53
        $di = Di::getDefault();
54
        if ($di !== null) {
55
            return $di->getConfig()->path('asterisk.monitordir');
56
        }
57
58
        return '/tmp';
59
    }
60
61
    /**
62
     * Возвращает директорию для хранения media файлов.
63
     *
64
     * @return string
65
     */
66
    public static function getMediaDir(): string
67
    {
68
        $di = Di::getDefault();
69
        if ($di !== null) {
70
            return $di->getConfig()->path('core.mediaMountPoint');
71
        }
72
73
        return '/tmp';
74
    }
75
76
77
    /**
78
     * Прверяем является ли диск хранилищем.
79
     *
80
     * @param $device
81
     *
82
     * @return bool
83
     */
84
    public static function isStorageDisk($device): bool
85
    {
86
        $result = false;
87
        if (!file_exists($device)) {
88
            return $result;
89
        }
90
91
        $tmp_dir = '/tmp/mnt_' . time();
92
        Util::mwMkdir($tmp_dir);
93
        $out = [];
94
95
        $storage = new self();
96
        $uid_part = 'UUID=' . $storage->getUuid($device) . '';
97
        $format = $storage->getFsType($device);
98
        if ($format === '') {
99
            return false;
100
        }
101
        $mountPath = Util::which('mount');
102
        $umountPath = Util::which('umount');
103
        $rmPath = Util::which('rm');
104
105
        Processes::mwExec("{$mountPath} -t {$format} {$uid_part} {$tmp_dir}", $out);
106
        if (is_dir("{$tmp_dir}/mikopbx") && trim(implode('', $out)) === '') {
107
            // $out - пустая строка, ошибок нет
108
            // присутствует каталог mikopbx.
109
            $result = true;
110
        }
111
        if (self::isStorageDiskMounted($device)) {
112
            Processes::mwExec("{$umountPath} {$device}");
113
        }
114
115
        if (!self::isStorageDiskMounted($device)) {
116
            Processes::mwExec("{$rmPath} -rf '{$tmp_dir}'");
117
        }
118
119
        return $result;
120
    }
121
122
    /**
123
     * Получение идентификатора устройства.
124
     *
125
     * @param $device
126
     *
127
     * @return string
128
     */
129
    public function getUuid($device): string
130
    {
131
        if (empty($device)) {
132
            return '';
133
        }
134
        $lsBlkPath = Util::which('lsblk');
135
        $busyboxPath = Util::which('busybox');
136
137
        $cmd = "{$lsBlkPath} -r -o NAME,UUID | {$busyboxPath} grep " . basename($device) . " | {$busyboxPath} cut -d ' ' -f 2";
138
        $res = Processes::mwExec($cmd, $output);
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
        Processes::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
     * Moves predefined sound files to storage disk
179
     * Changes SoundFiles records
180
     */
181
    public static function moveReadOnlySoundsToStorage(): void
182
    {
183
        if(!self::isStorageDiskMounted()) {
184
            return;
185
        }
186
        $di = Di::getDefault();
187
        if ($di === null) {
188
            return;
189
        }
190
        $currentMediaDir = $di->getConfig()->path('asterisk.customSoundDir') . '/';
191
        if ( !file_exists($currentMediaDir)) {
192
            Util::mwMkdir($currentMediaDir);
193
        }
194
        $soundFiles = SoundFiles::find();
195
        foreach ($soundFiles as $soundFile) {
196
            if (stripos($soundFile->path, '/offload/asterisk/sounds/other/') === 0) {
197
                $newPath = $currentMediaDir.pathinfo($soundFile->path)['basename'];
198
                if (copy($soundFile->path, $newPath)) {
199
                    SystemManagementProcessor::convertAudioFile($newPath);
200
                    $soundFile->path = Util::trimExtensionForFile($newPath) . ".mp3";
201
                    if(file_exists($soundFile->path)){
202
                        $soundFile->update();
203
                    }
204
                }
205
            }
206
        }
207
        unset($soundFiles);
208
    }
209
210
    /**
211
     * Copies MOH sound files to storage and creates record on SoundFiles table
212
     */
213
    public static function copyMohFilesToStorage(): void
214
    {
215
        if(!self::isStorageDiskMounted()) {
216
            return;
217
        }
218
        $di = Di::getDefault();
219
        if ($di === null) {
220
            return;
221
        }
222
        $config        = $di->getConfig();
223
        $oldMohDir     = $config->path('asterisk.astvarlibdir') . '/sounds/moh';
224
        $currentMohDir = $config->path('asterisk.mohdir');
225
        if ( ! file_exists($oldMohDir)||Util::mwMkdir($currentMohDir)) {
226
            return;
227
        }
228
        $files = scandir($oldMohDir);
229
        foreach ($files as $file) {
230
            if (in_array($file, ['.', '..'])) {
231
                continue;
232
            }
233
            if (copy($oldMohDir.'/'.$file, $currentMohDir.'/'.$file)) {
234
                $sound_file           = new SoundFiles();
235
                $sound_file->path     = $currentMohDir . '/' . $file;
236
                $sound_file->category = SoundFiles::CATEGORY_MOH;
237
                $sound_file->name     = $file;
238
                $sound_file->save();
239
            }
240
        }
241
    }
242
243
    /**
244
     * Проверка, смонтирован ли диск - хранилище.
245
     *
246
     * @param string $filter
247
     * @param string $mount_dir
248
     *
249
     * @return bool
250
     */
251
    public static function isStorageDiskMounted($filter = '', &$mount_dir = ''): bool
252
    {
253
        if (Util::isSystemctl() && file_exists('/storage/usbdisk1/')) {
254
            $mount_dir = '/storage/usbdisk1/';
255
            return true;
256
        }
257
        if ('' === $filter) {
258
            $di = Di::getDefault();
259
            if ($di !== null) {
260
                $varEtcDir = $di->getConfig()->path('core.varEtcDir');
261
            } else {
262
                $varEtcDir = '/var/etc';
263
            }
264
265
            $filename = "{$varEtcDir}/storage_device";
266
            if (file_exists($filename)) {
267
                $filter = file_get_contents($filename);
268
            } else {
269
                $filter = 'usbdisk1';
270
            }
271
        }
272
        $grepPath   = Util::which('grep');
273
        $mountPath  = Util::which('mount');
274
        $awkPath    = Util::which('awk');
275
276
        $filter = escapeshellarg($filter);
277
        $out        = shell_exec("$mountPath | $grepPath $filter | {$awkPath} '{print $3}'");
278
        $mount_dir  = trim($out);
279
        return ($mount_dir !== '');
280
    }
281
282
    /**
283
     * Монитирование каталога с удаленного сервера SFTP.
284
     *
285
     * @param        $host
286
     * @param int    $port
287
     * @param string $user
288
     * @param string $pass
289
     * @param string $remout_dir
290
     * @param string $local_dir
291
     *
292
     * @return bool
293
     */
294
    public static function mountSftpDisk($host, $port, $user, $pass, $remout_dir, $local_dir): bool
295
    {
296
        Util::mwMkdir($local_dir);
297
298
        $out = [];
299
        $timeoutPath = Util::which('timeout');
300
        $sshfsPath = Util::which('sshfs');
301
302
        $command = "{$timeoutPath} 3 {$sshfsPath} -p {$port} -o nonempty -o password_stdin -o 'StrictHostKeyChecking=no' " . "{$user}@{$host}:{$remout_dir} {$local_dir} << EOF\n" . "{$pass}\n" . "EOF\n";
303
        Processes::mwExec($command, $out);
304
        $response = trim(implode('', $out));
305
        if ('Terminated' === $response) {
306
            // Удаленный сервер не ответил / или не корректно указан пароль.
307
            unset($response);
308
        }
309
310
        return self::isStorageDiskMounted("$local_dir ");
311
    }
312
313
    /**
314
     * Монитирование каталога с удаленного сервера FTP.
315
     *
316
     * @param        $host
317
     * @param        $port
318
     * @param        $user
319
     * @param        $pass
320
     * @param string $remout_dir
321
     * @param        $local_dir
322
     *
323
     * @return bool
324
     */
325
    public static function mountFtp($host, $port, $user, $pass, $remout_dir, $local_dir): bool
326
    {
327
        Util::mwMkdir($local_dir);
328
        $out = [];
329
330
        // Собираем строку подключения к ftp.
331
        $auth_line = '';
332
        if (!empty($user)) {
333
            $auth_line .= 'user="' . $user;
334
            if (!empty($pass)) {
335
                $auth_line .= ":{$pass}";
336
            }
337
            $auth_line .= '",';
338
        }
339
340
        $connect_line = 'ftp://' . $host;
341
        if (!empty($port)) {
342
            $connect_line .= ":{$port}";
343
        }
344
        if (!empty($remout_dir)) {
345
            $connect_line .= "$remout_dir";
346
        }
347
348
        $timeoutPath = Util::which('timeout');
349
        $curlftpfsPath = Util::which('curlftpfs');
350
        $command = "{$timeoutPath} 3 {$curlftpfsPath}  -o allow_other -o {$auth_line}fsname={$host} {$connect_line} {$local_dir}";
351
        Processes::mwExec($command, $out);
352
        $response = trim(implode('', $out));
353
        if ('Terminated' === $response) {
354
            // Удаленный сервер не ответил / или не корректно указан пароль.
355
            unset($response);
356
        }
357
358
        return self::isStorageDiskMounted("$local_dir ");
359
    }
360
361
    /**
362
     * Монитирование каталога с удаленного сервера FTP.
363
     *
364
     * @param        $host
365
     * @param        $user
366
     * @param        $pass
367
     * @param        $dstDir
368
     * @param        $local_dir
369
     *
370
     * @return bool
371
     */
372
    public static function mountWebDav($host, $user, $pass, $dstDir, $local_dir): bool
373
    {
374
        $host = trim($host);
375
        $dstDir = trim($dstDir);
376
        if(substr($host, -1) === '/'){
377
            $host = substr($host, 0, -1);
378
        }
379
        if($dstDir[0] === '/'){
380
            $dstDir = substr($dstDir, 1);
381
        }
382
        Util::mwMkdir($local_dir);
383
        $out = [];
384
        $conf = 'dav_user www'.PHP_EOL.
385
                'dav_group www'.PHP_EOL;
386
387
        file_put_contents('/etc/davfs2/secrets', "{$host}{$dstDir} $user $pass");
388
        file_put_contents('/etc/davfs2/davfs2.conf', $conf);
389
        $timeoutPath = Util::which('timeout');
390
        $mount = Util::which('mount.davfs');
391
        $command = "$timeoutPath 3 yes | $mount {$host}{$dstDir} {$local_dir}";
392
        Processes::mwExec($command, $out);
393
        $response = trim(implode('', $out));
394
        if ('Terminated' === $response) {
395
            // Удаленный сервер не ответил / или не корректно указан пароль.
396
            unset($response);
397
        }
398
        return self::isStorageDiskMounted("$local_dir ");
399
    }
400
401
    /**
402
     * Запускает процесс форматирования диска.
403
     *
404
     * @param $dev
405
     *
406
     * @return array|bool
407
     */
408
    public static function mkfs_disk($dev)
409
    {
410
        if (!file_exists($dev)) {
411
            $dev = "/dev/{$dev}";
412
        }
413
        if (!file_exists($dev)) {
414
            return false;
415
        }
416
        $dir = '';
417
        self::isStorageDiskMounted($dev, $dir);
418
419
        if (empty($dir) || self::umountDisk($dir)) {
420
            // Диск размонтирован.
421
            $st = new Storage();
422
            // Будет запущен процесс:
423
            $st->formatDiskLocal($dev, true);
424
            sleep(1);
425
426
            return (self::statusMkfs($dev) === 'inprogress');
427
        }
428
429
        // Ошибка размонтирования диска.
430
        return false;
431
    }
432
433
    /**
434
     * Размонтирует диск. Удаляет каталог в случае успеха.
435
     *
436
     * @param $dir
437
     *
438
     * @return bool
439
     */
440
    public static function umountDisk($dir): bool
441
    {
442
        $umountPath = Util::which('umount');
443
        $rmPath     = Util::which('rm');
444
        if (self::isStorageDiskMounted($dir)) {
445
            Processes::mwExec("/sbin/shell_functions.sh 'killprocesses' '$dir' -TERM 0");
446
            Processes::mwExec("{$umountPath} {$dir}");
447
        }
448
        $result = ! self::isStorageDiskMounted($dir);
449
        if ($result && file_exists($dir)) {
450
            // Если диск не смонтирован, то удаляем каталог.
451
            Processes::mwExec("{$rmPath} -rf '{$dir}'");
452
        }
453
454
        return $result;
455
    }
456
457
    /**
458
     * Разметка диска.
459
     *
460
     * @param string $device
461
     * @param bool   $bg
462
     *
463
     * @return mixed
464
     */
465
    public function formatDiskLocal($device, $bg = false)
466
    {
467
        $partedPath = Util::which('parted');
468
        $retVal = Processes::mwExec(
469
            "{$partedPath} --script --align optimal '{$device}' 'mklabel msdos mkpart primary ext4 0% 100%'"
470
        );
471
        Util::sysLogMsg(__CLASS__, "{$partedPath} returned {$retVal}");
472
        if (false === $bg) {
473
            sleep(1);
474
        }
475
476
        return $this->formatDiskLocalPart2($device, $bg);
477
    }
478
479
    /**
480
     * Форматирование диска.
481
     *
482
     * @param string $device
483
     * @param bool   $bg
484
     *
485
     * @return mixed
486
     */
487
    private function formatDiskLocalPart2($device, $bg = false): bool
488
    {
489
        if (is_numeric(substr($device, -1))) {
490
            $device_id = "";
491
        } else {
492
            $device_id = "1";
493
        }
494
        $format = 'ext4';
495
        $mkfsPath = Util::which("mkfs.{$format}");
496
        $cmd = "{$mkfsPath} {$device}{$device_id}";
497
        if ($bg === false) {
498
            $retVal = (Processes::mwExec("{$cmd} 2>&1") === 0);
499
            Util::sysLogMsg(__CLASS__, "{$mkfsPath} returned {$retVal}");
500
        } else {
501
            usleep(200000);
502
            Processes::mwExecBg($cmd);
503
            $retVal = true;
504
        }
505
506
        return $retVal;
507
    }
508
509
    /**
510
     * Возвращает текущий статус форматирования диска.
511
     *
512
     * @param $dev
513
     *
514
     * @return string
515
     */
516
    public static function statusMkfs($dev): string
517
    {
518
        if (!file_exists($dev)) {
519
            $dev = "/dev/{$dev}";
520
        }
521
        $out = [];
522
        $psPath = Util::which('ps');
523
        $grepPath = Util::which('grep');
524
        Processes::mwExec("{$psPath} -A -f | {$grepPath} {$dev} | {$grepPath} mkfs | {$grepPath} -v grep", $out);
525
        $mount_dir = trim(implode('', $out));
526
527
        return empty($mount_dir) ? 'ended' : 'inprogress';
528
    }
529
530
    /**
531
     * Clear cache folders from PHP sessions files
532
     */
533
    public static function clearSessionsFiles(): void
534
    {
535
        $di = Di::getDefault();
536
        if ($di === null) {
537
            return;
538
        }
539
        $config = $di->getShared('config');
540
        $phpSessionDir = $config->path('www.phpSessionDir');
541
        if (!empty($phpSessionDir)) {
542
            $rmPath = Util::which('rm');
543
            Processes::mwExec("{$rmPath} -rf {$phpSessionDir}/*");
544
        }
545
    }
546
547
    /**
548
     * Возвращает все подключенные HDD.
549
     *
550
     * @param bool $mounted_only
551
     *
552
     * @return array
553
     */
554
    public function getAllHdd($mounted_only = false): array
555
    {
556
        $res_disks = [];
557
558
        if (Util::isSystemctl()) {
559
            $out = [];
560
            $grepPath = Util::which('grep');
561
            $dfPath = Util::which('df');
562
            $awkPath = Util::which('awk');
563
            Processes::mwExec(
564
                "{$dfPath} -k /storage/usbdisk1 | {$awkPath}  '{ print $1 \"|\" $3 \"|\" $4} ' | {$grepPath} -v 'Available'",
565
                $out
566
            );
567
            $disk_data = explode('|', implode(" ", $out));
568
            if (count($disk_data) === 3) {
569
                $m_size = round(($disk_data[1] + $disk_data[2]) / 1024, 1);
570
                $res_disks[] = [
571
                    'id' => $disk_data[0],
572
                    'size' => "" . $m_size,
573
                    'size_text' => "" . $m_size . " Mb",
574
                    'vendor' => 'Debian',
575
                    'mounted' => '/storage/usbdisk1',
576
                    'free_space' => round($disk_data[2] / 1024, 1),
577
                    'partitions' => [],
578
                    'sys_disk' => true,
579
                ];
580
            }
581
582
            return $res_disks;
583
        }
584
585
        $cd_disks   = $this->cdromGetDevices();
586
        $disks      = $this->diskGetDevices();
587
588
        $cf_disk = '';
589
        $varEtcDir = $this->config->path('core.varEtcDir');
590
591
        if (file_exists($varEtcDir . '/cfdevice')) {
592
            $cf_disk = trim(file_get_contents($varEtcDir . '/cfdevice'));
593
        }
594
595
        foreach ($disks as $disk => $diskInfo) {
596
            $type = $diskInfo['fstype']??'';
597
            if($type === 'linux_raid_member'){
598
                continue;
599
            }
600
            if (in_array($disk, $cd_disks, true)) {
601
                // Это CD-ROM.
602
                continue;
603
            }
604
            unset($temp_vendor, $temp_size, $original_size);
605
            $mounted = self::diskIsMounted($disk);
606
            if ($mounted_only === true && $mounted === false) {
607
                continue;
608
            }
609
            $sys_disk = ($cf_disk === $disk);
610
611
            $mb_size = 0;
612
            if (is_file("/sys/block/" . $disk . "/size")) {
613
                $original_size = trim(file_get_contents("/sys/block/" . $disk . "/size"));
614
                $original_size = ($original_size * 512 / 1024 / 1024);
615
                $mb_size = $original_size;
616
            }
617
            if ($mb_size > 100) {
618
                $temp_size   = sprintf("%.0f MB", $mb_size);
619
                $temp_vendor = $this->getVendorDisk($diskInfo);
620
                $free_space  = $this->getFreeSpace($disk);
621
622
                $arr_disk_info = $this->determineFormatFs($diskInfo);
623
624
                if (count($arr_disk_info) > 0) {
625
                    $used = 0;
626
                    foreach ($arr_disk_info as $disk_info) {
627
                        $used += $disk_info['used_space'];
628
                    }
629
                    if ($used > 0) {
630
                        $free_space = $mb_size - $used;
631
                    }
632
                }
633
634
                $res_disks[] = [
635
                    'id' => $disk,
636
                    'size' => $mb_size,
637
                    'size_text' => $temp_size,
638
                    'vendor' => $temp_vendor,
639
                    'mounted' => $mounted,
640
                    'free_space' => $free_space,
641
                    'partitions' => $arr_disk_info,
642
                    'sys_disk' => $sys_disk,
643
                ];
644
            }
645
        }
646
        return $res_disks;
647
    }
648
649
    /**
650
     * Получение массива подключенныйх cdrom.
651
     *
652
     * @return array
653
     */
654
    private function cdromGetDevices(): array
655
    {
656
        $disks = [];
657
        $blockDevices = $this->getLsBlkDiskInfo();
658
        foreach ($blockDevices as $diskData) {
659
            $type = $diskData['type'] ?? '';
660
            $name = $diskData['name'] ?? '';
661
            if ($type !== 'rom' || $name === '') {
662
                continue;
663
            }
664
            $disks[] = $name;
665
        }
666
        return $disks;
667
    }
668
669
    /**
670
     * Получение массива подключенныйх HDD.
671
     * @param false $diskOnly
672
     * @return array
673
     */
674
    public function diskGetDevices($diskOnly = false): array
675
    {
676
        $disks = [];
677
        $blockDevices = $this->getLsBlkDiskInfo();
678
679
        foreach ($blockDevices as $diskData) {
680
            $type = $diskData['type'] ?? '';
681
            $name = $diskData['name'] ?? '';
682
            if ($type !== 'disk' || $name === '') {
683
                continue;
684
            }
685
            $disks[$name] = $diskData;
686
            if ($diskOnly === true) {
687
                continue;
688
            }
689
            $children = $diskData['children'] ?? [];
690
691
            foreach ($children as $child) {
692
                $childName = $child['name'] ?? '';
693
                if ($childName === '') {
694
                    continue;
695
                }
696
                $disks[$childName] = $child;
697
            }
698
        }
699
        return $disks;
700
    }
701
702
    /**
703
     * Возвращает информацию о дисках.
704
     * @return array
705
     */
706
    private function getLsBlkDiskInfo(): array
707
    {
708
        $lsBlkPath = Util::which('lsblk');
709
        Processes::mwExec(
710
            "{$lsBlkPath} -J -b -o VENDOR,MODEL,SERIAL,LABEL,TYPE,FSTYPE,MOUNTPOINT,SUBSYSTEMS,NAME,UUID",
711
            $out
712
        );
713
        try {
714
            $data = json_decode(implode(PHP_EOL, $out), true, 512, JSON_THROW_ON_ERROR);
715
            $data = $data['blockdevices'] ?? [];
716
        } catch (JsonException $e) {
717
            $data = [];
718
        }
719
        return $data;
720
    }
721
722
    /**
723
     * Проверка, смонтирован ли диск.
724
     *
725
     * @param $disk
726
     * @param $filter
727
     *
728
     * @return string|bool
729
     */
730
    public static function diskIsMounted($disk, $filter = '/dev/')
731
    {
732
        $out = [];
733
        $grepPath = Util::which('grep');
734
        $mountPath = Util::which('mount');
735
        Processes::mwExec("{$mountPath} | {$grepPath} '{$filter}{$disk}'", $out);
736
        if (count($out) > 0) {
737
            $res_out = end($out);
738
        } else {
739
            $res_out = implode('', $out);
740
        }
741
        $data = explode(' ', trim($res_out));
742
743
        return (count($data) > 2) ? $data[2] : false;
744
    }
745
746
    /**
747
     * Получение сведений по диску.
748
     *
749
     * @param $diskInfo
750
     *
751
     * @return string
752
     */
753
    private function getVendorDisk($diskInfo): string
754
    {
755
        $temp_vendor = [];
756
        $keys = ['vendor', 'model', 'type'];
757
        foreach ($keys as $key) {
758
            $data = $diskInfo[$key] ?? '';
759
            if ($data !== '') {
760
                $temp_vendor[] = trim(str_replace(',', '', $data));
761
            }
762
        }
763
        if (count($temp_vendor) === 0) {
764
            $temp_vendor[] = $diskInfo['name'] ?? 'ERROR: NoName';
765
        }
766
        return implode(', ', $temp_vendor);
767
    }
768
769
    /**
770
     * Получаем свободное место на диске в Mb.
771
     *
772
     * @param $hdd
773
     *
774
     * @return mixed
775
     */
776
    public function getFreeSpace($hdd)
777
    {
778
        $out = [];
779
        $hdd = escapeshellarg($hdd);
780
        $grepPath = Util::which('grep');
781
        $awkPath = Util::which('awk');
782
        $dfPath = Util::which('df');
783
        Processes::mwExec("{$dfPath} -m | {$grepPath} {$hdd} | {$awkPath} '{print $4}'", $out);
784
        $result = 0;
785
        foreach ($out as $res) {
786
            if (!is_numeric($res)) {
787
                continue;
788
            }
789
            $result += (1 * $res);
790
        }
791
792
        return $result;
793
    }
794
795
    private function getDiskParted($diskName): array
796
    {
797
        $result = [];
798
        $lsBlkPath = Util::which('lsblk');
799
        Processes::mwExec("{$lsBlkPath} -J -b -o NAME,TYPE {$diskName}", $out);
800
        try {
801
            $data = json_decode(implode(PHP_EOL, $out), true, 512, JSON_THROW_ON_ERROR);
802
            $data = $data['blockdevices'][0] ?? [];
803
        } catch (\JsonException $e) {
804
            $data = [];
805
        }
806
807
        $type = $data['children'][0]['type'] ?? '';
808
        if (strpos($type, 'raid') === false) {
809
            $children = $data['children']??[];
810
            foreach ($children as $child) {
811
                $result[] = $child['name'];
812
            }
813
        }
814
815
        return $result;
816
    }
817
818
    /**
819
     * Определить формат файловой системы и размер дисков.
820
     *
821
     * @param $deviceInfo
822
     *
823
     * @return array|bool
824
     */
825
    public function determineFormatFs($deviceInfo)
826
    {
827
        $allow_formats = ['ext2', 'ext4', 'fat', 'ntfs', 'msdos'];
828
        $device = basename($deviceInfo['name'] ?? '');
829
830
        $devices = $this->getDiskParted('/dev/'.$deviceInfo['name'] ?? '');
831
        $result_data = [];
832
        foreach ($devices as $dev) {
833
            if (empty($dev) || (count($devices) > 1 && $device === $dev) || is_dir("/sys/block/{$dev}")) {
834
                continue;
835
            }
836
            $mb_size = 0;
837
            $path_size_info = '';
838
            $tmp_path = "/sys/block/{$device}/{$dev}/size";
839
            if (file_exists($tmp_path)) {
840
                $path_size_info = $tmp_path;
841
            }
842
            if (empty($path_size_info)) {
843
                $tmp_path = "/sys/block/" . substr($dev, 0, 3) . "/{$dev}/size";
844
                if (file_exists($tmp_path)) {
845
                    $path_size_info = $tmp_path;
846
                }
847
            }
848
849
            if (!empty($path_size_info)) {
850
                $original_size = trim(file_get_contents($path_size_info));
851
                $original_size = ($original_size * 512 / 1024 / 1024);
852
                $mb_size = $original_size;
853
            }
854
855
            $tmp_dir = "/tmp/{$dev}_" . time();
856
            $out = [];
857
858
            $fs = null;
859
            $need_unmount = false;
860
            $mount_dir = '';
861
            if (self::isStorageDiskMounted("/dev/{$dev} ", $mount_dir)) {
862
                $grepPath = Util::which('grep');
863
                $awkPath = Util::which('awk');
864
                $mountPath = Util::which('mount');
865
                Processes::mwExec("{$mountPath} | {$grepPath} '/dev/{$dev}' | {$awkPath} '{print $5}'", $out);
866
                $fs = trim(implode("", $out));
867
                $fs = ($fs === 'fuseblk') ? 'ntfs' : $fs;
868
                $free_space = $this->getFreeSpace("/dev/{$dev} ");
869
                $used_space = $mb_size - $free_space;
870
            } else {
871
                $format = $this->getFsType($device);
872
                if (in_array($format, $allow_formats)) {
873
                    $fs = $format;
874
                }
875
                self::mountDisk($dev, $format, $tmp_dir);
876
877
                $need_unmount = true;
878
                $used_space = Util::getSizeOfFile($tmp_dir);
879
            }
880
            $result_data[] = [
881
                "dev" => $dev,
882
                'size' => round($mb_size, 2),
883
                "used_space" => round($used_space, 2),
884
                "free_space" => round($mb_size - $used_space, 2),
885
                "uuid" => $this->getUuid("/dev/{$dev} "),
886
                "fs" => $fs,
887
            ];
888
            if ($need_unmount) {
889
                self::umountDisk($tmp_dir);
890
            }
891
        }
892
893
        return $result_data;
894
    }
895
896
    /**
897
     * Монтирует диск в указанный каталог.
898
     *
899
     * @param $dev
900
     * @param $format
901
     * @param $dir
902
     *
903
     * @return bool
904
     */
905
    public static function mountDisk($dev, $format, $dir): bool
906
    {
907
        if (self::isStorageDiskMounted("/dev/{$dev} ")) {
908
            return true;
909
        }
910
        Util::mwMkdir($dir);
911
912
        if (!file_exists($dir)) {
913
            Util::sysLogMsg('Storage', "Unable mount $dev $format to $dir. Unable create dir.");
914
915
            return false;
916
        }
917
        $dev = str_replace('/dev/', '', $dev);
918
        if ('ntfs' === $format) {
919
            $mountNtfs3gPath = Util::which('mount.ntfs-3g');
920
            Processes::mwExec("{$mountNtfs3gPath} /dev/{$dev} {$dir}", $out);
921
        } else {
922
            $storage = new self();
923
            $uid_part = 'UUID=' . $storage->getUuid("/dev/{$dev}") . '';
924
            $mountPath = Util::which('mount');
925
            Processes::mwExec("{$mountPath} -t {$format} {$uid_part} {$dir}", $out);
926
        }
927
928
        return self::isStorageDiskMounted("/dev/{$dev} ");
929
    }
930
931
    /**
932
     * Монтирование разделов диска с базой данных настроек.
933
     */
934
    public function configure(): void
935
    {
936
        if(Util::isSystemctl()){
937
            $this->updateConfigWithNewMountPoint("/storage/usbdisk1");
938
            $this->createWorkDirs();
939
            PHPConf::setupLog();
940
            return;
941
        }
942
943
        $cf_disk = '';
944
        $varEtcDir = $this->config->path('core.varEtcDir');
945
        $storage_dev_file = "{$varEtcDir}/storage_device";
946
        if (file_exists($storage_dev_file)) {
947
            unlink($storage_dev_file);
948
        }
949
950
        if (file_exists($varEtcDir . '/cfdevice')) {
951
            $cf_disk = trim(file_get_contents($varEtcDir . '/cfdevice'));
952
        }
953
        $disks = $this->getDiskSettings();
954
        $conf = '';
955
        foreach ($disks as $disk) {
956
            clearstatcache();
957
            $dev = $this->getStorageDev($disk, $cf_disk);
958
            if (!$this->hddExists($dev)) {
959
                // Диск не существует.
960
                continue;
961
            }
962
            if ($disk['media'] === '1' || !file_exists($storage_dev_file)) {
963
                file_put_contents($storage_dev_file, "/storage/usbdisk{$disk['id']}");
964
                $this->updateConfigWithNewMountPoint("/storage/usbdisk{$disk['id']}");
965
            }
966
            $formatFs = $this->getFsType($dev);
967
            if($formatFs !== $disk['filesystemtype'] && !($formatFs === 'ext4' && $disk['filesystemtype'] === 'ext2')){
968
                Util::sysLogMsg('Storage', "The file system type has changed {$disk['filesystemtype']} -> {$formatFs}. The disk will not be connected.");
969
                continue;
970
            }
971
            $str_uid = 'UUID=' . $this->getUuid($dev);
972
            $conf .= "{$str_uid} /storage/usbdisk{$disk['id']} {$formatFs} async,rw 0 0\n";
973
            $mount_point = "/storage/usbdisk{$disk['id']}";
974
            Util::mwMkdir($mount_point);
975
        }
976
        $this->saveFstab($conf);
977
        $this->createWorkDirs();
978
        PHPConf::setupLog();
979
    }
980
981
    /**
982
     * Поиск Storage устройства.
983
     * Возвращает полный путь к разделу.
984
     * @param $disk
985
     * @param $cf_disk
986
     * @return string
987
     */
988
    private function getStorageDev($disk, $cf_disk):string{
989
        if(!empty($disk['uniqid'])){
990
            // Ищим имя раздела по UID.
991
            $lsBlkPath   = Util::which('lsblk');
992
            $busyboxPath = Util::which('busybox');
993
            $cmd = "{$lsBlkPath} -r -o NAME,UUID | {$busyboxPath} grep {$disk['uniqid']} | {$busyboxPath} cut -d ' ' -f 1";
994
            $dev = '/dev/'.trim(shell_exec($cmd));
995
            if ($this->hddExists($dev)) {
996
                // Диск существует.
997
                return $dev;
998
            }
999
        }
1000
        // Определяем диск по его имени.
1001
        if ($disk['device'] !== "/dev/{$cf_disk}") {
1002
            // Если это обычный диск, то раздел 1.
1003
            $part = "1";
1004
        } else {
1005
            // Если это системный диск, то пытаемся подключить раздел 4.
1006
            $part = "4";
1007
        }
1008
        $devName = self::getDevPartName($disk['device'], $part);
1009
        return '/dev/' . $devName;
1010
    }
1011
1012
    /**
1013
     * Получаем настройки диска из базы данных.
1014
     *
1015
     * @param string $id
1016
     *
1017
     * @return array
1018
     */
1019
    public function getDiskSettings($id = ''): array
1020
    {
1021
        $data = [];
1022
        if ('' === $id) {
1023
            // Возвращаем данные до модификации.
1024
            $data = StorageModel::find()->toArray();
1025
        } else {
1026
            $pbxSettings = StorageModel::findFirst("id='$id'");
1027
            if ($pbxSettings !== null) {
1028
                $data = $pbxSettings->toArray();
1029
            }
1030
        }
1031
1032
        return $data;
1033
    }
1034
1035
    /**
1036
     * Проверяет, существует ли диск в массиве.
1037
     *
1038
     * @param $disk
1039
     *
1040
     * @return bool
1041
     */
1042
    private function hddExists($disk): bool
1043
    {
1044
        $result = false;
1045
        $uid = $this->getUuid($disk);
1046
        if ($uid !== false && file_exists($disk)) {
1047
            $result = true;
1048
        }
1049
        return $result;
1050
    }
1051
1052
    /**
1053
     * After mount storage we will change /mountpoint/ to new $mount_point value
1054
     *
1055
     * @param string $mount_point
1056
     *
1057
     */
1058
    private function updateConfigWithNewMountPoint(string $mount_point): void
1059
    {
1060
        $staticSettingsFile = '/etc/inc/mikopbx-settings.json';
1061
        $staticSettingsFileOrig = appPath('config/mikopbx-settings.json');
1062
1063
        $jsonString = file_get_contents($staticSettingsFileOrig);
1064
        try {
1065
            $data = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR);
1066
        } catch (JsonException $exception) {
1067
            throw new Error("{$staticSettingsFileOrig} has broken format");
1068
        }
1069
        foreach ($data as $rootKey => $rootEntry) {
1070
            foreach ($rootEntry as $nestedKey => $entry) {
1071
                if (stripos($entry, '/mountpoint') !== false) {
1072
                    $data[$rootKey][$nestedKey] = str_ireplace('/mountpoint', $mount_point, $entry);
1073
                }
1074
            }
1075
        }
1076
1077
        $newJsonString = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
1078
        file_put_contents($staticSettingsFile, $newJsonString);
1079
        $this->updateEnvironmentAfterChangeMountPoint();
1080
    }
1081
1082
1083
    /**
1084
     * Recreates DI services and reloads config from JSON file
1085
     *
1086
     */
1087
    private function updateEnvironmentAfterChangeMountPoint(): void
1088
    {
1089
        // Update config variable
1090
        ConfigProvider::recreateConfigProvider();
1091
        $this->config = $this->di->get('config');
1092
1093
        // Reload classes from system and storage disks
1094
        ClassLoader::init();
1095
1096
        // Reload all providers
1097
        RegisterDIServices::init();
1098
    }
1099
1100
    /**
1101
     * Generates fstab file
1102
     * Mounts volumes
1103
     *
1104
     * @param string $conf
1105
     */
1106
    public function saveFstab($conf = ''): void
1107
    {
1108
        if(Util::isSystemctl()){
1109
            // Не настраиваем.
1110
            return;
1111
        }
1112
1113
        $varEtcDir = $this->config->path('core.varEtcDir');
1114
        // Точка монтирования доп. дисков.
1115
        Util::mwMkdir('/storage');
1116
        $chmodPath = Util::which('chmod');
1117
        Processes::mwExec("{$chmodPath} 755 /storage");
1118
        if (!file_exists($varEtcDir . '/cfdevice')) {
1119
            return;
1120
        }
1121
        $fstab = '';
1122
        $file_data = file_get_contents($varEtcDir . '/cfdevice');
1123
        $cf_disk = trim($file_data);
1124
        if ('' === $cf_disk) {
1125
            return;
1126
        }
1127
        $part2 = self::getDevPartName($cf_disk, '2');
1128
        $part3 = self::getDevPartName($cf_disk, '3');
1129
1130
        $uid_part2 = 'UUID=' . $this->getUuid("/dev/{$part2}");
1131
        $format_p2 = $this->getFsType($part2);
1132
        $uid_part3 = 'UUID=' . $this->getUuid("/dev/{$part3}");
1133
        $format_p3 = $this->getFsType($part3);
1134
1135
        $fstab .= "{$uid_part2} /offload {$format_p2} ro 0 0\n";
1136
        $fstab .= "{$uid_part3} /cf {$format_p3} rw 1 1\n";
1137
        $fstab .= $conf;
1138
1139
        file_put_contents("/etc/fstab", $fstab);
1140
        // Дублируем для работы vmtoolsd.
1141
        file_put_contents("/etc/mtab", $fstab);
1142
        $mountPath = Util::which('mount');
1143
        Processes::mwExec("{$mountPath} -a 2> /dev/null");
1144
        Util::addRegularWWWRights('/cf');
1145
    }
1146
1147
    /**
1148
     * Возвращает имя раздела диска по имени и номеру.
1149
     * @param string $dev
1150
     * @param string $part
1151
     * @return string
1152
     */
1153
    public static function getDevPartName(string $dev, string $part): string
1154
    {
1155
        $lsBlkPath = Util::which('lsblk');
1156
        $cutPath = Util::which('cut');
1157
        $grepPath = Util::which('grep');
1158
        $sortPath = Util::which('sort');
1159
1160
        $command = "{$lsBlkPath} -r | {$grepPath} ' part' | {$sortPath} -u | {$cutPath} -d ' ' -f 1 | {$grepPath} \"" . basename(
1161
                $dev
1162
            ) . "\" | {$grepPath} \"{$part}\$\"";
1163
        Processes::mwExec($command, $out);
1164
        $devName = trim(implode('', $out));
1165
        return trim($devName);
1166
    }
1167
1168
    /**
1169
     * Creates system folders according to config file
1170
     *
1171
     * @return void
1172
     */
1173
    private function createWorkDirs(): void
1174
    {
1175
        $path = '';
1176
        $mountPath = Util::which('mount');
1177
        Processes::mwExec("{$mountPath} -o remount,rw /offload 2> /dev/null");
1178
1179
        $isLiveCd = file_exists('/offload/livecd');
1180
        // Create dirs
1181
        $arrConfig = $this->config->toArray();
1182
        foreach ($arrConfig as $rootEntry) {
1183
            foreach ($rootEntry as $key => $entry) {
1184
                if (stripos($key, 'path') === false && stripos($key, 'dir') === false) {
1185
                    continue;
1186
                }
1187
                if (file_exists($entry)) {
1188
                    continue;
1189
                }
1190
                if ($isLiveCd && strpos($entry, '/offload/') === 0) {
1191
                    continue;
1192
                }
1193
                $path .= " $entry";
1194
            }
1195
        }
1196
1197
        if (!empty($path)) {
1198
            Util::mwMkdir($path);
1199
        }
1200
1201
        $downloadCacheDir = appPath('sites/pbxcore/files/cache');
1202
        if (!$isLiveCd) {
1203
            Util::mwMkdir($downloadCacheDir);
1204
            Util::createUpdateSymlink($this->config->path('www.downloadCacheDir'), $downloadCacheDir);
1205
        }
1206
1207
        $this->createAssetsSymlinks();
1208
1209
        Util::createUpdateSymlink($this->config->path('www.phpSessionDir'), '/var/lib/php/session');
1210
        Util::createUpdateSymlink($this->config->path('www.uploadDir'), '/ultmp');
1211
1212
        $filePath = appPath('src/Core/Asterisk/Configs/lua/extensions.lua');
1213
        Util::createUpdateSymlink($filePath, '/etc/asterisk/extensions.lua');
1214
1215
        // Create symlinks to AGI-BIN
1216
        $agiBinDir = $this->config->path('asterisk.astagidir');
1217
        if ($isLiveCd && strpos($agiBinDir, '/offload/') !== 0) {
1218
            Util::mwMkdir($agiBinDir);
1219
        }
1220
1221
        $roAgiBinFolder = appPath('src/Core/Asterisk/agi-bin');
1222
        $files = glob("{$roAgiBinFolder}/*.{php}", GLOB_BRACE);
1223
        foreach ($files as $file) {
1224
            $fileInfo = pathinfo($file);
1225
            $newFilename = "{$agiBinDir}/{$fileInfo['filename']}.{$fileInfo['extension']}";
1226
            Util::createUpdateSymlink($file, $newFilename);
1227
        }
1228
        $this->clearCacheFiles();
1229
        $this->applyFolderRights();
1230
        Processes::mwExec("{$mountPath} -o remount,ro /offload 2> /dev/null");
1231
    }
1232
1233
    /**
1234
     * Creates JS, CSS, IMG cache folders and links
1235
     *
1236
     */
1237
    public function createAssetsSymlinks(): void
1238
    {
1239
        $jsCacheDir = appPath('sites/admin-cabinet/assets/js/cache');
1240
        Util::createUpdateSymlink($this->config->path('adminApplication.assetsCacheDir') . '/js', $jsCacheDir);
1241
1242
        $cssCacheDir = appPath('sites/admin-cabinet/assets/css/cache');
1243
        Util::createUpdateSymlink($this->config->path('adminApplication.assetsCacheDir') . '/css', $cssCacheDir);
1244
1245
        $imgCacheDir = appPath('sites/admin-cabinet/assets/img/cache');
1246
        Util::createUpdateSymlink($this->config->path('adminApplication.assetsCacheDir') . '/img', $imgCacheDir);
1247
    }
1248
1249
    /**
1250
     * Clears cache folders from old and orphaned files
1251
     */
1252
    public function clearCacheFiles(): void
1253
    {
1254
        $cacheDirs = [];
1255
        $cacheDirs[] = $this->config->path('www.uploadDir');
1256
        $cacheDirs[] = $this->config->path('www.downloadCacheDir');
1257
        $cacheDirs[] = $this->config->path('adminApplication.assetsCacheDir') . '/js';
1258
        $cacheDirs[] = $this->config->path('adminApplication.assetsCacheDir') . '/css';
1259
        $cacheDirs[] = $this->config->path('adminApplication.assetsCacheDir') . '/img';
1260
        $cacheDirs[] = $this->config->path('adminApplication.voltCacheDir');
1261
        $rmPath = Util::which('rm');
1262
        foreach ($cacheDirs as $cacheDir) {
1263
            if (!empty($cacheDir)) {
1264
                Processes::mwExec("{$rmPath} -rf {$cacheDir}/*");
1265
            }
1266
        }
1267
1268
        // Delete boot cache folders
1269
        if (is_dir('/mountpoint') && self::isStorageDiskMounted()) {
1270
            Processes::mwExec("{$rmPath} -rf /mountpoint");
1271
        }
1272
    }
1273
1274
    /**
1275
     * Create system folders and links after upgrade and connect config DB
1276
     */
1277
    public function createWorkDirsAfterDBUpgrade(): void
1278
    {
1279
        $mountPath = Util::which('mount');
1280
        Processes::mwExec("{$mountPath} -o remount,rw /offload 2> /dev/null");
1281
        $this->createModulesCacheSymlinks();
1282
        $this->applyFolderRights();
1283
        Processes::mwExec("{$mountPath} -o remount,ro /offload 2> /dev/null");
1284
    }
1285
1286
    /**
1287
     * Restore modules cache folders and symlinks
1288
     */
1289
    public function createModulesCacheSymlinks(): void
1290
    {
1291
        $modules = PbxExtensionModules::getModulesArray();
1292
        foreach ($modules as $module) {
1293
            PbxExtensionUtils::createAssetsSymlinks($module['uniqid']);
1294
            PbxExtensionUtils::createAgiBinSymlinks($module['uniqid']);
1295
        }
1296
    }
1297
1298
    /**
1299
     * Fixes permissions for Folder and Files
1300
     */
1301
    private function applyFolderRights(): void
1302
    {
1303
        // Add Rights to the WWW dirs plus some core dirs
1304
        $www_dirs = [];
1305
        $exec_dirs = [];
1306
1307
        $arrConfig = $this->config->toArray();
1308
        foreach ($arrConfig as $key => $entry) {
1309
            if (in_array($key, ['www', 'adminApplication'])) {
1310
                foreach ($entry as $subKey => $subEntry) {
1311
                    if (stripos($subKey, 'path') === false && stripos($subKey, 'dir') === false) {
1312
                        continue;
1313
                    }
1314
                    $www_dirs[] = $subEntry;
1315
                }
1316
            }
1317
        }
1318
1319
        $www_dirs[] = $this->config->path('core.tempDir');
1320
        $www_dirs[] = $this->config->path('core.logsDir');
1321
1322
        // Create empty log files with www rights
1323
        $logFiles = [
1324
            $this->config->path('database.debugLogFile'),
1325
            $this->config->path('cdrDatabase.debugLogFile'),
1326
            $this->config->path('eventsLogDatabase.debugLogFile')
1327
        ];
1328
1329
        foreach ($logFiles as $logFile) {
1330
            $filename = (string)$logFile;
1331
            if (!file_exists($filename)) {
1332
                file_put_contents($filename, '');
1333
            }
1334
            $www_dirs[] = $filename;
1335
        }
1336
1337
        $www_dirs[] = '/etc/version';
1338
        $www_dirs[] = appPath('/');
1339
1340
        // Add read rights
1341
        Util::addRegularWWWRights(implode(' ', $www_dirs));
1342
1343
        // Add executable rights
1344
        $exec_dirs[] = appPath('src/Core/Asterisk/agi-bin');
1345
        $exec_dirs[] = appPath('src/Core/Rc');
1346
        Util::addExecutableRights(implode(' ', $exec_dirs));
1347
1348
        $mountPath = Util::which('mount');
1349
        Processes::mwExec("{$mountPath} -o remount,ro /offload 2> /dev/null");
1350
    }
1351
1352
    /**
1353
     * Creates swap file on storage
1354
     */
1355
    public function mountSwap(): void
1356
    {
1357
        if(Util::isSystemctl()){
1358
            // Не настраиваем.
1359
            return;
1360
        }
1361
        $tempDir = $this->config->path('core.tempDir');
1362
        $swapFile = "{$tempDir}/swapfile";
1363
1364
        $swapOffCmd = Util::which('swapoff');
1365
        Processes::mwExec("{$swapOffCmd} {$swapFile}");
1366
1367
        $this->makeSwapFile($swapFile);
1368
        if (!file_exists($swapFile)) {
1369
            return;
1370
        }
1371
        $swapOnCmd = Util::which('swapon');
1372
        $result = Processes::mwExec("{$swapOnCmd} {$swapFile}");
1373
        Util::sysLogMsg('Swap', 'connect swap result: ' . $result, LOG_INFO);
1374
    }
1375
1376
    /**
1377
     * Создает swap файл на storage.
1378
     * @param $swapFile
1379
     */
1380
    private function makeSwapFile($swapFile): void
1381
    {
1382
        $swapLabel = Util::which('swaplabel');
1383
        if (Processes::mwExec("{$swapLabel} {$swapFile}") === 0) {
1384
            // Файл уже существует.
1385
            return;
1386
        }
1387
        if (file_exists($swapFile)) {
1388
            unlink($swapFile);
1389
        }
1390
1391
        $size = $this->getStorageFreeSpaceMb();
1392
        if ($size > 2000) {
1393
            $swapSize = 1024;
1394
        } elseif ($size > 1000) {
1395
            $swapSize = 512;
1396
        } else {
1397
            // Не достаточно свободного места.
1398
            return;
1399
        }
1400
        $bs = 1024;
1401
        $countBlock = $swapSize * $bs;
1402
        $ddCmd = Util::which('dd');
1403
1404
        Util::sysLogMsg('Swap', 'make swap ' . $swapFile, LOG_INFO);
1405
        Processes::mwExec("{$ddCmd} if=/dev/zero of={$swapFile} bs={$bs} count={$countBlock}");
1406
1407
        $mkSwapCmd = Util::which('mkswap');
1408
        Processes::mwExec("{$mkSwapCmd} {$swapFile}");
1409
    }
1410
1411
    /**
1412
     * Returns free space on mounted storage disk
1413
     *
1414
     * @return int size in megabytes
1415
     */
1416
    public function getStorageFreeSpaceMb(): int
1417
    {
1418
        $size = 0;
1419
        $mntDir = '';
1420
        $mounted = self::isStorageDiskMounted('', $mntDir);
1421
        if (!$mounted) {
1422
            return 0;
1423
        }
1424
        $hd = $this->getAllHdd(true);
1425
        foreach ($hd as $disk) {
1426
            if ($disk['mounted'] === $mntDir) {
1427
                $size = $disk['free_space'];
1428
                break;
1429
            }
1430
        }
1431
        return $size;
1432
    }
1433
1434
    /**
1435
     * Сохраняем новые данные диска.
1436
     *
1437
     * @param        $data
1438
     * @param string $id
1439
     */
1440
    public function saveDiskSettings($data, $id = '1'): void
1441
    {
1442
        if (!is_array($data)) {
1443
            return;
1444
        }
1445
        $disk_data = $this->getDiskSettings($id);
1446
        if (count($disk_data) === 0) {
1447
            $uniqid = strtoupper('STORAGE-DISK-' . md5(time()));
1448
            $storage_settings = new StorageModel();
1449
            foreach ($data as $key => $val) {
1450
                $storage_settings->writeAttribute($key, $val);
1451
            }
1452
            $storage_settings->writeAttribute('uniqid', $uniqid);
1453
            $storage_settings->save();
1454
        } else {
1455
            $storage_settings = StorageModel::findFirst("id = '$id'");
1456
            if ($storage_settings === null) {
1457
                return;
1458
            }
1459
            foreach ($data as $key => $value) {
1460
                $storage_settings->writeAttribute($key, $value);
1461
            }
1462
            $storage_settings->save();
1463
        }
1464
    }
1465
1466
    /**
1467
     * Получение имени диска, смонтированного на conf.recover.
1468
     * @return string
1469
     */
1470
    public function getRecoverDiskName(): string
1471
    {
1472
        $disks = $this->diskGetDevices(true);
1473
        foreach ($disks as $disk => $diskInfo) {
1474
            // RAID содержит вложенный массив "children"
1475
            if (isset($diskInfo['children'][0]['children'])) {
1476
                $diskInfo = $diskInfo['children'][0];
1477
                // Корректируем имя диска. Это RAID или иной виртуальный device.
1478
                $disk = $diskInfo['name'];
1479
            }
1480
            foreach ($diskInfo['children'] as $child) {
1481
                $mountpoint = $child['mountpoint'] ?? '';
1482
                $diskPath = "/dev/{$disk}";
1483
                if ($mountpoint === '/conf.recover' && file_exists($diskPath)) {
1484
                    return "/dev/{$disk}";
1485
                }
1486
            }
1487
        }
1488
        return '';
1489
    }
1490
}