Passed
Push — develop ( e37b00...b3ec5e )
by Nikolay
04:28
created

PBXInstaller::createStoragePartition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
dl 0
loc 4
rs 10
c 1
b 0
f 0
cc 1
nc 1
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 MikoPBX\Common\Models\PbxSettings;
23
use MikoPBX\Common\Models\PbxSettingsConstants;
24
use MikoPBX\Common\Providers\ConfigProvider;
25
use Phalcon\Config;
26
use Phalcon\Di;
27
28
/**
29
 * Class PBXInstaller
30
 * Handles the installation of MikoPBX onto a selected drive
31
 * @package MikoPBX\Core\System
32
 */
33
class PBXInstaller extends Di\Injectable
34
{
35
    /**
36
     * Access to the /etc/inc/mikopbx-settings.json values
37
     *
38
     * @var \Phalcon\Config
39
     */
40
    private Config $config;
41
42
    private array $valid_disks = [];
43
    private array $selected_disk = ['size' => 0, 'id' => ''];
44
    private string $target_disk = '';
45
46
    // File pointer
47
    private $fp;
48
49
    /**
50
     * PBXInstaller constructor.
51
     * Initiates the installation process.
52
     */
53
    public function __construct()
54
    {
55
56
        $this->config = Di::getDefault()->getShared(ConfigProvider::SERVICE_NAME);
57
58
        $this->fp = fopen('php://stdin', 'rb');
59
60
    }
61
62
    /**
63
     * Initiates the installation steps.
64
     */
65
    public function run()
66
    {
67
        $this->scanAllHdd();
68
        if ($this->processValidDisks()){
69
            $this->promptForTargetDisk();
70
            if ($this->confirmInstallation()) {
71
                $this->proceedInstallation();
72
            }
73
        }
74
    }
75
76
    /**
77
     * Scans all connected HDDs.
78
     */
79
    private function scanAllHdd()
80
    {
81
        $storage = new Storage();
82
        $all_hdd = $storage->getAllHdd();
83
        foreach ($all_hdd as $disk) {
84
            $this->processDisk($disk);
85
        }
86
    }
87
88
    /**
89
     * Process each disk and save valid ones.
90
     *
91
     * @param array $disk Information about the disk
92
     */
93
    private function processDisk(array $disk)
94
    {
95
        // Initialize a variable to hold additional info
96
        $other_info = '';
97
98
        // Add info if the disk is a system disk or is mounted
99
        if (true === $disk['sys_disk']) {
100
            $other_info .= ' System Disk ';
101
        }
102
        if (true === $disk['mounted']) {
103
            $other_info .= ' Mounted ';
104
        }
105
106
        // Add a visual effect to the additional info if it's not empty
107
        if ($other_info !== '') {
108
            $other_info = "[ \033[31;1m{$other_info}\033[0m ]";
109
        }
110
111
        // Update the selected disk if the current disk's size is smaller
112
        if ($this->selected_disk['size'] === 0 || $this->selected_disk['size'] > $disk['size']) {
113
            $this->selected_disk = $disk;
114
        }
115
116
        // Ignore disks that are less than 400 megabytes
117
        if ($disk['size'] < 400) {
118
            return;
119
        }
120
121
        // Add the disk to the list of valid disks
122
        $this->valid_disks[$disk['id']] = "  - {$disk['id']}, {$disk['size_text']}, {$disk['vendor']} $other_info \n";
123
    }
124
125
    /**
126
     * Process the valid disks and select one for installation.
127
     */
128
    private function processValidDisks(): bool
129
    {
130
        // If no valid disks were found, print a message and sleep for 3 seconds, then return 0
131
        if (count($this->valid_disks) === 0) {
132
            echo "\n " . Util::translate('Valid disks not found...') . " \n";
133
            sleep(3);
134
            return false;
135
        }
136
137
        // If valid disks were found, print prompts for the user to select a disk
138
        echo "\n " . Util::translate('Select the drive to install the system.') . ' ';
139
        echo "\n " . Util::translate('Selected disk:') . "\033[33;1m [{$this->selected_disk['id']}] \033[0m \n\n";
140
        echo "\n " . Util::translate('Valid disks are:') . " \n\n";
141
142
        // Print each valid disk
143
        foreach ($this->valid_disks as $disk) {
144
            echo $disk;
145
        }
146
        echo "\n";
147
        return true;
148
    }
149
150
    /**
151
     * Prompt the user to select a target disk.
152
     *
153
     * @return void The selected disk id
154
     */
155
    private function promptForTargetDisk(): void
156
    {
157
        // Prompt the user to enter a device name until a valid device name is entered
158
        do {
159
            echo "\n" . Util::translate('Enter the device name:') . Util::translate('(default value = ') . $this->selected_disk['id'] . ') :';
160
            $this->target_disk = trim(fgets($this->fp));
161
            if ($this->target_disk === '') {
162
                $this->target_disk = $this->selected_disk['id'];
163
            }
164
        } while (!array_key_exists($this->target_disk, $this->valid_disks));
165
166
    }
167
168
    /**
169
     * Confirm the installation from the user.
170
     */
171
    private function confirmInstallation(): bool
172
    {
173
        // Warning and confirmation prompt
174
        echo '
175
176
*******************************************************************************
177
* ' . Util::translate('WARNING') . '!
178
* ' . Util::translate('The PBX is about to be installed onto the') . " \033[33;1m{$this->target_disk}\033[0m.
179
* - " . Util::translate('everything on this device will be erased!') . '
180
* - ' . Util::translate('this cannot be undone!') . '
181
*******************************************************************************
182
183
' . Util::translate('The PBX will reboot after installation.') . '
184
185
' . Util::translate('Do you want to proceed? (y/n): ');
186
187
        // If the user doesn't confirm, save the system disk info to a temp file and exit
188
        if (strtolower(trim(fgets($this->fp))) !== 'y') {
189
            sleep(3);
190
            return false;
191
        }
192
193
        return true;
194
    }
195
196
    /**
197
     * Start the installation process.
198
     */
199
    private function proceedInstallation()
200
    {
201
        // Save the target disk to a file
202
        $varEtcDir = Directories::getDir(Directories::CORE_VAR_ETC_DIR);
203
        file_put_contents($varEtcDir . '/cfdevice', $this->target_disk);
204
205
        // Start the installation process
206
        echo "Installing PBX...\n";
207
        $this->unmountPartitions();
208
        $this->unpackImage();
209
        $this->mountStorage();
210
        $this->copyConfiguration();
211
        $umount = Util::which('umount');
212
        Processes::mwExec("$umount /mnttmp");
213
        echo "done\n";
214
215
        // Reboot
216
        file_put_contents('/tmp/ejectcd', '');
217
        System::reboot();
218
    }
219
220
    /**
221
     * Unmount the partitions of the selected disk.
222
     */
223
    private function unmountPartitions()
224
    {
225
        echo " - Unmounting partitions...\n";
226
        $grep = Util::which('grep');
227
        $awk = Util::which('awk');
228
        $mount = Util::which('mount');
229
        $umount = Util::which('umount');
230
231
        // Get all mounted partitions
232
        $mnt_dirs = [];
233
        Processes::mwExec("{$mount} | $grep '^/dev/{$this->target_disk}' | $awk '{print $3}'", $mnt_dirs);
234
        foreach ($mnt_dirs as $mnt) {
235
            // Terminate all related processes.
236
            Processes::mwExec("/sbin/shell_functions.sh killprocesses '$mnt' -TERM 0;");
237
            // Unmount.
238
            Processes::mwExec("{$umount} {$mnt}");
239
        }
240
    }
241
242
    /**
243
     * Unpack the image to the target disk.
244
     */
245
    private function unpackImage()
246
    {
247
        echo " - Unpacking img...\n";
248
        $pv = Util::which('pv');
249
        $dd = Util::which('dd');
250
        $gunzip = Util::which('gunzip');
251
252
        $install_cmd = 'exec < /dev/console > /dev/console 2>/dev/console;' .
253
            "{$pv} -p /offload/firmware.img.gz | {$gunzip} | {$dd} of=/dev/{$this->target_disk} bs=512 2> /dev/null";
254
        passthru($install_cmd);
255
    }
256
257
    /**
258
     * Mount the storage partition.
259
     */
260
    private function mountStorage()
261
    {
262
        // Connect the disk for data storage.
263
        Storage::selectAndConfigureStorageDisk(false,true);
264
    }
265
266
    /**
267
     * Copy the configuration to the target disk.
268
     */
269
    private function copyConfiguration()
270
    {
271
        // Back up the table with disk information.
272
        echo 'Copying configuration...';
273
        Util::mwMkdir('/mnttmp');
274
275
        $confPartitionName = Storage::getDevPartName("/dev/{$this->target_disk}", '3');
276
277
        // Mount the disk with settings.
278
        $mount = Util::which('mount');
279
        Processes::mwExec("{$mount} -w -o noatime {$confPartitionName} /mnttmp");
280
281
        $filename = $this->config->path('database.dbfile');
282
        $result_db_file = '/mnttmp/conf/mikopbx.db';
283
284
        /** Copy the settings database file. */
285
        $cp = Util::which('cp');
286
        $sqlite3 = Util::which('sqlite3');
287
        $grep = Util::which('grep');
288
        $dmpDbFile = tempnam('/tmp', 'storage');
289
290
        // Save dump of settings.
291
        $tables = ['m_Storage', 'm_LanInterfaces'];
292
        $grepOptions = '';
293
        foreach ($tables as $table) {
294
            $grepOptions .= " -e '^INSERT INTO {$table}'";
295
            $grepOptions .= " -e '^INSERT INTO \"{$table}'";
296
        }
297
        system("{$sqlite3} {$filename} .dump | {$grep} {$grepOptions} > " . $dmpDbFile);
298
299
        // If another language is selected - use another settings file.
300
        $lang = PbxSettings::getValueByKey(PbxSettingsConstants::SSH_LANGUAGE);
301
        $filename_lang = "/offload/conf/mikopbx-{$lang}.db";
302
        if ($lang !== 'en' && file_exists($filename_lang)) {
303
            $filename = $filename_lang;
304
        }
305
306
        // Replace the settings file.
307
        Processes::mwExec("{$cp} {$filename} {$result_db_file}");
308
        system("{$sqlite3} {$result_db_file} 'DELETE FROM m_Storage'");
309
        system("{$sqlite3} {$result_db_file} 'DELETE FROM m_LanInterfaces'");
310
311
        // Restore settings from backup file.
312
        system("{$sqlite3} {$result_db_file} < {$dmpDbFile}");
313
        unlink($dmpDbFile);
314
    }
315
}