Passed
Push — develop ( 3c5942...636dd4 )
by Nikolay
04:57
created

System::setDate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 25
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 16
dl 0
loc 25
rs 9.7333
c 0
b 0
f 0
cc 2
nc 2
nop 1
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, 5 2020
7
 */
8
9
namespace MikoPBX\Core\System;
10
11
use Exception;
12
use MikoPBX\Common\Models\CustomFiles;
13
use MikoPBX\Core\Asterisk\Configs\{QueueConf};
14
use MikoPBX\Core\Workers\Cron\WorkerSafeScriptsCore;
15
use MikoPBX\Core\Workers\WorkerDownloader;
16
use Phalcon\Di;
17
use function MikoPBX\Common\Config\appPath;
18
19
/**
20
 *
21
 */
22
class System
23
{
24
    private MikoPBXConfig $mikoPBXConfig;
25
    private $di;
26
27
    /**
28
     * System constructor.
29
     */
30
    public function __construct()
31
    {
32
        $this->di = Di::getDefault();
33
34
        // Класс / обертка для работы с настройками.
35
        $this->mikoPBXConfig = new MikoPBXConfig();
36
    }
37
38
    public static function setupPhpLog(): void
39
    {
40
        $src_log_file = '/var/log/php_error.log';
41
        $dst_log_file = self::getPhpFile();
42
        if ( ! file_exists($src_log_file)) {
43
            file_put_contents($src_log_file, '');
44
        }
45
        $options = file_exists($dst_log_file) ? '>' : '';
46
        $catPath = Util::which('cat');
47
        Util::mwExec("{$catPath} {$src_log_file} 2> /dev/null >{$options} {$dst_log_file}");
48
        Util::createUpdateSymlink($dst_log_file, $src_log_file);
49
    }
50
51
    public static function getPhpFile(): string
52
    {
53
        $logdir = self::getLogDir() . '/php';
54
        Util::mwMkdir($logdir);
55
        return "$logdir/error";
56
    }
57
58
    public static function getLogDir(): string
59
    {
60
        $di     = Di::getDefault();
61
        if ($di !== null){
62
            return $di->getConfig()->path('core.logsPath');
63
        }
64
        return '/var/log';
65
    }
66
67
    public static function rotatePhpLog(): void
68
    {
69
        $asteriskPath = Util::which('asterisk');
70
        $logrotatePath = Util::which('logrotate');
71
72
        $max_size    = 2;
73
        $f_name      = self::getPhpFile();
74
        $text_config = (string)($f_name) . " {
75
    nocreate
76
    nocopytruncate
77
    delaycompress
78
    nomissingok
79
    start 0
80
    rotate 9
81
    size {$max_size}M
82
    missingok
83
    noolddir
84
    postrotate
85
        {$asteriskPath} -rx 'logger reload' > /dev/null 2> /dev/null
86
    endscript
87
}";
88
        $di     = Di::getDefault();
89
        if ($di !== null){
90
            $varEtcPath = $di->getConfig()->path('core.varEtcPath');
91
        } else {
92
            $varEtcPath = '/var/etc';
93
        }
94
        $path_conf   = $varEtcPath . '/php_logrotate_' . basename($f_name) . '.conf';
95
        file_put_contents($path_conf, $text_config);
96
        $mb10 = $max_size * 1024 * 1024;
97
98
        $options = '';
99
        if (Util::mFileSize($f_name) > $mb10) {
100
            $options = '-f';
101
        }
102
        Util::mwExecBg("{$logrotatePath} {$options} '{$path_conf}' > /dev/null 2> /dev/null");
103
    }
104
105
    /**
106
     *
107
     */
108
    public static function gnatsLogRotate(): void
109
    {
110
        $log_dir = self::getLogDir() . '/nats';
111
        $gnatsdPath = Util::which('gnatsd');
112
        $pid     = Util::getPidOfProcess($gnatsdPath, 'custom_modules');
113
        $max_size = 1;
114
        if (empty($pid)) {
115
            $system = new System();
116
            $system->gnatsStart();
117
            sleep(1);
118
        }
119
        $text_config = "{$log_dir}/gnatsd.log {
120
    start 0
121
    rotate 9
122
    size {$max_size}M
123
    maxsize 1M
124
    daily
125
    missingok
126
    notifempty
127
    sharedscripts
128
    postrotate
129
        {$gnatsdPath} -sl reopen=$pid > /dev/null 2> /dev/null
130
    endscript
131
}";
132
133
        $mb10 = $max_size * 1024 * 1024;
134
135
        $options = '';
136
        if (Util::mFileSize("{$log_dir}/gnatsd.log") > $mb10) {
137
            $options = '-f';
138
        }
139
        $di     = Di::getDefault();
140
        if ($di !== null){
141
            $varEtcPath = $di->getConfig()->path('core.varEtcPath');
142
        } else {
143
            $varEtcPath = '/var/etc';
144
        }
145
        $path_conf  = $varEtcPath . '/gnatsd_logrotate.conf';
146
        file_put_contents($path_conf, $text_config);
147
        if (file_exists("{$log_dir}/gnatsd.log")) {
148
            $logrotatePath = Util::which('logrotate');
149
            Util::mwExecBg("{$logrotatePath} $options '{$path_conf}' > /dev/null 2> /dev/null");
150
        }
151
    }
152
153
    /**
154
     * Старт сервера обработки очередей задач.
155
     *
156
     * @return array
157
     */
158
    public function gnatsStart(): array
