2016-01-18 23:35:52 +01:00
< ? php
/**
2017-04-16 21:08:50 +02:00
* @ copyright Copyright ( c ) 2016 - 2017 Lukas Reschke < lukas @ statuscode . ch >
2016-06-25 18:10:37 +02:00
* @ copyright Copyright ( c ) 2016 Morris Jobke < hey @ morrisjobke . de >
2018-11-27 15:24:01 +01:00
* @ copyright Copyright ( c ) 2018 Jonas Sulzer < jonas @ violoncello . ch >
2016-01-18 23:35:52 +01:00
*
2016-06-24 00:24:51 +02:00
* @ license GNU AGPL version 3 or any later version
2016-01-18 23:35:52 +01:00
*
2016-06-24 00:24:51 +02:00
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
2016-01-18 23:35:52 +01:00
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
2016-06-24 00:24:51 +02:00
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2016-01-18 23:35:52 +01:00
* GNU Affero General Public License for more details .
*
2016-06-24 00:24:51 +02:00
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < http :// www . gnu . org / licenses />.
2016-01-18 23:35:52 +01:00
*
*/
2023-06-07 09:00:51 +02:00
2016-06-24 09:27:16 +02:00
class UpdateException extends \Exception {
2023-06-12 17:17:54 +02:00
/** @param list<string> $data */
public function __construct (
protected array $data ,
) {
2016-06-24 09:27:16 +02:00
}
2023-06-12 17:17:54 +02:00
/** @return list<string> */
public function getData () : array {
2016-06-24 09:27:16 +02:00
return $this -> data ;
}
}
2022-08-04 13:20:26 +02:00
2016-09-22 00:12:31 +02:00
class LogException extends \Exception {
}
2022-08-04 13:20:26 +02:00
2016-10-27 18:26:09 +02:00
class RecursiveDirectoryIteratorWithoutData extends \RecursiveFilterIterator {
2022-08-16 03:06:23 +02:00
public function accept () : bool {
2016-06-26 18:46:59 +02:00
$excludes = [
2019-07-22 00:59:56 +02:00
'.rnd' ,
2018-09-01 19:44:48 +02:00
'.well-known' ,
2016-06-26 18:46:59 +02:00
'data' ,
'..' ,
];
2023-06-07 09:00:51 +02:00
2023-06-12 17:17:54 +02:00
/** @var \SplFileInfo|false */
2023-06-07 09:00:51 +02:00
$current = $this -> current ();
if ( ! $current ) {
return false ;
}
return ! ( in_array ( $current -> getFilename (), $excludes , true ) || $current -> isDir ());
2016-06-25 19:35:51 +02:00
}
}
2016-06-25 13:36:35 +02:00
2016-06-24 00:24:51 +02:00
class Updater {
2023-06-12 17:17:54 +02:00
private string $baseDir ;
private array $configValues = [];
private string $currentVersion = 'unknown' ;
private string $buildTime ;
private bool $updateAvailable = false ;
private ? string $requestID = null ;
private bool $disabled = false ;
2016-06-24 00:24:51 +02:00
2016-11-03 17:33:58 +01:00
/**
* Updater constructor
2023-06-12 17:17:54 +02:00
* @ param string $baseDir the absolute path to the / updater / directory in the Nextcloud root
2016-11-03 17:33:58 +01:00
* @ throws \Exception
*/
2023-06-12 17:17:54 +02:00
public function __construct ( string $baseDir ) {
2016-11-03 17:33:58 +01:00
$this -> baseDir = $baseDir ;
2022-07-25 12:12:34 +02:00
if ( $dir = getenv ( 'NEXTCLOUD_CONFIG_DIR' )) {
2024-03-14 15:39:52 +01:00
$configFileName = rtrim ( $dir , '/' ) . '/config.php' ;
2016-09-22 01:21:28 +02:00
} else {
2024-03-14 15:39:52 +01:00
$configFileName = $this -> baseDir . '/../config/config.php' ;
2016-09-22 01:21:28 +02:00
}
2024-03-14 15:39:52 +01:00
if ( ! file_exists ( $configFileName )) {
2016-09-22 18:15:27 +02:00
throw new \Exception ( 'Could not find config.php. Is this file in the "updater" subfolder of Nextcloud?' );
2016-06-24 00:24:51 +02:00
}
2024-03-14 15:39:52 +01:00
/** @var array $CONFIG */
require_once $configFileName ;
$this -> configValues = $CONFIG ;
2016-06-25 11:49:37 +02:00
2021-02-17 12:47:58 +01:00
if ( php_sapi_name () !== 'cli' && ( $this -> configValues [ 'upgrade.disable-web' ] ? ? false )) {
2021-02-17 09:37:18 +01:00
// updater disabled
$this -> disabled = true ;
return ;
}
2022-07-02 08:33:28 +02:00
$dataDir = $this -> getUpdateDirectoryLocation ();
2023-06-12 17:17:54 +02:00
if ( empty ( $dataDir )) {
2016-09-23 14:12:35 +02:00
throw new \Exception ( 'Could not read data directory from config.php.' );
}
2016-11-03 17:33:58 +01:00
$versionFileName = $this -> baseDir . '/../version.php' ;
2016-06-25 11:49:37 +02:00
if ( ! file_exists ( $versionFileName )) {
// fallback to version in config.php
2023-06-12 17:17:54 +02:00
$version = $this -> getConfigOptionString ( 'version' );
2016-06-26 17:15:15 +02:00
$buildTime = '' ;
2016-06-25 11:49:37 +02:00
} else {
2023-06-12 17:17:54 +02:00
/** @var ?string $OC_Build */
2016-06-25 11:49:37 +02:00
require_once $versionFileName ;
2023-06-12 17:17:54 +02:00
/** @ psalm - suppress UndefinedVariable
* @ var ? string $version
*/
2016-06-25 11:49:37 +02:00
$version = $OC_VersionString ;
2016-06-26 17:15:15 +02:00
$buildTime = $OC_Build ;
2016-06-25 11:49:37 +02:00
}
2023-06-12 17:17:54 +02:00
if ( ! is_string ( $version ) || ! is_string ( $buildTime )) {
2016-06-26 17:15:15 +02:00
return ;
}
2016-06-25 11:49:37 +02:00
// normalize version to 3 digits
$splittedVersion = explode ( '.' , $version );
2022-07-25 12:12:34 +02:00
if ( sizeof ( $splittedVersion ) >= 3 ) {
2016-06-25 11:49:37 +02:00
$splittedVersion = array_slice ( $splittedVersion , 0 , 3 );
}
$this -> currentVersion = implode ( '.' , $splittedVersion );
2016-06-26 17:15:15 +02:00
$this -> buildTime = $buildTime ;
2016-06-25 11:49:37 +02:00
}
2021-02-17 09:37:18 +01:00
/**
* Returns whether the web updater is disabled
*
* @ return bool
*/
public function isDisabled () {
return $this -> disabled ;
}
2016-06-25 11:49:37 +02:00
/**
* Returns current version or " unknown " if this could not be determined .
*
* @ return string
*/
public function getCurrentVersion () {
return $this -> currentVersion ;
}
2017-04-16 21:08:50 +02:00
/**
* Returns currently used release channel
*/
2022-08-25 10:40:56 +02:00
private function getCurrentReleaseChannel () : string {
2023-06-12 17:17:54 +02:00
return ( $this -> getConfigOptionString ( 'updater.release.channel' ) ? ? 'stable' );
2017-04-16 21:08:50 +02:00
}
2016-06-25 11:49:37 +02:00
/**
* @ return string
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-25 11:49:37 +02:00
*/
public function checkForUpdate () {
$response = $this -> getUpdateServerResponse ();
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] checkForUpdate() ' . print_r ( $response , true ));
2023-06-12 17:17:54 +02:00
$version = isset ( $response [ 'version' ]) ? ( string ) $response [ 'version' ] : '' ;
$versionString = isset ( $response [ 'versionstring' ]) ? ( string ) $response [ 'versionstring' ] : '' ;
2016-06-25 11:49:37 +02:00
2016-06-25 12:25:01 +02:00
if ( $version !== '' && $version !== $this -> currentVersion ) {
2016-06-25 11:49:37 +02:00
$this -> updateAvailable = true ;
2017-04-16 21:08:50 +02:00
$releaseChannel = $this -> getCurrentReleaseChannel ();
2023-06-12 17:17:54 +02:00
$updateText = 'Update to ' . htmlentities ( $versionString ) . ' available. (channel: "' . htmlentities ( $releaseChannel ) . '")<br /><span class="light">Following file will be downloaded automatically:</span> <code class="light">' . ( string ) $response [ 'url' ] . '</code>' ;
2019-02-26 17:30:14 +01:00
// only show changelog link for stable releases (non-RC & non-beta)
if ( ! preg_match ( '!(rc|beta)!i' , $versionString )) {
2023-06-12 17:17:54 +02:00
$changelogURL = $this -> getChangelogURL ( substr ( $version , 0 , strrpos ( $version , '.' ) ? : 0 ));
2019-02-26 17:30:14 +01:00
$updateText .= '<br /><a class="external_link" href="' . $changelogURL . '" target="_blank" rel="noreferrer noopener">Open changelog ↗</a>' ;
}
2016-06-25 11:49:37 +02:00
} else {
$updateText = 'No update available.' ;
}
2016-12-07 11:17:30 +01:00
if ( $this -> updateAvailable && isset ( $response [ 'autoupdater' ]) && ! ( $response [ 'autoupdater' ] === 1 || $response [ 'autoupdater' ] === '1' )) {
$this -> updateAvailable = false ;
2017-04-16 21:08:50 +02:00
$updateText .= '<br />The updater is disabled for this update - please update manually.' ;
2016-12-07 11:17:30 +01:00
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of checkForUpdate() ' . $updateText );
2016-06-25 11:49:37 +02:00
return $updateText ;
}
/**
* Returns bool whether update is available or not
*/
2023-06-12 17:17:54 +02:00
public function updateAvailable () : bool {
2016-06-25 11:49:37 +02:00
return $this -> updateAvailable ;
2016-06-24 00:24:51 +02:00
}
/**
2023-06-12 17:17:54 +02:00
* Returns the specified config option
*/
public function getConfigOption ( string $key ) : mixed {
return $this -> configValues [ $key ] ? ? null ;
}
/**
* Returns the specified string config option
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
public function getConfigOptionString ( string $key ) : ? string {
if ( isset ( $this -> configValues [ $key ])) {
if ( ! is_string ( $this -> configValues [ $key ])) {
$this -> silentLog ( '[error] Config key ' . $key . ' should be a string, found ' . gettype ( $this -> configValues [ $key ]));
}
return ( string ) $this -> configValues [ $key ];
} else {
return null ;
}
}
/**
* Returns the specified mandatory string config option
*/
public function getConfigOptionMandatoryString ( string $key ) : string {
if ( isset ( $this -> configValues [ $key ])) {
if ( ! is_string ( $this -> configValues [ $key ])) {
$this -> silentLog ( '[error] Config key ' . $key . ' should be a string, found ' . gettype ( $this -> configValues [ $key ]));
}
return ( string ) $this -> configValues [ $key ];
} else {
throw new \Exception ( 'Config key ' . $key . ' is missing' );
}
2016-06-24 00:24:51 +02:00
}
/**
* Gets the data directory location on the local filesystem
*/
2023-06-12 17:17:54 +02:00
private function getUpdateDirectoryLocation () : string {
return $this -> getConfigOptionString ( 'updatedirectory' ) ? ? $this -> getConfigOptionString ( 'datadirectory' ) ? ? '' ;
2016-06-24 00:24:51 +02:00
}
/**
* Returns the expected files and folders as array
*/
2023-06-12 17:17:54 +02:00
private function getExpectedElementsList () : array {
2017-05-03 08:50:21 +02:00
$expected = [
2016-06-24 00:24:51 +02:00
// Generic
'.' ,
'..' ,
// Folders
2018-09-01 19:44:48 +02:00
'.well-known' ,
2016-06-24 00:24:51 +02:00
'3rdparty' ,
'apps' ,
'config' ,
'core' ,
'data' ,
2022-04-03 10:30:11 +02:00
'dist' ,
2016-06-24 00:24:51 +02:00
'l10n' ,
'lib' ,
'ocs' ,
'ocs-provider' ,
2018-07-26 12:24:03 +02:00
'ocm-provider' ,
2016-06-24 00:24:51 +02:00
'resources' ,
'settings' ,
'themes' ,
'updater' ,
// Files
2019-07-22 00:59:56 +02:00
'.rnd' ,
2016-06-24 00:24:51 +02:00
'index.html' ,
2016-06-24 09:27:16 +02:00
'indie.json' ,
2016-06-24 00:24:51 +02:00
'.user.ini' ,
2023-09-12 12:32:53 +02:00
'composer.json' ,
'composer.lock' ,
2016-06-24 00:24:51 +02:00
'console.php' ,
'cron.php' ,
'index.php' ,
2023-09-12 12:32:53 +02:00
'package.json' ,
'package-lock.json' ,
2016-06-24 00:24:51 +02:00
'public.php' ,
'remote.php' ,
'status.php' ,
'version.php' ,
'robots.txt' ,
'.htaccess' ,
'AUTHORS' ,
2016-11-25 12:27:27 +01:00
'CHANGELOG.md' ,
'COPYING' ,
2016-06-24 00:24:51 +02:00
'COPYING-AGPL' ,
'occ' ,
'db_structure.xml' ,
];
2017-05-03 08:50:21 +02:00
return array_merge ( $expected , $this -> getAppDirectories ());
}
/**
* Returns app directories specified in config . php
*
2023-06-12 17:17:54 +02:00
* @ return list < string >
2017-05-03 08:50:21 +02:00
*/
2023-06-12 17:17:54 +02:00
private function getAppDirectories () : array {
2017-05-03 08:50:21 +02:00
$expected = [];
2022-07-25 12:12:34 +02:00
if ( $appsPaths = $this -> getConfigOption ( 'apps_paths' )) {
2023-06-12 17:17:54 +02:00
if ( ! is_array ( $appsPaths )) {
throw new \Exception ( 'Configuration key apps_paths should be an array' );
}
2017-05-03 08:50:21 +02:00
foreach ( $appsPaths as $appsPath ) {
2023-06-12 17:17:54 +02:00
if ( ! is_array ( $appsPath ) || ! isset ( $appsPath [ 'path' ]) || ! is_string ( $appsPath [ 'path' ])) {
throw new \Exception ( 'Invalid configuration in apps_paths configuration key' );
}
2017-05-03 08:50:21 +02:00
$parentDir = realpath ( $this -> baseDir . '/../' );
$appDir = basename ( $appsPath [ 'path' ]);
2022-07-25 12:12:34 +02:00
if ( strpos ( $appsPath [ 'path' ], $parentDir ) === 0 && $appDir !== 'apps' ) {
2017-05-03 08:50:21 +02:00
$expected [] = $appDir ;
}
}
}
return $expected ;
2016-06-24 00:24:51 +02:00
}
/**
* Gets the recursive directory iterator over the Nextcloud folder
*
2023-06-12 17:17:54 +02:00
* @ return \RecursiveIteratorIterator < \RecursiveDirectoryIterator >
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
private function getRecursiveDirectoryIterator ( ? string $folder = null ) : \RecursiveIteratorIterator {
2016-06-24 09:27:16 +02:00
if ( $folder === null ) {
2016-11-03 17:33:58 +01:00
$folder = $this -> baseDir . '/../' ;
2016-06-24 09:27:16 +02:00
}
2016-10-27 18:26:09 +02:00
return new \RecursiveIteratorIterator (
new \RecursiveDirectoryIterator ( $folder , \RecursiveDirectoryIterator :: SKIP_DOTS ),
\RecursiveIteratorIterator :: CHILD_FIRST
2016-06-24 00:24:51 +02:00
);
}
/**
2016-06-24 09:27:16 +02:00
* Checks for files that are unexpected .
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
public function checkForExpectedFilesAndFolders () : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] checkForExpectedFilesAndFolders()' );
2016-06-24 00:24:51 +02:00
$expectedElements = $this -> getExpectedElementsList ();
$unexpectedElements = [];
2016-11-03 17:33:58 +01:00
foreach ( new \DirectoryIterator ( $this -> baseDir . '/../' ) as $fileInfo ) {
2022-07-25 12:12:34 +02:00
if ( array_search ( $fileInfo -> getFilename (), $expectedElements ) === false ) {
2016-06-24 00:24:51 +02:00
$unexpectedElements [] = $fileInfo -> getFilename ();
}
}
2016-06-24 09:27:16 +02:00
if ( count ( $unexpectedElements ) !== 0 ) {
throw new UpdateException ( $unexpectedElements );
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of checkForExpectedFilesAndFolders()' );
2016-06-24 00:24:51 +02:00
}
/**
2016-06-24 09:27:16 +02:00
* Checks for files that are not writable
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
public function checkWritePermissions () : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] checkWritePermissions()' );
2016-06-25 11:51:18 +02:00
$notWritablePaths = array ();
2016-11-03 17:33:58 +01:00
$dir = new \RecursiveDirectoryIterator ( $this -> baseDir . '/../' );
2016-06-25 19:35:51 +02:00
$filter = new RecursiveDirectoryIteratorWithoutData ( $dir );
2023-06-12 17:17:54 +02:00
/** @var iterable<string, \SplFileInfo> */
2016-10-27 18:26:09 +02:00
$it = new \RecursiveIteratorIterator ( $filter );
2016-06-25 19:35:51 +02:00
foreach ( $it as $path => $dir ) {
2022-07-25 12:12:34 +02:00
if ( ! is_writable ( $path )) {
2016-06-25 11:51:18 +02:00
$notWritablePaths [] = $path ;
2016-06-24 00:24:51 +02:00
}
}
2022-07-25 12:12:34 +02:00
if ( count ( $notWritablePaths ) > 0 ) {
2016-06-25 11:51:18 +02:00
throw new UpdateException ( $notWritablePaths );
2016-06-24 09:27:16 +02:00
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of checkWritePermissions()' );
2016-06-24 00:24:51 +02:00
}
/**
* Sets the maintenance mode to the defined value
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception when config . php can ' t be written
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
public function setMaintenanceMode ( bool $state ) : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] setMaintenanceMode("' . ( $state ? 'true' : 'false' ) . '")' );
2022-07-25 12:12:34 +02:00
if ( $dir = getenv ( 'NEXTCLOUD_CONFIG_DIR' )) {
2016-09-22 01:21:28 +02:00
$configFileName = rtrim ( $dir , '/' ) . '/config.php' ;
} else {
2016-11-03 17:33:58 +01:00
$configFileName = $this -> baseDir . '/../config/config.php' ;
2016-09-22 01:21:28 +02:00
}
$this -> silentLog ( '[info] configFileName ' . $configFileName );
2016-09-22 18:15:27 +02:00
// usually is already tested in the constructor but just to be on the safe side
if ( ! file_exists ( $configFileName )) {
throw new \Exception ( 'Could not find config.php.' );
}
2016-06-24 00:24:51 +02:00
/** @var array $CONFIG */
require $configFileName ;
$CONFIG [ 'maintenance' ] = $state ;
$content = " <?php \n " ;
$content .= '$CONFIG = ' ;
$content .= var_export ( $CONFIG , true );
$content .= " ; \n " ;
$state = file_put_contents ( $configFileName , $content );
2016-06-24 09:27:16 +02:00
if ( $state === false ) {
throw new \Exception ( 'Could not write to config.php' );
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of setMaintenanceMode()' );
2016-06-24 00:24:51 +02:00
}
/**
* Creates a backup of all files and moves it into data / updater - $instanceid / backups / nextcloud - X - Y - Z /
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
public function createBackup () : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] createBackup()' );
2016-06-24 00:24:51 +02:00
$excludedElements = [
2019-07-22 00:59:56 +02:00
'.rnd' ,
2018-09-01 19:44:48 +02:00
'.well-known' ,
2016-06-24 00:24:51 +02:00
'data' ,
];
// Create new folder for the backup
2023-06-12 17:17:54 +02:00
$backupFolderLocation = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' ) . '/backups/nextcloud-' . $this -> getConfigOptionMandatoryString ( 'version' ) . '-' . time () . '/' ;
2020-09-15 15:47:14 +02:00
$this -> silentLog ( '[info] backup folder location: ' . $backupFolderLocation );
2016-09-22 00:12:31 +02:00
2016-06-24 00:24:51 +02:00
$state = mkdir ( $backupFolderLocation , 0750 , true );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2016-06-24 00:24:51 +02:00
throw new \Exception ( 'Could not create backup folder location' );
}
// Copy the backup files
2016-11-03 17:33:58 +01:00
$currentDir = $this -> baseDir . '/../' ;
2016-06-24 00:24:51 +02:00
/**
* @ var string $path
2016-10-27 18:26:09 +02:00
* @ var \SplFileInfo $fileInfo
2016-06-24 00:24:51 +02:00
*/
foreach ( $this -> getRecursiveDirectoryIterator ( $currentDir ) as $path => $fileInfo ) {
$fileName = explode ( $currentDir , $path )[ 1 ];
$folderStructure = explode ( '/' , $fileName , - 1 );
// Exclude the exclusions
2022-07-25 12:12:34 +02:00
if ( isset ( $folderStructure [ 0 ])) {
if ( array_search ( $folderStructure [ 0 ], $excludedElements ) !== false ) {
2016-06-24 00:24:51 +02:00
continue ;
}
} else {
2022-07-25 12:12:34 +02:00
if ( array_search ( $fileName , $excludedElements ) !== false ) {
2016-06-24 00:24:51 +02:00
continue ;
}
}
// Create folder if it doesn't exist
2022-07-25 12:12:34 +02:00
if ( ! file_exists ( $backupFolderLocation . '/' . dirname ( $fileName ))) {
2016-06-24 00:24:51 +02:00
$state = mkdir ( $backupFolderLocation . '/' . dirname ( $fileName ), 0750 , true );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( 'Could not create folder: ' . $backupFolderLocation . '/' . dirname ( $fileName ));
2016-06-24 00:24:51 +02:00
}
}
// If it is a file copy it
2022-07-25 12:12:34 +02:00
if ( $fileInfo -> isFile ()) {
2016-06-24 00:24:51 +02:00
$state = copy ( $fileInfo -> getRealPath (), $backupFolderLocation . $fileName );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2018-08-11 17:55:07 +02:00
$message = sprintf (
'Could not copy "%s" to "%s"' ,
$fileInfo -> getRealPath (),
$backupFolderLocation . $fileName
2016-06-24 00:24:51 +02:00
);
2018-08-11 17:55:07 +02:00
2022-07-25 12:12:34 +02:00
if ( is_readable ( $fileInfo -> getRealPath ()) === false ) {
2018-08-11 17:55:07 +02:00
$message = sprintf (
'%s. Source %s is not readable' ,
$message ,
$fileInfo -> getRealPath ()
);
}
2022-07-25 12:12:34 +02:00
if ( is_writable ( $backupFolderLocation . $fileName ) === false ) {
2018-08-11 17:55:07 +02:00
$message = sprintf (
'%s. Destination %s is not writable' ,
$message ,
$backupFolderLocation . $fileName
);
}
throw new \Exception ( $message );
2016-06-24 00:24:51 +02:00
}
}
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of createBackup()' );
2016-06-24 00:24:51 +02:00
}
2023-06-12 17:17:54 +02:00
private function getChangelogURL ( string $versionString ) : string {
2018-11-27 15:24:01 +01:00
$this -> silentLog ( '[info] getChangelogURL()' );
$changelogWebsite = 'https://nextcloud.com/changelog/' ;
2019-02-26 17:30:14 +01:00
$changelogURL = $changelogWebsite . '#' . str_replace ( '.' , '-' , $versionString );
2018-11-27 15:24:01 +01:00
return $changelogURL ;
}
2016-06-24 00:24:51 +02:00
/**
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
private function getUpdateServerResponse () : array {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] getUpdateServerResponse()' );
2023-06-12 17:17:54 +02:00
$updaterServer = $this -> getConfigOptionString ( 'updater.server.url' );
2022-07-25 12:12:34 +02:00
if ( $updaterServer === null ) {
2016-06-24 00:24:51 +02:00
// FIXME: used deployed URL
2019-04-10 13:17:59 +02:00
$updaterServer = 'https://updates.nextcloud.com/updater_server/' ;
2016-06-24 00:24:51 +02:00
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] updaterServer: ' . $updaterServer );
2016-06-24 00:24:51 +02:00
2017-04-16 21:08:50 +02:00
$releaseChannel = $this -> getCurrentReleaseChannel ();
2016-09-22 00:38:07 +02:00
$this -> silentLog ( '[info] releaseChannel: ' . $releaseChannel );
2023-06-12 17:17:54 +02:00
$this -> silentLog ( '[info] internal version: ' . $this -> getConfigOptionMandatoryString ( 'version' ));
2016-06-26 17:09:48 +02:00
2023-06-12 17:17:54 +02:00
$updateURL = $updaterServer . '?version=' . str_replace ( '.' , 'x' , $this -> getConfigOptionMandatoryString ( 'version' )) . 'xxx' . $releaseChannel . 'xx' . urlencode ( $this -> buildTime ) . 'x' . PHP_MAJOR_VERSION . 'x' . PHP_MINOR_VERSION . 'x' . PHP_RELEASE_VERSION ;
2017-04-25 16:43:02 +02:00
$this -> silentLog ( '[info] updateURL: ' . $updateURL );
2016-06-24 00:24:51 +02:00
// Download update response
$curl = curl_init ();
curl_setopt_array ( $curl , [
CURLOPT_RETURNTRANSFER => 1 ,
2017-04-25 16:43:02 +02:00
CURLOPT_URL => $updateURL ,
2016-06-24 00:24:51 +02:00
CURLOPT_USERAGENT => 'Nextcloud Updater' ,
]);
2017-01-22 14:19:11 +01:00
if ( $this -> getConfigOption ( 'proxy' ) !== null ) {
curl_setopt_array ( $curl , [
2023-06-12 17:17:54 +02:00
CURLOPT_PROXY => $this -> getConfigOptionString ( 'proxy' ),
CURLOPT_PROXYUSERPWD => $this -> getConfigOptionString ( 'proxyuserpwd' ),
2017-01-22 14:19:11 +01:00
CURLOPT_HTTPPROXYTUNNEL => $this -> getConfigOption ( 'proxy' ) ? 1 : 0 ,
]);
}
2022-08-30 10:39:26 +02:00
/** @var false|string $response */
2016-06-24 00:24:51 +02:00
$response = curl_exec ( $curl );
2022-07-25 12:12:34 +02:00
if ( $response === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( 'Could not do request to updater server: ' . curl_error ( $curl ));
2016-06-24 00:24:51 +02:00
}
curl_close ( $curl );
2016-06-25 12:25:01 +02:00
// Response can be empty when no update is available
2022-07-25 12:12:34 +02:00
if ( $response === '' ) {
2016-06-25 12:25:01 +02:00
return [];
}
2016-06-24 00:24:51 +02:00
$xml = simplexml_load_string ( $response );
2022-07-25 12:12:34 +02:00
if ( $xml === false ) {
2016-06-24 00:24:51 +02:00
throw new \Exception ( 'Could not parse updater server XML response' );
}
2023-06-12 17:17:54 +02:00
$response = get_object_vars ( $xml );
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] getUpdateServerResponse response: ' . print_r ( $response , true ));
2016-06-24 00:24:51 +02:00
return $response ;
}
/**
* Downloads the nextcloud folder to $DATADIR / updater - $instanceid / downloads / $filename
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
public function downloadUpdate () : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] downloadUpdate()' );
2016-06-24 00:24:51 +02:00
$response = $this -> getUpdateServerResponse ();
2022-08-20 19:36:22 +02:00
2023-06-12 17:17:54 +02:00
$storageLocation = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' ) . '/downloads/' ;
2022-07-25 12:12:34 +02:00
if ( file_exists ( $storageLocation )) {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] storage location exists' );
2016-06-25 11:52:40 +02:00
$this -> recursiveDelete ( $storageLocation );
}
2016-06-24 00:24:51 +02:00
$state = mkdir ( $storageLocation , 0750 , true );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2016-06-24 00:24:51 +02:00
throw new \Exception ( 'Could not mkdir storage location' );
}
2016-06-25 11:52:40 +02:00
2023-06-12 17:17:54 +02:00
if ( ! isset ( $response [ 'url' ]) || ! is_string ( $response [ 'url' ])) {
throw new \Exception ( 'Response from update server is missing url' );
}
2016-06-24 00:24:51 +02:00
$fp = fopen ( $storageLocation . basename ( $response [ 'url' ]), 'w+' );
$ch = curl_init ( $response [ 'url' ]);
2017-01-22 14:19:11 +01:00
curl_setopt_array ( $ch , [
CURLOPT_FILE => $fp ,
CURLOPT_USERAGENT => 'Nextcloud Updater' ,
]);
if ( $this -> getConfigOption ( 'proxy' ) !== null ) {
curl_setopt_array ( $ch , [
2023-06-12 17:17:54 +02:00
CURLOPT_PROXY => $this -> getConfigOptionString ( 'proxy' ),
CURLOPT_PROXYUSERPWD => $this -> getConfigOptionString ( 'proxyuserpwd' ),
2017-01-22 14:19:11 +01:00
CURLOPT_HTTPPROXYTUNNEL => $this -> getConfigOption ( 'proxy' ) ? 1 : 0 ,
]);
}
2022-07-25 12:12:34 +02:00
if ( curl_exec ( $ch ) === false ) {
2016-06-24 00:24:51 +02:00
throw new \Exception ( 'Curl error: ' . curl_error ( $ch ));
}
2016-09-21 21:34:38 +02:00
$httpCode = curl_getinfo ( $ch , CURLINFO_HTTP_CODE );
2022-07-25 12:12:34 +02:00
if ( $httpCode !== 200 ) {
2016-09-21 21:34:38 +02:00
$statusCodes = [
400 => 'Bad request' ,
401 => 'Unauthorized' ,
403 => 'Forbidden' ,
404 => 'Not Found' ,
500 => 'Internal Server Error' ,
502 => 'Bad Gateway' ,
503 => 'Service Unavailable' ,
504 => 'Gateway Timeout' ,
];
$message = 'Download failed' ;
2023-06-12 17:17:54 +02:00
if ( is_int ( $httpCode ) && isset ( $statusCodes [ $httpCode ])) {
2016-09-21 21:34:38 +02:00
$message .= ' - ' . $statusCodes [ $httpCode ] . ' (HTTP ' . $httpCode . ')' ;
} else {
2023-06-12 17:17:54 +02:00
$message .= ' - HTTP status code: ' . ( string ) $httpCode ;
2016-09-21 21:34:38 +02:00
}
$curlErrorMessage = curl_error ( $ch );
2022-07-25 12:12:34 +02:00
if ( ! empty ( $curlErrorMessage )) {
2016-09-21 21:34:38 +02:00
$message .= ' - curl error message: ' . $curlErrorMessage ;
}
2016-09-26 16:25:30 +02:00
$message .= ' - URL: ' . htmlentities ( $response [ 'url' ]);
2016-09-21 21:34:38 +02:00
throw new \Exception ( $message );
}
2016-06-24 00:24:51 +02:00
curl_close ( $ch );
fclose ( $fp );
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of downloadUpdate()' );
2016-06-24 00:24:51 +02:00
}
/**
2017-04-17 14:20:37 +02:00
* @ throws \Exception
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
private function getDownloadedFilePath () : string {
$storageLocation = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' ) . '/downloads/' ;
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] storage location: ' . $storageLocation );
2020-09-15 16:31:06 +02:00
$filesInStorageLocation = scandir ( $storageLocation );
2023-06-12 17:17:54 +02:00
$files = array_values ( array_filter ( $filesInStorageLocation , function ( string $path ) {
2020-09-15 16:31:06 +02:00
return $path !== '.' && $path !== '..' ;
2020-09-15 19:35:28 +02:00
}));
2020-09-15 16:31:06 +02:00
// only the downloaded archive
2022-07-25 12:12:34 +02:00
if ( count ( $files ) !== 1 ) {
2020-09-15 16:31:06 +02:00
throw new \Exception ( 'There are more files than the downloaded archive in the downloads/ folder.' );
2016-06-24 00:24:51 +02:00
}
2020-09-15 19:35:28 +02:00
return $storageLocation . '/' . $files [ 0 ];
2017-04-16 21:08:50 +02:00
}
/**
* Verifies the integrity of the downloaded file
*
* @ throws \Exception
*/
2023-06-12 17:17:54 +02:00
public function verifyIntegrity () : void {
2017-04-16 21:08:50 +02:00
$this -> silentLog ( '[info] verifyIntegrity()' );
2022-07-25 12:12:34 +02:00
if ( $this -> getCurrentReleaseChannel () === 'daily' ) {
2017-04-16 21:08:50 +02:00
$this -> silentLog ( '[info] current channel is "daily" which is not signed. Skipping verification.' );
return ;
}
$response = $this -> getUpdateServerResponse ();
2023-06-12 09:34:07 +02:00
if ( empty ( $response [ 'signature' ])) {
2017-04-16 21:08:50 +02:00
throw new \Exception ( 'No signature specified for defined update' );
}
2023-06-12 17:17:54 +02:00
if ( ! is_string ( $response [ 'signature' ])) {
throw new \Exception ( 'Signature specified for defined update should be a string' );
}
2017-04-16 21:08:50 +02:00
$certificate = <<< EOF
----- BEGIN CERTIFICATE -----
MIIEojCCA4qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwezELMAkGA1UEBhMCREUx
GzAZBgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzEXMBUGA1UECgwOTmV4dGNsb3Vk
IEdtYkgxNjA0BgNVBAMMLU5leHRjbG91ZCBDb2RlIFNpZ25pbmcgSW50ZXJtZWRp
YXRlIEF1dGhvcml0eTAeFw0xNjA2MTIyMTA1MDZaFw00MTA2MDYyMTA1MDZaMGYx
CzAJBgNVBAYTAkRFMRswGQYDVQQIDBJCYWRlbi1XdWVydHRlbWJlcmcxEjAQBgNV
BAcMCVN0dXR0Z2FydDEXMBUGA1UECgwOTmV4dGNsb3VkIEdtYkgxDTALBgNVBAMM
BGNvcmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUxcrn2DC892IX
8 + dJjZVh9YeHF65n2ha886oeAizOuHBdWBfzqt + GoUYTOjqZF93HZMcwy0P + xyCf
Qqak5Ke9dybN06RXUuGP45k9UYBp03qzlUzCDalrkj + Jd30LqcSC1sjRTsfuhc + u
vH1IBuBnf7SMUJUcoEffbmmpAPlEcLHxlUGlGnz0q1e8UFzjbEFj3JucMO4ys35F
qZS4dhvCngQhRW3DaMlQLXEUL9k3kFV + BzlkPzVZEtSmk4HJujFCnZj1vMcjQBg /
Bqq1HCmUB6tulnGcxUzt / Z / oSIgnuGyENeke077W3EyryINL7EIyD4Xp7sxLizTM
FCFCjjH1AgMBAAGjggFDMIIBPzAJBgNVHRMEAjAAMBEGCWCGSAGG + EIBAQQEAwIG
QDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgU2VydmVyIENlcnRp
ZmljYXRlMB0GA1UdDgQWBBQwc1H9AL8pRlW2e5SLCfPPqtqc0DCBpQYDVR0jBIGd
MIGagBRt6m6qqTcsPIktFz79Ru7DnnjtdKF + pHwwejELMAkGA1UEBhMCREUxGzAZ
BgNVBAgMEkJhZGVuLVd1ZXJ0dGVtYmVyZzESMBAGA1UEBwwJU3R1dHRnYXJ0MRcw
FQYDVQQKDA5OZXh0Y2xvdWQgR21iSDEhMB8GA1UEAwwYTmV4dGNsb3VkIFJvb3Qg
QXV0aG9yaXR5ggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
AwEwDQYJKoZIhvcNAQELBQADggEBADZ6 + HV /+ 0 NEH3nahTBFxO6nKyR / VWigACH0
naV0ecTcoQwDjKDNNFr + 4 S1WlHdwITlnNabC7v9rZ / 6 QvbkrOTuO9fOR6azp1EwW
2 pixWqj0Sb9 / dSIVRpSq + jpBE6JAiX44dSR7zoBxRB8DgVO2Afy0s80xEpr5JAzb
NYuPS7M5UHdAv2dr16fDcDIvn + vk92KpNh1NTeZFjBbRVQ9DXrgkRGW34TK8uSLI
YG6jnfJ6eJgTaO431ywWPXNg1mUMaT /+ QBOgB299QVCKQU + lcZWptQt + RdsJUm46
NY / nARy4Oi4uOe88SuWITj9KhrFmEvrUlgM8FvoXA1ldrR7KiEg =
----- END CERTIFICATE -----
EOF ;
$validSignature = ( bool ) openssl_verify (
file_get_contents ( $this -> getDownloadedFilePath ()),
base64_decode ( $response [ 'signature' ]),
$certificate ,
OPENSSL_ALGO_SHA512
);
2022-07-25 12:12:34 +02:00
if ( $validSignature === false ) {
2017-04-16 21:08:50 +02:00
throw new \Exception ( 'Signature of update is not valid' );
}
$this -> silentLog ( '[info] end of verifyIntegrity()' );
}
2017-04-17 14:20:37 +02:00
/**
* Gets the version as declared in $versionFile
*
* @ throws \Exception If $OC_Version is not defined in $versionFile
*/
2023-06-12 17:17:54 +02:00
private function getVersionByVersionFile ( string $versionFile ) : string {
/** @psalm-suppress UnresolvableInclude */
2017-04-17 14:20:37 +02:00
require $versionFile ;
2022-08-30 10:39:26 +02:00
/** @psalm-suppress UndefinedVariable */
2022-07-25 12:12:34 +02:00
if ( isset ( $OC_Version )) {
2023-06-12 17:17:54 +02:00
/** @var string[] $OC_Version */
2017-04-17 14:20:37 +02:00
return implode ( '.' , $OC_Version );
}
2023-11-14 22:59:37 +01:00
throw new \Exception ( " OC_Version not found in $versionFile " );
2017-04-17 14:20:37 +02:00
}
2017-04-16 21:08:50 +02:00
/**
* Extracts the download
*
* @ throws \Exception
*/
2023-06-12 17:17:54 +02:00
public function extractDownload () : void {
2017-04-16 21:08:50 +02:00
$this -> silentLog ( '[info] extractDownload()' );
$downloadedFilePath = $this -> getDownloadedFilePath ();
2016-06-24 00:24:51 +02:00
2016-10-27 18:26:09 +02:00
$zip = new \ZipArchive ;
2017-04-16 21:08:50 +02:00
$zipState = $zip -> open ( $downloadedFilePath );
2016-06-24 00:24:51 +02:00
if ( $zipState === true ) {
2018-02-17 21:37:08 +01:00
$extraction = $zip -> extractTo ( dirname ( $downloadedFilePath ));
2022-07-25 12:12:34 +02:00
if ( $extraction === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( 'Error during unpacking zipfile: ' . ( $zip -> getStatusString ()));
2018-02-17 21:37:08 +01:00
}
2016-06-24 00:24:51 +02:00
$zip -> close ();
2017-04-16 21:08:50 +02:00
$state = unlink ( $downloadedFilePath );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( " Can't unlink " . $downloadedFilePath );
2016-06-24 00:24:51 +02:00
}
} else {
2023-11-14 22:59:37 +01:00
throw new \Exception ( " Can't handle ZIP file. Error code is: " . print_r ( $zipState , true ));
2016-06-24 00:24:51 +02:00
}
2016-09-22 00:12:31 +02:00
2017-04-17 14:20:37 +02:00
// Ensure that the downloaded version is not lower
$downloadedVersion = $this -> getVersionByVersionFile ( dirname ( $downloadedFilePath ) . '/nextcloud/version.php' );
$currentVersion = $this -> getVersionByVersionFile ( $this -> baseDir . '/../version.php' );
2022-07-25 12:12:34 +02:00
if ( version_compare ( $downloadedVersion , $currentVersion , '<' )) {
2017-04-17 14:20:37 +02:00
throw new \Exception ( 'Downloaded version is lower than installed version' );
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of extractDownload()' );
2016-06-24 00:24:51 +02:00
}
/**
* Replaces the entry point files with files that only return a 503
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
public function replaceEntryPoints () : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] replaceEntryPoints()' );
2016-06-24 00:24:51 +02:00
$filesToReplace = [
'index.php' ,
'status.php' ,
'remote.php' ,
'public.php' ,
'ocs/v1.php' ,
2017-04-17 18:42:59 +02:00
'ocs/v2.php' ,
2016-06-24 00:24:51 +02:00
];
$content = " <?php \n http_response_code(503); \n die('Update in process.'); " ;
2022-07-25 12:12:34 +02:00
foreach ( $filesToReplace as $file ) {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] replace ' . $file );
2016-11-03 17:33:58 +01:00
$parentDir = dirname ( $this -> baseDir . '/../' . $file );
2022-07-25 12:12:34 +02:00
if ( ! file_exists ( $parentDir )) {
2016-06-25 11:52:40 +02:00
$r = mkdir ( $parentDir );
2022-07-25 12:12:34 +02:00
if ( $r !== true ) {
2016-06-25 11:52:40 +02:00
throw new \Exception ( 'Can\'t create parent directory for entry point: ' . $file );
}
}
2016-11-03 17:33:58 +01:00
$state = file_put_contents ( $this -> baseDir . '/../' . $file , $content );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( 'Can\'t replace entry point: ' . $file );
2016-06-24 00:24:51 +02:00
}
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of replaceEntryPoints()' );
2016-06-24 00:24:51 +02:00
}
/**
* Recursively deletes the specified folder from the system
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
private function recursiveDelete ( string $folder ) : void {
2022-07-25 12:12:34 +02:00
if ( ! file_exists ( $folder )) {
2016-06-24 00:24:51 +02:00
return ;
}
2023-06-12 17:17:54 +02:00
/** @var iterable<\SplFileInfo> $iterator */
2016-10-27 18:26:09 +02:00
$iterator = new \RecursiveIteratorIterator (
new \RecursiveDirectoryIterator ( $folder , \RecursiveDirectoryIterator :: SKIP_DOTS ),
\RecursiveIteratorIterator :: CHILD_FIRST
2016-06-24 00:24:51 +02:00
);
2023-06-12 17:17:54 +02:00
$directories = [];
$files = [];
2016-06-24 00:24:51 +02:00
foreach ( $iterator as $fileInfo ) {
2017-05-27 08:45:40 +02:00
if ( $fileInfo -> isDir ()) {
$directories [] = $fileInfo -> getRealPath ();
} else {
2018-08-26 20:59:15 +02:00
if ( $fileInfo -> isLink ()) {
$files [] = $fileInfo -> getPathName ();
} else {
$files [] = $fileInfo -> getRealPath ();
}
2017-05-27 08:45:40 +02:00
}
}
2019-02-26 17:30:14 +01:00
2017-05-27 08:45:40 +02:00
foreach ( $files as $file ) {
2023-11-14 22:59:37 +01:00
unlink ( $file );
2017-05-27 08:45:40 +02:00
}
foreach ( $directories as $dir ) {
2023-11-14 22:59:37 +01:00
rmdir ( $dir );
2016-06-24 00:24:51 +02:00
}
2019-02-26 17:30:14 +01:00
2016-06-24 00:24:51 +02:00
$state = rmdir ( $folder );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2016-06-24 00:24:51 +02:00
throw new \Exception ( 'Could not rmdir ' . $folder );
}
}
/**
* Delete old files from the system as much as possible
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
public function deleteOldFiles () : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] deleteOldFiles()' );
2020-07-07 11:28:00 +02:00
$shippedAppsFile = $this -> baseDir . '/../core/shipped.json' ;
2023-06-12 17:17:54 +02:00
$shippedAppsFileContent = file_get_contents ( $shippedAppsFile );
if ( $shippedAppsFileContent === false ) {
2020-07-07 11:28:00 +02:00
throw new \Exception ( 'core/shipped.json is not available' );
}
2023-06-12 17:17:54 +02:00
$shippedAppsFileContentDecoded = json_decode ( $shippedAppsFileContent , true );
if ( ! is_array ( $shippedAppsFileContentDecoded ) ||
! is_array ( $shippedApps = $shippedAppsFileContentDecoded [ 'shippedApps' ] ? ? [])) {
throw new \Exception ( 'core/shipped.json content is invalid' );
}
2020-07-07 11:28:00 +02:00
2023-06-12 17:17:54 +02:00
$newShippedAppsFile = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' ) . '/downloads/nextcloud/core/shipped.json' ;
$newShippedAppsFileContent = file_get_contents ( $newShippedAppsFile );
if ( $newShippedAppsFileContent === false ) {
2019-11-28 16:11:41 +01:00
throw new \Exception ( 'core/shipped.json is not available in the new release' );
2016-06-25 11:52:40 +02:00
}
2023-06-12 17:17:54 +02:00
$newShippedAppsFileContentDecoded = json_decode ( $newShippedAppsFileContent , true );
if ( ! is_array ( $newShippedAppsFileContentDecoded ) ||
! is_array ( $newShippedApps = $newShippedAppsFileContentDecoded [ 'shippedApps' ] ? ? [])) {
throw new \Exception ( 'core/shipped.json content is invalid in the new release' );
}
2020-07-07 11:28:00 +02:00
2016-06-24 00:24:51 +02:00
// Delete shipped apps
2023-06-12 17:17:54 +02:00
$shippedApps = array_merge ( $shippedApps , $newShippedApps );
/** @var string $app */
2022-07-25 12:12:34 +02:00
foreach ( $shippedApps as $app ) {
2016-11-03 17:33:58 +01:00
$this -> recursiveDelete ( $this -> baseDir . '/../apps/' . $app );
2016-06-24 00:24:51 +02:00
}
2016-11-03 17:33:58 +01:00
$configSampleFile = $this -> baseDir . '/../config/config.sample.php' ;
2022-07-25 12:12:34 +02:00
if ( file_exists ( $configSampleFile )) {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] config sample exists' );
2016-06-25 11:52:40 +02:00
// Delete example config
$state = unlink ( $configSampleFile );
if ( $state === false ) {
throw new \Exception ( 'Could not unlink sample config' );
}
2016-06-24 00:24:51 +02:00
}
2016-11-03 17:33:58 +01:00
$themesReadme = $this -> baseDir . '/../themes/README' ;
2022-07-25 12:12:34 +02:00
if ( file_exists ( $themesReadme )) {
2017-09-26 16:42:54 +02:00
$this -> silentLog ( '[info] themes README exists' );
2016-09-22 00:12:31 +02:00
2016-06-25 11:52:40 +02:00
// Delete themes
$state = unlink ( $themesReadme );
if ( $state === false ) {
throw new \Exception ( 'Could not delete themes README' );
}
2016-06-24 00:24:51 +02:00
}
2016-11-03 17:33:58 +01:00
$this -> recursiveDelete ( $this -> baseDir . '/../themes/example/' );
2016-06-24 00:24:51 +02:00
// Delete the rest
$excludedElements = [
2018-09-01 19:44:48 +02:00
'.well-known' ,
2016-06-24 00:24:51 +02:00
'data' ,
'index.php' ,
'status.php' ,
'remote.php' ,
'public.php' ,
'ocs/v1.php' ,
2017-04-17 18:42:59 +02:00
'ocs/v2.php' ,
2016-06-24 00:24:51 +02:00
'config' ,
'themes' ,
'apps' ,
'updater' ,
];
2017-05-03 08:50:21 +02:00
$excludedElements = array_merge ( $excludedElements , $this -> getAppDirectories ());
2016-06-24 00:24:51 +02:00
/**
* @ var string $path
2016-10-27 18:26:09 +02:00
* @ var \SplFileInfo $fileInfo
2016-06-24 00:24:51 +02:00
*/
2023-11-14 22:59:37 +01:00
foreach ( $this -> getRecursiveDirectoryIterator () as $path => $fileInfo ) {
2016-11-03 17:33:58 +01:00
$currentDir = $this -> baseDir . '/../' ;
2016-06-24 00:24:51 +02:00
$fileName = explode ( $currentDir , $path )[ 1 ];
$folderStructure = explode ( '/' , $fileName , - 1 );
// Exclude the exclusions
2022-07-25 12:12:34 +02:00
if ( isset ( $folderStructure [ 0 ])) {
if ( array_search ( $folderStructure [ 0 ], $excludedElements ) !== false ) {
2016-06-24 00:24:51 +02:00
continue ;
}
} else {
2022-07-25 12:12:34 +02:00
if ( array_search ( $fileName , $excludedElements ) !== false ) {
2016-06-24 00:24:51 +02:00
continue ;
}
}
2022-07-25 12:12:34 +02:00
if ( $fileInfo -> isFile () || $fileInfo -> isLink ()) {
2016-06-24 00:24:51 +02:00
$state = unlink ( $path );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( 'Could not unlink: ' . $path );
2016-06-24 00:24:51 +02:00
}
2022-07-25 12:12:34 +02:00
} elseif ( $fileInfo -> isDir ()) {
2016-06-24 00:24:51 +02:00
$state = rmdir ( $path );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2016-06-24 00:24:51 +02:00
throw new \Exception ( 'Could not rmdir: ' . $path );
}
}
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of deleteOldFiles()' );
2016-06-24 00:24:51 +02:00
}
/**
2016-06-25 18:06:07 +02:00
* Moves the specified filed except the excluded elements to the correct position
2016-06-24 00:24:51 +02:00
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-24 00:24:51 +02:00
*/
2023-06-12 17:17:54 +02:00
private function moveWithExclusions ( string $dataLocation , array $excludedElements ) : void {
2016-06-24 00:24:51 +02:00
/**
2023-06-12 17:17:54 +02:00
* @ var string $path
2016-10-27 18:26:09 +02:00
* @ var \SplFileInfo $fileInfo
2016-06-24 00:24:51 +02:00
*/
2023-11-14 22:59:37 +01:00
foreach ( $this -> getRecursiveDirectoryIterator ( $dataLocation ) as $path => $fileInfo ) {
2016-06-25 18:06:07 +02:00
$fileName = explode ( $dataLocation , $path )[ 1 ];
2016-06-24 00:24:51 +02:00
$folderStructure = explode ( '/' , $fileName , - 1 );
// Exclude the exclusions
if ( isset ( $folderStructure [ 0 ])) {
if ( array_search ( $folderStructure [ 0 ], $excludedElements ) !== false ) {
continue ;
}
} else {
if ( array_search ( $fileName , $excludedElements ) !== false ) {
continue ;
}
}
2022-07-25 12:12:34 +02:00
if ( $fileInfo -> isFile ()) {
if ( ! file_exists ( $this -> baseDir . '/../' . dirname ( $fileName ))) {
2016-11-03 17:33:58 +01:00
$state = mkdir ( $this -> baseDir . '/../' . dirname ( $fileName ), 0755 , true );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2016-11-03 17:33:58 +01:00
throw new \Exception ( 'Could not mkdir ' . $this -> baseDir . '/../' . dirname ( $fileName ));
2016-06-24 00:24:51 +02:00
}
}
2016-11-03 17:33:58 +01:00
$state = rename ( $path , $this -> baseDir . '/../' . $fileName );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2016-06-24 00:24:51 +02:00
throw new \Exception (
sprintf (
'Could not rename %s to %s' ,
$path ,
2016-11-03 17:33:58 +01:00
$this -> baseDir . '/../' . $fileName
2016-06-24 00:24:51 +02:00
)
);
}
}
2022-07-25 12:12:34 +02:00
if ( $fileInfo -> isDir ()) {
2016-06-24 00:24:51 +02:00
$state = rmdir ( $path );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2016-06-24 00:24:51 +02:00
throw new \Exception ( 'Could not rmdir ' . $path );
}
}
}
2016-06-25 18:06:07 +02:00
}
2016-06-24 00:24:51 +02:00
2016-06-25 18:06:07 +02:00
/**
* Moves the newly downloaded files into place
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-25 18:06:07 +02:00
*/
2023-06-12 17:17:54 +02:00
public function moveNewVersionInPlace () : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] moveNewVersionInPlace()' );
2016-06-25 18:06:07 +02:00
// Rename everything else except the entry and updater files
$excludedElements = [
'updater' ,
'index.php' ,
'status.php' ,
'remote.php' ,
'public.php' ,
'ocs/v1.php' ,
2017-04-17 18:42:59 +02:00
'ocs/v2.php' ,
2016-06-25 18:06:07 +02:00
];
2023-06-12 17:17:54 +02:00
$storageLocation = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' ) . '/downloads/nextcloud/' ;
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] storage location: ' . $storageLocation );
2016-06-25 18:06:07 +02:00
$this -> moveWithExclusions ( $storageLocation , $excludedElements );
// Rename everything except the updater files
$this -> moveWithExclusions ( $storageLocation , [ 'updater' ]);
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of moveNewVersionInPlace()' );
2016-06-25 18:06:07 +02:00
}
2016-06-24 00:24:51 +02:00
2016-06-25 18:06:07 +02:00
/**
* Finalize and cleanup the updater by finally replacing the updater script
*/
2023-06-12 17:17:54 +02:00
public function finalize () : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] finalize()' );
2023-06-12 17:17:54 +02:00
$storageLocation = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' ) . '/downloads/nextcloud/' ;
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] storage location: ' . $storageLocation );
2016-06-25 18:06:07 +02:00
$this -> moveWithExclusions ( $storageLocation , []);
2016-06-24 00:24:51 +02:00
$state = rmdir ( $storageLocation );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( 'Could not rmdir $storagelocation' );
2016-06-24 00:24:51 +02:00
}
2022-08-20 19:36:22 +02:00
2023-06-12 17:17:54 +02:00
$state = unlink ( $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' ) . '/.step' );
2022-07-25 12:12:34 +02:00
if ( $state === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( 'Could not rmdir .step' );
2016-06-25 19:15:37 +02:00
}
2017-05-22 01:51:36 +02:00
if ( function_exists ( 'opcache_reset' )) {
$this -> silentLog ( '[info] call opcache_reset()' );
opcache_reset ();
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of finalize()' );
2016-06-25 19:15:37 +02:00
}
/**
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-25 19:15:37 +02:00
*/
2023-06-12 17:17:54 +02:00
private function writeStep ( string $state , int $step ) : void {
$updaterDir = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' );
2022-07-25 12:12:34 +02:00
if ( ! file_exists ( $updaterDir . '/.step' )) {
if ( ! file_exists ( $updaterDir )) {
2016-06-26 00:50:47 +02:00
$result = mkdir ( $updaterDir );
if ( $result === false ) {
2023-11-14 22:59:37 +01:00
throw new \Exception ( 'Could not create $updaterDir' );
2016-06-25 19:15:37 +02:00
}
}
2016-06-26 00:50:47 +02:00
$result = touch ( $updaterDir . '/.step' );
2022-07-25 12:12:34 +02:00
if ( $result === false ) {
2016-06-25 19:15:37 +02:00
throw new \Exception ( 'Could not create .step' );
}
}
2016-06-26 00:50:47 +02:00
$result = file_put_contents ( $updaterDir . '/.step' , json_encode ([ 'state' => $state , 'step' => $step ]));
2022-07-25 12:12:34 +02:00
if ( $result === false ) {
2016-06-25 19:15:37 +02:00
throw new \Exception ( 'Could not write to .step' );
}
}
/**
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-25 19:15:37 +02:00
*/
2023-06-12 17:17:54 +02:00
public function startStep ( int $step ) : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] startStep("' . $step . '")' );
2016-06-25 19:15:37 +02:00
$this -> writeStep ( 'start' , $step );
}
/**
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-25 19:15:37 +02:00
*/
2023-06-12 17:17:54 +02:00
public function endStep ( int $step ) : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] endStep("' . $step . '")' );
2016-06-25 19:15:37 +02:00
$this -> writeStep ( 'end' , $step );
}
/**
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-25 19:15:37 +02:00
*/
2023-06-12 17:17:54 +02:00
public function currentStep () : array {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] currentStep()' );
2023-06-12 17:17:54 +02:00
$updaterDir = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' );
if ( ! file_exists ( $updaterDir . '/.step' )) {
return [];
}
2016-06-25 19:15:37 +02:00
2023-06-12 17:17:54 +02:00
$state = file_get_contents ( $updaterDir . '/.step' );
if ( $state === false ) {
throw new \Exception ( 'Could not read from .step' );
}
$jsonData = json_decode ( $state , true );
if ( ! is_array ( $jsonData )) {
throw new \Exception ( 'Can\'t decode .step JSON data' );
2016-06-25 19:15:37 +02:00
}
2023-06-12 17:17:54 +02:00
2016-06-25 19:15:37 +02:00
return $jsonData ;
2016-06-24 00:24:51 +02:00
}
2016-06-25 19:43:34 +02:00
2023-08-03 16:59:44 +02:00
public function getUpdateStepFileLocation () : string {
2023-05-04 13:03:22 +02:00
$updaterDir = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOption ( 'instanceid' );
return $updaterDir . '/.step' ;
}
2023-05-04 11:37:42 +02:00
2016-06-25 19:43:34 +02:00
/**
* Rollback the changes if $step has failed
*
2016-10-27 18:26:09 +02:00
* @ throws \Exception
2016-06-25 19:43:34 +02:00
*/
2023-06-12 17:17:54 +02:00
public function rollbackChanges ( int $step ) : void {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] rollbackChanges("' . $step . '")' );
2023-06-12 17:17:54 +02:00
$updaterDir = $this -> getUpdateDirectoryLocation () . '/updater-' . $this -> getConfigOptionMandatoryString ( 'instanceid' );
2022-07-25 12:12:34 +02:00
if ( file_exists ( $updaterDir . '/.step' )) {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] unlink .step' );
2016-06-25 19:43:34 +02:00
$state = unlink ( $updaterDir . '/.step' );
if ( $state === false ) {
throw new \Exception ( 'Could not delete .step' );
}
}
2022-07-25 12:12:34 +02:00
if ( $step >= 7 ) {
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] rollbackChanges - step >= 7' );
2016-06-25 19:43:34 +02:00
// TODO: If it fails after step 7: Rollback
}
2016-09-22 00:12:31 +02:00
$this -> silentLog ( '[info] end of rollbackChanges()' );
}
/**
* Logs an exception with current datetime prepended to updater . log
*
* @ throws LogException
*/
2023-06-12 17:17:54 +02:00
public function logException ( \Exception $e ) : void {
2016-09-22 00:12:31 +02:00
$message = '[error] ' ;
$message .= 'Exception: ' . get_class ( $e ) . PHP_EOL ;
$message .= 'Message: ' . $e -> getMessage () . PHP_EOL ;
$message .= 'Code:' . $e -> getCode () . PHP_EOL ;
$message .= 'Trace:' . PHP_EOL . $e -> getTraceAsString () . PHP_EOL ;
$message .= 'File:' . $e -> getFile () . PHP_EOL ;
$message .= 'Line:' . $e -> getLine () . PHP_EOL ;
2022-07-25 12:12:34 +02:00
if ( $e instanceof UpdateException ) {
2016-09-22 00:12:31 +02:00
$message .= 'Data:' . PHP_EOL . print_r ( $e -> getData (), true ) . PHP_EOL ;
}
$this -> log ( $message );
}
/**
* Logs a message with current datetime prepended to updater . log
*
* @ throws LogException
*/
2023-06-12 17:17:54 +02:00
public function log ( string $message ) : void {
2022-07-02 08:33:28 +02:00
$updaterLogPath = $this -> getUpdateDirectoryLocation () . '/updater.log' ;
2016-09-22 00:12:31 +02:00
$fh = fopen ( $updaterLogPath , 'a' );
2022-07-25 12:12:34 +02:00
if ( $fh === false ) {
2016-10-27 18:26:09 +02:00
throw new LogException ( 'Could not open updater.log' );
2016-09-22 00:12:31 +02:00
}
2022-07-25 12:12:34 +02:00
if ( $this -> requestID === null ) {
2016-09-22 00:38:07 +02:00
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ;
$charactersLength = strlen ( $characters );
$randomString = '' ;
for ( $i = 0 ; $i < 10 ; $i ++ ) {
$randomString .= $characters [ rand ( 0 , $charactersLength - 1 )];
}
$this -> requestID = $randomString ;
}
$logLine = date ( \DateTime :: ISO8601 ) . ' ' . $this -> requestID . ' ' . $message . PHP_EOL ;
2016-09-22 00:12:31 +02:00
$result = fwrite ( $fh , $logLine );
2022-07-25 12:12:34 +02:00
if ( $result === false ) {
2016-10-27 18:26:09 +02:00
throw new LogException ( 'Could not write to updater.log' );
2016-09-22 00:12:31 +02:00
}
fclose ( $fh );
}
/**
* Logs a message with current datetime prepended to updater . log but drops possible LogException
*/
2023-06-12 17:17:54 +02:00
public function silentLog ( string $message ) : void {
2016-09-22 00:12:31 +02:00
try {
$this -> log ( $message );
} catch ( LogException $logE ) {
/* ignore log exception here (already detected later anyways) */
}
2016-06-25 19:43:34 +02:00
}
2016-09-22 00:38:07 +02:00
/**
* Logs current version
*/
2023-06-12 17:17:54 +02:00
public function logVersion () : void {
2016-09-22 00:38:07 +02:00
$this -> silentLog ( '[info] current version: ' . $this -> currentVersion . ' build time: ' . $this -> buildTime );
}
2016-06-24 00:24:51 +02:00
}
2022-08-04 13:20:26 +02:00
class Auth {
2023-06-12 17:17:54 +02:00
public function __construct (
private Updater $updater ,
private string $password ,
) {
2022-08-04 13:20:26 +02:00
$this -> updater = $updater ;
$this -> password = $password ;
}
/**
* Whether the current user is authenticated
*/
2023-06-12 17:17:54 +02:00
public function isAuthenticated () : bool {
$storedHash = $this -> updater -> getConfigOptionString ( 'updater.secret' );
2022-08-04 13:20:26 +02:00
2023-06-12 17:17:54 +02:00
// As a sanity check the stored hash can never be empty
if ( $storedHash === '' || $storedHash === null ) {
2022-08-04 13:20:26 +02:00
return false ;
}
2023-06-12 17:17:54 +02:00
return password_verify ( $this -> password , $storedHash );
2022-08-04 13:20:26 +02:00
}
}
2016-09-22 00:12:31 +02:00
ini_set ( 'display_errors' , '0' );
ini_set ( 'log_errors' , '1' );
2016-06-24 00:24:51 +02:00
// Check if the config.php is at the expected place
try {
2016-11-03 17:33:58 +01:00
$updater = new Updater ( __DIR__ );
2021-02-17 09:37:18 +01:00
if ( $updater -> isDisabled ()) {
http_response_code ( 403 );
die ( 'Updater is disabled, please use the command line' );
}
2016-06-24 00:24:51 +02:00
} catch ( \Exception $e ) {
2016-09-22 00:12:31 +02:00
// logging here is not possible because we don't know the data directory
2021-02-17 09:37:18 +01:00
http_response_code ( 500 );
2016-09-22 00:12:31 +02:00
die ( $e -> getMessage ());
}
// Check if the updater.log can be written to
try {
$updater -> log ( '[info] request to updater' );
} catch ( \Exception $e ) {
2022-07-25 12:12:34 +02:00
if ( isset ( $_POST [ 'step' ])) {
2016-09-22 00:12:31 +02:00
// mark step as failed
http_response_code ( 500 );
echo ( json_encode ([ 'proceed' => false , 'response' => $e -> getMessage ()]));
die ();
}
// show logging error to user
2016-06-24 00:24:51 +02:00
die ( $e -> getMessage ());
}
2016-06-25 13:36:35 +02:00
// Check for authentication
2023-06-12 17:17:54 +02:00
$password = ( $_SERVER [ 'HTTP_X_UPDATER_AUTH' ] ? ? $_POST [ 'updater-secret-input' ] ? ? '' );
if ( ! is_string ( $password )) {
die ( 'Invalid type ' . gettype ( $password ) . ' for password' );
}
2016-06-25 13:36:35 +02:00
$auth = new Auth ( $updater , $password );
2016-06-25 19:15:37 +02:00
// Check if already a step is in process
$currentStep = $updater -> currentStep ();
$stepNumber = 0 ;
2022-07-25 12:12:34 +02:00
if ( $currentStep !== []) {
2023-06-12 17:17:54 +02:00
$stepState = ( string ) $currentStep [ 'state' ];
$stepNumber = ( int ) $currentStep [ 'step' ];
2016-09-22 00:12:31 +02:00
$updater -> log ( '[info] Step ' . $stepNumber . ' is in state "' . $stepState . '".' );
2016-06-25 19:15:37 +02:00
2022-07-25 12:12:34 +02:00
if ( $stepState === 'start' ) {
2016-06-25 19:15:37 +02:00
die (
2022-07-25 12:12:34 +02:00
sprintf (
2023-06-12 17:17:54 +02:00
'Step %d is currently in process. Please reload this page later or remove the following file to start from scratch: %s' ,
2023-05-04 11:37:42 +02:00
$stepNumber ,
2023-08-03 16:59:44 +02:00
$updater -> getUpdateStepFileLocation ()
2016-06-25 19:15:37 +02:00
)
);
}
}
2023-06-12 17:17:54 +02:00
if ( isset ( $_POST [ 'step' ]) && ! is_array ( $_POST [ 'step' ])) {
2016-09-22 00:12:31 +02:00
$updater -> log ( '[info] POST request for step "' . $_POST [ 'step' ] . '"' );
2016-06-24 00:24:51 +02:00
set_time_limit ( 0 );
2016-06-24 09:27:16 +02:00
try {
2022-07-25 12:12:34 +02:00
if ( ! $auth -> isAuthenticated ()) {
2016-10-27 18:26:09 +02:00
throw new \Exception ( 'Not authenticated' );
2016-06-25 13:36:35 +02:00
}
2016-06-25 18:39:55 +02:00
$step = ( int ) $_POST [ 'step' ];
2022-07-25 12:12:34 +02:00
if ( $step > 12 || $step < 1 ) {
2016-10-27 18:26:09 +02:00
throw new \Exception ( 'Invalid step' );
2016-06-25 18:39:55 +02:00
}
2016-06-25 19:15:37 +02:00
$updater -> startStep ( $step );
2016-06-25 18:39:55 +02:00
switch ( $step ) {
case 1 :
2016-06-24 09:27:16 +02:00
$updater -> checkForExpectedFilesAndFolders ();
break ;
2016-06-25 18:39:55 +02:00
case 2 :
2016-06-24 09:27:16 +02:00
$updater -> checkWritePermissions ();
break ;
2016-06-25 18:39:55 +02:00
case 3 :
2018-06-14 16:09:43 +02:00
$updater -> createBackup ();
2016-06-24 09:27:16 +02:00
break ;
2016-06-25 18:39:55 +02:00
case 4 :
2018-06-14 16:09:43 +02:00
$updater -> downloadUpdate ();
2016-06-24 09:27:16 +02:00
break ;
2016-06-25 18:39:55 +02:00
case 5 :
2018-06-14 16:09:43 +02:00
$updater -> verifyIntegrity ();
2016-06-24 09:27:16 +02:00
break ;
2016-06-25 18:39:55 +02:00
case 6 :
2018-06-14 16:09:43 +02:00
$updater -> extractDownload ();
2016-06-24 09:27:16 +02:00
break ;
2016-06-25 18:39:55 +02:00
case 7 :
2018-06-14 16:09:43 +02:00
$updater -> setMaintenanceMode ( true );
2016-06-24 09:27:16 +02:00
break ;
2016-06-25 18:39:55 +02:00
case 8 :
2017-04-16 21:08:50 +02:00
$updater -> replaceEntryPoints ();
2016-06-24 09:27:16 +02:00
break ;
2016-06-25 18:39:55 +02:00
case 9 :
2017-04-16 21:08:50 +02:00
$updater -> deleteOldFiles ();
2016-06-25 15:53:35 +02:00
break ;
2016-06-25 18:39:55 +02:00
case 10 :
2017-04-16 21:08:50 +02:00
$updater -> moveNewVersionInPlace ();
2016-06-24 09:27:16 +02:00
break ;
2016-06-25 18:39:55 +02:00
case 11 :
2017-04-16 21:08:50 +02:00
$updater -> setMaintenanceMode ( false );
break ;
case 12 :
2016-06-25 18:06:07 +02:00
$updater -> finalize ();
break ;
2016-06-24 09:27:16 +02:00
}
2016-06-25 19:43:34 +02:00
$updater -> endStep ( $step );
2016-06-24 09:27:16 +02:00
echo ( json_encode ([ 'proceed' => true ]));
} catch ( UpdateException $e ) {
2023-06-12 17:17:54 +02:00
$data = $e -> getData ();
2016-09-22 00:12:31 +02:00
try {
$updater -> log ( '[error] POST request failed with UpdateException' );
$updater -> logException ( $e );
} catch ( LogException $logE ) {
2023-06-12 17:17:54 +02:00
$data [] = ' (and writing to log failed also with: ' . $logE -> getMessage () . ')' ;
2016-09-22 00:12:31 +02:00
}
2022-07-25 12:12:34 +02:00
if ( isset ( $step )) {
2016-09-22 00:12:31 +02:00
$updater -> rollbackChanges ( $step );
}
2016-06-26 19:53:58 +02:00
http_response_code ( 500 );
2023-06-12 17:17:54 +02:00
echo ( json_encode ([ 'proceed' => false , 'response' => $data ]));
2016-06-24 09:27:16 +02:00
} catch ( \Exception $e ) {
2016-09-22 00:12:31 +02:00
$message = $e -> getMessage ();
try {
$updater -> log ( '[error] POST request failed with other exception' );
$updater -> logException ( $e );
} catch ( LogException $logE ) {
$message .= ' (and writing to log failed also with: ' . $logE -> getMessage () . ')' ;
}
2022-07-25 12:12:34 +02:00
if ( isset ( $step )) {
2016-09-22 00:12:31 +02:00
$updater -> rollbackChanges ( $step );
}
2016-06-26 19:53:58 +02:00
http_response_code ( 500 );
2016-09-22 00:12:31 +02:00
echo ( json_encode ([ 'proceed' => false , 'response' => $message ]));
2016-06-24 00:24:51 +02:00
}
die ();
}
2016-06-25 18:06:07 +02:00
2016-09-22 00:12:31 +02:00
$updater -> log ( '[info] show HTML page' );
2016-09-22 00:38:07 +02:00
$updater -> logVersion ();
2016-06-24 00:24:51 +02:00
?>
< html >
2016-06-24 16:34:10 +02:00
< head >
< style >
2016-06-25 11:56:11 +02:00
html , body , div , span , object , iframe , h1 , h2 , h3 , h4 , h5 , h6 , p , blockquote , pre , a , abbr , acronym , address , code , del , dfn , em , img , q , dl , dt , dd , ol , ul , li , fieldset , form , label , legend , table , caption , tbody , tfoot , thead , tr , th , td , article , aside , dialog , figure , footer , header , nav , section {
2016-06-24 16:34:10 +02:00
margin : 0 ;
padding : 0 ;
border : 0 ;
outline : 0 ;
font - weight : inherit ;
font - size : 100 % ;
font - family : inherit ;
vertical - align : baseline ;
cursor : default ;
}
body {
font - family : 'Open Sans' , Frutiger , Calibri , 'Myriad Pro' , Myriad , sans - serif ;
background - color : #ffffff;
font - weight : 400 ;
font - size : . 8 em ;
line - height : 1.6 em ;
color : #000;
height : auto ;
}
a {
border : 0 ;
color : #000;
text - decoration : none ;
cursor : pointer ;
}
2018-11-27 15:24:01 +01:00
. external_link {
text - decoration : underline ;
}
2016-06-24 16:34:10 +02:00
ul {
list - style : none ;
}
2016-06-25 11:56:11 +02:00
. output ul {
list - style : initial ;
padding : 0 30 px ;
}
2016-06-24 16:34:10 +02:00
#header {
position : fixed ;
top : 0 ;
left : 0 ;
right : 0 ;
height : 45 px ;
line - height : 2.5 em ;
background - color : #0082c9;
box - sizing : border - box ;
}
. header - appname {
color : #fff;
font - size : 20 px ;
font - weight : 300 ;
line - height : 45 px ;
padding : 0 ;
margin : 0 ;
display : inline - block ;
position : absolute ;
margin - left : 5 px ;
}
#header svg {
margin : 5 px ;
}
#content-wrapper {
position : absolute ;
height : 100 % ;
width : 100 % ;
overflow - x : hidden ;
padding - top : 45 px ;
box - sizing : border - box ;
}
#content {
position : relative ;
height : 100 % ;
margin : 0 auto ;
}
#app-navigation {
width : 250 px ;
height : 100 % ;
float : left ;
box - sizing : border - box ;
background - color : #fff;
padding - bottom : 44 px ;
- webkit - user - select : none ;
- moz - user - select : none ;
- ms - user - select : none ;
user - select : none ;
border - right : 1 px solid #eee;
}
#app-navigation > ul {
position : relative ;
height : 100 % ;
width : inherit ;
overflow : auto ;
box - sizing : border - box ;
}
#app-navigation li {
position : relative ;
width : 100 % ;
box - sizing : border - box ;
}
#app-navigation li > a {
display : block ;
width : 100 % ;
line - height : 44 px ;
min - height : 44 px ;
padding : 0 12 px ;
overflow : hidden ;
box - sizing : border - box ;
white - space : nowrap ;
text - overflow : ellipsis ;
color : #000;
opacity : . 57 ;
}
#app-navigation li:hover > a, #app-navigation li:focus > a {
opacity : 1 ;
}
#app-content {
position : relative ;
height : 100 % ;
overflow - y : auto ;
}
#progress {
width : 600 px ;
}
. section {
padding : 25 px 30 px ;
}
. hidden {
display : none ;
}
2016-09-14 17:10:11 +02:00
li . step , . light {
2016-06-24 16:34:10 +02:00
- ms - filter : " progid:DXImageTransform.Microsoft.Alpha(Opacity=57) " ;
opacity : . 57 ;
}
li . step h2 {
padding : 5 px 2 px 5 px 30 px ;
margin - top : 12 px ;
margin - bottom : 0 ;
- ms - filter : " progid:DXImageTransform.Microsoft.Alpha(Opacity=57) " ;
opacity : . 57 ;
background - position : 8 px 50 % ;
background - repeat : no - repeat ;
}
2016-06-25 15:53:35 +02:00
li . current - step , li . passed - step , li . failed - step , li . waiting - step {
2016-06-24 16:34:10 +02:00
- ms - filter : " progid:DXImageTransform.Microsoft.Alpha(Opacity=100) " ;
opacity : 1 ;
}
2018-06-14 16:01:27 +02:00
. current - step {
background - repeat : no - repeat ;
background - position : center ;
min - width : 16 px ;
min - height : 16 px ;
position : relative ;
}
. current - step : after {
z - index : 2 ;
content : '' ;
height : 12 px ;
width : 12 px ;
margin : - 8 px 0 0 - 8 px ;
position : absolute ;
top : 14 px ;
left : 16 px ;
border - radius : 100 % ;
- webkit - animation : rotate . 8 s infinite linear ;
animation : rotate . 8 s infinite linear ;
- webkit - transform - origin : center ;
- ms - transform - origin : center ;
transform - origin : center ;
border : 2 px solid rgba ( 150 , 150 , 150 , 0.5 );
border - top - color : #969696;
}
@ keyframes rotate {
from {
transform : rotate ( 0 deg );
}
to {
transform : rotate ( 360 deg );
}
2016-06-24 16:34:10 +02:00
}
2016-06-25 15:53:35 +02:00
li . current - step h2 , li . passed - step h2 , li . failed - step h2 , li . waiting - step h2 {
2016-06-24 16:34:10 +02:00
- ms - filter : " progid:DXImageTransform.Microsoft.Alpha(Opacity=100) " ;
opacity : 1 ;
}
li . passed - step h2 {
2018-06-14 16:01:27 +02:00
background - image : url ( data : image / svg + xml ; base64 , PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMTYgMTYiPjxwYXRoIGQ9Im0yLjM1IDcuMyA0IDRsNy4zLTcuMyIgc3Ryb2tlPSIjNDZiYTYxIiBzdHJva2Utd2lkdGg9IjIiIGZpbGw9Im5vbmUiLz48L3N2Zz4NCg == );
2016-06-24 16:34:10 +02:00
}
2016-06-25 11:56:11 +02:00
li . failed - step {
background - color : #ffd4d4;
2018-06-14 16:01:27 +02:00
border - radius : 3 px ;
2016-06-25 11:56:11 +02:00
}
2016-06-24 16:34:10 +02:00
li . failed - step h2 {
2016-06-25 11:56:11 +02:00
color : #000;
2018-06-14 16:01:27 +02:00
background - image : url ( data : image / svg + xml ; base64 , PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTYiIHdpZHRoPSIxNiIgdmVyc2lvbj0iMS4xIiB2aWV3Ym94PSIwIDAgMTYgMTYiPjxwYXRoIGQ9Im0xNCAxMi4zLTEuNyAxLjctNC4zLTQuMy00LjMgNC4zLTEuNy0xLjcgNC4zLTQuMy00LjMtNC4zIDEuNy0xLjcgNC4zIDQuMyA0LjMtNC4zIDEuNyAxLjctNC4zIDQuM3oiIGZpbGw9IiNkNDAwMDAiLz48L3N2Zz4NCg == );
2016-06-24 16:34:10 +02:00
}
li . step . output {
position : relative ;
padding : 5 px 5 px 5 px 32 px ;
}
h2 {
font - size : 20 px ;
font - weight : 300 ;
margin - bottom : 12 px ;
color : #555;
}
2016-06-25 15:53:35 +02:00
button , a . button {
2016-06-25 13:17:23 +02:00
font - family : 'Open Sans' , Frutiger , Calibri , 'Myriad Pro' , Myriad , sans - serif ;
font - size : 13 px ;
font - weight : 600 ;
2018-06-14 16:01:27 +02:00
color : #545454;
2016-06-25 13:17:23 +02:00
margin : 3 px 3 px 3 px 0 ;
2018-06-14 16:01:27 +02:00
padding : 6 px 12 px ;
background - color : #f7f7f7;
2016-06-25 13:17:23 +02:00
border - radius : 3 px ;
2018-06-14 16:01:27 +02:00
border : 1 px solid #dbdbdb;
2016-06-25 13:17:23 +02:00
cursor : pointer ;
outline : none ;
2018-06-14 16:01:27 +02:00
min - height : 34 px ;
box - sizing : border - box ;
2016-06-25 13:17:23 +02:00
}
2016-06-25 15:53:35 +02:00
button : hover , button : focus , a . button : hover , a . button : focus {
2018-06-14 16:01:27 +02:00
border - color : #0082c9;
2016-06-25 13:17:23 +02:00
}
2016-06-26 00:53:04 +02:00
code {
2016-06-25 15:53:35 +02:00
font - family : monospace ;
font - size : 1.2 em ;
background - color : #eee;
border - radius : 2 px ;
padding : 2 px 6 px 2 px 4 px ;
2016-06-25 13:36:35 +02:00
}
2016-06-26 00:53:04 +02:00
#login code {
display : block ;
border - radius : 3 px ;
}
#login form {
margin - top : 5 px ;
}
#login input {
border - radius : 3 px ;
border : 1 px solid rgba ( 240 , 240 , 240 , . 9 );
2018-11-27 10:43:01 +01:00
margin : 3 px 3 px 3 px 0 ;
padding : 9 px 6 px ;
font - size : 13 px ;
outline : none ;
cursor : text ;
2016-06-26 00:53:04 +02:00
}
2017-08-31 14:09:42 +02:00
. section {
max - width : 600 px ;
margin : 0 auto ;
}
2020-09-15 17:36:51 +02:00
pre {
word - wrap : break - word ;
}
2016-06-24 16:34:10 +02:00
</ style >
</ head >
2016-06-24 00:24:51 +02:00
< body >
2016-06-24 16:34:10 +02:00
< div id = " header " >
< svg xmlns = " http://www.w3.org/2000/svg " version = " 1.1 " xml : space = " preserve " height = " 34 " width = " 62 " enable - background = " new 0 0 196.6 72 " y = " 0px " x = " 0px " viewBox = " 0 0 62.000002 34 " >< path style = " color-rendering:auto;text-decoration-color:#000000;color:#000000;isolation:auto;mix-blend-mode:normal;shape-rendering:auto;solid-color:#000000;block-progression:tb;text-decoration-line:none;image-rendering:auto;white-space:normal;text-indent:0;enable-background:accumulate;text-transform:none;text-decoration-style:solid " fill = " #fff " d = " m31.6 4.0001c-5.95 0.0006-10.947 4.0745-12.473 9.5549-1.333-2.931-4.266-5.0088-7.674-5.0092-4.6384 0.0005-8.4524 3.8142-8.453 8.4532-0.0008321 4.6397 3.8137 8.4544 8.4534 8.455 3.4081-0.000409 6.3392-2.0792 7.6716-5.011 1.5261 5.4817 6.5242 9.5569 12.475 9.5569 5.918 0.000457 10.89-4.0302 12.448-9.4649 1.3541 2.8776 4.242 4.9184 7.6106 4.9188 4.6406 0.000828 8.4558-3.8144 8.4551-8.455-0.000457-4.6397-3.8154-8.454-8.4551-8.4533-3.3687 0.0008566-6.2587 2.0412-7.6123 4.9188-1.559-5.4338-6.528-9.4644-12.446-9.464zm0 4.9623c4.4687-0.000297 8.0384 3.5683 8.0389 8.0371 0.000228 4.4693-3.5696 8.0391-8.0389 8.0388-4.4687-0.000438-8.0375-3.5701-8.0372-8.0388 0.000457-4.4682 3.5689-8.0366 8.0372-8.0371zm-20.147 4.5456c1.9576 0.000226 3.4908 1.5334 3.4911 3.491 0.000343 1.958-1.533 3.4925-3.4911 3.4927-1.958-0.000228-3.4913-1.5347-3.4911-3.4927 0.0002284-1.9575 1.5334-3.4907 3.4911-3.491zm40.205 0c1.9579-0.000343 3.4925 1.533 3.4927 3.491 0.000457 1.9584-1.5343 3.493-3.4927 3.4927-1.958-0.000228-3.4914-1.5347-3.4911-3.4927 0.000221-1.9575 1.5335-3.4907 3.4911-3.491z " /></ svg >
2016-10-04 16:40:32 +02:00
< h1 class = " header-appname " > Updater </ h1 >
2016-06-24 16:34:10 +02:00
</ div >
2016-06-25 13:36:35 +02:00
< input type = " hidden " id = " updater-access-key " value = " <?php echo htmlentities( $password ) ?> " />
2016-06-25 19:15:37 +02:00
< input type = " hidden " id = " updater-step-start " value = " <?php echo $stepNumber ?> " />
2016-06-24 16:34:10 +02:00
< div id = " content-wrapper " >
< div id = " content " >
< div id = " app-content " >
2022-07-25 12:12:34 +02:00
< ? php if ( $auth -> isAuthenticated ()) : ?>
2016-06-24 16:34:10 +02:00
< ul id = " progress " class = " section " >
< li id = " step-init " class = " step icon-loading passed-step " >
< h2 > Initializing </ h2 >
2016-06-25 11:49:37 +02:00
< div class = " output " > Current version is < ? php echo ( $updater -> getCurrentVersion ()); ?> .<br>
< ? php echo ( $updater -> checkForUpdate ()); ?> <br>
2016-06-25 13:17:23 +02:00
< ? php
2016-06-26 00:51:51 +02:00
if ( $updater -> updateAvailable () || $stepNumber > 0 ) {
2016-06-25 19:15:37 +02:00
$buttonText = 'Start update' ;
2022-07-25 12:12:34 +02:00
if ( $stepNumber > 0 ) {
2016-06-25 19:15:37 +02:00
$buttonText = 'Continue update' ;
2022-07-25 12:12:34 +02:00
} ?>
2016-06-25 19:15:37 +02:00
< button id = " startUpdateButton " >< ? php echo $buttonText ?> </button>
2016-06-25 13:17:23 +02:00
< ? php
}
2022-07-25 12:12:34 +02:00
?>
2016-09-26 16:25:30 +02:00
< button id = " retryUpdateButton " class = " hidden " > Retry update </ button >
2016-06-25 13:17:23 +02:00
</ div >
2016-06-24 16:34:10 +02:00
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-check-files " class = " step <?php if ( $stepNumber >= 1) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Check for expected files </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-check-permissions " class = " step <?php if ( $stepNumber >= 2) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Check for write permissions </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-backup " class = " step <?php if ( $stepNumber >= 3) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Create backup </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-download " class = " step <?php if ( $stepNumber >= 4) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Downloading </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-verify-integrity " class = " step <?php if ( $stepNumber >= 5) {
echo 'passed-step' ;
} ?> ">
2017-04-16 21:08:50 +02:00
< h2 > Verifying integrity </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-extract " class = " step <?php if ( $stepNumber >= 6) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Extracting </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-enable-maintenance " class = " step <?php if ( $stepNumber >= 7) {
echo 'passed-step' ;
} ?> ">
2018-06-14 16:09:43 +02:00
< h2 > Enable maintenance mode </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-entrypoints " class = " step <?php if ( $stepNumber >= 8) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Replace entry points </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-delete " class = " step <?php if ( $stepNumber >= 9) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Delete old files </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-move " class = " step <?php if ( $stepNumber >= 10) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Move new files in place </ h2 >
< div class = " output hidden " ></ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-maintenance-mode " class = " step <?php if ( $stepNumber >= 11) {
echo 'passed-step' ;
} ?> ">
2021-03-24 10:26:15 +01:00
< h2 > Continue with web based updater </ h2 >
2016-06-25 15:53:35 +02:00
< div class = " output hidden " >
2021-03-24 10:26:15 +01:00
< button id = " maintenance-disable " > Disable maintenance mode and continue in the web based updater </ button >
2016-06-25 15:53:35 +02:00
</ div >
</ li >
2022-07-25 12:12:34 +02:00
< li id = " step-done " class = " step <?php if ( $stepNumber >= 12) {
echo 'passed-step' ;
} ?> ">
2016-06-24 16:34:10 +02:00
< h2 > Done </ h2 >
2016-06-25 15:53:35 +02:00
< div class = " output hidden " >
2023-06-07 09:00:51 +02:00
< a id = " back-to-nextcloud " class = " button " > Go back to your Nextcloud instance to finish the update </ a >
2016-06-25 15:53:35 +02:00
</ div >
2016-06-24 16:34:10 +02:00
</ li >
</ ul >
2023-06-07 09:00:51 +02:00
< ? php else : ?>
2016-06-26 00:53:04 +02:00
< div id = " login " class = " section " >
< h2 > Authentication </ h2 >
< p > To login you need to provide the unhashed value of " updater.secret " in your config file .</ p >
< p > If you don ' t know that value , you can access this updater directly via the Nextcloud admin screen or generate
your own secret :</ p >
2023-06-07 09:00:51 +02:00
< code > php - r '$password = trim(shell_exec("openssl rand -base64 48"));if(strlen($password) === 64) {$hash = password_hash($password, PASSWORD_DEFAULT) . "\n"; echo "Insert as \"updater.secret\": ".$hash; echo "The plaintext value is: ".$password."\n";}else{echo "Could not execute OpenSSL.\n";};' </ code >
2016-06-26 00:53:04 +02:00
< form method = " post " name = " login " >
< fieldset >
2018-11-27 10:43:01 +01:00
< input type = " password " name = " updater-secret-input " value = " "
2016-06-26 00:53:04 +02:00
placeholder = " Secret "
autocomplete = " on " required >
< button id = " updater-secret-submit " > Login </ button >
</ fieldset >
</ form >
2022-07-25 12:12:34 +02:00
< ? php if ( isset ( $_POST [ 'updater-secret-input' ]) && ! $auth -> isAuthenticated ()) : ?>
2016-06-26 00:53:04 +02:00
< p > Invalid password </ p >
< ? php endif ; ?>
</ div >
2016-06-25 13:36:35 +02:00
< ? php endif ; ?>
2016-06-24 16:34:10 +02:00
</ div >
</ div >
</ div >
2016-06-24 00:24:51 +02:00
</ body >
2023-06-07 09:00:51 +02:00
< ? php if ( $auth -> isAuthenticated ()) : ?>
2016-06-24 00:24:51 +02:00
< script >
2023-06-07 09:00:51 +02:00
var nextcloudUrl = window . location . href . replace ( 'updater/' , '' ) . replace ( 'index.php' , '' );
var backToButton = document . getElementById ( 'back-to-nextcloud' );
if ( backToButton ) {
backToButton . href = nextcloudUrl ;
}
2016-09-22 18:15:27 +02:00
function escapeHTML ( s ) {
return s . toString () . split ( '&' ) . join ( '&' ) . split ( '<' ) . join ( '<' ) . split ( '>' ) . join ( '>' ) . split ( '"' ) . join ( '"' ) . split ( '\'' ) . join ( ''' );
}
2016-06-25 18:23:35 +02:00
var done = false ;
var started = false ;
2016-06-25 19:15:37 +02:00
var updaterStepStart = parseInt ( document . getElementById ( 'updater-step-start' ) . value );
2016-09-26 16:25:30 +02:00
var elementId = false ;
2016-06-25 11:58:34 +02:00
function addStepText ( id , text ) {
var el = document . getElementById ( id );
2016-09-26 16:25:30 +02:00
var output = el . getElementsByClassName ( 'output' )[ 0 ];
2016-06-25 11:58:34 +02:00
if ( typeof text === 'object' ) {
text = JSON . stringify ( text );
}
2016-06-25 15:53:35 +02:00
output . innerHTML = output . innerHTML + text ;
2016-06-25 11:58:34 +02:00
output . classList . remove ( 'hidden' );
2016-06-24 00:24:51 +02:00
}
2016-09-26 16:25:30 +02:00
function removeStepText ( id ) {
var el = document . getElementById ( id );
var output = el . getElementsByClassName ( 'output' )[ 0 ];
output . innerHTML = '' ;
output . classList . add ( 'hidden' );
}
2016-06-24 00:24:51 +02:00
2016-06-24 16:34:10 +02:00
function currentStep ( id ) {
var el = document . getElementById ( id );
el . classList . remove ( 'failed-step' );
el . classList . remove ( 'passed-step' );
2016-06-25 15:53:35 +02:00
el . classList . remove ( 'waiting-step' );
2016-06-24 16:34:10 +02:00
el . classList . add ( 'current-step' );
}
2016-09-26 16:25:30 +02:00
function errorStep ( id , numericId ) {
2016-06-24 16:34:10 +02:00
var el = document . getElementById ( id );
el . classList . remove ( 'passed-step' );
el . classList . remove ( 'current-step' );
2016-06-25 15:53:35 +02:00
el . classList . remove ( 'waiting-step' );
2016-06-24 16:34:10 +02:00
el . classList . add ( 'failed-step' );
2016-09-26 16:25:30 +02:00
// set start step to previous one
updaterStepStart = numericId - 1 ;
elementId = id ;
// show restart button
var button = document . getElementById ( 'retryUpdateButton' );
button . classList . remove ( 'hidden' );
2016-06-24 16:34:10 +02:00
}
function successStep ( id ) {
var el = document . getElementById ( id );
el . classList . remove ( 'failed-step' );
el . classList . remove ( 'current-step' );
2016-06-25 15:53:35 +02:00
el . classList . remove ( 'waiting-step' );
2016-06-24 16:34:10 +02:00
el . classList . add ( 'passed-step' );
}
2016-06-25 15:53:35 +02:00
function waitingStep ( id ) {
var el = document . getElementById ( id );
el . classList . remove ( 'failed-step' );
el . classList . remove ( 'current-step' );
el . classList . remove ( 'passed-step' );
el . classList . add ( 'waiting-step' );
}
2016-06-24 10:14:22 +02:00
function performStep ( number , callback ) {
2016-06-25 18:23:35 +02:00
started = true ;
2016-06-24 00:24:51 +02:00
var httpRequest = new XMLHttpRequest ();
2023-06-07 09:00:51 +02:00
httpRequest . open ( 'POST' , window . location . href );
2016-06-25 13:36:35 +02:00
httpRequest . setRequestHeader ( 'Content-type' , 'application/x-www-form-urlencoded' );
httpRequest . setRequestHeader ( 'X-Updater-Auth' , document . getElementById ( 'updater-access-key' ) . value );
2016-06-24 10:14:22 +02:00
httpRequest . onreadystatechange = function () {
if ( httpRequest . readyState != 4 ) { // 4 - request done
return ;
}
if ( httpRequest . status != 200 ) {
// failure
}
2016-06-25 11:57:36 +02:00
if ( httpRequest . responseText . substr ( 0 , 1 ) !== '{' ) {
// it seems that this is not a JSON object
var response = {
processed : false ,
2020-09-15 17:36:51 +02:00
response : 'Parsing response failed.' ,
detailedResponseText : httpRequest . responseText ,
2016-06-25 11:57:36 +02:00
};
callback ( response );
} else {
// parse JSON
callback ( JSON . parse ( httpRequest . responseText ));
}
2016-06-24 10:14:22 +02:00
};
2016-06-24 00:24:51 +02:00
httpRequest . send ( " step= " + number );
}
2016-06-24 10:14:22 +02:00
var performStepCallbacks = {
2016-06-26 00:53:35 +02:00
0 : function () { // placeholder that is called on start of the updater
currentStep ( 'step-check-files' );
performStep ( 1 , performStepCallbacks [ 1 ]);
},
2016-06-24 10:14:22 +02:00
1 : function ( response ) {
if ( response . proceed === true ) {
2016-06-24 16:34:10 +02:00
successStep ( 'step-check-files' );
currentStep ( 'step-check-permissions' );
2016-06-26 00:53:35 +02:00
performStep ( 2 , performStepCallbacks [ 2 ]);
2016-06-24 10:14:22 +02:00
} else {
2016-09-26 16:25:30 +02:00
errorStep ( 'step-check-files' , 1 );
2016-06-25 11:58:34 +02:00
2016-09-22 00:12:31 +02:00
var text = '' ;
if ( typeof response [ 'response' ] === 'string' ) {
2016-09-22 18:15:27 +02:00
text = escapeHTML ( response [ 'response' ]);
2020-09-15 17:36:51 +02:00
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response [ 'detailedResponseText' ]) + '</code></pre></details>' ;
2016-09-22 00:12:31 +02:00
} else {
text = 'The following extra files have been found:<ul>' ;
response [ 'response' ] . forEach ( function ( file ) {
2016-09-22 18:15:27 +02:00
text += '<li>' + escapeHTML ( file ) + '</li>' ;
2016-09-22 00:12:31 +02:00
});
text += '</ul>' ;
}
2016-06-25 11:58:34 +02:00
addStepText ( 'step-check-files' , text );
2016-06-24 10:14:22 +02:00
}
},
2 : function ( response ) {
if ( response . proceed === true ) {
2016-06-24 16:34:10 +02:00
successStep ( 'step-check-permissions' );
2018-06-14 16:09:43 +02:00
currentStep ( 'step-backup' );
2016-06-24 10:14:22 +02:00
performStep ( 3 , performStepCallbacks [ 3 ]);
} else {
2016-09-26 16:25:30 +02:00
errorStep ( 'step-check-permissions' , 2 );
2016-06-25 11:58:34 +02:00
2016-09-22 00:12:31 +02:00
var text = '' ;
if ( typeof response [ 'response' ] === 'string' ) {
2016-09-22 18:15:27 +02:00
text = escapeHTML ( response [ 'response' ]);
2020-09-15 17:36:51 +02:00
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response [ 'detailedResponseText' ]) + '</code></pre></details>' ;
2016-09-22 00:12:31 +02:00
} else {
text = 'The following places can not be written to:<ul>' ;
response [ 'response' ] . forEach ( function ( file ) {
2016-09-22 18:15:27 +02:00
text += '<li>' + escapeHTML ( file ) + '</li>' ;
2016-09-22 00:12:31 +02:00
});
text += '</ul>' ;
}
2016-06-25 11:58:34 +02:00
addStepText ( 'step-check-permissions' , text );
2016-06-24 10:14:22 +02:00
}
},
2018-06-14 16:09:43 +02:00
3 : function ( response ) {
if ( response . proceed === true ) {
successStep ( 'step-backup' );
currentStep ( 'step-download' );
2016-06-24 10:14:22 +02:00
performStep ( 4 , performStepCallbacks [ 4 ]);
2016-06-24 00:24:51 +02:00
} else {
2018-06-14 16:09:43 +02:00
errorStep ( 'step-backup' , 3 );
2016-06-25 11:58:34 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-backup' , text );
2016-06-25 11:58:34 +02:00
}
2016-06-24 00:24:51 +02:00
}
2016-06-24 10:14:22 +02:00
},
2016-06-25 11:58:34 +02:00
4 : function ( response ) {
if ( response . proceed === true ) {
2018-06-14 16:09:43 +02:00
successStep ( 'step-download' );
currentStep ( 'step-verify-integrity' );
2016-06-25 11:58:34 +02:00
performStep ( 5 , performStepCallbacks [ 5 ]);
} else {
2018-07-26 11:57:28 +02:00
errorStep ( 'step-download' , 4 );
2016-06-25 11:58:34 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-download' , text );
2016-06-25 11:58:34 +02:00
}
}
2016-06-24 10:14:22 +02:00
},
2016-06-25 11:58:34 +02:00
5 : function ( response ) {
if ( response . proceed === true ) {
2018-06-14 16:09:43 +02:00
successStep ( 'step-verify-integrity' );
currentStep ( 'step-extract' );
2016-06-25 11:58:34 +02:00
performStep ( 6 , performStepCallbacks [ 6 ]);
} else {
2017-04-16 21:08:50 +02:00
errorStep ( 'step-verify-integrity' , 5 );
2016-06-25 11:58:34 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-verify-integrity' , text );
2016-06-25 11:58:34 +02:00
}
}
2016-06-24 10:14:22 +02:00
},
2016-06-25 11:58:34 +02:00
6 : function ( response ) {
2017-04-16 21:08:50 +02:00
if ( response . proceed === true ) {
2018-06-14 16:09:43 +02:00
successStep ( 'step-extract' );
currentStep ( 'step-enable-maintenance' );
2017-04-16 21:08:50 +02:00
performStep ( 7 , performStepCallbacks [ 7 ]);
} else {
2018-06-14 16:09:43 +02:00
errorStep ( 'step-extract' , 6 );
2017-04-16 21:08:50 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-extract' , text );
2017-04-16 21:08:50 +02:00
}
}
},
7 : function ( response ) {
2016-06-25 11:58:34 +02:00
if ( response . proceed === true ) {
2018-06-14 16:09:43 +02:00
successStep ( 'step-enable-maintenance' );
2016-06-25 11:58:34 +02:00
currentStep ( 'step-entrypoints' );
2017-04-16 21:08:50 +02:00
performStep ( 8 , performStepCallbacks [ 8 ]);
2016-06-25 11:58:34 +02:00
} else {
2018-06-14 16:09:43 +02:00
errorStep ( 'step-enable-maintenance' , 7 );
2016-06-25 11:58:34 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-enable-maintenance' , text );
2016-06-25 11:58:34 +02:00
}
}
2016-06-24 10:14:22 +02:00
},
2017-04-16 21:08:50 +02:00
8 : function ( response ) {
2016-06-25 11:58:34 +02:00
if ( response . proceed === true ) {
successStep ( 'step-entrypoints' );
currentStep ( 'step-delete' );
2017-04-16 21:08:50 +02:00
performStep ( 9 , performStepCallbacks [ 9 ]);
2016-06-25 11:58:34 +02:00
} else {
2017-04-16 21:08:50 +02:00
errorStep ( 'step-entrypoints' , 8 );
2016-06-25 11:58:34 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-entrypoints' , text );
2016-06-25 11:58:34 +02:00
}
}
2016-06-24 10:14:22 +02:00
},
2017-04-16 21:08:50 +02:00
9 : function ( response ) {
2016-06-25 11:58:34 +02:00
if ( response . proceed === true ) {
successStep ( 'step-delete' );
currentStep ( 'step-move' );
2017-04-16 21:08:50 +02:00
performStep ( 10 , performStepCallbacks [ 10 ]);
2016-06-25 11:58:34 +02:00
} else {
2017-04-16 21:08:50 +02:00
errorStep ( 'step-delete' , 9 );
2016-06-25 11:58:34 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-delete' , text );
2016-06-25 11:58:34 +02:00
}
}
2016-06-24 10:14:22 +02:00
},
2017-04-16 21:08:50 +02:00
10 : function ( response ) {
2016-06-25 11:58:34 +02:00
if ( response . proceed === true ) {
successStep ( 'step-move' );
2016-06-25 15:53:35 +02:00
waitingStep ( 'step-maintenance-mode' );
// show buttons to decide on maintenance mode
var el = document . getElementById ( 'step-maintenance-mode' )
. getElementsByClassName ( 'output' )[ 0 ];
el . classList . remove ( 'hidden' );
2016-06-25 11:58:34 +02:00
} else {
2017-04-16 21:08:50 +02:00
errorStep ( 'step-move' , 10 );
2016-06-25 11:58:34 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-move' , text );
2016-06-25 11:58:34 +02:00
}
}
2016-06-25 15:53:35 +02:00
},
2017-04-16 21:08:50 +02:00
11 : function ( response ) {
2016-06-25 15:53:35 +02:00
if ( response . proceed === true ) {
successStep ( 'step-maintenance-mode' );
2016-06-26 00:53:35 +02:00
currentStep ( 'step-done' );
2017-04-16 21:08:50 +02:00
performStep ( 12 , performStepCallbacks [ 12 ]);
2016-06-25 18:06:07 +02:00
} else {
2017-04-16 21:08:50 +02:00
errorStep ( 'step-maintenance-mode' , 11 );
2016-06-25 18:06:07 +02:00
if ( response . response ) {
2020-09-15 17:36:51 +02:00
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-maintenance-mode' , text );
2016-06-25 18:06:07 +02:00
}
}
},
2017-04-16 21:08:50 +02:00
12 : function ( response ) {
2021-03-24 10:26:15 +01:00
done = true ;
window . removeEventListener ( 'beforeunload' , confirmExit );
2016-06-25 18:06:07 +02:00
if ( response . proceed === true ) {
2016-06-25 15:53:35 +02:00
successStep ( 'step-done' );
// show button to get to the web based migration steps
var el = document . getElementById ( 'step-done' )
. getElementsByClassName ( 'output' )[ 0 ];
el . classList . remove ( 'hidden' );
2021-03-24 10:26:15 +01:00
// above is the fallback if the Javascript redirect doesn't work
2023-06-07 09:00:51 +02:00
window . location . href = nextcloudUrl ;
2016-06-25 15:53:35 +02:00
} else {
2021-03-24 10:26:15 +01:00
errorStep ( 'step-done' , 12 );
var text = escapeHTML ( response . response );
text += '<br><details><summary>Show detailed response</summary><pre><code>' +
escapeHTML ( response . detailedResponseText ) + '</code></pre></details>' ;
addStepText ( 'step-done' , text );
2016-06-25 15:53:35 +02:00
}
2016-06-25 18:06:07 +02:00
},
2016-06-24 10:14:22 +02:00
};
2016-06-25 13:17:23 +02:00
function startUpdate () {
2016-06-26 00:53:35 +02:00
performStepCallbacks [ updaterStepStart ]({
proceed : true
});
2016-06-25 11:55:33 +02:00
}
2016-06-25 13:17:23 +02:00
2016-09-26 16:25:30 +02:00
function retryUpdate () {
//remove failed log
if ( elementId !== false ) {
var el = document . getElementById ( elementId );
el . classList . remove ( 'passed-step' );
el . classList . remove ( 'current-step' );
el . classList . remove ( 'waiting-step' );
el . classList . remove ( 'failed-step' );
removeStepText ( elementId );
elementId = false ;
}
// hide restart button
var button = document . getElementById ( 'retryUpdateButton' );
button . classList . add ( 'hidden' );
startUpdate ();
}
2021-03-24 10:26:15 +01:00
function askForMaintenance () {
2016-06-25 15:53:35 +02:00
var el = document . getElementById ( 'step-maintenance-mode' )
. getElementsByClassName ( 'output' )[ 0 ];
2021-03-24 10:26:15 +01:00
el . innerHTML = 'Maintenance mode will get disabled.<br>' ;
currentStep ( 'step-maintenance-mode' );
performStep ( 11 , performStepCallbacks [ 11 ]);
2016-06-25 15:53:35 +02:00
}
2016-06-25 13:36:35 +02:00
if ( document . getElementById ( 'startUpdateButton' )) {
document . getElementById ( 'startUpdateButton' ) . onclick = function ( e ) {
e . preventDefault ();
2016-09-26 16:25:30 +02:00
this . classList . add ( 'hidden' );
2016-06-25 13:36:35 +02:00
startUpdate ();
};
}
2016-09-26 16:25:30 +02:00
if ( document . getElementById ( 'retryUpdateButton' )) {
document . getElementById ( 'retryUpdateButton' ) . onclick = function ( e ) {
e . preventDefault ();
retryUpdate ();
};
}
2016-06-25 18:06:07 +02:00
if ( document . getElementById ( 'maintenance-disable' )) {
document . getElementById ( 'maintenance-disable' ) . onclick = function ( e ) {
e . preventDefault ();
2021-03-24 10:26:15 +01:00
askForMaintenance ();
2016-06-25 18:06:07 +02:00
};
}
2016-06-25 18:23:35 +02:00
// Show a popup when user tries to close page
function confirmExit () {
if ( done === false && started === true ) {
return 'Update is in progress. Are you sure, you want to close?' ;
}
}
2021-03-24 10:26:15 +01:00
// this is unregistered in step 12
window . addEventListener ( 'beforeunload' , confirmExit );
2016-06-25 13:36:35 +02:00
</ script >
2016-06-24 00:24:51 +02:00
< ? php endif ; ?>
2016-01-18 23:35:52 +01:00
2016-06-24 00:24:51 +02:00
</ html >
2023-06-07 09:00:51 +02:00