Passed
Push — develop ( bbba6b...68db6a )
by Nikolay
05:49 queued 11s
created

System::syslogDaemonStart()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 21
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
c 0
b 0
f 0
dl 0
loc 21
rs 9.7666
cc 4
nc 6
nop 0
1
<?php
2
/**
3
 * Copyright © MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Alexey Portnov, 7 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
    /**
26
     * @var mixed|\Phalcon\Di\DiInterface|null
27
     */
28
    private $di;
29
30
    /**
31
     * System constructor.
32
     */
33
    public function __construct()
34
    {
35
        $this->di = Di::getDefault();
36
37
        // Класс / обертка для работы с настройками.
38
        $this->mikoPBXConfig = new MikoPBXConfig();
39
    }
40
41
    /**
42
     * Relocate PHP error log to storage mount
43
     */
44
    public static function setupPhpLog(): void
45
    {
46
        $src_log_file = '/var/log/php_error.log';
47
        $dst_log_file = self::getPhpFile();
48
        if ( ! file_exists($src_log_file)) {
49
            file_put_contents($src_log_file, '');
50
        }
51
        $options = file_exists($dst_log_file) ? '>' : '';
52
        $catPath = Util::which('cat');
53
        Util::mwExec("{$catPath} {$src_log_file} 2> /dev/null >{$options} {$dst_log_file}");
54
        Util::createUpdateSymlink($dst_log_file, $src_log_file);
55
    }
56
57
    /**
58
     * @return string
59
     */
60
    public static function getPhpFile(): string
61
    {
62
        $logdir = self::getLogDir() . '/php';
63
        Util::mwMkdir($logdir);
64
        return "$logdir/error.log";
65
    }
66
67
    /**
68
     * @return string
69
     */
70
    public static function getLogDir(): string
71
    {
72
        $di     = Di::getDefault();
73
        if ($di !== null){
74
            return $di->getConfig()->path('core.logsPath');
75
        }
76
        return '/var/log';
77
    }
78
79
    /**
80
     *
81
     */
82
    public static function rotatePhpLog(): void
83
    {
84
        $asteriskPath = Util::which('asterisk');
85
        $logrotatePath = Util::which('logrotate');
86
87
        $max_size    = 2;
88
        $f_name      = self::getPhpFile();
89
        $text_config = (string)($f_name) . " {
90
    nocreate
91
    nocopytruncate
92
    delaycompress
93
    nomissingok
94
    start 0
95
    rotate 9
96
    size {$max_size}M
97
    missingok
98
    noolddir
99
    postrotate
100
        {$asteriskPath} -rx 'logger reload' > /dev/null 2> /dev/null
101
    endscript
102
}";
103
        $di     = Di::getDefault();
104
        if ($di !== null){
105
            $varEtcPath = $di->getConfig()->path('core.varEtcPath');
106
        } else {
107
            $varEtcPath = '/var/etc';
108
        }
109
        $path_conf   = $varEtcPath . '/php_logrotate_' . basename($f_name) . '.conf';
110
        file_put_contents($path_conf, $text_config);
111
        $mb10 = $max_size * 1024 * 1024;
112
113
        $options = '';
114
        if (Util::mFileSize($f_name) > $mb10) {
115
            $options = '-f';
116
        }
117
        Util::mwExecBg("{$logrotatePath} {$options} '{$path_conf}' > /dev/null 2> /dev/null");
118
    }
119
120
    /**
121
     *
122
     */
123
    public static function gnatsLogRotate(): void
124
    {
125
        $log_dir = self::getLogDir() . '/nats';
126
        $gnatsdPath = Util::which('gnatsd');
127
        $pid     = Util::getPidOfProcess($gnatsdPath, 'custom_modules');
128
        $max_size = 1;
129
        if (empty($pid)) {
130
            $system = new System();
131
            $system->gnatsStart();
132
            sleep(1);
133
        }
134
        $text_config = "{$log_dir}/gnatsd.log {
135
    start 0
136
    rotate 9
137
    size {$max_size}M
138
    maxsize 1M
139
    daily
140
    missingok
141
    notifempty
142
    sharedscripts
143
    postrotate
144
        {$gnatsdPath} -sl reopen=$pid > /dev/null 2> /dev/null
145
    endscript
146
}";
147
148
        $mb10 = $max_size * 1024 * 1024;
149
150
        $options = '';
151
        if (Util::mFileSize("{$log_dir}/gnatsd.log") > $mb10) {
152
            $options = '-f';
153
        }
154
        $di     = Di::getDefault();
155
        if ($di !== null){
156
            $varEtcPath = $di->getConfig()->path('core.varEtcPath');
157
        } else {
158
            $varEtcPath = '/var/etc';
159
        }
160
        $path_conf  = $varEtcPath . '/gnatsd_logrotate.conf';
161
        file_put_contents($path_conf, $text_config);
162
        if (file_exists("{$log_dir}/gnatsd.log")) {
163
            $logrotatePath = Util::which('logrotate');
164
            Util::mwExecBg("{$logrotatePath} $options '{$path_conf}' > /dev/null 2> /dev/null");
165
        }
166
    }
167
168
    /**
169
     * Старт сервера обработки очередей задач.
170
     *
171
     * @return array
172
     */