159
    {
160
        $confdir = '/etc/nats';
161
        Util::mwMkdir($confdir);
162
        $logdir = self::getLogDir() . '/nats';
163
        Util::mwMkdir($logdir);
164
165
        $pid_file = '/var/run/gnatsd.pid';
166
        $settings = [
167
            'port'             => '4223',
168
            'http_port'        => '8223',
169
            'debug'            => 'false',
170
            'trace'            => 'false',
171
            'logtime'          => 'true',
172
            'pid_file'         => $pid_file,
173
            'max_connections'  => '1000',
174
            'max_payload'      => '1000000',
175
            'max_control_line' => '512',
176
            'sessions_path'    => $logdir,
177
            'log_file'         => "{$logdir}/gnatsd.log",
178
        ];
179
        $config   = '';
180
        foreach ($settings as $key => $val) {
181
            $config .= "{$key}: {$val} \n";
182
        }
183
        $conf_file = "{$confdir}/natsd.conf";
184
        Util::fileWriteContent($conf_file, $config);
185
186
        $lic = $this->mikoPBXConfig->getGeneralSettings('PBXLicense');
187
        file_put_contents($logdir . '/license.key', $lic);
188
189
        if (file_exists($pid_file)) {
190
            $killPath = Util::which('kill');
191
            $catPath = Util::which('kill');
192
            Util::mwExec("{$killPath} $({$catPath} {$pid_file})");
193
        }
194
        $gnatsdPath = Util::which('gnatsd');
195
        Util::mwExecBg("{$gnatsdPath} --config {$conf_file}", "{$logdir}/gnats_process.log");
196
197
        $result = [
198
            'result' => 'Success',
199
        ];
200
201
        // Перезапуск сервисов.
202
        $additionalModules = $this->di->getShared('pbxConfModules');
0 ignored issues
show
Bug introduced by
The method getShared() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

202
        /** @scrutinizer ignore-call */ 
203
        $additionalModules = $this->di->getShared('pbxConfModules');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
203
        foreach ($additionalModules as $appClass) {
204
            /** @var \MikoPBX\Modules\Config\ConfigClass $appClass */
205
            $appClass->onNatsReload();
206
        }
207
208
        return $result;
209
    }
210
211
    /**
212
     * Reboots the system after calling system_reboot_cleanup()
213
     *
214
     */
215
    public static function rebootSync(): void
216
    {
217
        $mikopbx_rebootPath = Util::which('mikopbx_reboot');
218
        Util::mwExec("{$mikopbx_rebootPath} > /dev/null 2>&1");
219
    }
220
221
    /**
222
     * Reboots the system after calling system_reboot_cleanup()
223
     *
224
     */
225
    public static function rebootSyncBg(): void
226
    {
227
        $mikopbx_rebootPath = Util::which('mikopbx_reboot');
228
        Util::mwExecBg("{$mikopbx_rebootPath} > /dev/null 2>&1");
229
    }
230
231
    /**
232
     * Shutdown the system.
233
     */
234
    public static function shutdown(): void
235
    {
236
        $shutdownPath = Util::which('shutdown');
237
        Util::mwExec("{$shutdownPath} > /dev/null 2>&1");
238
    }
239
240
    /**
241
     * Рестарт сетевых интерфейсов.
242
     */
243
    public static function networkReload(): void
244
    {
245
        $system = new System();
246
        $system->hostnameConfigure();
247
248
        $network = new Network();
249
        $network->resolvConfGenerate();
250
        $network->loConfigure();
251
        $network->lanConfigure();
252
    }
253
254
    /**
255
     *    Устанавливаем имя хост текущей машины.
256
     **/
257
    public function hostnameConfigure(): int
258
    {
259
        $data       = Network::getHostName();
260
        $hosts_conf = "127.0.0.1 localhost\n" .
261
            "127.0.0.1 {$data['hostname']}\n";
262
        if ( ! empty($data['domain'])) {
263
            $hosts_conf .= "127.0.0.1 {$data['hostname']}.{$data['domain']}\n";
264
        }
265
        Util::fileWriteContent('/etc/hosts', $hosts_conf);
266
267
        $hostnamePath = Util::which('hostname');
268
        return Util::mwExec($hostnamePath.' '. escapeshellarg("{$data['hostname']}"));
0 ignored issues
show
Bug Best Practice introduced by
The expression return MikoPBX\Core\Syst...arg($data['hostname'])) could return the type null which is incompatible with the type-hinted return integer. Consider adding an additional type-check to rule them out.
Loading history...
269
    }
270
271
272
    /**
273
     * Получение сведений о системе.
274
     *
275
     * @return array
276
     */
277
    public static function getInfo(): array
278
    {
279
        $result = [
280
            'result' => 'Success',
281
        ];
282
283
        $storage        = new Storage();
284
        $data           = [
285
            'disks'  => $storage->getAllHdd(),
286
            'cpu'    => self::getCpu(),
287
            'uptime' => self::getUpTime(),
288
            'mem'    => self::getMemInfo(),
289
        ];
290
        $result['data'] = $data;
291
292
        return $result;
293
    }
294
295
    /**
296
     * Возвращает информацию по загрузке CPU.
297
     */
298
    public static function getCpu()
