Skip to content

Commit 960b482

Browse files
committed
Implements #6828 Extension installer GUI
1 parent 84d3eb1 commit 960b482

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1372
-10
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,5 @@ Temporary Items
6565
/server/lib/config.inc.local.php
6666
/interface/lib/config.inc.local.php
6767
/install/existing_db.sql
68+
69+
sync_config.jsonc

install/sql/incremental/upd_dev_collection.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ ALTER TABLE `client_template` ADD `limit_database_postgresql` INT NOT NULL DEFAU
55
ALTER TABLE `server_php` ADD `php_cli_binary` varchar(255) DEFAULT NULL AFTER `php_fpm_socket_dir`;
66
ALTER TABLE `server_php` ADD `php_jk_section` varchar(255) DEFAULT NULL AFTER `php_cli_binary`;
77
ALTER TABLE `mail_domain` ADD `local_delivery` enum('n','y') NOT NULL DEFAULT 'y' AFTER `active`;
8+
ALTER TABLE `sys_remoteaction` CHANGE `action_type` `action_type` VARCHAR(64) NOT NULL;
Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
<?php
2+
3+
/*
4+
Copyright (c) 2025, Till Brehm, ISPConfig UG
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without modification,
8+
are permitted provided that the following conditions are met:
9+
10+
* Redistributions of source code must retain the above copyright notice,
11+
this list of conditions and the following disclaimer.
12+
* Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
* Neither the name of ISPConfig nor the names of its contributors
16+
may be used to endorse or promote products derived from this software without
17+
specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22+
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23+
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24+
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26+
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28+
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
31+
class extension_installer
32+
{
33+
34+
/**
35+
* The repository list URL
36+
* @var string
37+
*/
38+
private $repo_list_url = 'https://repo.ispconfig.com/api/v1/list/';
39+
private $extension_basedir = '/usr/local/ispconfig/extensions';
40+
private $error = [];
41+
42+
private $repo_cache = [];
43+
44+
/**
45+
* Get the repository list URL
46+
* @return string
47+
*/
48+
public function getRepoListUrl() {
49+
if(file_exists($this->extension_basedir.'/devkey')) {
50+
$devkey = trim(file_get_contents($this->extension_basedir.'/devkey'));
51+
return $this->repo_list_url.'?devkey='.urlencode($devkey);
52+
}
53+
54+
return $this->repo_list_url;
55+
}
56+
57+
// add error
58+
public function addError($error) {
59+
$this->error[] = $error;
60+
}
61+
62+
// get errors
63+
public function getErrors() {
64+
return $this->error;
65+
}
66+
67+
// has errors
68+
public function hasErrors() {
69+
return count($this->error) > 0;
70+
}
71+
72+
/**
73+
* Get the list of available extensions from the repository
74+
* @return array
75+
*/
76+
public function getRepoExtensions() {
77+
if(empty($this->repo_cache)) {
78+
$response = file_get_contents($this->getRepoListUrl());
79+
if (empty($response)) {
80+
return [];
81+
} else {
82+
$this->repo_cache = json_decode($response, true);
83+
}
84+
}
85+
86+
return $this->repo_cache;
87+
}
88+
89+
public function getRepoExtension($name) {
90+
$repo_extensions = $this->getRepoExtensions();
91+
92+
$repo_extension = array_filter($repo_extensions, fn($ext) => $ext['name'] === $name);
93+
94+
if(!empty($repo_extension)) {
95+
return reset($repo_extension);
96+
} else {
97+
return null;
98+
}
99+
}
100+
101+
public function getInstalledExtensions($server_id = null) {
102+
global $app, $conf;
103+
104+
// get extensions from monitor_data table
105+
if($server_id === null) {
106+
$sql = 'SELECT * FROM `monitor_data` WHERE `type` = ?';
107+
$records = $app->db->queryAllRecords($sql, 'extensions');
108+
} else {
109+
$sql = 'SELECT * FROM `monitor_data` WHERE `type` = ? and `server_id` = ?';
110+
$records = $app->db->queryAllRecords($sql, 'extensions', $server_id);
111+
}
112+
113+
// get repo extensions
114+
$repo_extensions = $this->getRepoExtensions();
115+
116+
if(empty($records)) {
117+
return [];
118+
} else {
119+
$extensions = [];
120+
foreach($records as $record) {
121+
$data_records = json_decode($record['data'], true);
122+
if(!empty($data_records) && is_array($data_records)) {
123+
foreach($data_records as $data) {
124+
// get title by searching in repo extensions
125+
$repo_extension = array_filter($repo_extensions, fn($ext) => $ext['name'] === $data['name']);
126+
if(!empty($repo_extension)) {
127+
$repo_extension = reset($repo_extension);
128+
$extensions[] = [
129+
'name' => $data['name'],
130+
'version' => $data['version'],
131+
'license' => $data['license'],
132+
'title' => $repo_extension['title'],
133+
'active' => $data['active'],
134+
'server_id' => $record['server_id']
135+
];
136+
}
137+
}
138+
}
139+
}
140+
return $extensions;
141+
}
142+
}
143+
144+
public function getInstalledExtension($name, $server_id) {
145+
global $app, $conf;
146+
147+
// getInstalledExtensions
148+
$extensions = $this->getInstalledExtensions($server_id);
149+
150+
// search in extensions
151+
$extension = array_filter($extensions, fn($ext) => $ext['name'] === $name);
152+
if(!empty($extension)) {
153+
return reset($extension);
154+
} else {
155+
return null;
156+
}
157+
}
158+
159+
public function installExtension($name, $server_id) {
160+
global $app, $conf;
161+
162+
// check if extension exists in repository
163+
$repo_extension = $this->getRepoExtension($name);
164+
if($repo_extension === null) {
165+
$this->addError('Extension not found in repository');
166+
return false;
167+
}
168+
169+
// check if extension is already installed
170+
$installed_extensions = $this->getInstalledExtensions($server_id);
171+
$extension = array_filter($installed_extensions, fn($ext) => $ext['name'] === $name);
172+
if(!empty($extension)) {
173+
$this->addError('Extension already installed');
174+
return false;
175+
}
176+
177+
// check server_id in server table
178+
$sql = 'SELECT * FROM `server` WHERE `server_id` = ?';
179+
$record = $app->db->queryOneRecord($sql, $server_id);
180+
if($record === null) {
181+
$this->addError('Server not found');
182+
return false;
183+
}
184+
185+
// Install the extension
186+
$sql = "INSERT INTO sys_remoteaction (server_id, tstamp, action_type, action_param, action_state, response) " .
187+
"VALUES (?, UNIX_TIMESTAMP(), 'extension_install', ?, 'pending', '')";
188+
$app->db->query($sql, $server_id, $name);
189+
190+
return true;
191+
}
192+
193+
public function updateExtension($name, $server_id) {
194+
global $app, $conf;
195+
196+
// check if extension exists in repository
197+
$repo_extension = $this->getRepoExtension($name);
198+
if($repo_extension === null) {
199+
$this->addError('Extension not found in repository');
200+
return false;
201+
}
202+
203+
// check if extension is already installed
204+
$installed_extensions = $this->getInstalledExtensions($server_id);
205+
$extension = array_filter($installed_extensions, fn($ext) => $ext['name'] === $name);
206+
if(empty($extension)) {
207+
$this->addError('Extension not installed');
208+
return false;
209+
}
210+
211+
// check server_id in server table
212+
$sql = 'SELECT * FROM `server` WHERE `server_id` = ?';
213+
$record = $app->db->queryOneRecord($sql, $server_id);
214+
if($record === null) {
215+
$this->addError('Server not found');
216+
return false;
217+
}
218+
219+
// Update the extension
220+
$sql = "INSERT INTO sys_remoteaction (server_id, tstamp, action_type, action_param, action_state, response) " .
221+
"VALUES (?, UNIX_TIMESTAMP(), 'extension_update', ?, 'pending', '')";
222+
$app->db->query($sql, $server_id, $name);
223+
224+
return true;
225+
}
226+
227+
public function deleteExtension($name, $server_id) {
228+
global $app, $conf;
229+
230+
// check if extension is already installed
231+
$installed_extensions = $this->getInstalledExtensions($server_id);
232+
$extension = array_filter($installed_extensions, fn($ext) => $ext['name'] === $name);
233+
if(empty($extension)) {
234+
$this->addError('Extension not installed');
235+
return false;
236+
}
237+
238+
// check server_id in server table
239+
$sql = 'SELECT * FROM `server` WHERE `server_id` = ?';
240+
$record = $app->db->queryOneRecord($sql, $server_id);
241+
if($record === null) {
242+
$this->addError('Server not found');
243+
return false;
244+
}
245+
246+
// Delete the extension
247+
$sql = "INSERT INTO sys_remoteaction (server_id, tstamp, action_type, action_param, action_state, response) " .
248+
"VALUES (?, UNIX_TIMESTAMP(), 'extension_uninstall', ?, 'pending', '')";
249+
$app->db->query($sql, $server_id, $name);
250+
251+
return true;
252+
}
253+
254+
public function enableExtension($name, $server_id) {
255+
global $app, $conf;
256+
257+
// check if extension is already installed
258+
$installed_extensions = $this->getInstalledExtensions($server_id);
259+
$extension = array_filter($installed_extensions, fn($ext) => $ext['name'] === $name);
260+
if(empty($extension)) {
261+
$this->addError('Extension not installed');
262+
return false;
263+
}
264+
265+
// check server_id in server table
266+
$sql = 'SELECT * FROM `server` WHERE `server_id` = ?';
267+
$record = $app->db->queryOneRecord($sql, $server_id);
268+
if($record === null) {
269+
$this->addError('Server not found');
270+
return false;
271+
}
272+
273+
// Enable the extension
274+
$sql = "INSERT INTO sys_remoteaction (server_id, tstamp, action_type, action_param, action_state, response) " .
275+
"VALUES (?, UNIX_TIMESTAMP(), 'extension_enable', ?, 'pending', '')";
276+
$app->db->query($sql, $server_id, $name);
277+
278+
return true;
279+
}
280+
281+
public function disableExtension($name, $server_id) {
282+
global $app, $conf;
283+
284+
// check if extension is already installed
285+
$installed_extensions = $this->getInstalledExtensions($server_id);
286+
$extension = array_filter($installed_extensions, fn($ext) => $ext['name'] === $name);
287+
if(empty($extension)) {
288+
$this->addError('Extension not installed');
289+
return false;
290+
}
291+
292+
// check server_id in server table
293+
$sql = 'SELECT * FROM `server` WHERE `server_id` = ?';
294+
$record = $app->db->queryOneRecord($sql, $server_id);
295+
if($record === null) {
296+
$this->addError('Server not found');
297+
return false;
298+
}
299+
300+
// Disable the extension
301+
$sql = "INSERT INTO sys_remoteaction (server_id, tstamp, action_type, action_param, action_state, response) " .
302+
"VALUES (?, UNIX_TIMESTAMP(), 'extension_disable', ?, 'pending', '')";
303+
$app->db->query($sql, $server_id, $name);
304+
305+
return true;
306+
}
307+
308+
public function updateLicense($name, $server_id, $license) {
309+
global $app, $conf;
310+
311+
// check if extension is already installed
312+
$installed_extensions = $this->getInstalledExtensions($server_id);
313+
$extension = array_filter($installed_extensions, fn($ext) => $ext['name'] === $name);
314+
if(empty($extension)) {
315+
$this->addError('Extension not installed');
316+
return false;
317+
}
318+
319+
// check server_id in server table
320+
$sql = 'SELECT * FROM `server` WHERE `server_id` = ?';
321+
$record = $app->db->queryOneRecord($sql, $server_id);
322+
if($record === null) {
323+
$this->addError('Server not found');
324+
return false;
325+
}
326+
327+
// Update the license
328+
$data = ['name' => $name, 'license' => $license];
329+
$sql = "INSERT INTO sys_remoteaction (server_id, tstamp, action_type, action_param, action_state, response) " .
330+
"VALUES (?, UNIX_TIMESTAMP(), 'extension_license_update', ?, 'pending', '')";
331+
$app->db->query($sql, $server_id, json_encode($data));
332+
333+
return true;
334+
}
335+
336+
public function getServerName($server_id) {
337+
global $app;
338+
$sql = 'SELECT `server_name` FROM `server` WHERE `server_id` = ?';
339+
$record = $app->db->queryOneRecord($sql, $server_id);
340+
if($record === null) {
341+
return null;
342+
}
343+
return $record['server_name'];
344+
}
345+
346+
}

0 commit comments

Comments
 (0)