173
    public function gnatsStart(): array
174
    {
175
        $confdir = '/etc/nats';
176
        Util::mwMkdir($confdir);
177
        $logdir = self::getLogDir() . '/nats';
178
        Util::mwMkdir($logdir);
179
180
        $pid_file = '/var/run/gnatsd.pid';
181
        $settings = [
182
            'port'             => '4223',
183
            'http_port'        => '8223',
184
            'debug'            => 'false',
185
            'trace'            => 'false',
186
            'logtime'          => 'true',
187
            'pid_file'         => $pid_file,
188
            'max_connections'  => '1000',
189
            'max_payload'      => '1000000',
190
            'max_control_line' => '512',
191
            'sessions_path'    => $logdir,
192
            'log_file'         => "{$logdir}/gnatsd.log",
193
        ];
194
        $config   = '';
195
        foreach ($settings as $key => $val) {
196
            $config .= "{$key}: {$val} \n";
197
        }
198
        $conf_file = "{$confdir}/natsd.conf";
199
        Util::fileWriteContent($conf_file, $config);
200
201
        $lic = $this->mikoPBXConfig->getGeneralSettings('PBXLicense');
202
        file_put_contents($logdir . '/license.key', $lic);
203
204
        if (file_exists($pid_file)) {
205
            $killPath = Util::which('kill');
206
            $catPath = Util::which('kill');
207
            Util::mwExec("{$killPath} $({$catPath} {$pid_file})");
208
        }
209
        $gnatsdPath = Util::which('gnatsd');
210
        Util::mwExecBg("{$gnatsdPath} --config {$conf_file}", "{$logdir}/gnats_process.log");
211
212
        return [
213
            'result' => 'Success',
214
        ];
215
    }
216
217
    /**
218
     * Reboots the system after calling system_reboot_cleanup()
219
     *
220
     */
221
    public static function rebootSync(): void
222
    {
223
        $mikopbx_rebootPath = Util::which('mikopbx_reboot');
224
        Util::mwExec("{$mikopbx_rebootPath} > /dev/null 2>&1");
225
    }
226
227
    /**
228
     * Reboots the system after calling system_reboot_cleanup()
229
     *
230
     */
231
    public static function rebootSyncBg(): void
232
    {
233
        $mikopbx_rebootPath = Util::which('mikopbx_reboot');
234
        Util::mwExecBg("{$mikopbx_rebootPath} > /dev/null 2>&1");
235
    }
236
237
    /**
238
     * Shutdown the system.
239
     */
240
    public static function shutdown(): void
241
    {
242
        $shutdownPath = Util::which('shutdown');
243
        Util::mwExec("{$shutdownPath} > /dev/null 2>&1");
244
    }
245
246
    /**
247
     * Рестарт сетевых интерфейсов.
248
     */
249
    public static function networkReload(): void
250
    {
251
        $system = new System();
252
        $system->hostnameConfigure();
253
254
        $network = new Network();
255
        $network->resolvConfGenerate();
256
        $network->loConfigure();
257
        $network->lanConfigure();
258
    }
259
260
    /**
261
     *    Устанавливаем имя хост текущей машины.
262
     **/
263
    public function hostnameConfigure(): int
264
    {
265
        $data       = Network::getHostName();
266
        $hosts_conf = "127.0.0.1 localhost\n" .
267
            "127.0.0.1 {$data['hostname']}\n";
268
        if ( ! empty($data['domain'])) {
269
            $hosts_conf .= "127.0.0.1 {$data['hostname']}.{$data['domain']}\n";
270
        }
271
        Util::fileWriteContent('/etc/hosts', $hosts_conf);
272
273
        $hostnamePath = Util::which('hostname');
274
        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...
275
    }
276
277
278
    /**
279
     * Получение сведений о системе.
280
     *
281
     * @return array
282
     */
283
    public static function getInfo(): array
284
    {
285
        $result = [
286
            'result' => 'Success',
287
        ];
288
289
        $storage        = new Storage();
290
        $data           = [
291
            'disks'  => $storage->getAllHdd(),
292
            'cpu'    => self::getCpu(),
293
            'uptime' => self::getUpTime(),
294
            'mem'    => self::getMemInfo(),
295
        ];
296
        $result['data'] = $data;
297
298
        return $result;
299
    }
300
301
    /**
302
     * Возвращает информацию по загрузке CPU.
303
     */
304
    public static function getCpu()
305
    {
306
        $ut = [];
307
        $grepPath = Util::which('grep');
308
        $mpstatPath = Util::which('mpstat');
309
        Util::mwExec("{$mpstatPath} | {$grepPath} all", $ut);
310
        preg_match("/^.*\s+all\s+.*\s+.*\s+.*\s+.*\s+.*\s+.*\s+.*\s+.*\s+(.*)\s*.*/i", $ut[0], $matches);
311
        $rv = 100 - $matches[1];
312
313
        if (100 < $rv) {
314
            $rv = 100;
315
        }
316
317
        return round($rv, 2);
318
    }
319
320
    /**
321
     * Получаем информацию по времени работы ПК.
322
     */
323
    public static function getUpTime(): string
