Passed
Push — develop ( dd518c...cbf695 )
by Портнов
05:42
created

System::tryRestoreConf()   B

Complexity

Conditions 8
Paths 22

Size

Total Lines 41
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 34
c 0
b 0
f 0
dl 0
loc 41
rs 8.1315
cc 8
nc 22
nop 0
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2023 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 DateTime;
23
use DateTimeZone;
24
use MikoPBX\Common\Models\CustomFiles;
25
use MikoPBX\Common\Models\PbxSettings;
26
use MikoPBX\Core\Asterisk\Configs\H323Conf;
27
use MikoPBX\Core\Asterisk\Configs\HepConf;
28
use MikoPBX\Core\System\Configs\CronConf;
29
use MikoPBX\Core\System\Configs\IptablesConf;
30
use MikoPBX\Core\System\Configs\PHPConf;
31
use MikoPBX\Core\System\Configs\NTPConf;
32
use MikoPBX\Core\Asterisk\Configs\QueueConf;
33
use Phalcon\Di;
34
35
36
/**
37
 * Class System
38
 *
39
 * This class provides various system-level functionalities.
40
 *
41
 * @package MikoPBX\Core\System
42
 * @property \Phalcon\Config config
43
 */
44
class System extends Di\Injectable
45
{
46
    private MikoPBXConfig $mikoPBXConfig;
47
48
    /**
49
     * System constructor - Instantiates MikoPBXConfig.
50
     */
51
    public function __construct()
52
    {
53
        $this->mikoPBXConfig = new MikoPBXConfig();
54
    }
55
56
    /**
57
     * Is the configuration default?
58
     * @return bool
59
     */
60
    public function isDefaultConf():bool
61
    {
62
        $di = Di::getDefault();
63
        if ($di === null) {
64
            return false;
65
        }
66
        $sqlite3 = Util::which('sqlite3');
67
        $md5sum = Util::which('md5sum');
68
        $busybox = Util::which('busybox');
69
        $md5_1 = shell_exec("$sqlite3 ".$di->getConfig()->path('database.dbfile')." .dump | $md5sum | $busybox cut -f 1 -d ' '");
70
        $md5_2 = shell_exec("$sqlite3 /conf.default/mikopbx.db .dump | $md5sum | $busybox cut -f 1 -d ' '");
71
        return $md5_1 === $md5_2;
72
    }
73
74
    /**
75
     * Trying to restore the backup
76
     * @return void
77
     */
78
    public function tryRestoreConf():void
79
    {
80
        $di = Di::getDefault();
81
        if ($di === null) {
82
            return;
83
        }
84
        $storage = new Storage();
85
        $storages = $storage->getStorageCandidate();
86
        $tmpMountDir = '/tmp/mnt';
87
        $backupDir   = str_replace(['/storage/usbdisk1','/mountpoint'],['',''],$di->getConfig()->path('core.confBackupDir'));
88
        $confFile    = $di->getConfig()->path('database.dbfile');
89
        foreach ($storages as $dev => $fs){
90
            Util::teletypeEcho("    - mount $dev ..."."\n");
91
            Util::mwMkdir($tmpMountDir."/$dev");
92
            $res = Storage::mountDisk($dev, $fs, $tmpMountDir."/$dev");
93
            if(!$res){
94
                Util::teletypeEcho("    - fail mount $dev ..."."\n");
95
            }
96
        }
97
        $pathBusybox = Util::which('busybox');
98
        $pathFind    = Util::which('find');
99
        $pathMount   = Util::which('umount');
100
        $pathRm    = Util::which('rm');
101
        $pathGzip    = Util::which('gzip');
102
        $pathSqlite3    = Util::which('sqlite3');
103
        $lastBackUp  = trim(shell_exec("$pathFind $tmpMountDir/dev/*$backupDir -type f -printf '%T@ %p\\n' | $pathBusybox sort -n | $pathBusybox tail -1 | $pathBusybox cut -f2- -d' '"));
104
        if(empty($lastBackUp)){
105
            return;
106
        }
107
        Util::teletypeEcho("    - Restore $lastBackUp ..."."\n");
108
        shell_exec("$pathRm -rf {$confFile}*");
109
        shell_exec("$pathGzip -c -d $lastBackUp | sqlite3 $confFile");
110
        Processes::mwExec("$pathSqlite3 $confFile 'select * from m_Storage'", $out, $ret);
111
        if($ret !== 0){
112
            Util::teletypeEcho("    - fail restore $lastBackUp ..."."\n");
113
            copy('/conf.default/mikopbx.db', $confFile);
114
        }elseif(!$this->isDefaultConf()){
115
            self::rebootSync();
116
        }
117
        foreach ($storages as $dev => $fs){
118
            shell_exec("$pathMount $dev");
119
        }
120
    }
121
122
    /**
123
     * Returns the directory where logs are stored.
124
     *
125
     * @return string - Directory path where logs are stored.
126
     */
127
    public static function getLogDir(): string
128
    {
129
        $di = Di::getDefault();
130
        if ($di !== null) {
131
            return $di->getConfig()->path('core.logsDir');
132
        }
133
134
        // Default logs directory
135
        return '/var/log';
136
    }
137
138
    /**
139
     * Refreshes networks configs and restarts network daemon.
140
     *
141
     * @return void
142
     */
143
    public static function networkReload(): void
144
    {
145
        // Create Network object and configure settings
146
        $network = new Network();
147
        $network->hostnameConfigure();
148
        $network->resolvConfGenerate();
149
        $network->loConfigure();
150
        $network->lanConfigure();
151
    }
152
153
    /**
154
     * Updates custom changes in config files
155
     *
156
     * @return void
157
     */
158
    public static function updateCustomFiles():void
159
    {
160
        $actions = [];
161
162
        // Find all custom files marked as changed
163
        /** @var CustomFiles $res_data */
164
        $res_data = CustomFiles::find("changed = '1'");
165
166
        // Process each changed file
167
        foreach ($res_data as $file_data) {
168
            // Always restart asterisk after any custom file change
169
            $actions['asterisk_core_reload'] = 100;
170
            $filename                        = basename($file_data->filepath);
171
172
            // Process based on file name
173
            switch ($filename) {
174
                // Set actions based on the name of the changed file
175
                case 'manager.conf':
176
                    $actions['manager'] = 10;
177
                    break;
178
                case 'musiconhold.conf':
179
                    $actions['musiconhold'] = 100;
180
                    break;
181
                case 'modules.conf':
182
                    $actions['modules'] = 10;
183
                    break;
184
                case 'http.conf':
185
                    $actions['manager'] = 10; //
186
                    break;
187
                case 'hep.conf':
188
                    $actions['hep'] = 10; //
189
                    break;
190
                case 'root': // crontabs
191
                    $actions['cron'] = 10;
192
                    break;
193
                case 'queues.conf':
194
                    $actions['queues'] = 10;
195
                    break;
196
                case 'features.conf':
197
                    $actions['features'] = 10;
198
                    break;
199
                case 'ntp.conf':
200
                    $actions['ntp'] = 100;
201
                    break;
202
                case 'ooh323.conf':
203
                    $actions['h323'] = 100;
204
                    break;
205
                case 'rtp.conf':
206
                    $actions['rtp'] = 10;
207
                    break;
208
                case 'static-routes':
209
                case 'openvpn.ovpn':
210
                    $actions['network'] = 100;
211
                    break;
212
                case 'firewall_additional':
213
                case 'jail.local':
214
                    $actions['firewall'] = 100;
215
                    break;
216
                default:
217
                    break;
218
            }
219
        }
220
221
        // Sort actions and invoke them
222
        asort($actions);
223
        self::invokeActions($actions);
224
225
        // After actions are invoked, reset the changed status and save the file data
226
        foreach ($res_data as $file_data) {
227
            /** @var CustomFiles $file_data */
228
            $file_data->writeAttribute("changed", '0');
229
            $file_data->save();
230
        }
231
    }
232
233
    /**
234
     * Restart modules or services based on the provided actions.
235
     *
236
     * @param array $actions - The actions to be performed.
237
     *
238
     * @return void
239
     */
240
    public static function invokeActions(array $actions): void
241
    {
242
243
        // Process each action
244
        foreach ($actions as $action => $value) {
245
            // Restart modules or services based on action
246
            switch ($action) {
247
                case 'manager':
248
                    PBX::managerReload();
249
                    break;
250
                case 'musiconhold':
251
                    PBX::musicOnHoldReload();
252
                    break;
253
                case 'rtp':
254
                    PBX::rtpReload();
255
                    break;
256
                case 'modules':
257
                    PBX::modulesReload();
258
                    break;
259
                case 'cron':
260
                    $cron = new CronConf();
261
                    $cron->reStart();
262
                    break;
263
                case 'queues':
264
                    QueueConf::queueReload();
265
                    break;
266
                case 'features':
267
                    PBX::managerReload(); //
268
                    break;
269
                case 'ntp':
270
                    NTPConf::configure();
271
                    break;
272
                case 'firewall':
273
                    IptablesConf::reloadFirewall();
274
                    break;
275
                case 'hep':
276
                    HepConf::reload();
277
                    break;
278
                case 'h323':
279
                    H323Conf::reload();
280
                    break;
281
                case 'network':
282
                    self::networkReload();
283
                    break;
284
                case 'asterisk_core_reload':
285
                    PBX::sipReload();
286
                    PBX::iaxReload();
287
                    PBX::dialplanReload();
288
                    PBX::coreReload();
289
                    break;
290
                default:
291
            }
292
        }
293
    }
294
295
    /**
296
     * Sets the system date and time based on timestamp and timezone.
297
     *
298
     * @param int    $timeStamp - Unix timestamp.
299
     * @param string $remote_tz - Timezone string.
300
     *
301
     * @return bool
302
     * @throws \Exception
303
     */
304
    public static function setDate(int $timeStamp, string $remote_tz): bool
305
    {
306
        $datePath = Util::which('date');
307
308
        // Fetch timezone from database
309
        $db_tz = PbxSettings::getValueByKey('PBXTimezone');
310
        $origin_tz = '';
311
312
        // Read existing timezone from file if it exists
313
        if (file_exists('/etc/TZ')) {
314
            $origin_tz = file_get_contents("/etc/TZ");
315
        }
316
317
        // If the timezones are different, configure the timezone
318
        if ($origin_tz !== $db_tz){
319
            self::timezoneConfigure();
320
        }
321
322
        // Calculate the time offset and set the system time
323
        $origin_tz = $db_tz;
324
        $origin_dtz = new DateTimeZone($origin_tz);
325
        $remote_dtz = new DateTimeZone($remote_tz);
326
        $origin_dt  = new DateTime('now', $origin_dtz);
327
        $remote_dt  = new DateTime('now', $remote_dtz);
328
        $offset     = $origin_dtz->getOffset($origin_dt) - $remote_dtz->getOffset($remote_dt);
329
        $timeStamp  = $timeStamp - $offset;
330
331
        // Execute date command to set system time
332
        Processes::mwExec("{$datePath} +%s -s @{$timeStamp}");
333
334
        return true;
335
    }
336
337
    /**
338
     * Reboots the system after calling system_reboot_cleanup()
339
     *
340
     * @return void
341
     */
342
    public static function rebootSync(): void
343
    {
344
        $pbx_rebootPath = Util::which('pbx_reboot');
345
        Processes::mwExec("{$pbx_rebootPath} > /dev/null 2>&1");
346
    }
347
348
    /**
349
     * Reboots the system after calling system_reboot_cleanup()
350
     */
351
    public static function rebootSyncBg(): void
352
    {
353
        $pbx_rebootPath = Util::which('pbx_reboot');
354
        Processes::mwExecBg("{$pbx_rebootPath} > /dev/null 2>&1");
355
    }
356
357
    /**
358
     * Shutdown the system
359
     */
360
    public static function shutdown(): void
361
    {
362
        $shutdownPath = Util::which('shutdown');
363
        Processes::mwExec("{$shutdownPath} > /dev/null 2>&1");
364
    }
365
366
367
    /**
368
     * Configures the system timezone according to the PBXTimezone setting.
369
     *
370
     * @return void
371
     */
372
    public static function timezoneConfigure(): void
373
    {
374
375
        // Get the timezone setting from the database
376
        $timezone = PbxSettings::getValueByKey('PBXTimezone');
377
378
        // If /etc/TZ or /etc/localtime exist, delete them
379
        if (file_exists('/etc/TZ')) {
380
            unlink("/etc/TZ");
381
        }
382
        if (file_exists('/etc/localtime')) {
383
            unlink("/etc/localtime");
384
        }
385
386
        // If a timezone is set, configure it
387
        if ($timezone) {
388
389
            // The path to the zone file
390
            $zone_file = "/usr/share/zoneinfo/{$timezone}";
391
392
            // If the zone file exists, copy it to /etc/localtime
393
            if ( ! file_exists($zone_file)) {
394
                return;
395
            }
396
            $cpPath = Util::which('cp');
397
            Processes::mwExec("{$cpPath}  {$zone_file} /etc/localtime");
398
399
            // Write the timezone to /etc/TZ and set the TZ environment variable
400
            file_put_contents('/etc/TZ', $timezone);
401
            putenv("TZ={$timezone}");
402
403
            // Execute the export TZ command and configure PHP's timezone
404
            Processes::mwExec("export TZ;");
405
            PHPConf::phpTimeZoneConfigure();
406
        }
407
408
    }
409
410
    /**
411
     * Loads additional kernel modules.
412
     *
413
     * @return bool - Returns true if modules are loaded successfully.
414
     */
415
    public function loadKernelModules(): bool
416
    {
417
        // If the system is running in Docker, no need to load kernel modules
418
        if(Util::isDocker()){
419
            return true;
420
        }
421
422
        // Paths to system commands
423
        $modprobePath = Util::which('modprobe');
424
        $ulimitPath   = Util::which('ulimit');
425
426
        // Load dahdi and dahdi_transcode modules and set ulimit values
427
        $res1 = Processes::mwExec("{$modprobePath} -q dahdi");
428
        $res2 = Processes::mwExec("{$modprobePath} -q dahdi_transcode");
429
        Processes::mwExec("{$ulimitPath} -n 4096");
430
        Processes::mwExec("{$ulimitPath} -p 4096");
431
432
        // Return true if both modules loaded successfully
433
        return ($res1 === 0 && $res2 === 0);
434
    }
435
436
    /**
437
     * Calculate the hash of SSL certificates and extract them from ca-certificates.crt.
438
     *
439
     * @return void
440
     */
441
    public static function sslRehash(): void
442
    {
443
        // Paths to system commands
444
        $openSslPath = Util::which('openssl');
445
        $cutPath     = Util::which('cut');
446
447
        // Get OpenSSL directory and cert file
448
        $openSslDir  = trim(shell_exec("$openSslPath version -d | $cutPath -d '\"' -f 2"));
449
        $certFile    = "$openSslDir/certs/ca-certificates.crt";
450
        $tmpFile     = tempnam('/tmp', 'cert-');
451
        $rawData     = file_get_contents($certFile);
452
        $certs       = explode(PHP_EOL.PHP_EOL, $rawData);
453
        foreach ($certs as $cert){
454
            if(strpos($cert, '-----BEGIN CERTIFICATE-----') === false){
455
                continue;
456
            }
457
            file_put_contents($tmpFile, $cert);
458
            $hash = trim(shell_exec("$openSslPath x509 -subject_hash -noout -in '$tmpFile'"));
459
            rename($tmpFile, "$openSslDir/certs/$hash.0");
460
        }
461
        if(file_exists($tmpFile)){
462
            unlink($tmpFile);
463
        }
464
    }
465
}
466