299
    {
300
        $ut = [];
301
        $grepPath = Util::which('grep');
302
        $mpstatPath = Util::which('mpstat');
303
        Util::mwExec("{$mpstatPath} | {$grepPath} all", $ut);
304
        preg_match("/^.*\s+all\s+.*\s+.*\s+.*\s+.*\s+.*\s+.*\s+.*\s+.*\s+(.*)\s*.*/i", $ut[0], $matches);
305
        $rv = 100 - $matches[1];
306
307
        if (100 < $rv) {
308
            $rv = 100;
309
        }
310
311
        return round($rv, 2);
312
    }
313
314
    /**
315
     * Получаем информацию по времени работы ПК.
316
     */
317
    public static function getUpTime(): string
318
    {
319
        $ut = [];
320
        $uptimePath = Util::which('uptime');
321
        $awkPath = Util::which('awk');
322
        Util::mwExec("{$uptimePath} | {$awkPath} -F \" |,\" '{print $5}'", $ut);
323
324
        return implode('', $ut);
325
    }
326
327
    /**
328
     * Получаем информацию по оперативной памяти.
329
     */
330
    public static function getMemInfo(): array
331
    {
332
        $result = [];
333
        $out    = [];
334
        $catPath = Util::which('cat');
335
        $grepPath = Util::which('grep');
336
        $awkPath = Util::which('awk');
337
        Util::mwExec("{$catPath} /proc/meminfo | {$grepPath} -C 0 'Inactive:' | {$awkPath} '{print $2}'", $out);
338
        $result['inactive'] = round((1 * implode($out)) / 1024, 2);
339
        Util::mwExec("{$catPath} /proc/meminfo | {$grepPath} -C 0 'MemFree:' | {$awkPath} '{print $2}'", $out);
340
        $result['free'] = round((1 * implode($out)) / 1024, 2);
341
        Util::mwExec("{$catPath} /proc/meminfo | {$grepPath} -C 0 'MemTotal:' | {$awkPath} '{print $2}'", $out);
342
        $result['total'] = round((1 * implode($out)) / 1024, 2);
343
344
        return $result;
345
    }
346
347
    /**
348
     * Обновление кофнигурации кастомных файлов.
349
     *
350
     * @return array
351
     */
352
    public static function updateCustomFiles()
353
    {
354
        $actions = [];
355
        /** @var \MikoPBX\Common\Models\CustomFiles $res_data */
356
        $res_data = CustomFiles::find("changed = '1'");
357
        foreach ($res_data as $file_data) {
358
            // Всегда рестрартуем все модули asterisk (только чтение конфигурации).
359
            $actions['asterisk_coreReload'] = 100;
360
            $filename                       = basename($file_data->filepath);
361
            switch ($filename) {
362
                case 'manager.conf':
363
                    $actions['manager'] = 10;
364
                    break;
365
                case 'musiconhold.conf':
366
                    $actions['musiconhold'] = 100;
367
                    break;
368
                case 'modules.conf':
369
                    $actions['modules'] = 10;
370
                    break;
371
                case 'http.conf':
372
                    $actions['manager'] = 10;
373
                    break;
374
                case 'root': // crontabs
375
                    $actions['cron'] = 10;
376
                    break;
377
                case 'queues.conf':
378
                    $actions['queues'] = 10;
379
                    break;
380
                case 'features.conf':
381
                    $actions['features'] = 10;
382
                    break;
383
                case 'ntp.conf':
384
                    $actions['systemtime'] = 100;
385
                    break;
386
                case 'jail.local': // fail2ban
387
                    $actions['firewall'] = 100;
388
                    break;
389
            }
390
        }
391
392
        asort($actions);
393
        $result = self::invokeActions($actions);
394
        if ($result['result'] !== 'ERROR') {
395
            // Зафиксируем результат работы.
396
            foreach ($res_data as $file_data) {
397
                /** @var \MikoPBX\Common\Models\CustomFiles $file_data */
398
                $file_data->writeAttribute("changed", '0');
399
                $file_data->save();
400
            }
401
        }
402
403
        return $result;
404
    }
405
406
    /**
407
     * Выполнение набора действий по рестарту модулей системы.
408
     *
409
     * @param $actions
410
     *
411
     * @return array|mixed
412
     */
413
    public static function invokeActions($actions)
414
    {
415
        $result = [
416
            'result' => 'Success',
417
        ];
418
        foreach ($actions as $action => $value) {
419
            $res = null;
420
            switch ($action) {
421
                case 'manager':
422
                    $res = PBX::managerReload();
423
                    break;
424
                case 'musiconhold':
425
                    $res = PBX::musicOnHoldReload();
426
                    break;
427
                case 'modules':
428
                    $res = PBX::modulesReload();
429
                    break;
430
                case 'cron':
431
                    $system = new System();
432
                    $system->cronConfigure();
433
                    break;
434
                case 'queues':
435
                    $res = QueueConf::queueReload();
436
                    break;
437
                case 'features':
438
                    $res = PBX::managerReload();
439
                    break;
440
                case 'systemtime':
441
                    $res = DateTime::setDate('');
442
                    break;
443
                case 'firewall':
444
                    $res = Firewall::reloadFirewall();
445
                    break;
446
                case 'asterisk_coreReload':
447
                    PBX::sipReload();
448
                    PBX::iaxReload();
449
                    PBX::dialplanReload();
450
                    $res = PBX::coreReload();
451
                    break;
452
            }
453
            if ($res !== null && $res['result'] === 'ERROR') {
454
                $result = $res['result'];
455
                break;
456
            }
457
        }
458
459
        return $result;
460
    }
461
462
    /**
463
     * Настройка cron. Запуск демона.
464
     *
465
     * @return int
466
     */
467
    public function cronConfigure(): int
