Passed
Push — develop ( 01149b...e172a2 )
by Nikolay
05:41 queued 20s
created

DockerEntrypoint::checkUpdate()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 30
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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