Passed
Push — develop ( 225864...73f8c7 )
by Nikolay
08:07 queued 03:40
created

DockerEntrypoint::start()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 19
rs 9.7666
cc 2
nc 2
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 Error;
23
use JsonException;
24
use MikoPBX\Common\Models\PbxSettingsConstants;
25
use Phalcon\Di;
26
27
/**
28
 * The entry point class for MikoPBX.
29
 */
30
class DockerEntrypoint extends Di\Injectable
31
{
32
    public const  PATH_DB = '/cf/conf/mikopbx.db';
33
    private const  pathInc = '/etc/inc/mikopbx-settings.json';
34
    public float $workerStartTime;
35
    private array $env;
36
    private array $incSettings;
37
    private array $settings;
38
39
    /**
40
     * Constructs the Entrypoint class.
41
     */
42
    public function __construct()
43
    {
44
        pcntl_async_signals(true);
45
        register_shutdown_function([$this, 'shutdownHandler']);
46
47
        $this->env = [
48
            // Identification of the WWW user.
49
            'ID_WWW_USER' => '',
50
            'ID_WWW_GROUP' => '',
51
52
            //
53
            'BEANSTALK_PORT' => 'beanstalk',
54
            'REDIS_PORT' => 'redis',
55
            'GNATS_PORT' => 'gnats',
56
57
            // General settings.
58
            'SSH_PORT' => PbxSettingsConstants::SSH_PORT,
59
            'WEB_PORT' => PbxSettingsConstants::WEB_PORT,
60
            'WEB_HTTPS_PORT' => PbxSettingsConstants::WEB_HTTPS_PORT,
61
            'SIP_PORT' => PbxSettingsConstants::SIP_PORT,
62
            'TLS_PORT' => PbxSettingsConstants::TLS_PORT,
63
            'RTP_FROM' => PbxSettingsConstants::RTP_PORT_FROM,
64
            'RTP_TO' => PbxSettingsConstants::RTP_PORT_TO,
65
            'IAX_PORT' => PbxSettingsConstants::IAX_PORT,
66
            'AMI_PORT' => PbxSettingsConstants::AMI_PORT,
67
            'AJAM_PORT' => PbxSettingsConstants::AJAM_PORT,
68
            'AJAM_PORT_TLS' => PbxSettingsConstants::AJAM_PORT_TLS,
69
70
            // Environment
71
            PbxSettingsConstants::VIRTUAL_HARDWARE_TYPE=>'Docker'
72
        ];
73
    }
74
75
    /**
76
     * Handles the shutdown event.
77
     */
78
    public function shutdownHandler(): void
79
    {
80
        $e = error_get_last();
81
        $delta = round(microtime(true) - $this->workerStartTime, 2);
82
        if ($e === null) {
83
            Util::sysLogMsg(static::class, "shutdownHandler after $delta seconds", LOG_DEBUG);
84
        } else {
85
            $details = (string)print_r($e, true);
86
            Util::sysLogMsg(static::class, "shutdownHandler after $delta seconds with error: $details", LOG_DEBUG);
87
        }
88
    }
89
90
    /**
91
     * Starts the MikoPBX system.
92
     */
93
    public function start(): void
94
    {
95
        $this->workerStartTime = microtime(true);
96
        $sysLogdPath = Util::which('syslogd');
97
        // Start the system log.
98
        Processes::mwExecBg($sysLogdPath . ' -S -C512');
99
        $out = [];
100
        Processes::mwExec('sqlite3 ' . self::PATH_DB . ' .tables', $out);
101
        if (trim(implode('', $out)) === '') {
102
            Util::mwMkdir(dirname(self::PATH_DB));
103
            Processes::mwExec("rm -rf " . self::PATH_DB . "; cp /conf.default/mikopbx.db " . self::PATH_DB);
104
            Util::addRegularWWWRights(self::PATH_DB);
105
        }
106
        $this->checkUpdate();
107
        shell_exec("rm -rf /tmp/*");
108
        $commands = 'exec </dev/console >/dev/console 2>/dev/console;' .
109
            '/etc/rc/bootup 2>/dev/null && ' .
110
            '/etc/rc/bootup_pbx 2>/dev/null';
111
        passthru($commands);
112
    }
113
114
    /**
115
     * Checks if there is an indication to apply a custom port value and calls the updateSetting function.
116
     */
117
    public function checkUpdate(): void
118
    {
119
        Util::echoWithSyslog(' - Check update... ' . PHP_EOL);
120
        $this->initSettings();
121
        foreach ($this->env as $key => $dataPath) {
122
            $newValue = getenv($key);
123
            if (!is_numeric($newValue)) {
124
                continue;
125
            }
126
127
            if (empty($dataPath)) {
128
                $this->env[$key] = $newValue;
129
                continue;
130
            }
131
132
            $isInc = false;
133
            $oldValue = $this->settings[$dataPath] ?? '';
134
            if (empty($oldValue)) {
135
                $oldValue = 1 * $this->incSettings[$dataPath]['port'] ?? 0;
136
                $newValue = 1 * $newValue;
137
                $isInc = true;
138
            }
139
140
            if (empty($oldValue) || $newValue === $oldValue) {
141
                continue;
142
            }
143
            $this->updateSetting($dataPath, $newValue, $isInc);
144
        }
145
146
        $this->changeWwwUserID($this->env['ID_WWW_USER'], $this->env['ID_WWW_GROUP']);
147
    }
148
149
    /**
150
     * Initializes the settings. Required for checking and updating port settings.
151
     */
152
    private function initSettings(): void
153
    {
154
        $jsonString = file_get_contents(self::pathInc);
155
        try {
156
            $this->incSettings = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR);
157
        } catch (JsonException $exception) {
158
            $this->incSettings = [];
159
            throw new Error(self::pathInc . " has broken format");
160
        }
161
162
        $out = [];
163
        Processes::mwExec("sqlite3 " . self::PATH_DB . " 'SELECT * FROM m_PbxSettings' | grep -i port", $out);
164
        $this->settings = [];
165
        $keys = array_flip($this->env);
166
        foreach ($out as $row) {
167
            $data = explode('|', $row);
168
            $key = $data[0] ?? '';
169
            $value = $data[1] ?? '';
170
171
            if (!isset($keys[$key]) || empty($value)) {
172
                continue;
173
            }
174
            $this->settings[$key] = $value;
175
        }
176
    }
