PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/MCU-CLI-server/php/mcu-cli.php

https://gitlab.com/Slind/MCUpdater
PHP | 380 lines | 305 code | 34 blank | 41 comment | 78 complexity | 22b2057e6ab90708ab9d5699a29a4198 MD5 | raw file
  1. #!/usr/bin/env php
  2. <?php
  3. /**
  4. * MCU-CLI v1
  5. *
  6. * This is a brute force script for performing MCU updates on a forge server.
  7. * It's neither pretty nor efficient, but it will get the job done.
  8. *
  9. * 0) Copy this script and the default config into a folder with the minecraft
  10. * server jar.
  11. * 1) Copy the default config to 'mcu-cli-config.php'
  12. * 2) Edit your new config, updating at a minimum the pack url, server id,
  13. * server jar, and memory settings.
  14. * 3) Execute mcu-cli.php and hope for the best :)
  15. *
  16. * - allaryin [2013-12-30]
  17. */
  18. function msg($str, $error = false) {
  19. echo ($error?"[!!] ":"[--] ") . $str . "\n";
  20. }
  21. msg("MCU-CLI.php Starting...");
  22. msg(date("r"));
  23. // verify that we have curl enabled
  24. if( !extension_loaded("curl") ) {
  25. msg("curl extension not found.", true);
  26. exit(1);
  27. }
  28. // check for zip on our path
  29. $zip_bin = exec( "which unzip", $tmp, $zip_error );
  30. if( $zip_error ) {
  31. msg("unable to find unzip command in path.", true);
  32. exit(1);
  33. }
  34. // load config, copying from default if one is not found
  35. $cfg_default_filename = "mcu-cli-config.default.php";
  36. $cfg_filename = "mcu-cli-config.php";
  37. if( !file_exists($cfg_filename) ) {
  38. if( file_exists($cfg_default_filename) ) {
  39. copy( $cfg_default_filename, $cfg_filename );
  40. } else {
  41. msg("Unable to load settings from default!", true);
  42. exit(1);
  43. }
  44. }
  45. if( file_exists($cfg_default_filename) )
  46. include_once $cfg_default_filename;
  47. include_once $cfg_filename;
  48. // load mcu cached state
  49. if( file_exists($mcu_cache_filename) ) {
  50. msg("Loading mcu cached settings...");
  51. $cache_json = file_get_contents($mcu_cache_filename);
  52. $cache = json_decode($cache_json, true);
  53. if( $cache === NULL ) {
  54. msg("Unable to parse cached settings, aborting launch", true);
  55. msg(json_last_error_msg(), true);
  56. exit(1);
  57. }
  58. } else {
  59. msg("First time run detected, starting with fresh cache");
  60. $cache = array();
  61. }
  62. libxml_use_internal_errors(); // suppress xml error spam
  63. // fetch updated serverpack
  64. $pack_xml = file_get_contents($pack_url);
  65. if( $pack_xml === FALSE ) {
  66. msg("Unable to read pack from $pack_url", true);
  67. exit(1);
  68. } else {
  69. msg("Read serverpack from $pack_url");
  70. }
  71. $pack = simplexml_load_string($pack_xml);
  72. if( $pack === FALSE ) {
  73. msg("Unable to parse malformed XML", true);
  74. print_r(libxml_get_errors());
  75. exit(1);
  76. }
  77. // identify our desired server entry
  78. function identify_server($server, $id) {
  79. if( $id == $server->attributes()->id )
  80. return $server;
  81. return false;
  82. }
  83. function find_server($pack, $server_id) {
  84. if( !$pack->Server ) {
  85. msg("Unable to find any <Server/> directive in xml", true);
  86. return false;
  87. }
  88. foreach ($pack->Server as $key => $val) {
  89. $base = identify_server($val, $server_id);
  90. if( $base )
  91. break;
  92. }
  93. return $base;
  94. }
  95. $base = find_server($pack, $pack_server_id);
  96. if( !$base ) {
  97. msg("Unable to find server id $pack_server_id", true);
  98. exit(2);
  99. }
  100. //print_r($base);
  101. // import all imports
  102. function append_children($base, $child) {
  103. $dom_base = dom_import_simplexml($base);
  104. foreach( $child->children() as $key => $val ) {
  105. $dom_child = dom_import_simplexml($val);
  106. $dom_child = $dom_base->ownerDocument->importNode($dom_child, TRUE);
  107. $dom_base->appendChild($dom_child);
  108. }
  109. }
  110. function parse_import($xml) {
  111. global $cache, $pack, $need_update;
  112. $url = (string)($xml->attributes()->url);
  113. $import_id = (string)$xml;
  114. msg("Parsing import of '$import_id' from ".($url?$url:"pack")."...");
  115. if( $url ) {
  116. $import = new SimpleXMLElement($url, 0, true);
  117. } else {
  118. $import = $pack;
  119. }
  120. $result = find_server($import, $import_id);
  121. if( $result ) {
  122. $old_revision = $cache["import.".$import_id];
  123. $cache["import.".$import_id] = (string)$result->attributes()->revision;
  124. if( $old_revision != $cache["import.".$import_id] ) {
  125. msg("Got new revision of $import_id, need update...");
  126. $need_update = true;
  127. } else {
  128. msg("Version of $import_id matches cache, not updating");
  129. }
  130. } else {
  131. msg("Unable to find requested import.",true);
  132. }
  133. return $result;
  134. }
  135. function import_imports($base) {
  136. $result = new SimpleXMLElement($base->asXML());
  137. $imports = $base->Import;
  138. unset($result->Import);
  139. foreach($imports as $key => $val) {
  140. $import = parse_import($val);
  141. if( $import ) {
  142. append_children($result, $import);
  143. }
  144. }
  145. return $result;
  146. }
  147. if( $base->Import ) {
  148. msg("Handling imports...");
  149. $base = import_imports($base);
  150. //print_r($base);
  151. }
  152. // check if we need to change
  153. if( ($got_revision = (string)($base->attributes()->revision)) != $cache["revision"]) {
  154. msg("Identified new pack revision $got_revision");
  155. $need_update = true;
  156. } else {
  157. msg("Pack revision $got_revision matches current rev, not updating");
  158. }
  159. // perform update if necessary
  160. function download($url) {
  161. $fname = tempnam(".", "mcu-download-");
  162. $ch = curl_init($url);
  163. $fp = fopen($fname, "w");
  164. curl_setopt($ch, CURLOPT_FILE, $fp);
  165. curl_setopt($ch, CURLOPT_HEADER, 0);
  166. curl_exec($ch);
  167. curl_close($ch);
  168. fclose($fp);
  169. return $fname;
  170. }
  171. function parse_config($xml) {
  172. $md5 = (string)$xml->MD5;
  173. $path = (string)$xml->Path;
  174. msg(" Parsing ConfigFile - $path");
  175. $need_download = true;
  176. if( file_exists($path) ) {
  177. $local_md5 = md5_file($path);
  178. if( $local_md5 == $md5 )
  179. $need_download = false;
  180. }
  181. if( $need_download ) {
  182. $cache_file = "cache/$md5";
  183. if( $md5 && file_exists($cache_file) ) {
  184. msg(" - Cached");
  185. } else {
  186. $url = (string)$xml->URL;
  187. msg(" + Downloading $url...");
  188. $tmp = download($url);
  189. if( $md5 ) {
  190. // validate the checksum
  191. $dl_md5 = md5_file($tmp);
  192. if( $dl_md5 != $md5 ) {
  193. msg(" ! MD5 mismatch on downloaded file", true);
  194. unlink($tmp);
  195. return;
  196. }
  197. } else {
  198. msg(" - no MD5 specified, trusting download");
  199. }
  200. rename($tmp, $cache_file);
  201. }
  202. msg(" - Installing");
  203. copy( $cache_file, $path );
  204. }
  205. }
  206. function parse_module($xml, $is_submod = false) {
  207. global $zip_bin;
  208. $attrs = $xml->attributes();
  209. msg("Parsing ".($is_submod?"Submodule":"Module")." ".$attrs->name);
  210. if( (string)$attrs->side == "CLIENT" ) {
  211. msg(" - skipping client-only mod");
  212. return;
  213. }
  214. $type = (string)$xml->ModType;
  215. if( $type != "Regular" && $type != "Extract" ) {
  216. msg(" - skipping unsupported ModType $type");
  217. return;
  218. }
  219. $required = (string)$xml->Required == "true";
  220. if( !$required ) {
  221. msg(" - skipping optional mod");
  222. return;
  223. }
  224. $id = (string)$attrs->id;
  225. $md5 = (string)$xml->MD5;
  226. // check md5
  227. $cache_file = ($md5?"cache/$md5":"cache/tmp");
  228. $need_download = true;
  229. switch( $type ) {
  230. case "Regular":
  231. // check md5 against installed mod
  232. if( $xml->ModPath )
  233. $path = "./".(string)$xml->ModPath;
  234. else
  235. $path = "mods/${id}.jar";
  236. if( file_exists($path) ) {
  237. $local_md5 = md5_file($path);
  238. if( $local_md5 == $md5 )
  239. $need_download = false;
  240. }
  241. break;
  242. case "Extract":
  243. default:
  244. // check md5 against download cache
  245. $need_download = !($md5 && file_exists($cache_file));
  246. }
  247. // download & cache
  248. if( $need_download ) {
  249. if( $md5 && file_exists($cache_file) ) {
  250. msg(" + Cached");
  251. } else {
  252. $url = (string)$xml->URL;
  253. msg(" + Downloading $url...");
  254. $tmp = download($url);
  255. if( $md5 ) {
  256. // validate the checksum
  257. $dl_md5 = md5_file($tmp);
  258. if( $dl_md5 != $md5 ) {
  259. msg(" ! MD5 mismatch on downloaded file", true);
  260. unlink($tmp);
  261. return;
  262. }
  263. } else {
  264. msg(" - no MD5 specified, trusting download");
  265. }
  266. rename($tmp, $cache_file);
  267. }
  268. msg(" - Installing to $path");
  269. switch( $type ) {
  270. case "Extract":
  271. if( (string)($xml->ModType->attributes()->inRoot == "true") )
  272. $dir = ".";
  273. else
  274. $dir = "mods";
  275. $cmd = "$zip_bin $cache_file -d $dir";
  276. msg(" - \$ $cmd");
  277. passthru($cmd);
  278. break;
  279. case "Regular":
  280. // make sure the target dir exists
  281. $dir = dirname($path);
  282. if( !is_dir($dir) ) {
  283. msg(" - Creating directory $dir");
  284. if( !@mkdir($dir, 0755, true) ) {
  285. msg(" ! mkdir failed", true);
  286. print_r(error_get_last());
  287. return;
  288. }
  289. }
  290. copy($cache_file, $path);
  291. }
  292. } else {
  293. msg(" - Skipping, cache hit.");
  294. }
  295. // parse configfiles
  296. if( $xml->ConfigFile ) {
  297. foreach( $xml->ConfigFile as $key => $val ) {
  298. parse_config($val);
  299. }
  300. }
  301. // check for submods
  302. if( !$is_submod && $xml->Submodule ) {
  303. foreach( $xml->Submodule as $key => $val ) {
  304. parse_module($val, true);
  305. }
  306. }
  307. }
  308. if( $need_update ) {
  309. msg("Performing update...");
  310. // verify that we have a download cache dir
  311. @mkdir("cache", 0755);
  312. @mkdir("mods", 0755);
  313. @mkdir("config", 0755);
  314. // actually update :)
  315. if( !$base->Module ) {
  316. msg("No modules defined?!", true);
  317. } else {
  318. foreach( $base->Module as $key => $val ) {
  319. parse_module($val);
  320. }
  321. }
  322. }
  323. // flush cache to disk
  324. $cache["revision"] = $got_revision;
  325. $cache_json = json_encode($cache);
  326. file_put_contents($mcu_cache_filename, $cache_json);
  327. // TODO: back up existing world data
  328. if( file_exists("server.properties") ) {
  329. }
  330. // start server
  331. if( $server_autostart ) {
  332. msg("Starting server...");
  333. if( file_exists($server_jar) ) {
  334. if( function_exists("pcntl_exec") ) {
  335. $args = array(
  336. "-Xms".$server_memory_min,
  337. "-Xmx".$server_memory_max,
  338. "-jar", $server_jar);
  339. pcntl_exec( $java_bin, array_merge($args,$server_args) );
  340. } else {
  341. exec($java_bin . " -Xms".$server_memory_min . " -Xmx".$server_memory_max . " -jar ".$server_jar . " " . $server_args);
  342. }
  343. } else {
  344. msg("Unable to locate $server_jar", true);
  345. }
  346. }