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+
326abstract 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
1872class 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
38191class 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