177
178
    /**
179
     * Updates a setting on DB or in mikopbx-settings.json with a new value.
180
     *
181
     * @param string $dataPath
182
     * @param string $newValue
183
     * @param bool $isInc
184
     */
185
    private function updateSetting(string $dataPath, string $newValue, bool $isInc): void
186
    {
187
        $msg = " - Update $dataPath (port) to '$newValue' ..." . PHP_EOL;
188
        Util::echoWithSyslog($msg);
189
        if ($isInc === true) {
190
            $this->incSettings[$dataPath]['port'] = $newValue;
191
            $newData = json_encode($this->incSettings, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
192
            $res = file_put_contents(self::pathInc, $newData);
193
            $result = (false !== $res);
194
        } else {
195
            $res = Processes::mwExec("sqlite3 " . self::PATH_DB . " 'UPDATE m_PbxSettings SET value=\"$newValue\" WHERE key=\"$dataPath\"'");
196
            $result = ($res === 0);
197
        }
198
        Util::echoResult($msg, $result);
199
    }
200
201
    /**
202
     * Changes the ID of the WWW user.
203
     *
204
     * @param string $newUserId
205
     * @param string $newGroupId
206
     */
207
    private function changeWwwUserID(string $newUserId, string $newGroupId): void
208
    {
209
        Util::echoWithSyslog(' - Check user id... ' . PHP_EOL);
210
        $pidIdPath = '/cf/conf/user.id';
211
        $pidGrPath = '/cf/conf/group.id';
212
213
        if (empty($newUserId) && file_exists($pidIdPath)) {
214
            $newUserId = file_get_contents($pidIdPath);
215
        }
216
        if (empty($newGroupId) && file_exists($pidGrPath)) {
217
            $newGroupId = file_get_contents($pidGrPath);
218
        }
219
220
        $commands = [];
221
        $userID = 'www';
222
        $currentUserId = trim(shell_exec("grep '^$userID:' < /etc/shadow | cut -d ':' -f 3"));
223
        $currentGroupId = trim(shell_exec("grep '^$userID:' < /etc/shadow | cut -d ':' -f 4"));
224
225
        Util::echoWithSyslog(" - Old user id: $currentUserId; New user id: $newUserId" . PHP_EOL);
226
        Util::echoWithSyslog(" - Old group id: $currentGroupId; New user id: $newGroupId" . PHP_EOL);
227
        if (!empty($currentUserId) && !empty($newUserId) && $currentUserId !== $newUserId) {
228
            $commands[] = "sed -i 's/$userID:x:$currentUserId:/$userID:x:$newUserId:/g' /etc/shadow*";
229
            $id = '';
230
            if (file_exists($pidIdPath)) {
231
                $id = file_get_contents($pidIdPath);
232
            }
233
            if ($id !== $newUserId) {
234
                $commands[] = "find / -not -path '/proc/*' -user $currentUserId -exec chown -h $userID {} \;";
235
                file_put_contents($pidIdPath, $newUserId);
236
            }
237
        }
238
        if (!empty($currentGroupId) && !empty($newGroupId) && $currentGroupId !== $newGroupId) {
239
            $commands[] = "sed -i 's/$userID:x:$currentGroupId:/$userID:x:$newGroupId:/g' /etc/group";
240
            $commands[] = "sed -i 's/:$currentGroupId:Web/:$newGroupId:Web/g' /etc/shadow";
241
242
            $id = '';
243
            if (file_exists($pidGrPath)) {
244
                $id = file_get_contents($pidGrPath);
245
            }
246
            if ($id !== $newGroupId) {
247
                $commands[] = "find / -not -path '/proc/*' -group $currentGroupId -exec chgrp -h $newGroupId {} \;";
248
                file_put_contents($pidGrPath, $newGroupId);
249
            }
250
        }
251
        if (!empty($commands)) {
252
            passthru(implode('; ', $commands));
253
        }
254
    }
255
}