324
    {
325
        $ut = [];
326
        $uptimePath = Util::which('uptime');
327
        $awkPath = Util::which('awk');
328
        Util::mwExec("{$uptimePath} | {$awkPath} -F \" |,\" '{print $5}'", $ut);
329
330
        return implode('', $ut);
331
    }
332
333
    /**
334
     * Получаем информацию по оперативной памяти.
335
     */
336
    public static function getMemInfo(): array
337
    {
338
        $result = [];
339
        $out    = [];
340
        $catPath = Util::which('cat');
341
        $grepPath = Util::which('grep');
342
        $awkPath = Util::which('awk');
343
        Util::mwExec("{$catPath} /proc/meminfo | {$grepPath} -C 0 'Inactive:' | {$awkPath} '{print $2}'", $out);
344
        $result['inactive'] = round((1 * implode($out)) / 1024, 2);
345
        Util::mwExec("{$catPath} /proc/meminfo | {$grepPath} -C 0 'MemFree:' | {$awkPath} '{print $2}'", $out);
346
        $result['free'] = round((1 * implode($out)) / 1024, 2);
347
        Util::mwExec("{$catPath} /proc/meminfo | {$grepPath} -C 0 'MemTotal:' | {$awkPath} '{print $2}'", $out);
348
        $result['total'] = round((1 * implode($out)) / 1024, 2);
349
350
        return $result;
351
    }
352
353
    /**
354
     * Обновление кофнигурации кастомных файлов.
355
     *
356
     * @return array
357
     */
358
    public static function updateCustomFiles()
359
    {
360
        $actions = [];
361
        /** @var \MikoPBX\Common\Models\CustomFiles $res_data */
362
        $res_data = CustomFiles::find("changed = '1'");
363
        foreach ($res_data as $file_data) {
364
            // Всегда рестрартуем все модули asterisk (только чтение конфигурации).
365
            $actions['asterisk_coreReload'] = 100;
366
            $filename                       = basename($file_data->filepath);
367
            switch ($filename) {
368
                case 'manager.conf':
369
                    $actions['manager'] = 10;
370
                    break;
371
                case 'musiconhold.conf':
372
                    $actions['musiconhold'] = 100;
373
                    break;
374
                case 'modules.conf':
375
                    $actions['modules'] = 10;
376
                    break;
377
                case 'http.conf':
378
                    $actions['manager'] = 10;
379
                    break;
380
                case 'root': // crontabs
381
                    $actions['cron'] = 10;
382
                    break;
383
                case 'queues.conf':
384
                    $actions['queues'] = 10;
385
                    break;
386
                case 'features.conf':
387
                    $actions['features'] = 10;
388
                    break;
389
                case 'ntp.conf':
390
                    $actions['systemtime'] = 100;
391
                    break;
392
                case 'jail.local': // fail2ban
393
                    $actions['firewall'] = 100;
394
                    break;
395
            }
396
        }
397
398
        asort($actions);
399
        $result = self::invokeActions($actions);
400
        if ($result['result'] !== 'ERROR') {
401
            // Зафиксируем результат работы.
402
            foreach ($res_data as $file_data) {
403
                /** @var \MikoPBX\Common\Models\CustomFiles $file_data */
404
                $file_data->writeAttribute("changed", '0');
405
                $file_data->save();
406
            }
407
        }
408
409
        return $result;
410
    }
411
412
    /**
413
     * Выполнение набора действий по рестарту модулей системы.
414
     *
415
     * @param $actions
416
     *
417
     * @return array|mixed
418
     */
419
    public static function invokeActions($actions)
420
    {
421
        $result = [
422
            'result' => 'Success',
423
        ];
424
        foreach ($actions as $action => $value) {
425
            $res = null;
426
            switch ($action) {
427
                case 'manager':
428
                    $res = PBX::managerReload();
429
                    break;
430
                case 'musiconhold':
431
                    $res = PBX::musicOnHoldReload();
432
                    break;
433
                case 'modules':
434
                    $res = PBX::modulesReload();
435
                    break;
436
                case 'cron':
437
                    $system = new System();
438
                    $system->cronConfigure();
439
                    break;
440
                case 'queues':
441
                    $res = QueueConf::queueReload();
442
                    break;
443
                case 'features':
444
                    $res = PBX::managerReload();
445
                    break;
446
                case 'systemtime':
447
                    $res = TimeManagement::setDate('');
448
                    break;
449
                case 'firewall':
450
                    $res = Firewall::reloadFirewall();
451
                    break;
452
                case 'asterisk_coreReload':
453
                    PBX::sipReload();
454
                    PBX::iaxReload();
455
                    PBX::dialplanReload();
456
                    $res = PBX::coreReload();
457
                    break;
458
            }
459
            if ($res !== null && $res['result'] === 'ERROR') {
460
                $result = $res['result'];
461
                break;
462
            }
463
        }
464
465
        return $result;
466
    }
467
468
    /**
469
     * Настройка cron. Запуск демона.
470
     *
471
     * @return int
472
     */
473
    public function cronConfigure(): int
