Skip to content

Commit cd24bac

Browse files
committed
WebAppInstaller: Implemented installers for Opencart and Wordpress
1 parent 1d07a46 commit cd24bac

File tree

1 file changed

+261
-21
lines changed

1 file changed

+261
-21
lines changed

web/add/webapp/installer.php

Lines changed: 261 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,41 @@
11
<?php
22

3+
4+
5+
function join_paths() {
6+
$paths = array();
7+
8+
foreach (func_get_args() as $arg) {
9+
if ($arg !== '') { $paths[] = $arg; }
10+
}
11+
12+
return preg_replace('#/+#','/',join('/', $paths));
13+
}
14+
15+
function generate_string($length = 16) {
16+
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~`!@|#[]$%^&*() _-=+{}:;<>?,./';
17+
$random_string = '';
18+
for($i = 0; $i < $length; $i++) {
19+
$random_string .= $chars[random_int(0, strlen($chars) - 1)];
20+
}
21+
return $random_string;
22+
}
23+
24+
25+
326
abstract class BaseSetup {
27+
28+
protected $domain;
29+
public function __construct($domain, $appcontext) {
30+
31+
// validate domain name
32+
if(filter_var($domain, FILTER_VALIDATE_DOMAIN) === false) {
33+
throw new Exception("Invalid domain name");
34+
}
35+
36+
$this->domain = $domain;
37+
$this->appcontext = $appcontext;
38+
}
439
public function getConfig($section=null) {
540
return (!empty($section))? $this->config[$section] : $this->config;
641
}
@@ -12,10 +47,31 @@ public function getOptions() {
1247
public function withDatabase() {
1348
return ($this->getConfig('database') === true);
1449
}
50+
51+
public function getDocRoot($docrelative=null) {
52+
$domain_path = $this->appcontext->getWebDomainPath($this->domain);
53+
if(empty($domain_path)){
54+
return false;
55+
}
56+
57+
return join_paths($domain_path, "public_html", $docrelative);
58+
}
59+
60+
public function retrieveResources() {
61+
return $this->appcontext->archiveExtract(
62+
$this->getConfig('url'),
63+
$this->getDocRoot(), 1);
64+
}
65+
66+
public function install($options) {
67+
return $this->retrieveResources();
68+
}
1569
}
1670

1771

1872
class WordpressSetup extends BaseSetup {
73+
74+
protected $appname = 'wordpress';
1975
protected $config = [
2076
'form' => [
2177
'protocol' => [
@@ -27,18 +83,120 @@ class WordpressSetup extends BaseSetup {
2783
'site_name' => ['type'=>'text', 'value'=>'Wordpress Blog'],
2884
'site_description' => ['value'=>'Another wordpresss site'],
2985
'wordpress_account_username' => ['value'=>'wpadmin'],
86+
'wordpress_account_email' => 'text',
3087
'wordpress_account_password' => 'password',
3188
],
3289
'database' => true,
3390
'url' => 'https://wordpress.org/wordpress-5.2.2.tar.gz'
3491
];
92+
93+
public function install($options) {
94+
parent::install($options);
95+
96+
$this->appcontext->runUser('v-open-fs-file',[$this->getDocRoot("wp-config-sample.php")], $result);
97+
98+
$distconfig = preg_replace( [
99+
'/database_name_here/', '/username_here/', '/password_here/'
100+
], [
101+
$this->appcontext->user() . '_' . $options['database_name'],
102+
$this->appcontext->user() . '_' . $options['database_user'],
103+
$options['database_password']
104+
],
105+
$result->text);
106+
107+
while (strpos($distconfig, 'put your unique phrase here') !== false) {
108+
$distconfig = preg_replace( '/put your unique phrase here/', generate_string(64), $distconfig, 1);
109+
}
110+
111+
$tmp_configpath = $this->appcontext->saveTempFile($distconfig);
112+
113+
if(!$this->appcontext->runUser('v-copy-fs-file',[$tmp_configpath, $this->getDocRoot("wp-config.php")], $result)) {
114+
return false;
115+
}
116+
117+
exec("/usr/bin/curl --post301 --insecure --resolve ".$this->domain.":80:".$this->appcontext->getWebDomainIp($this->domain)." "
118+
. escapeshellarg("http://".$this->domain."/wp-admin/install.php?step=2")
119+
. " -d " . escapeshellarg(
120+
"weblog_title=" . rawurlencode($options['site_name'])
121+
. "&user_name=" . rawurlencode($options['wordpress_account_username'])
122+
. "&admin_password=" . rawurlencode($options['wordpress_account_password'])
123+
. "&admin_password2=". rawurlencode($options['wordpress_account_password'])
124+
. "&admin_email=" . rawurlencode($options['wordpress_account_email'])), $output, $return_var);
125+
126+
return ($return_var === 0);
127+
}
35128
}
36129

130+
class OpencartSetup extends BaseSetup {
131+
132+
protected $appname = 'opencart';
133+
protected $config = [
134+
'form' => [
135+
'protocol' => [
136+
'type' => 'select',
137+
'options' => ['http','ion','https'],
138+
'value' => 'ion',
139+
],
140+
'subdir' => ['type'=>'text', 'value'=>'/'],
141+
'opencart_account_username' => ['value'=>'ocadmin'],
142+
'opencart_account_email' => 'text',
143+
'opencart_account_password' => 'password',
144+
],
145+
'database' => true,
146+
'url' => 'https://github.com/opencart/opencart/releases/download/3.0.3.2/opencart-3.0.3.2.zip'
147+
//'url' => 'https://github.com/opencart/opencart/archive/3.0.3.2.tar.gz'
148+
];
149+
150+
public function retrieveResources() {
151+
152+
#cleanup temp folder
153+
$this->appcontext->runUser('v-delete-fs-directory', [$this->getDocRoot("/tmp-opencart")], $result);
154+
155+
$this->appcontext->archiveExtract($this->getConfig('url'), $this->getDocRoot("/tmp-opencart"), 1);
156+
157+
$this->appcontext->runUser('v-copy-fs-directory',[
158+
$this->getDocRoot("/tmp-opencart/upload/."),
159+
$this->getDocRoot()], $result);
160+
161+
$this->appcontext->runUser('v-delete-fs-directory',[$this->getDocRoot("/tmp-opencart")], $result);
162+
return true;
163+
}
164+
165+
public function install($options) {
166+
parent::install($options);
167+
168+
$this->appcontext->runUser('v-copy-fs-file',[$this->getDocRoot("config-dist.php"), $this->getDocRoot("config.php")]);
169+
$this->appcontext->runUser('v-copy-fs-file',[$this->getDocRoot("admin/config-dist.php"), $this->getDocRoot("admin/config.php")]);
170+
171+
$this->appcontext->runUser('v-change-fs-file-permission',[$this->getDocRoot("config.php"), '666']);
172+
$this->appcontext->runUser('v-change-fs-file-permission',[$this->getDocRoot("admin/config.php"), '666']);
173+
174+
exec("/usr/bin/php " . escapeshellarg($this->getDocRoot("/install/cli_install.php")) . " install"
175+
. " --db_username " . escapeshellarg($this->appcontext->user() . '_' .$options['database_user'])
176+
. " --db_password " . escapeshellarg($options['database_password'])
177+
. " --db_database " . escapeshellarg($this->appcontext->user() . '_' .$options['database_name'])
178+
. " --username " . escapeshellarg($options['opencart_account_username'])
179+
. " --password " . escapeshellarg($options['opencart_account_password'])
180+
. " --email " . escapeshellarg($options['opencart_account_email'])
181+
. " --http_server " . escapeshellarg("http://" . $this->domain . "/")
182+
, $output, $return_var);
183+
184+
$this->appcontext->runUser('v-change-fs-file-permission',[$this->getDocRoot("config.php"), '640']);
185+
$this->appcontext->runUser('v-change-fs-file-permission',[$this->getDocRoot("admin/config.php"), '640']);
186+
187+
return ($return_var === 0);
188+
}
189+
}
37190

38191
class HestiaApp {
39192

40-
public function run($cmd, $args, &$return_code=null) {
193+
protected const TMPDIR_DOWNLOADS="/tmp/hestia-webapp";
194+
195+
public function __construct() {
196+
mkdir(self::TMPDIR_DOWNLOADS);
197+
}
41198

199+
public function run($cmd, $args, &$cmd_result=null) {
42200
$cli_script = HESTIA_CMD . '/' . basename($cmd);
43201
$cli_arguments = '';
44202

@@ -50,43 +208,117 @@ public function run($cmd, $args, &$return_code=null) {
50208
$cli_arguments = escapeshellarg($args);
51209
}
52210

53-
exec ($cli_script . ' ' . $cli_arguments, $output, $return_code);
211+
exec ($cli_script . ' ' . $cli_arguments, $output, $exit_code);
54212

55-
$result['code'] = $return_code;
213+
$result['code'] = $exit_code;
214+
$result['args'] = $cli_arguments;
56215
$result['raw'] = $output;
57-
$result['text'] = implode( PHP_EOL, $result['raw']);
216+
$result['text'] = implode( PHP_EOL, $output);
58217
$result['json'] = json_decode($result['text'], true);
218+
$cmd_result = (object)$result;
59219

60-
return (object)$result;
220+
return ($exit_code === 0);
61221
}
62222

223+
public function runUser($cmd, $args, &$cmd_result=null) {
224+
if (!empty($args) && is_array($args)) {
225+
array_unshift($args, $this->user());
226+
}
227+
else {
228+
$args = [$this->user(), $args];
229+
}
230+
return $this->run($cmd, $args, $cmd_result);
231+
}
232+
233+
// Logged in user
63234
public function realuser() {
64-
// Logged in user
65235
return $_SESSION['user'];
66236
}
67237

238+
// Effective user
68239
public function user() {
69-
// Effective user
70-
if ($this->realuser() == 'admin' && !empty($_SESSION['look'])) {
71-
return $_SESSION['look'];
240+
$user = $this->realuser();
241+
if ($user == 'admin' && !empty($_SESSION['look'])) {
242+
$user = $_SESSION['look'];
72243
}
73-
return $this->realuser();
244+
245+
if(strpos($user, DIRECTORY_SEPARATOR) !== false) {
246+
throw new Exception("illegal characthers in username");
247+
}
248+
return $user;
74249
}
75250

76251
public function userOwnsDomain($domain) {
77-
$status = null;
78-
$this->run('v-list-web-domain', [$this->user(), $domain, 'json'], $status);
79-
return ($status === 0);
252+
return $this->runUser('v-list-web-domain', [$domain, 'json']);
80253
}
81254

82-
public function databaseAdd($dbname,$dbuser,$dbpass) {
83-
$v_password = tempnam("/tmp","vst");
255+
public function databaseAdd($dbname, $dbuser, $dbpass) {
256+
$v_password = tempnam("/tmp","hst");
84257
$fp = fopen($v_password, "w");
85258
fwrite($fp, $dbpass."\n");
86259
fclose($fp);
87-
$this->run('v-add-database', [$this->user(), $dbname, $dbuser, $v_password], $status);
260+
$status = $this->runUser('v-add-database', [$dbname, $dbuser, $v_password]);
88261
unlink($v_password);
89-
return ($status === 0);
262+
return $status;
263+
}
264+
265+
public function getWebDomainIp($domain) {
266+
$this->runUser('v-list-web-domain', [$domain, 'json'], $result);
267+
$ip = $result->json[$domain]['IP'];
268+
return filter_var($ip, FILTER_VALIDATE_IP);
269+
}
270+
271+
public function getWebDomainPath($domain) {
272+
return join_paths("/home", $this->user() , "/web", $domain);
273+
}
274+
275+
public function downloadUrl($src, $path=null, &$result=null) {
276+
if (strpos($src,'http://') !== 0 &&
277+
strpos($src,'https://')!== 0 ) {
278+
return false;
279+
}
280+
281+
exec("/usr/bin/wget --tries 3 -nv " . escapeshellarg($src). " -P " . escapeshellarg(self::TMPDIR_DOWNLOADS) . ' 2>&1', $output, $return_var);
282+
if ($return_var !== 0) {
283+
return false;
284+
}
285+
286+
if(!preg_match('/URL:\s*(.+?)\s*\[(.+?)\]\s*->\s*"(.+?)"/', implode(PHP_EOL, $output), $matches)) {
287+
return false;
288+
}
289+
290+
if(empty($matches) || count($matches) != 4) {
291+
return false;
292+
}
293+
294+
$status['url'] = $matches[1];
295+
$status['file'] = $matches[3];
296+
$result = (object)$status;
297+
return true;
298+
}
299+
300+
public function archiveExtract($src, $path, $skip_components=null) {
301+
if (file_exists($src)) {
302+
$archive_file = $src;
303+
} else {
304+
if( !$this->downloadUrl($src, null, $download_result) ) {
305+
return false;
306+
}
307+
$archive_file = $download_result->file;
308+
}
309+
$status = $this->runUser('v-extract-fs-archive', [ $archive_file, $path, null, $skip_components]);
310+
unlink($download_result->file);
311+
return status;
312+
}
313+
314+
public function saveTempFile($data) {
315+
$tmp_file = tempnam("/tmp","hst");
316+
chmod($tmp_file, 0644);
317+
318+
if (file_put_contents($tmp_file, $data) > 0) {
319+
return $tmp_file;
320+
}
321+
return false;
90322
}
91323
}
92324

@@ -100,6 +332,7 @@ class AppInstaller {
100332
private $errors;
101333

102334
private $database_config = [
335+
'database_create' => ['type'=>'boolean', 'value'=>false],
103336
'database_name' => 'text',
104337
'database_user' => 'text',
105338
'database_password' => 'password',
@@ -115,7 +348,7 @@ public function __construct($app, $domain, $context) {
115348

116349
$appclass = ucfirst($app).'Setup';
117350
if (class_exists($appclass)) {
118-
$this->appsetup = new $appclass();
351+
$this->appsetup = new $appclass($domain, $this->appcontext);
119352
}
120353

121354
if (!$this->appsetup) {
@@ -159,8 +392,7 @@ public function execute($options) {
159392
$options = $this->filterOptions($options);
160393

161394
$random_num = random_int(10000, 99999);
162-
if ($this->appsetup->withDatabase()) {
163-
395+
if ($this->appsetup->withDatabase() && !empty($options['database_create'])) {
164396
if(empty($options['database_name'])) {
165397
$options['database_name'] = $random_num;
166398
}
@@ -173,10 +405,18 @@ public function execute($options) {
173405
$options['database_password'] = bin2hex(random_bytes(10));
174406
}
175407

176-
if(!$this->appcontext->databaseAdd($options['database_name'], $options['database_user'], $options['database_password'])){
408+
if(!$this->appcontext->databaseAdd($options['database_name'], $options['database_user'], $options['database_password'])) {
177409
$this->errors[] = "Error adding database";
178410
return false;
179411
}
180412
}
413+
414+
if(empty($this->errors)) {
415+
return $this->appsetup->install($options);
416+
}
181417
}
182418
}
419+
420+
421+
422+
// TO DO : create a WebDomain model class, hidrate from v-list-web-domain(json)

0 commit comments

Comments
 (0)