468
    {
469
        $booting = $this->di->getRegistry()->booting;
470
        $this->cronGenerate($booting);
471
        if (Util::isSystemctl()) {
472
            $systemctlPath = Util::which('systemctl');
473
            Util::mwExec("{$systemctlPath} restart cron");
474
        } else {
475
            $crondPath = Util::which('crond');
476
            Util::killByName($crondPath);
477
            Util::mwExec("{$crondPath} -L /dev/null -l 8");
478
        }
479
        return 0;
480
    }
481
482
    /**
483
     * Генератор конфига cron.
484
     *
485
     * @param bool $boot
486
     */
487
    private function cronGenerate($boot = true): void
488
    {
489
        $additionalModules = $this->di->getShared('pbxConfModules');
490
        $mast_have         = [];
491
492
        if (Util::isSystemctl()) {
493
            $mast_have[]   = "SHELL=/bin/sh\n";
494
            $mast_have[]   = "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n\n";
495
            $cron_filename = '/etc/cron.d/mikopbx';
496
            $cron_user     = 'root ';
497
        } else {
498
            $cron_filename = '/var/spool/cron/crontabs/root';
499
            $cron_user     = '';
500
        }
501
502
503
        $workerSafeScriptsPath = Util::getFilePathByClassName(WorkerSafeScriptsCore::class);
504
        $phpPath = Util::which('php');
505
        $WorkerSafeScripts = "{$phpPath} -f {$workerSafeScriptsPath} start > /dev/null 2> /dev/null";
506
507
        $workersPath = appPath('src/Core/Workers');
508
509
        $restart_night = $this->mikoPBXConfig->getGeneralSettings('RestartEveryNight');
510
        $asteriskPath = Util::which('asterisk');
511
        $ntpdPath = Util::which('ntpd');
512
        $shPath = Util::which('sh');
513
        if ($restart_night === '1') {
514
            $mast_have[] = '0 1 * * * ' . $cron_user .$asteriskPath.' -rx"core restart now" > /dev/null 2> /dev/null' . "\n";
515
        }
516
        $mast_have[] = '*/5 * * * * ' . $cron_user . $ntpdPath.' -q > /dev/null 2> /dev/null' . "\n";
517
        $mast_have[] = '*/6 * * * * ' . $cron_user . "{$shPath} {$workersPath}/Cron/cleaner_download_links.sh  download_link > /dev/null 2> /dev/null\n";
518
        $mast_have[] = '*/1 * * * * ' . $cron_user . "{$WorkerSafeScripts}\n";
519
520
        $tasks = [];
521
        // Дополнительные модули также могут добавить задачи в cron.
522
        foreach ($additionalModules as $appClass) {
523
            /** @var \MikoPBX\Modules\Config\ConfigClass $appClass */
524
            $appClass->createCronTasks($tasks);
525
        }
526
        $conf = implode('', array_merge($mast_have, $tasks));
527
528
        if (Util::isSystemctl()) {
529
            // Обеспечим совместимость всех существующих модулей с Debian.
530
            $conf = str_replace(' * * * * /', ' * * * * root /', $conf);
531
        }
532
533
        if ($boot === true) {
534
            Util::mwExecBg($WorkerSafeScripts);
535
        }
536
537
        Util::fileWriteContent($cron_filename, $conf);
538
    }
539
540
    public static function convertConfig($config_file = ''): array
541
    {
542
        $result  = [
543
            'result'  => 'Success',
544
            'message' => '',
545
        ];
546
        $di     = Di::getDefault();
547
        if ($di !== null){
548
            $tempDir = $di->getConfig()->path('core.tempPath');
549
        } else {
550
            $tempDir = '/tmp';
551
        }
552
        if (empty($config_file)) {
553
            $config_file = "{$tempDir}/old_config.xml";
554
        }
555
556
        if (file_exists($config_file)) {
557
            try {
558
                $cntr = new OldConfigConverter($config_file);
559
                $cntr->parse();
560
                $cntr->makeConfig();
561
                file_put_contents('/tmp/ejectcd', '');
562
                $mikopbx_rebootPath = Util::which('mikopbx_reboot');
563
                Util::mwExecBg($mikopbx_rebootPath, '/dev/null', 3);
564
            } catch (Exception $e) {
565
                $result = [
566
                    'result'  => 'Error',
567
                    'message' => $e->getMessage(),
568
                ];
569
            }
570
        } else {
571
            $result = [
572
                'result'  => 'Error',
573
                'message' => 'XML config not found',
574
            ];
575
        }
576
577
        return $result;
578
    }
579
580
    /**
581
     * Запускает процесс обновление АТС из img образа.
582
     *
583
     * @return array
584
     */
585
    public static function upgradeFromImg(): array