474
    {
475
        $booting = $this->di->getRegistry()->booting;
0 ignored issues
show
Bug introduced by
The method getRegistry() 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

475
        $booting = $this->di->/** @scrutinizer ignore-call */ getRegistry()->booting;

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...
476
        $this->cronGenerate($booting);
477
        if (Util::isSystemctl()) {
478
            $systemctlPath = Util::which('systemctl');
479
            Util::mwExec("{$systemctlPath} restart cron");
480
        } else {
481
            $crondPath = Util::which('crond');
482
            Util::killByName($crondPath);
483
            Util::mwExec("{$crondPath} -L /dev/null -l 8");
484
        }
485
        return 0;
486
    }
487
488
    /**
489
     * Генератор конфига cron.
490
     *
491
     * @param bool $boot
492
     */
493
    private function cronGenerate($boot = true): void
494
    {
495
        $additionalModules = $this->di->getShared('pbxConfModules');
496
        $mast_have         = [];
497
498
        if (Util::isSystemctl()) {
499
            $mast_have[]   = "SHELL=/bin/sh\n";
500
            $mast_have[]   = "PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n\n";
501
            $cron_filename = '/etc/cron.d/mikopbx';
502
            $cron_user     = 'root ';
503
        } else {
504
            $cron_filename = '/var/spool/cron/crontabs/root';
505
            $cron_user     = '';
506
        }
507
508
509
        $workerSafeScriptsPath = Util::getFilePathByClassName(WorkerSafeScriptsCore::class);
510
        $phpPath = Util::which('php');
511
        $WorkerSafeScripts = "{$phpPath} -f {$workerSafeScriptsPath} start > /dev/null 2> /dev/null";
512
513
        $workersPath = appPath('src/Core/Workers');
514
515
        $restart_night = $this->mikoPBXConfig->getGeneralSettings('RestartEveryNight');
516
        $asteriskPath = Util::which('asterisk');
517
        $ntpdPath = Util::which('ntpd');
518
        $shPath = Util::which('sh');
519
        if ($restart_night === '1') {
520
            $mast_have[] = '0 1 * * * ' . $cron_user .$asteriskPath.' -rx"core restart now" > /dev/null 2> /dev/null' . "\n";
521
        }
522
        $mast_have[] = '*/5 * * * * ' . $cron_user . $ntpdPath.' -q > /dev/null 2> /dev/null' . "\n";
523
        $mast_have[] = '*/6 * * * * ' . $cron_user . "{$shPath} {$workersPath}/Cron/cleaner_download_links.sh > /dev/null 2> /dev/null\n";
524
        $mast_have[] = '*/1 * * * * ' . $cron_user . "{$WorkerSafeScripts}\n";
525
526
        $tasks = [];
527
        // Дополнительные модули также могут добавить задачи в cron.
528
        foreach ($additionalModules as $appClass) {
529
            /** @var \MikoPBX\Modules\Config\ConfigClass $appClass */
530
            $appClass->createCronTasks($tasks);
531
        }
532
        $conf = implode('', array_merge($mast_have, $tasks));
533
534
        if (Util::isSystemctl()) {
535
            // Обеспечим совместимость всех существующих модулей с Debian.
536
            $conf = str_replace(' * * * * /', ' * * * * root /', $conf);
537
        }
538
539
        if ($boot === true) {
540
            Util::mwExecBg($WorkerSafeScripts);
541
        }
542
543
        Util::fileWriteContent($cron_filename, $conf);
544
    }
545
546
    public static function convertConfig($config_file = ''): array
547
    {
548
        $result  = [
549
            'result'  => 'Success',
550
            'message' => '',
551
        ];
552
        $di     = Di::getDefault();
553
        if ($di !== null){
554
            $tempDir = $di->getConfig()->path('core.tempPath');
555
        } else {
556
            $tempDir = '/tmp';
557
        }
558
        if (empty($config_file)) {
559
            $config_file = "{$tempDir}/old_config.xml";
560
        }
561
562
        if (file_exists($config_file)) {
563
            try {
564
                $cntr = new OldConfigConverter($config_file);
565
                $cntr->parse();
566
                $cntr->makeConfig();
567
                file_put_contents('/tmp/ejectcd', '');
568
                $mikopbx_rebootPath = Util::which('mikopbx_reboot');
569
                Util::mwExecBg($mikopbx_rebootPath, '/dev/null', 3);
570
            } catch (Exception $e) {
571
                $result = [
572
                    'result'  => 'Error',
573
                    'message' => $e->getMessage(),
574
                ];
575
            }
576
        } else {
577
            $result = [
578
                'result'  => 'Error',
579
                'message' => 'XML config not found',
580
            ];
581
        }
582
583
        return $result;
584
    }
585
586
    /**
587
     * Upgrade MikoPBX from uploaded IMG file
588
     *
589
     * @param string $tempFilename path to uploaded image
590
     *
591
     * @return array
592
     */
593
    public static function upgradeFromImg(string $tempFilename): array
