1
0

make-test-containers.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. #!/usr/bin/env php
  2. <?php
  3. #
  4. # Auto create multiple Hesia containers with various features enabled/disabled
  5. # lxc/lxd should be allready configured
  6. # echo "root:1000:1" | sudo tee -a /etc/subuid
  7. # echo "root:1000:1" | sudo tee -a /etc/subgid
  8. #
  9. # - container name will be generated depending on enabled features (os,proxy,webserver and php)
  10. # - 'SHARED_HOST_FOLDER' will be mounted in the (guest lxc) container at '/home/ubuntu/source/' and hestiacp src folder is expected to be there
  11. # - wildcard dns *.hst.domain.tld can be used to point to vm host
  12. # - watch install log ex:(host) tail -n 100 -f /tmp/hst_installer_hst-ub1604-a2-mphp
  13. #
  14. # CONFIG HOST STEPS:
  15. # export SHARED_HOST_FOLDER="/home/myuser/projectfiles"
  16. # mkdir -p $SHARED_HOST_FOLDER
  17. # cd $SHARED_HOST_FOLDER && git clone https://github.com/hestiacp/hestiacp.git && cd hestiacp && git checkout ..branch..
  18. #
  19. /*
  20. # Nginx reverse proxy config: /etc/nginx/conf.d/lxc-hestia.conf
  21. server {
  22. listen 80;
  23. server_name ~(?<lxcname>hst-.+)\.hst\.domain\.tld$;
  24. location / {
  25. set $backend_upstream "http://$lxcname:80";
  26. proxy_pass $backend_upstream;
  27. proxy_set_header Host $host;
  28. proxy_set_header X-Forwarded-For $remote_addr;
  29. }
  30. }
  31. server {
  32. listen 8083;
  33. server_name ~^(?<lxcname>hst-.+)\.hst\.domain\.tld$;
  34. location / {
  35. set $backend_upstream "https://$lxcname:8083";
  36. proxy_pass $backend_upstream;
  37. }
  38. }
  39. # use lxc resolver /etc/nginx/nginx.conf
  40. # test resolver ip ex: dig +short @10.240.232.1 hst-ub1804-ngx-a2-mphp
  41. http {
  42. ...
  43. resolver 10.240.232.1 ipv6=off valid=5s;
  44. ...
  45. }
  46. */
  47. ## Uncomment and configure the following vars
  48. # define('DOMAIN', 'hst.domain.tld');
  49. # define('SHARED_HOST_FOLDER', '/home/myuser/projectfiles');
  50. # define('HST_PASS', ''); // <- # openssl rand -base64 12
  51. # define('HST_EMAIL', 'user@domain.tld');
  52. define("HST_BRANCH", "~localsrc");
  53. define("HST_ARGS", "--force --interactive no --clamav no -p " . HST_PASS . " --email " . HST_EMAIL);
  54. define("LXC_TIMEOUT", 30);
  55. if (
  56. !defined("SHARED_HOST_FOLDER") ||
  57. !defined("HST_PASS") ||
  58. !defined("HST_EMAIL") ||
  59. !defined("HST_BRANCH") ||
  60. !defined("DOMAIN")
  61. ) {
  62. die("Error: missing variables" . PHP_EOL);
  63. }
  64. $containers = [
  65. // ['description'=>'hst-d9-ngx-a2-mphp', 'os'=>'debian9', 'nginx'=>true, 'apache2'=>true, 'php'=>'multiphp', 'dns'=>'auto', 'exim'=>'auto'],
  66. [
  67. "description" => "ub1804 ngx mphp",
  68. "os" => "ubuntu18.04",
  69. "nginx" => true,
  70. "apache2" => false,
  71. "php" => "multiphp",
  72. "dns" => "auto",
  73. "exim" => "auto",
  74. ],
  75. [
  76. "description" => "ub1804 ngx fpm",
  77. "os" => "ubuntu18.04",
  78. "nginx" => true,
  79. "apache2" => false,
  80. "php" => "fpm",
  81. "dns" => "auto",
  82. "exim" => "auto",
  83. ],
  84. [
  85. "description" => "ub1804 ngx a2",
  86. "os" => "ubuntu18.04",
  87. "nginx" => true,
  88. "apache2" => true,
  89. "php" => "auto",
  90. "dns" => "auto",
  91. "exim" => "auto",
  92. ],
  93. [
  94. "description" => "ub1804 ngx a2 mphp",
  95. "os" => "ubuntu18.04",
  96. "nginx" => true,
  97. "apache2" => true,
  98. "php" => "multiphp",
  99. "dns" => "auto",
  100. "exim" => "auto",
  101. ],
  102. [
  103. "description" => "ub1804 ngx a2 fpm",
  104. "os" => "ubuntu18.04",
  105. "nginx" => true,
  106. "apache2" => true,
  107. "php" => "fpm",
  108. "dns" => "auto",
  109. "exim" => "auto",
  110. ],
  111. [
  112. "description" => "ub1804 a2 mphp",
  113. "os" => "ubuntu18.04",
  114. "nginx" => false,
  115. "apache2" => true,
  116. "php" => "multiphp",
  117. "dns" => "auto",
  118. "exim" => "auto",
  119. ],
  120. [
  121. "description" => "ub1804 a2 fpm",
  122. "os" => "ubuntu18.04",
  123. "nginx" => false,
  124. "apache2" => true,
  125. "php" => "fpm",
  126. "dns" => "auto",
  127. "exim" => "auto",
  128. ],
  129. [
  130. "description" => "ub1804 a2",
  131. "os" => "ubuntu18.04",
  132. "nginx" => false,
  133. "apache2" => true,
  134. "php" => "auto",
  135. "dns" => "auto",
  136. ],
  137. [
  138. "description" => "ub1604 a2 mphp",
  139. "os" => "ubuntu16.04",
  140. "nginx" => false,
  141. "apache2" => true,
  142. "php" => "multiphp",
  143. "dns" => "auto",
  144. "exim" => "auto",
  145. ],
  146. ];
  147. array_walk($containers, function (&$element) {
  148. $lxc_name = "hst-"; // hostname and lxc name prefix. Update nginx reverse proxy config after altering this value
  149. $hst_args = HST_ARGS;
  150. $element["hst_installer"] = "hst-install-ubuntu.sh";
  151. $element["lxc_image"] = "ubuntu:18.04";
  152. if ($element["os"] == "ubuntu16.04") {
  153. $element["lxc_image"] = "ubuntu:16.04";
  154. $lxc_name .= "ub1604";
  155. } elseif ($element["os"] == "debian8") {
  156. $element["lxc_image"] = "images:debian/8";
  157. $element["hst_installer"] = "hst-install-debian.sh";
  158. $lxc_name .= "d8";
  159. } elseif ($element["os"] == "debian9") {
  160. $element["lxc_image"] = "images:debian/9";
  161. $element["hst_installer"] = "hst-install-debian.sh";
  162. $lxc_name .= "d9";
  163. } else {
  164. $lxc_name .= "ub1804";
  165. $element["os"] = "ubuntu18.04";
  166. }
  167. if ($element["nginx"] === true) {
  168. $lxc_name .= "-ngx";
  169. $hst_args .= " --nginx yes";
  170. } else {
  171. $hst_args .= " --nginx no";
  172. }
  173. if ($element["apache2"] === true) {
  174. $lxc_name .= "-a2";
  175. $hst_args .= " --apache yes";
  176. } else {
  177. $hst_args .= " --apache no";
  178. }
  179. if ($element["php"] == "fpm") {
  180. $lxc_name .= "-fpm";
  181. $hst_args .= " --phpfpm yes";
  182. } elseif ($element["php"] == "multiphp") {
  183. $lxc_name .= "-mphp";
  184. $hst_args .= " --multiphp yes";
  185. }
  186. if (isset($element["dns"])) {
  187. if ($element["dns"] === true || $element["dns"] == "auto") {
  188. $hst_args .= " --named yes";
  189. } else {
  190. $hst_args .= " --named no";
  191. }
  192. }
  193. if (isset($element["exim"])) {
  194. if ($element["exim"] === true || $element["exim"] == "auto") {
  195. $hst_args .= " --exim yes";
  196. } else {
  197. $hst_args .= " --exim no";
  198. }
  199. }
  200. if (isset($element["webmail"])) {
  201. if ($element["webmail"] === true || $element["webmail"] == "auto") {
  202. $hst_args .= " --dovecot yes";
  203. } else {
  204. $hst_args .= " --dovecot no";
  205. }
  206. }
  207. $element["lxc_name"] = $lxc_name;
  208. $element["hostname"] = $lxc_name . "." . DOMAIN;
  209. // $hst_args .= ' --with-debs /home/ubuntu/source/hestiacp/src/pkgs/develop/' . $element['os'];
  210. $hst_args .= " --with-debs /tmp/hestiacp-src/debs";
  211. $hst_args .= " --hostname " . $element["hostname"];
  212. $element["hst_args"] = $hst_args;
  213. });
  214. function lxc_run($args, &$rc) {
  215. $cmd_args = "";
  216. if (is_array($args)) {
  217. foreach ($args as $arg) {
  218. $cmd_args .= " " . escapeshellarg($arg);
  219. }
  220. } else {
  221. $cmd_args = $args;
  222. }
  223. exec("lxc " . $cmd_args . " 2>/dev/null", $cmdout, $rc);
  224. if (isset($rc) && $rc !== 0) {
  225. return false;
  226. }
  227. if (json_decode(implode(PHP_EOL, $cmdout), true) === null) {
  228. return $cmdout;
  229. }
  230. return json_decode(implode(PHP_EOL, $cmdout), true);
  231. }
  232. function getHestiaVersion($branch) {
  233. $control_file = "";
  234. if ($branch === "~localsrc") {
  235. $control_file = file_get_contents(SHARED_HOST_FOLDER . "/hestiacp/src/deb/hestia/control");
  236. } else {
  237. $control_file = file_get_contents(
  238. "https://raw.githubusercontent.com/hestiacp/hestiacp/${branch}/src/deb/hestia/control",
  239. );
  240. }
  241. foreach (explode(PHP_EOL, $control_file) as $line) {
  242. if (empty($line)) {
  243. continue;
  244. }
  245. [$key, $value] = explode(":", $line);
  246. if (strtolower($key) === "version") {
  247. return trim($value);
  248. }
  249. }
  250. throw new Exception("Error reading Hestia version for branch: [${branch}]", 1);
  251. }
  252. function get_lxc_ip($name) {
  253. $result = lxc_run(["list", "--format", "csv", "-c", "n,4"], $rc);
  254. if (empty($result)) {
  255. return false;
  256. }
  257. foreach ($result as $line) {
  258. [$cnt, $address] = explode(",", $line);
  259. if ($cnt == $name) {
  260. $iface = explode(" ", $address);
  261. if (filter_var($iface[0], FILTER_VALIDATE_IP)) {
  262. return $iface[0];
  263. } else {
  264. return false;
  265. }
  266. }
  267. }
  268. }
  269. function check_lxc_container($container) {
  270. echo "Check container:" . $container["lxc_name"] . PHP_EOL;
  271. lxc_run(["info", $container["lxc_name"]], $rc);
  272. if (isset($rc) && $rc === 0) {
  273. return;
  274. }
  275. $pid = pcntl_fork();
  276. if ($pid > 0) {
  277. return $pid;
  278. }
  279. echo "Creating container " . $container["lxc_name"] . PHP_EOL;
  280. lxc_run(["init", $container["lxc_image"], $container["lxc_name"]], $rc);
  281. exec(
  282. "lxc config set " .
  283. escapeshellarg($container["lxc_name"]) .
  284. ' raw.idmap "both 1000 1000" 2>/dev/null',
  285. $devnull,
  286. $rc,
  287. );
  288. exec(
  289. "lxc config device add " .
  290. escapeshellarg($container["lxc_name"]) .
  291. " hestiasrc disk path=/home/ubuntu/source source=" .
  292. SHARED_HOST_FOLDER .
  293. " 2>/dev/null",
  294. $devnull,
  295. $rc,
  296. );
  297. lxc_run(["start", $container["lxc_name"]], $rc);
  298. $lxc_retry = 0;
  299. do {
  300. $lxc_retry++;
  301. $cip = get_lxc_ip($container["lxc_name"]);
  302. if ($cip) {
  303. echo "Container " . $container["lxc_name"] . " IP: $cip" . PHP_EOL;
  304. }
  305. sleep(1);
  306. } while ($lxc_retry <= LXC_TIMEOUT && filter_var($cip, FILTER_VALIDATE_IP) === false);
  307. echo "Updating container: " . $container["lxc_name"] . PHP_EOL;
  308. exec("lxc exec " . $container["lxc_name"] . " -- apt update", $devnull, $rc);
  309. exit(0);
  310. }
  311. function hst_installer_worker($container) {
  312. $pid = pcntl_fork();
  313. if ($pid > 0) {
  314. return $pid;
  315. }
  316. system(
  317. "lxc exec " .
  318. $container["lxc_name"] .
  319. ' -- bash -c "/home/ubuntu/source/hestiacp/src/hst_autocompile.sh --hestia \"' .
  320. HST_BRANCH .
  321. '\" no"',
  322. );
  323. $hver = getHestiaVersion(HST_BRANCH);
  324. echo "Install Hestia ${hver} on " . $container["lxc_name"] . PHP_EOL;
  325. echo "Args: " . $container["hst_args"] . PHP_EOL;
  326. system(
  327. "lxc exec " .
  328. $container["lxc_name"] .
  329. ' -- bash -c "cd \"/home/ubuntu/source/hestiacp\"; install/' .
  330. $container["hst_installer"] .
  331. " " .
  332. $container["hst_args"] .
  333. '" 2>&1 > /tmp/hst_installer_' .
  334. $container["lxc_name"],
  335. );
  336. exit(0);
  337. }
  338. // Create and update containers
  339. $worker_pool = [];
  340. foreach ($containers as $container) {
  341. $worker_pid = check_lxc_container($container);
  342. if ($worker_pid > 0) {
  343. $worker_pool[] = $worker_pid;
  344. }
  345. }
  346. echo count($worker_pool) . " LXC workers started" . PHP_EOL;
  347. # waiting for workers to finish
  348. while (count($worker_pool)) {
  349. echo "Wait for LXC workers to finish" . PHP_EOL;
  350. $child_pid = pcntl_wait($status);
  351. if ($child_pid) {
  352. $worker_pos = array_search($child_pid, $worker_pool);
  353. unset($worker_pool[$worker_pos]);
  354. }
  355. }
  356. // Install Hestia
  357. $worker_pool = [];
  358. foreach ($containers as $container) {
  359. # Is hestia installed?
  360. lxc_run("exec " . $container["lxc_name"] . ' -- sudo --login "v-list-sys-config"', $rc);
  361. if (isset($rc) && $rc === 0) {
  362. continue;
  363. }
  364. $worker_pid = hst_installer_worker($container);
  365. if ($worker_pid > 0) {
  366. $worker_pool[] = $worker_pid;
  367. }
  368. }
  369. echo count($worker_pool) . " background workers started" . PHP_EOL;
  370. # waiting for workers to finish
  371. while (count($worker_pool)) {
  372. echo "Wait for workers to finish" . PHP_EOL;
  373. $child_pid = pcntl_wait($status);
  374. if ($child_pid) {
  375. $worker_pos = array_search($child_pid, $worker_pool);
  376. unset($worker_pool[$worker_pos]);
  377. }
  378. }
  379. // Custom config
  380. foreach ($containers as $container) {
  381. echo "Apply custom config on: " . $container["lxc_name"] . PHP_EOL;
  382. # Allow running a reverse proxy in front of Hestia
  383. system(
  384. "lxc exec " .
  385. $container["lxc_name"] .
  386. ' -- bash -c "sed -i \'s/session.cookie_secure] = on\$/session.cookie_secure] = off/\' /usr/local/hestia/php/etc/php-fpm.conf"',
  387. );
  388. # get rid off "mesg: ttyname failed: No such device" error
  389. system(
  390. "lxc exec " .
  391. $container["lxc_name"] .
  392. ' -- bash -c "sed -i -re \'s/^(mesg n)(.*)$/#\1\2/g\' /root/.profile"',
  393. );
  394. # Use LE sandbox server, prevents hitting rate limits
  395. system(
  396. "lxc exec " .
  397. $container["lxc_name"] .
  398. ' -- bash -c "sed -i \'/LE_STAGING/d\' /usr/local/hestia/conf/hestia.conf"',
  399. );
  400. system(
  401. "lxc exec " .
  402. $container["lxc_name"] .
  403. ' -- bash -c "echo \'LE_STAGING=\"yes\"\' >> /usr/local/hestia/conf/hestia.conf"',
  404. );
  405. system("lxc exec " . $container["lxc_name"] . ' -- bash -c "service hestia restart"');
  406. }
  407. echo "Hestia containers configured" . PHP_EOL;