586
    {
587
        $result = [
588
            'result'  => 'Success',
589
            'message' => 'In progress...',
590
            'info'    => 'Update from local file',
591
        ];
592
593
        $di     = Di::getDefault();
594
        if ($di !== null){
595
            $tempDir = $di->getConfig()->path('core.tempPath');
596
        } else {
597
            $tempDir = '/tmp';
598
        }
599
        $upd_file = "{$tempDir}/update.img";
600
        if ( ! file_exists($upd_file)) {
601
            $upd_file       = "{$tempDir}/upgradeOnline/update.img";
602
            $result['info'] = 'Online update';
603
        }
604
        if ( ! file_exists($upd_file)) {
605
            $result['result']  = 'Error';
606
            $result['message'] = 'IMG file not found';
607
            $result['path']    = $upd_file;
608
609
            return $result;
610
        }
611
        if ( ! file_exists('/var/etc/cfdevice')) {
612
            $result['result']  = 'Error';
613
            $result['message'] = 'The system is not installed';
614
            $result['path']    = $upd_file;
615
616
            return $result;
617
        }
618
        $dev = trim(file_get_contents('/var/etc/cfdevice'));
619
620
        $link = '/tmp/firmware_update.img';
621
        Util::createUpdateSymlink($upd_file, $link);
622
        $mikopbx_firmwarePath = Util::which('mikopbx_firmware');
623
        Util::mwExecBg("{$mikopbx_firmwarePath} recover_upgrade {$link} /dev/{$dev}");
624
625
        return $result;
626
    }
627
628
    /**
629
     * Обновление АТС путем скачивания образа с ресурса МИКО.
630
     *
631
     * @param $data
632
     *
633
     * @return mixed
634
     */
635
    public static function upgradeOnline($data)