594
    {
595
        $result = [
596
            'result'  => 'Success',
597
            'message' => 'In progress...',
598
        ];
599
600
        if (!file_exists($tempFilename)){
601
            return [
602
                'result'  => 'ERROR',
603
                'data' => "Update file '{$tempFilename}' not found.",
604
            ];
605
        }
606
607
        if ( ! file_exists('/var/etc/cfdevice')) {
608
            return [
609
                'result' => 'ERROR',
610
                'data' => "The system is not installed",
611
                'path'=> $tempFilename
612
            ];
613
        }
614
        $dev = trim(file_get_contents('/var/etc/cfdevice'));
615
616
        $link = '/tmp/firmware_update.img';
617
        Util::createUpdateSymlink($tempFilename, $link);
618
        $mikopbx_firmwarePath = Util::which('mikopbx_firmware');
619
        Util::mwExecBg("{$mikopbx_firmwarePath} recover_upgrade {$link} /dev/{$dev}");
620
621
        return $result;
622
    }
623
624
    /**
625
     * Download IMG from MikoPBX repository
626
     *
627
     * @param $data
628
     *
629
     * @return mixed
630
     */
631
    public static function downloadNewFirmware($data)
632
    {
633
        $di     = Di::getDefault();
634
        if ($di !== null){
635
            $tempDir = $di->getConfig()->path('core.uploadPath');
636
        } else {
637
            $tempDir = '/tmp';
638
        }
639
        $rmPath = Util::which('rm');
640
        $module  = 'NewFirmware';
641
        if ( ! file_exists($tempDir . "/{$module}")) {
642
            Util::mwMkdir($tempDir . "/{$module}");
643
        } else {
644
            // Чистим файлы, загруженные онлайн.
645
            Util::mwExec("{$rmPath} -rf {$tempDir}/{$module}/* ");
646
        }
647
        if (file_exists("{$tempDir}/update.img")) {
648
            // Чистим вручную загруженный файл.
649
            Util::mwExec("{$rmPath} -rf {$tempDir}/update.img");
650
        }
651
652
        $download_settings = [
653
            'res_file' => "{$tempDir}/{$module}/update.img",
654
            'url'      => $data['url'],
655
            'module'   => $module,
656
            'md5'      => $data['md5'],
657
            'action'   => $module,
658
        ];
659
660
        $workerDownloaderPath = Util::getFilePathByClassName(WorkerDownloader::class);
661
662
        file_put_contents($tempDir . "/{$module}/progress", '0');
663
        file_put_contents(
664
            $tempDir . "/{$module}/download_settings.json",
665
            json_encode($download_settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
666
        );
667
        $phpPath = Util::which('php');
668
        Util::mwExecBg("{$phpPath} -f {$workerDownloaderPath} " . $tempDir . "/{$module}/download_settings.json");
669
        $result = [];
670
        $result['result'] = 'Success';
671
672
        return $result;
673
    }
674
675
    /**
676
     * Return download Firnware from remote repository progress
677
     *
678
     * @return array
679
     */
680
    public static function firmwareDownloadStatus(): array
681
    {
682
        clearstatcache();
683
        $result        = [
684
            'result' => 'Success',
685
        ];
686
        $di     = Di::getDefault();
687
        if ($di !== null){
688
            $tempDir = $di->getConfig()->path('core.uploadPath');
689
        } else {
690
            $tempDir = '/tmp';
691
        }
692
        $modulesDir    = $tempDir . '/NewFirmware';
693
        $progress_file = $modulesDir . '/progress';
694
695
        $error = '';
696
        if (file_exists($modulesDir . '/error')) {
697
            $error = trim(file_get_contents($modulesDir . '/error'));
698
        }
699
700
        if ( ! file_exists($progress_file)) {
701
            $result['d_status_progress'] = '0';
702
            $result['d_status']          = 'NOT_FOUND';
703
        } elseif ('' !== $error) {
704
            $result['d_status']          = 'DOWNLOAD_ERROR';
705
            $result['d_status_progress'] = file_get_contents($progress_file);
706
            $result['d_error']           = $error;
707
        } elseif ('100' === file_get_contents($progress_file)) {
708
            $result['d_status_progress'] = '100';
709
            $result['d_status']          = 'DOWNLOAD_COMPLETE';
710
            $result['filePath'] = "{$tempDir}/NewFirmware/update.img";
711
        } else {
712
            $result['d_status_progress'] = file_get_contents($progress_file);
713
            $d_pid                       = Util::getPidOfProcess($tempDir . '/NewFirmware/download_settings.json');
714
            if (empty($d_pid)) {
715
                $result['d_status'] = 'DOWNLOAD_ERROR';
716
                $error              = '';
717
                if (file_exists($modulesDir . '/error')) {
718
                    $error = file_get_contents($modulesDir . '/error');
719
                }
720
                $result['d_error'] = $error;
721
            } else {
722
                $result['d_status'] = 'DOWNLOAD_IN_PROGRESS';
723
            }
724
        }
725
726
        return $result;
727
    }
728
729
    /**
730
     * Запуск процесса фоновой загрузки доп. модуля АТС.
731
     *
732
     * @param $module
733
     * @param $url
734
     * @param $md5
735
     *
736
     * @return void
737
     */
738
    public static function moduleStartDownload($module, $url, $md5): void
739
    {
740
        $di     = Di::getDefault();
741
        if ($di !== null){
742
            $tempDir = $di->getConfig()->path('core.uploadPath');
743
        } else {
744
            $tempDir = '/tmp';
745
        }
746
747
        $moduleDirTmp = "{$tempDir}/{$module}";
748
        Util::mwMkdir($moduleDirTmp);
749
750
        $download_settings = [
751
            'res_file' => "$moduleDirTmp/modulefile.zip",
752
            'url'      => $url,
753
            'module'   => $module,
754
            'md5'      => $md5,
755
            'action'   => 'moduleInstall',
756
        ];
757
        if (file_exists("$moduleDirTmp/error")) {
758
            unlink("$moduleDirTmp/error");
759
        }
760
        if (file_exists("$moduleDirTmp/installed")) {
761
            unlink("$moduleDirTmp/installed");
762
        }
763
        file_put_contents("$moduleDirTmp/progress", '0');
764
        file_put_contents(
765
            "$moduleDirTmp/download_settings.json",
766
            json_encode($download_settings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
767
        );
768
        $workerDownloaderPath = Util::getFilePathByClassName(WorkerDownloader::class);
769
        $phpPath = Util::which('php');
770
        Util::mwExecBg("{$phpPath} -f {$workerDownloaderPath} $moduleDirTmp/download_settings.json");
771
    }
772
773
    /**
774
     * Возвращает статус скачивания модуля.
775
     *
776
     * @param $moduleUniqueID
777
     *
778
     * @return array
779
     */
780
    public static function moduleDownloadStatus(string $moduleUniqueID): array
781
    {
782
        clearstatcache();
783
        $result        = [
784
            'result' => 'Success',
785
            'data'   => null,
786
        ];
787
        $di     = Di::getDefault();
788
        if ($di !== null){
789
            $tempDir = $di->getConfig()->path('core.uploadPath');
790
        } else {
791
            $tempDir = '/tmp';
792
        }
793
        $moduleDirTmp  = $tempDir . '/' . $moduleUniqueID;
794
        $progress_file = $moduleDirTmp . '/progress';
795
        $error         = '';
796
        if (file_exists($moduleDirTmp . '/error')) {
797
            $error = trim(file_get_contents($moduleDirTmp . '/error'));
798
        }
799
800
        // Ожидание запуска процесса загрузки.
801
        $d_pid = Util::getPidOfProcess("{$moduleDirTmp}/download_settings.json");
802
        if (empty($d_pid)) {
803
            usleep(500000);
804
        }
805
806
        if ( ! file_exists($progress_file)) {
807
            $result['d_status_progress'] = '0';
808
            $result['d_status']          = 'NOT_FOUND';
809
        } elseif ('' !== $error) {
810
            $result['d_status']          = 'DOWNLOAD_ERROR';
811
            $result['d_status_progress'] = file_get_contents($progress_file);
812
            $result['d_error']           = $error;
813
        } elseif ('100' === file_get_contents($progress_file)) {
814
            $result['d_status_progress'] = '100';
815
            $result['d_status']          = 'DOWNLOAD_COMPLETE';
816
            $result['filePath'] =  "$moduleDirTmp/modulefile.zip";
817
        } else {
818
            $result['d_status_progress'] = file_get_contents($progress_file);
819
            $d_pid                       = Util::getPidOfProcess($moduleDirTmp . '/download_settings.json');
820
            if (empty($d_pid)) {
821
                $result['d_status'] = 'DOWNLOAD_ERROR';
822
                $error              = '';
823
                if (file_exists($moduleDirTmp . '/error')) {
824
                    $error = file_get_contents($moduleDirTmp . '/error');
825
                }
826
                $result['d_error'] = $error;
827
            } else {
828
                $result['d_status'] = 'DOWNLOAD_IN_PROGRESS';
829
            }
830
        }
831
832
        return $result;
833
    }
834
835
836
    /**
837
     * Получение информации о публичном IP.
838
     *
839
     * @return array
840
     */
841
    public static function getExternalIpInfo(): array
842
    {
843
        $result = [
844
            'result'  => 'Error',
845
            'message' => null,
846
            'data'    => null,
847
        ];
848
        $curl   = curl_init();
849
        if($curl === false){
850
            $result['message'] = 'Can not init cURL';
851
            return $result;
852
        }
853
        $url    = 'https://ipinfo.io/json';
854
        curl_setopt($curl, CURLOPT_URL, $url);
855
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
856
        curl_setopt($curl, CURLOPT_TIMEOUT, 2);
857
858
        try {
859
            $resultrequest = curl_exec($curl);
860
        } catch (Exception $e) {
861
            $result['message'] = $e->getMessage();
862
            return $result;
863
        }
864
        curl_close($curl);
865
        if (Util::isJson($resultrequest)) {
866
            $result['result'] = 'Success';
867
            $result['data']   = json_decode($resultrequest, true);
868
        } else {
869
            $result['message'] = 'Error format data ' . $resultrequest;
870
        }
871
872
        return $result;
873
    }
874
875
    /**
876
     * Подгрузка дополнительных модулей ядра.
877
     */
878
    public function loadKernelModules(): void
879
    {
880
        $modprobePath = Util::which('modprobe');
881
        $ulimitPath = Util::which('ulimit');
882
883
        Util::mwExec("{$modprobePath} -q dahdi");
884
        Util::mwExec("{$modprobePath} -q dahdi_transcode");
885
        Util::mwExec("{$ulimitPath} -n 4096");
886
        Util::mwExec("{$ulimitPath} -p 4096");
887
    }
888
889
    /**
890
     *   Start Nginx and php-fpm
891
     **/
892
    public function nginxStart(): void
893
    {
894
        if (Util::isSystemctl()) {
895
            Util::mwExec('systemctl restart php7.4-fpm');
896
            Util::mwExec('systemctl restart nginx.service');
897
        } else {
898
            Util::killByName('php-fpm');
899
            Util::killByName('nginx');
900
            Util::mwExec('php-fpm -c /etc/php.ini');
901
            Util::mwExec('nginx');
902
        }
903
    }
904
905
    /**
906
     * Write additional settings the nginx.conf
907
     *
908
     * @param bool $not_ssl
909
     * @param int  $level
910
     */
911
    public function nginxGenerateConf($not_ssl = false, $level = 0): void
912
    {
913
        $configPath      = '/etc/nginx/mikopbx/conf.d';
914
        $httpConfigFile  = "{$configPath}/http-server.conf";
915
        $httpsConfigFile = "{$configPath}/https-server.conf";
916
917
        $dns_server = '127.0.0.1';
918
919
        $net = new Network();
920
        $dns = $net->getHostDNS();
921
        foreach ($dns as $ns) {
922
            if (Verify::isIpAddress($ns)) {
923
                $dns_server = trim($ns);
924
                break;
925
            }
926
        }
927
928
        // HTTP
929
        $WEBPort      = $this->mikoPBXConfig->getGeneralSettings('WEBPort');
930
        $WEBHTTPSPort = $this->mikoPBXConfig->getGeneralSettings('WEBHTTPSPort');
931
932
        $config = file_get_contents("{$httpConfigFile}.original");
933
        $config = str_replace(['<DNS>', '<WEBPort>'], [$dns_server, $WEBPort], $config);
934
935
        $RedirectToHttps = $this->mikoPBXConfig->getGeneralSettings('RedirectToHttps');
936
        if ($RedirectToHttps === '1' && $not_ssl === false) {
937
            $conf_data = 'if ( $remote_addr != "127.0.0.1" ) {' . PHP_EOL
938
                . '        ' . 'return 301 https://$host:' . $WEBHTTPSPort . '$request_uri;' . PHP_EOL
939
                . '       }' . PHP_EOL;
940
            $config    = str_replace('include mikopbx/locations/*.conf;', $conf_data, $config);
941
        }
942
        file_put_contents($httpConfigFile, $config);
943
944
        // SSL
945
        $WEBHTTPSPublicKey  = $this->mikoPBXConfig->getGeneralSettings('WEBHTTPSPublicKey');
946
        $WEBHTTPSPrivateKey = $this->mikoPBXConfig->getGeneralSettings('WEBHTTPSPrivateKey');
947
        if (
948
            $not_ssl === false
949
            && ! empty($WEBHTTPSPublicKey)
950
            && ! empty($WEBHTTPSPrivateKey)
951
        ) {
952
            $public_filename  = '/etc/ssl/certs/nginx.crt';
953
            $private_filename = '/etc/ssl/private/nginx.key';
954
            file_put_contents($public_filename, $WEBHTTPSPublicKey);
955
            file_put_contents($private_filename, $WEBHTTPSPrivateKey);
956
            $config = file_get_contents("{$httpsConfigFile}.original");
957
            $config = str_replace(['<DNS>', '<WEBHTTPSPort>'], [$dns_server, $WEBHTTPSPort], $config);
958
            file_put_contents($httpsConfigFile, $config);
959
        } elseif (file_exists($httpsConfigFile)) {
960
            unlink($httpsConfigFile);
961
        }
962
963
        // Test work
964
        $out = [];
965
        Util::mwExec('nginx -t', $out);
966
        $res = implode($out);
967
        if ($level < 1 && false !== strpos($res, 'test failed')) {
968
            ++$level;
969
            Util::sysLogMsg('nginx', 'Failed test config file. SSL will be disable...');
970
            $this->nginxGenerateConf(true, $level);
971
        }
972
    }
973
974
    public function syslogDaemonStart(): void
975
    {
976
        $syslog_file = '/var/log/messages';
977
        $log_file    = self::getSyslogFile();
978
        if ( ! file_exists($syslog_file)) {
979
            file_put_contents($syslog_file, '');
980
        }
981
        $syslogdPath = Util::which('syslogd');
982
        $busyboxPath = Util::which('busybox');
983
        $logreadPath = Util::which('logread');
984
        $killPath = Util::which('kill');
985
        $pid = Util::getPidOfProcess($syslogdPath);
986
        if ( ! empty($pid)) {
987
            $options = file_exists($log_file) ? '>' : '';
988
            Util::mwExec("{$busyboxPath} {$logreadPath} 2> /dev/null >" . $options . $log_file);
989
            // Завершаем процесс.
990
            Util::mwExec("{$busyboxPath} {$killPath} '$pid'");
991
        }
992
993
        Util::createUpdateSymlink($log_file, $syslog_file);
994
        Util::mwExec("{$syslogdPath} -O {$log_file} -b 10 -s 10240");
995
    }
996
997
    public static function getSyslogFile(): string
998
    {
999
        $logdir = self::getLogDir() . '/system';
1000
        Util::mwMkdir($logdir);
1001
        return "$logdir/messages";
1002
    }
1003
1004
    /**
1005
     * Будет вызван после старта asterisk.
1006
     */
1007
    public function onAfterPbxStarted(): void
1008
    {
1009
        $additionalModules = $this->di->getShared('pbxConfModules');
1010
        foreach ($additionalModules as $appClass) {
1011
            /** @var \MikoPBX\Modules\Config\ConfigClass $appClass */
1012
            $appClass->onAfterPbxStarted();
1013
        }
1014
    }
1015
1016
    /**
1017
     * Запуск SSH сервера.
1018
     **/
1019
    public function sshdConfigure(): array
1020
    {
1021
        $result       = [
1022
            'result' => 'Success',
1023
        ];
1024
        $dropbear_dir = '/etc/dropbear';
1025
        Util::mwMkdir($dropbear_dir);
1026
1027
        $keytypes = [
1028
            "rsa"   => "SSHRsaKey",
1029
            "dss"   => "SSHDssKey",
1030
            "ecdsa" => "SSHecdsaKey" // SSHecdsaKey // SSHEcdsaKey
1031
        ];
1032
        // Получаем ключ из базы данных.
1033
        // $config = array();
1034
        foreach ($keytypes as $keytype => $db_key) {
1035
            $res_keyfilepath = "{$dropbear_dir}/dropbear_" . $keytype . "_host_key";
1036
            $key             = $this->mikoPBXConfig->getGeneralSettings($db_key);
1037
            $key             = (isset($key) && is_string($key)) ? trim($key) : "";
1038
            if (strlen($key) > 100) {
1039
                // Сохраняем ключ в файл.
1040
                file_put_contents($res_keyfilepath, base64_decode($key));
1041
            }
1042
            // Если ключ не существует, создадим его и обновим информацию в базе данных.
1043
            if ( ! file_exists($res_keyfilepath)) {
1044
                // Генерация.
1045
                $dropbearkeyPath = Util::which('dropbearkey');
1046
                Util::mwExec("{$dropbearkeyPath} -t $keytype -f $res_keyfilepath");
1047
                // Сохранение.
1048
                $new_key = base64_encode(file_get_contents($res_keyfilepath));
1049
                $this->mikoPBXConfig->setGeneralSettings("$db_key", "$new_key");
1050
            }
1051
        }
1052
        $ssh_port = escapeshellcmd($this->mikoPBXConfig->getGeneralSettings('SSHPort'));
1053
        // Перезапускаем сервис dropbear;
1054
        Util::killByName('dropbear');
1055
        usleep(500000);
1056
        Util::mwExec("dropbear -p '{$ssh_port}' -c /etc/rc/hello > /var/log/dropbear_start.log");
1057
        $this->generateAuthorizedKeys();
1058
1059
        $result['data'] = @file_get_contents('/var/log/dropbear_start.log');
1060
        if ( ! empty($result['data'])) {
1061
            $result['result'] = 'ERROR';
1062
        }
1063
1064
        // Устанавливаем пароль на пользователя ОС.
1065
        $this->updateShellPassword();
1066
1067
        return $result;
1068
    }
1069
1070
    /**
1071
     * Сохранение ключей аторизации.
1072
     */
1073
    public function generateAuthorizedKeys(): void
1074
    {
1075
        $ssh_dir = '/root/.ssh';
1076
        Util::mwMkdir($ssh_dir);
1077
1078
1079
        $conf_data = $this->mikoPBXConfig->getGeneralSettings('SSHAuthorizedKeys');
1080
        file_put_contents("{$ssh_dir}/authorized_keys", $conf_data);
1081
    }
1082
1083
    /**
1084
     * Устанавливаем пароль для пользователя системы.
1085
     **/
1086
    public function updateShellPassword(): void
1087
    {
1088
        $password = $this->mikoPBXConfig->getGeneralSettings('SSHPassword');
1089
        $echoPath = Util::which('echo');
1090
        $chpasswdPath = Util::which('chpasswd');
1091
        Util::mwExec("{$echoPath} \"root:$password\" | {$chpasswdPath}");
1092
    }
1093
1094
    /**
1095
     * Запуск open vmware tools.
1096
     */
1097
    public function vmwareToolsConfigure(): void
1098
    {
1099
        Util::killByName("vmtoolsd");
1100
        $virtualHW = $this->mikoPBXConfig->getGeneralSettings('VirtualHardwareType');
1101
        if ('VMWARE' === $virtualHW) {
1102
            $conf = "[logging]\n"
1103
                . "log = false\n"
1104
                . "vmtoolsd.level = none\n"
1105
                . ";vmsvc.data = /dev/null\n"
1106
                . "vmsvc.level = none\n";
1107
1108
            $dirVM = '/etc/vmware-tools';
1109
            if(!file_exists($dirVM)){
1110
                Util::mwMkdir($dirVM);
1111
            }
1112
1113
            file_put_contents("{$dirVM}/tools.conf", $conf);
1114
            $vmtoolsdPath = Util::which('vmtoolsd');
1115
            Util::mwExec("{$vmtoolsdPath} --background=/var/run/vmtoolsd.pid > /dev/null 2> /dev/null");
1116
        }
1117
    }
1118
1119
}
1120