diff --git a/factorio-settings.env b/factorio-settings.env new file mode 100755 index 0000000..a9aa570 --- /dev/null +++ b/factorio-settings.env @@ -0,0 +1,5 @@ +FACTORIO_SAVE_PATH="/home/factorio/saves" +FACTORIO_SAVE_NAME="spaceage-01-2024-11-01-v2" +FACTORIO_MOD_PATH="/home/factorio/mods/default" +FACTORIO_SETTINGS_JSON="/home/factorio/server-settings.json" +FACTORIO_VERSION="2.0.14" diff --git a/factorio_update.php b/factorio_update.php new file mode 100644 index 0000000..927180f --- /dev/null +++ b/factorio_update.php @@ -0,0 +1,276 @@ +getVersion().PHP_EOL; + + + +// Aktuelle Versionsnummern aus der Factorio-API abrufen. +$live_versions = json_decode(file_get_contents("https://factorio.com/api/latest-releases"), true); + +// Anhand von Build und Edition die richtige Version auswählen. +$latest_version = new \NAE\Factorio\FactorioVersion($live_versions[$target_build][$target_edition]); +echo "LATEST_VERSION: ".$latest_version->getVersion().PHP_EOL.PHP_EOL; + + + + +/** + * Schreibt die neue Versionsnummer in die environment (env) datei des servers + * @return bool Schreiben erfolgreich? + */ +function writeBackVersion() : bool { + + global $latest_version; + + $env_file = __DIR__."/factorio-settings.env"; + $env = file_get_contents($env_file); + $new_env = preg_replace("/FACTORIO_VERSION\=\"((\d+\.)+\d+)\"/su", "FACTORIO_VERSION=\"".$latest_version->getVersion()."\"", $env); + + return ( file_put_contents($env_file, $new_env) !== false ); + +} + + + + + +// Variablen zum speichern des Fortschrittes bzw. Erfolgs. +$download_success = false; +$extract_success = false; + +echo "# [ PRÜFE AUF FACTORIO UPDATES ]".PHP_EOL; + +if($latest_version->isNewer($current_version)) { // neue Version verfügbar? + + echo "## [ NEUE VERSION GEFUNDEN : UPDATE STARTET ]".PHP_EOL; + + if(file_exists($download_output)) { // liegt noch ein altes Server Archiv im Vertzeichnis? + + echo PHP_EOL."[ ALTE SERVER FILES GEFUNDEN : STARTE LÖSCHVORGANG ]".PHP_EOL; + + if( unlink($download_output) ) { // altes Archiv löschen + + echo "-> ALTE SERVER FILES ERFOLGREICH GELÖSCHT!".PHP_EOL; + + } else { + + echo "-> ERR : ALTE SERVER FILES ERFOLGREICH GELÖSCHT!".PHP_EOL; + die(1); + + } + } + + + echo PHP_EOL."[ STARTE DOWNLOAD DER SERVER FILES ]".PHP_EOL; + + if(\NAE\Functions\Curl\downloadFile($download_output, $download_source)) { // Download der neuen Server Files + + echo "-> DOWNLOAD SUCCESS!".PHP_EOL; + $download_success = true; + + } else { + + echo "-> ERR : DOWNLOAD FEHLGESCHLAGEN!".PHP_EOL; + die(1); + + } + + + if($download_success) { + + echo PHP_EOL."[ STARTE DAS ENTPACKEN DER NEUEN SERVER FILES ]".PHP_EOL; + + exec("tar -xvf \"$download_output\" > /dev/null", $output, $return); // Entpacken + + if($return === 0) { + + if( rename(__DIR__."/factorio", __DIR__."/factorio-".$latest_version->getVersion()) ) { // Umbenennen mit Versionsnummer + + echo "-> Neue Server Files wurden Erfolgreich entpackt!".PHP_EOL; + $extract_success = true; + + } else { + + echo "-> ERR : Umbenennen des Factorio Ordners mit Versionsnummer Fehlgeschlagen!".PHP_EOL; + die(1); + + } + + } else { + + echo "-> ERR : Entpacken der Server Files ist Fehlgeschlagen!".PHP_EOL; + die(1); + + } + + } + + if($extract_success) { + + // RCON Kommunikation zum Server starten + $rcon_data = (function() : ?array { + + global $current_version; + + $cfg = parse_ini_file(__DIR__."/factorio-".$current_version->getVersion()."/config/config.ini", true); + if(!$cfg) return null; + + $cd = []; + + if(isset($cfg["other"])) { + + $parts = explode(":", $cfg["other"]["local-rcon-socket"]??"127.0.0.1:27015"); + $cd["ip"] = $parts[0]; + $cd["port"] = $parts[1]; + + if(isset($cfg["other"]["local-rcon-socket"])) { + $cd["password"] = $cfg["other"]["local-rcon-password"]??""; + } + + } + + return $cd; + + })(); + + $rcon = null; + if($rcon_data) { + try { + + $rcon = new \NAE\Valve\Rcon\ValveRcon($rcon_data["ip"], $rcon_data["port"], $rcon_data["password"]); + $rcon->sendCommand("ACHTUNG: Server wird in kürze für ein Update gestoppt, bitte trennen Sie Ihre Verbindung zum Server. Der Server wird in kürze wieder verfügbar sein. Zeit bis zum Stopp: 60 Sekunden.", false); + + if($rcon->getError() !== null) { + echo "[!] ERR : RCON Verbindung konnte nicht aufgebaut werden!".PHP_EOL; + $rcon = null; + } + + } catch(\Exception $e) { + + echo "[!] ERR : RCON Verbindung konnte nicht aufgebaut werden!".PHP_EOL; + $rcon = null; + + } + } + + if($rcon) sleep(60); // Falls die Meldung geklappt hat 60s warten, damit Spieler den Server verlassen können. + + + // inititialize Systemctl Service interface + $service = new \NAE\Service\Systemctl("factorio"); + + // Server Stoppen + if($service->stop()) { + echo "[+] FACTORIO SERVER WURDE GESTOPPT!".PHP_EOL; + } + + // neue Versionsnummer in .env Datei übernehmen + if(writeBackVersion()) { + echo "[+] Version in Environment File ist aktualisiert".PHP_EOL; + } + + // Dateien aus alten Server Files kopieren.. + if( is_dir(__DIR__."/factorio-".$current_version->getVersion()) && is_dir(__DIR__."/factorio-".$latest_version->getVersion()) ) { + + echo PHP_EOL."[ Kopiere Konfigurations-Dateien in den neuen Server Pfad ]".PHP_EOL; + + foreach($copy_paths as $copy_id => $copy_name) { + + $copy_source = __DIR__."factorio-/".$current_version->getVersion().$copy_name; + $copy_dest = __DIR__."factorio-/".$latest_version->getVersion().$copy_name; + + if(file_exists($copy_source)) { + + echo "[ Kopierenvorgang Nr. ".($copy_id+1)." von ".count($copy_paths)." - $copy_name ] "; + if(copy($copy_source, $copy_dest)) { + + echo "-> Kopiervorgang Erfolgreich!".PHP_EOL; + + } else { + + echo "-> ERR : Kopiervorgang Fehlgeschlagen!".PHP_EOL; + + } + + } else { + + echo " - Quelle nicht vorhanden!".PHP_EOL; + + } + + } + + } + + // Fix file permissions.. + (function(){ + + global $latest_version; + + exec("chown -R factorio:factorio \"".__DIR__."/factorio-".$latest_version->getVersion()."\"", $output, $return); + if($return === 0) { + echo "[+] Rechte im neuen Server verzeichnis korrigiert.".PHP_EOL; + } else { + echo "[!] ERR : Rechte im neuen Server verzeichnis konnten nicht korrigiert werden.".PHP_EOL; + } + + })(); + + + if($service->start()) { + echo "[+] FACTORIO SERVER WURDE WIEDER GESTARTET!".PHP_EOL; + } + + } + + + +} else { + + // Keine aktuellere Version verfügbar! + echo "-> Kein Update verfügbar.".PHP_EOL; + +} diff --git a/scripts/class.FactorioVersion.php b/scripts/class.FactorioVersion.php new file mode 100644 index 0000000..ec5651d --- /dev/null +++ b/scripts/class.FactorioVersion.php @@ -0,0 +1,72 @@ += $target_length) { + return $str; + } + $missing = ( $target_length - strlen($str) ); + for($i=0;$i<$target_length;$i++) { + $str = "0".$str; + } + + return $str; + + } + + /** + * Formatiert die Factorio Versionsnummern um zu einem Vergleichbaren Integer. + * @param string Factorio Versions Zeichenkette Beispiel: 2.0.14 + * @return int zum einfachen Vergleich formatierter Versions-Integer. Beispiel: versionsFormat("2.0.14") -> "000200000014" -> int(200000014) + */ + private function versionFormat(string $vs) : int { + + $parts = explode(".", $vs); + $new = ""; + + foreach($parts as $i => $v) { + $new .= $this->zero_fill($v, 4); + } + + return intval($new); + + } + + + public function __construct(string $version) { + $this->version = $version; + $this->intVersion = $this->versionFormat($version); + } + + public function getVersion() : string { + return $this->version; + } + + public function getIntVersion() : int { + return $this->intVersion; + } + + public function isNewer(FactorioVersion $other) : bool { + return $this->intVersion > $other->getIntVersion(); + } + + public function isOlder(FactorioVersion $other) : bool { + return $this->intVersion < $other->getIntVersion(); + } + +} \ No newline at end of file diff --git a/scripts/class.Systemctl.php b/scripts/class.Systemctl.php new file mode 100644 index 0000000..cd6fe51 --- /dev/null +++ b/scripts/class.Systemctl.php @@ -0,0 +1,71 @@ + + * @license MIT License (http://www.opensource.org/licenses/mit) + */ +class Systemctl { + + /** + * @var string Name of the service to manage + */ + private $service_name = ""; + + /** + * Constructor + * + * @param string $service_name Name of the service to manage + */ + public function __construct(string $service_name) { + $this->service_name = $service_name; + } + + /** + * Start the Service + * + * @return bool True if the service was started successfully, false otherwise + */ + public function start () : bool { + exec("systemctl start ".$this->service_name." > /dev/null", $output, $return); + return ( $return === 0 ); + } + + /** + * Stop the Service + * + * @return bool True if the service was stopped successfully + */ + public function stop () : bool { + exec("systemctl stop ".$this->service_name." > /dev/null", $output, $return); + return ( $return === 0 ); + } + + /** + * Restart the Service + * + * @return bool True if the service was restarted successfully, false otherwise + */ + public function restart () : bool { + exec("systemctl restart ".$this->service_name." > /dev/null", $output, $return); + return ( $return === 0 ); + } + + /** + * Check if the Service is running + * + * @return bool True if the service is running, false otherwise + */ + public function isRunning () : bool { + + exec("systemctl status ".$this->service_name, $output, $return); + + $search_string = implode(";", $output); + return ( strpos($search_string, "Active: active (running)") >= 0 ); + + } + +} diff --git a/scripts/class.ValveRcon.php b/scripts/class.ValveRcon.php new file mode 100644 index 0000000..91c13ed --- /dev/null +++ b/scripts/class.ValveRcon.php @@ -0,0 +1,139 @@ +ip = $ip; + $this->port = $port; + $this->password = $password; + $this->timeout = $timeout; + } + + private function connect() : bool { + + $errno = null; + $errmsg = ""; + + $this->socket = fsockopen( + "udp://{$this->ip}", + $this->port, + $errno, + $errmsg, + $this->timeout + ); + + if(!$this->socket) { + $this->err = [ + "code" => $errno, + "message" => "Socket connection failed: $errmsg" + ]; + return false; + } + + return true; + + } + + private function disconnect() : void { + + if ( $this->isConnected() ) { + fclose($this->socket); + $this->socket = NULL; + } + + } + + private function isConnected() : bool { + + return ( get_resource_type($this->socket) !== null ); // check if socket is resource type, as it should be + + } + + private function read() : ?string { + + if (!$this->isConnected()) { + return null; + } + + stream_set_timeout($this->socket, 0, $this->timeout * 100000); // set timeout + + $response = ''; + while ($buffer = fread($this->socket, 4096)) { // loop while there's more data + list($header, $content) = explode("\n", $buffer, 2); // split into header and content + $response .= $content; // append content to response + } + + $response = trim($response); // remove whitespaces around the response + + if (empty($response)) // no data received, return null + return null; + + return preg_replace("/\^./","", $response); // return response without prefix + + } + + private function write(string $cmd) : bool { + + if (!$this->isConnected()) { + return false; + } + + if( false === fwrite($this->socket, str_repeat(chr(255), 4) . "rcon {$this->password} {$cmd}\n") ) { + return false; + } + + return true; + + } + + public function sendCommand(string $cmd, bool $read=false) : ?string { + + $this->connect(); + if(!$this->write($cmd)) { // sending the command + $this->err = [ + "code" => 0, + "message" => "Command could not be sent" + ]; + $this->disconnect(); + return null; + } + + if (!$read) { + $this->disconnect(); + return null; + } + + if(!$res = $this->read()) { // reading the response + $this->err = [ + "code" => 0, + "message" => "Response could not be read" + ]; + } + $this->disconnect(); + + return $res; + + } + + public function getError() :?object { + + if($this->err === null) { + return null; // no error has occurred, return null + } + + return json_decode(json_encode($this->err)); // return last error as object + + } + + +} \ No newline at end of file diff --git a/scripts/func.downloadFile.php b/scripts/func.downloadFile.php new file mode 100644 index 0000000..f992696 --- /dev/null +++ b/scripts/func.downloadFile.php @@ -0,0 +1,29 @@ +