636
    {
637
        $di     = Di::getDefault();
638
        if ($di !== null){
639
            $tempDir = $di->getConfig()->path('core.tempPath');
640
        } else {
641
            $tempDir = '/tmp';
642
        }
643
        $rmPath = Util::which('rm');
644
        $module  = 'upgradeOnline';
645
        if ( ! file_exists($tempDir . "/{$module}")) {
646
            Util::mwMkdir($tempDir . "/{$module}");
647
        } else {
648
            // Чистим файлы, загруженные онлайн.
649
            Util::mwExec("{$rmPath} -rf {$tempDir}/{$module}/* ");
650
        }
651
        if (file_exists("{$tempDir}/update.img")) {
652
            // Чистим вручную загруженный файл.
653
            Util::mwExec("{$rmPath} -rf {$tempDir}/update.img");
654
        }
655
656
        $download_settings = [
657
            'res_file' => "{$tempDir}/{$module}/update.img",
658
            'url'      => $data['url'],
659
            'module'   => $module,
660
            'md5'      => $data['md5'],
661
            'action'   => $module,
662
        ];
663
664
        $workerDownloaderPath = Util::getFilePathByClassName(WorkerDownloader::class);
665
666
        file_put_contents($tempDir . "/{$module}/progress", '0');
667
        file_put_contents(
668
            $tempDir . "/{$module}/download_settings.json",
669
            json_encode($download_settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
670
        );
671
        $phpPath = Util::which('php');
672
        Util::mwExecBg("{$phpPath} -f {$workerDownloaderPath} " . $tempDir . "/{$module}/download_settings.json");
673
        // Ожидание запуска процесса загрузки.
674
        usleep(500000);
675
        $d_pid = Util::getPidOfProcess($tempDir . "/{$module}/download_settings.json");
676
        if (empty($d_pid)) {
677
            sleep(1);
678
        }
679
680
        $result['result'] = 'Success';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.
Loading history...
681
682
        return $result;
683
    }
684
685
    /**
686
     * Возвращает информацию по статусу загрузки файла обновления img.
687
     *
688
     * @return array
689
     */
690
    public static function statusUpgrade(): array
691
    {
692
        clearstatcache();
693
        $result        = [
694
            'result' => 'Success',
695
        ];
696
        $di     = Di::getDefault();
697
        if ($di !== null){
698
            $tempDir = $di->getConfig()->path('core.tempPath');
699
        } else {
700
            $tempDir = '/tmp';
701
        }
702
        $modulesDir    = $tempDir . '/upgradeOnline';
703
        $progress_file = $modulesDir . '/progress';
704
705
        $error = '';
706
        if (file_exists($modulesDir . '/error')) {
707
            $error = trim(file_get_contents($modulesDir . '/error'));
708
        }
709
710
        if ( ! file_exists($progress_file)) {
711
            $result['d_status_progress'] = '0';
712
            $result['d_status']          = 'NOT_FOUND';
713
        } elseif ('' !== $error) {
714
            $result['d_status']          = 'DOWNLOAD_ERROR';
715
            $result['d_status_progress'] = file_get_contents($progress_file);
716
            $result['d_error']           = $error;
717
        } elseif ('100' === file_get_contents($progress_file)) {
718
            $result['d_status_progress'] = '100';
719
            $result['d_status']          = 'DOWNLOAD_COMPLETE';
720
        } else {
721
            $result['d_status_progress'] = file_get_contents($progress_file);
722
            $d_pid                       = Util::getPidOfProcess($tempDir . '/upgradeOnline/download_settings.json');
723
            if (empty($d_pid)) {
724
                $result['d_status'] = 'DOWNLOAD_ERROR';
725
                $error              = '';
726
                if (file_exists($modulesDir . '/error')) {
727
                    $error = file_get_contents($modulesDir . '/error');
728
                }
729
                $result['d_error'] = $error;
730
            } else {
731
                $result['d_status'] = 'DOWNLOAD_IN_PROGRESS';
732
            }
733
        }
734
735
        return $result;
736
    }
737
738
    /**
739
     * Возвращает статус скачивания модуля.
740
     *
741
     * @param $module - Module ID
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
742
     *
743
     * @return array
744
     */
745
    public static function moduleDownloadStatus($module): array
746
    {
747
        clearstatcache();
748
        $result        = [
749
            'result' => 'Success',
750
            'data'   => null,
751
        ];
752
        $di     = Di::getDefault();
753
        if ($di !== null){
754
            $tempDir = $di->getConfig()->path('core.tempPath');
755
        } else {
756
            $tempDir = '/tmp';
757
        }
758
        $moduleDirTmp  = $tempDir . '/' . $module;
759
        $progress_file = $moduleDirTmp . '/progress';
760
        $error         = '';
761
        if (file_exists($moduleDirTmp . '/error')) {
762
            $error = trim(file_get_contents($moduleDirTmp . '/error'));
763
        }
764
765
        // Ожидание запуска процесса загрузки.
766
        $d_pid = Util::getPidOfProcess("{$moduleDirTmp}/download_settings.json");
767
        if (empty($d_pid)) {
768
            usleep(500000);
769
        }
770
771
        if ( ! file_exists($progress_file)) {
772
            $result['d_status_progress'] = '0';
773
            $result['d_status']          = 'NOT_FOUND';
774
            $result['i_status']          = false;
775
        } elseif ('' !== $error) {
776
            $result['d_status']          = 'DOWNLOAD_ERROR';
777
            $result['d_status_progress'] = file_get_contents($progress_file);
778
            $result['d_error']           = $error;
779
            $result['i_status']          = false;
780
        } elseif ('100' === file_get_contents($progress_file)) {
781
            $result['d_status_progress'] = '100';
782
            $result['d_status']          = 'DOWNLOAD_COMPLETE';
783
            $result['i_status']          = file_exists($moduleDirTmp . '/installed');
784
        } else {
785
            $result['d_status_progress'] = file_get_contents($progress_file);
786
            $d_pid                       = Util::getPidOfProcess($moduleDirTmp . '/download_settings.json');
787
            if (empty($d_pid)) {
788
                $result['d_status'] = 'DOWNLOAD_ERROR';
789
                $error              = '';
790
                if (file_exists($moduleDirTmp . '/error')) {
791
                    $error = file_get_contents($moduleDirTmp . '/error');
792
                }
793
                $result['d_error'] = $error;
794
            } else {
795
                $result['d_status'] = 'DOWNLOAD_IN_PROGRESS';
796
            }
797
            $result['i_status'] = false;
798
        }
799
800
        return $result;
801
    }
802
803
    /**
804
     * Запуск процесса фоновой загрузки доп. модуля АТС.
805
     *
806
     * @param $module
807
     * @param $url
808
     * @param $md5
809
     *
810
     * @return void
811
     */
812
    public static function moduleStartDownload($module, $url, $md5): void
813
    {
814
        $di     = Di::getDefault();
815
        if ($di !== null){
816
            $tempDir = $di->getConfig()->path('core.tempPath');
817
        } else {
818
            $tempDir = '/tmp';
819
        }
820
821
        $moduleDirTmp = "{$tempDir}/{$module}";
822
        Util::mwMkdir($moduleDirTmp);
823
824
        $download_settings = [
825
            'res_file' => "$moduleDirTmp/modulefile.zip",
826
            'url'      => $url,
827
            'module'   => $module,
828
            'md5'      => $md5,
829
            'action'   => 'module_install',
830
        ];
831
        if (file_exists("$moduleDirTmp/error")) {
832
            unlink("$moduleDirTmp/error");
833
        }
834
        if (file_exists("$moduleDirTmp/installed")) {
835
            unlink("$moduleDirTmp/installed");
836
        }
837
        file_put_contents("$moduleDirTmp/progress", '0');
838
        file_put_contents(
839
            "$moduleDirTmp/download_settings.json",
840
            json_encode($download_settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
841
        );
842
        $workerDownloaderPath = Util::getFilePathByClassName(WorkerDownloader::class);
843
        $phpPath = Util::which('php');
844
        Util::mwExecBg("{$phpPath} -f {$workerDownloaderPath} $moduleDirTmp/download_settings.json");
845
    }
846
847
    /**
848
     * Получение информации о публичном IP.
849
     *
850
     * @return array
851
     */
852
    public static function getExternalIpInfo(): array
853
    {
854
        $result = [
855
            'result'  => 'Error',
856
            'message' => null,
857
            'data'    => null,
858
        ];
859
        $curl   = curl_init();
860
        $url    = 'https://ipinfo.io/json';
861
        curl_setopt($curl, CURLOPT_URL, $url);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_setopt() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

861
        curl_setopt(/** @scrutinizer ignore-type */ $curl, CURLOPT_URL, $url);
Loading history...
862
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
863
        curl_setopt($curl, CURLOPT_TIMEOUT, 2);
864
865
        try {
866
            $resultrequest = curl_exec($curl);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_exec() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

866
            $resultrequest = curl_exec(/** @scrutinizer ignore-type */ $curl);
Loading history...
867
        } catch (Exception $e) {
868
            $result['message'] = $e->getMessage();
869
870
            return $result;
871
        }
872
        curl_close($curl);
0 ignored issues
show
Bug introduced by
It seems like $curl can also be of type false; however, parameter $ch of curl_close() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

872
        curl_close(/** @scrutinizer ignore-type */ $curl);
Loading history...
873
        if (Util::isJson($resultrequest)) {
874
            $result['result'] = 'Success';
875
            $result['data']   = json_decode($resultrequest, true);
876
        } else {
877
            $result['message'] = 'Error format data ' . $resultrequest;
878
        }
879
880
        return $result;
881
    }
882
883
    /**
884
     * Подгрузка дополнительных модулей ядра.
885
     */
886
    public function loadKernelModules(): void
887
    {
888
        $modprobePath = Util::which('modprobe');
889
        $ulimitPath = Util::which('ulimit');
890
891
        Util::mwExec("{$modprobePath} -q dahdi");
892
        Util::mwExec("{$modprobePath} -q dahdi_transcode");
893
        Util::mwExec("{$ulimitPath} -n 4096");
894
        Util::mwExec("{$ulimitPath} -p 4096");
895
    }
896
897
    /**
898
     *   Start Nginx and php-fpm
899
     **/
900
    public function nginxStart(): void
901
    {
902
        if (Util::isSystemctl()) {
903
            Util::mwExec('systemctl restart php7.4-fpm');
904
            Util::mwExec('systemctl restart nginx.service');
905
        } else {
906
            Util::killByName('php-fpm');
907
            Util::killByName('nginx');
908
            Util::mwExec('php-fpm -c /etc/php.ini');
909
            Util::mwExec('nginx');
910
        }
911
    }
912
913
    /**
914
     * Write additional settings the nginx.conf
915
     *
916
     * @param bool $not_ssl
917
     * @param int  $level
918
     */
919
    public function nginxGenerateConf($not_ssl = false, $level = 0): void
920
    {
921
        $configPath      = '/etc/nginx/mikopbx/conf.d';
922
        $httpConfigFile  = "{$configPath}/http-server.conf";
923
        $httpsConfigFile = "{$configPath}/https-server.conf";
924
925
        $dns_server = '127.0.0.1';
926
927
        $net = new Network();
928
        $dns = $net->getHostDNS();
929
        foreach ($dns as $ns) {
930
            if (Verify::isIpAddress($ns)) {
931
                $dns_server = trim($ns);
932
                break;
933
            }
934
        }
935
936
        // HTTP
937
        $WEBPort      = $this->mikoPBXConfig->getGeneralSettings('WEBPort');
938
        $WEBHTTPSPort = $this->mikoPBXConfig->getGeneralSettings('WEBHTTPSPort');
939
940
        $config = file_get_contents("{$httpConfigFile}.original");
941
        $config = str_replace(['<DNS>', '<WEBPort>'], [$dns_server, $WEBPort], $config);
942
943
        $RedirectToHttps = $this->mikoPBXConfig->getGeneralSettings('RedirectToHttps');
944
        if ($RedirectToHttps === '1' && $not_ssl === false) {
945
            $conf_data = 'if ( $remote_addr != "127.0.0.1" ) {' . PHP_EOL
946
                . '        ' . 'return 301 https://$host:' . $WEBHTTPSPort . '$request_uri;' . PHP_EOL
947
                . '       }' . PHP_EOL;
948
            $config    = str_replace('include mikopbx/locations/*.conf;', $conf_data, $config);
949
        }
950
        file_put_contents($httpConfigFile, $config);
951
952
        // SSL
953
        $WEBHTTPSPublicKey  = $this->mikoPBXConfig->getGeneralSettings('WEBHTTPSPublicKey');
954
        $WEBHTTPSPrivateKey = $this->mikoPBXConfig->getGeneralSettings('WEBHTTPSPrivateKey');
955
        if (
956
            $not_ssl === false
957
            && ! empty($WEBHTTPSPublicKey)
958
            && ! empty($WEBHTTPSPrivateKey)
959
        ) {
960
            $public_filename  = '/etc/ssl/certs/nginx.crt';
961
            $private_filename = '/etc/ssl/private/nginx.key';
962
            file_put_contents($public_filename, $WEBHTTPSPublicKey);
963
            file_put_contents($private_filename, $WEBHTTPSPrivateKey);
964
            $config = file_get_contents("{$httpsConfigFile}.original");
965
            $config = str_replace(['<DNS>', '<WEBHTTPSPort>'], [$dns_server, $WEBHTTPSPort], $config);
966
            file_put_contents($httpsConfigFile, $config);
967
        } elseif (file_exists($httpsConfigFile)) {
968
            unlink($httpsConfigFile);
969
        }
970
971
        // Test work
972
        $out = [];
973
        Util::mwExec('nginx -t', $out);
974
        $res = implode($out);
975
        if ($level < 1 && false !== strpos($res, 'test failed')) {
976
            ++$level;
977
            Util::sysLogMsg('nginx', 'Failed test config file. SSL will be disable...');
978
            $this->nginxGenerateConf(true, $level);
979
        }
980
    }
981
982
    public function syslogDaemonStart(): void
983
    {
984
        $syslog_file = '/var/log/messages';
985
        $log_file    = self::getSyslogFile();
986
        if ( ! file_exists($syslog_file)) {
987
            file_put_contents($syslog_file, '');
988
        }
989
        $syslogdPath = Util::which('syslogd');
990
        $busyboxPath = Util::which('busybox');
991
        $logreadPath = Util::which('logread');
992
        $killPath = Util::which('kill');
993
        $pid = Util::getPidOfProcess($syslogdPath);
994
        if ( ! empty($pid)) {
995
            $options = file_exists($log_file) ? '>' : '';
996
            Util::mwExec("{$busyboxPath} {$logreadPath} 2> /dev/null >" . $options . $log_file);
997
            // Завершаем процесс.
998
            Util::mwExec("{$busyboxPath} {$killPath} '$pid'");
999
        }
1000
1001
        Util::createUpdateSymlink($log_file, $syslog_file);
1002
        Util::mwExec("{$syslogdPath} -O {$log_file} -b 10 -s 10240");
1003
    }
1004
1005
    public static function getSyslogFile(): string
1006
    {
1007
        $logdir = self::getLogDir() . '/system';
1008
        Util::mwMkdir($logdir);
1009
        return "$logdir/messages";
1010
    }
1011
1012
    /**
1013
     * Будет вызван после старта asterisk.
1014
     */
1015
    public function onAfterPbxStarted(): void
1016
    {
1017
        $additionalModules = $this->di->getShared('pbxConfModules');
1018
        foreach ($additionalModules as $appClass) {
1019
            /** @var \MikoPBX\Modules\Config\ConfigClass $appClass */
1020
            $appClass->onAfterPbxStarted();
1021
        }
1022
    }
1023
1024
    /**
1025
     * Запуск SSH сервера.
1026
     **/
1027
    public function sshdConfigure(): array
1028
    {
1029
        @file_put_contents('/var/log/lastlog', '');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for file_put_contents(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1029
        /** @scrutinizer ignore-unhandled */ @file_put_contents('/var/log/lastlog', '');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1030
        $result       = [
1031
            'result' => 'Success',
1032
        ];
1033
        $dropbear_dir = '/etc/dropbear';
1034
        Util::mwMkdir($dropbear_dir);
1035
1036
        $keytypes = [
1037
            "rsa"   => "SSHRsaKey",
1038
            "dss"   => "SSHDssKey",
1039
            "ecdsa" => "SSHecdsaKey" // SSHecdsaKey // SSHEcdsaKey
1040
        ];
1041
        // Получаем ключ из базы данных.
1042
        // $config = array();
1043
        foreach ($keytypes as $keytype => $db_key) {
1044
            $res_keyfilepath = "{$dropbear_dir}/dropbear_" . $keytype . "_host_key";
1045
            $key             = $this->mikoPBXConfig->getGeneralSettings($db_key);
1046
            $key             = (isset($key)) ? trim($key) : "";
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type array and array<string,string>; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1046
            $key             = (isset($key)) ? trim(/** @scrutinizer ignore-type */ $key) : "";
Loading history...
1047
            if (strlen($key) > 100) {
1048
                // Сохраняем ключ в файл.
1049
                file_put_contents($res_keyfilepath, base64_decode($key));
1050
            }
1051
            // Если ключ не существует, создадим его и обновим информацию в базе данных.
1052
            if ( ! file_exists($res_keyfilepath)) {
1053
                // Генерация.
1054
                $dropbearkeyPath = Util::which('dropbearkey');
1055
                Util::mwExec("{$dropbearkeyPath} -t $keytype -f $res_keyfilepath");
1056
                // Сохранение.
1057
                $new_key = base64_encode(file_get_contents($res_keyfilepath));
1058
                $this->mikoPBXConfig->setGeneralSettings("$db_key", "$new_key");
1059
            }
1060
        }
1061
        $ssh_port = escapeshellcmd($this->mikoPBXConfig->getGeneralSettings('SSHPort'));
1062
        // Перезапускаем сервис dropbear;
1063
        Util::killByName('dropbear');
1064
        usleep(500000);
1065
        Util::mwExec("dropbear -p '{$ssh_port}' -c /etc/rc/hello > /var/log/dropbear_start.log");
1066
        $this->generateAuthorizedKeys();
1067
1068
        $result['data'] = @file_get_contents('/var/log/dropbear_start.log');
1069
        if ( ! empty($result['data'])) {
1070
            $result['result'] = 'ERROR';
1071
        }
1072
1073
        // Устанавливаем пароль на пользователя ОС.
1074
        $this->updateShellPassword();
1075
1076
        return $result;
1077
    }
1078
1079
    /**
1080
     * Сохранение ключей аторизации.
1081
     */
1082
    public function generateAuthorizedKeys(): void
1083
    {
1084
        $ssh_dir = '/root/.ssh';
1085
        Util::mwMkdir($ssh_dir);
1086
1087
1088
        $conf_data = $this->mikoPBXConfig->getGeneralSettings('SSHAuthorizedKeys');
1089
        file_put_contents("{$ssh_dir}/authorized_keys", $conf_data);
1090
    }
1091
1092
    /**
1093
     * Устанавливаем пароль для пользователя системы.
1094
     **/
1095
    public function updateShellPassword(): void
1096
    {
1097
        $password = $this->mikoPBXConfig->getGeneralSettings('SSHPassword');
1098
        $echoPath = Util::which('echo');
1099
        $chpasswdPath = Util::which('chpasswd');
1100
        Util::mwExec("{$echoPath} \"root:$password\" | {$chpasswdPath}");
1101
    }
1102
1103
    /**
1104
     * Запуск open vmware tools.
1105
     */
1106
    public function vmwareToolsConfigure(): void
1107
    {
1108
        Util::killByName("vmtoolsd");
1109
        $virtualHW = $this->mikoPBXConfig->getGeneralSettings('VirtualHardwareType');
1110
        if ('VMWARE' === $virtualHW) {
1111
            $conf = "[logging]\n"
1112
                . "log = false\n"
1113
                . "vmtoolsd.level = none\n"
1114
                . ";vmsvc.data = /dev/null\n"
1115
                . "vmsvc.level = none\n";
1116
            file_put_contents('/etc/vmware-tools/tools.conf', $conf);
1117
            $vmtoolsdPath = Util::which('vmtoolsd');
1118
            Util::mwExec("{$vmtoolsdPath} --background=/var/run/vmtoolsd.pid > /dev/null 2> /dev/null");
1119
        }
1120
    }
1121
1122
}
1123