2021-11-30 11:20:42 +01:00
< ? php
namespace AIO\Data ;
use AIO\Auth\PasswordGenerator ;
use AIO\Controller\DockerController ;
class ConfigurationManager
{
2025-09-09 14:44:23 -07:00
private array $secrets = [];
2026-01-07 17:29:24 +01:00
private array $config = [];
private bool $noWrite = false ;
2026-01-23 17:12:45 +01:00
public string $aioToken {
2026-01-19 12:37:36 +01:00
get => $this -> get ( 'AIO_TOKEN' , '' );
set { $this -> set ( 'AIO_TOKEN' , $value ); }
}
2026-01-19 14:55:29 +01:00
public string $password {
get => $this -> get ( 'password' , '' );
set { $this -> set ( 'password' , $value ); }
}
2026-01-19 12:56:10 +01:00
public bool $isDockerSocketProxyEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'isDockerSocketProxyEnabled' , false );
2026-01-19 12:56:10 +01:00
set { $this -> set ( 'isDockerSocketProxyEnabled' , $value ); }
}
2026-01-19 13:00:22 +01:00
public bool $isWhiteboardEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'isWhiteboardEnabled' , true );
2026-01-19 13:00:22 +01:00
set { $this -> set ( 'isWhiteboardEnabled' , $value ); }
}
2026-01-19 13:02:19 +01:00
public bool $restoreExcludePreviews {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have '1'/'' for this key.
get => ( bool ) $this -> get ( 'restore-exclude-previews' , false );
2026-01-19 13:02:19 +01:00
set { $this -> set ( 'restore-exclude-previews' , $value ); }
}
2026-01-19 13:05:10 +01:00
public string $selectedRestoreTime {
get => $this -> get ( 'selected-restore-time' , '' );
set { $this -> set ( 'selected-restore-time' , $value ); }
}
2026-01-19 12:58:50 +01:00
public string $backupMode {
get => $this -> get ( 'backup-mode' , '' );
set { $this -> set ( 'backup-mode' , $value ); }
}
2026-01-23 17:16:02 +01:00
public bool $instanceRestoreAttempt {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/'' for this key.
get => ( bool ) $this -> get ( 'instance_restore_attempt' , false );
2026-01-19 12:52:03 +01:00
set { $this -> set ( 'instance_restore_attempt' , $value ); }
}
2026-01-23 17:17:54 +01:00
public string $aioUrl {
2026-01-19 13:09:52 +01:00
get => $this -> get ( 'AIO_URL' , '' );
set { $this -> set ( 'AIO_URL' , $value ); }
}
2026-01-19 13:11:20 +01:00
public bool $wasStartButtonClicked {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'wasStartButtonClicked' , false );
2026-01-19 13:11:20 +01:00
set { $this -> set ( 'wasStartButtonClicked' , $value ); }
}
2026-01-23 17:10:21 +01:00
public string $install_latest_major {
// Type-cast because old configs could have integers for this key.
get => ( string ) $this -> get ( 'install_latest_major' , '' );
2026-01-19 13:06:59 +01:00
set { $this -> set ( 'install_latest_major' , $value ); }
}
2026-01-19 12:01:53 +01:00
public bool $isClamavEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'isClamavEnabled' , false );
2026-01-19 12:01:53 +01:00
set { $this -> set ( 'isClamavEnabled' , $value ); }
}
2026-01-19 12:02:41 +01:00
public bool $isOnlyofficeEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'isOnlyofficeEnabled' , false );
2026-01-19 12:02:41 +01:00
set { $this -> set ( 'isOnlyofficeEnabled' , $value ); }
}
2026-01-19 12:03:12 +01:00
public bool $isCollaboraEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'isCollaboraEnabled' , true );
2026-01-19 12:03:12 +01:00
set { $this -> set ( 'isCollaboraEnabled' , $value ); }
}
2026-01-19 12:03:38 +01:00
public bool $isTalkEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'isTalkEnabled' , true );
2026-01-19 12:03:38 +01:00
set { $this -> set ( 'isTalkEnabled' , $value ); }
}
2026-01-19 12:04:04 +01:00
public bool $isTalkRecordingEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> isTalkEnabled && $this -> get ( 'isTalkRecordingEnabled' , false );
2026-01-19 12:04:04 +01:00
set { $this -> set ( 'isTalkRecordingEnabled' , $this -> isTalkEnabled && $value ); }
}
2026-01-19 12:04:24 +01:00
public bool $isImaginaryEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'isImaginaryEnabled' , true );
2026-01-19 12:04:24 +01:00
set { $this -> set ( 'isImaginaryEnabled' , $value ); }
}
2026-01-19 12:04:48 +01:00
public bool $isFulltextsearchEnabled {
2026-01-23 16:55:15 +01:00
// Type-cast because old configs could have 1/0 for this key.
get => ( bool ) $this -> get ( 'isFulltextsearchEnabled' , false );
2026-01-19 12:04:48 +01:00
// Elasticsearch does not work on kernels without seccomp anymore. See https://github.com/nextcloud/all-in-one/discussions/5768
set { $this -> set ( 'isFulltextsearchEnabled' , ( $this -> isSeccompDisabled () && $value )); }
}
2026-01-19 12:06:20 +01:00
public string $domain {
get => $this -> get ( 'domain' , '' );
set { $this -> SetDomain ( $value ); }
}
2026-01-19 12:07:45 +01:00
public string $borg_backup_host_location {
get => $this -> get ( 'borg_backup_host_location' , '' );
set { $this -> set ( 'borg_backup_host_location' , $value ); }
}
2026-01-19 12:14:39 +01:00
public string $borg_remote_repo {
get => $this -> get ( 'borg_remote_repo' , '' );
set { $this -> set ( 'borg_remote_repo' , $value ); }
}
2026-01-19 12:16:30 +01:00
public string $borg_restore_password {
get => $this -> get ( 'borg_restore_password' , '' );
set { $this -> set ( 'borg_restore_password' , $value ); }
}
2026-01-20 12:45:00 +01:00
public string $apache_ip_binding {
get => $this -> GetEnvironmentalVariableOrConfig ( 'APACHE_IP_BINDING' , 'apache_ip_binding' , '' );
set { $this -> set ( 'apache_ip_binding' , $value ); }
}
2026-01-19 12:17:25 +01:00
/**
* @ throws InvalidSettingConfigurationException
*/
public string $timezone {
get => $this -> get ( 'timezone' , '' );
set {
// This throws an exception if the validation fails.
$this -> validateTimezone ( $value );
$this -> set ( 'timezone' , $value );
}
}
2026-01-19 12:18:45 +01:00
/**
* @ throws InvalidSettingConfigurationException
*/
public string $collabora_dictionaries {
get => $this -> get ( 'collabora_dictionaries' , '' );
set {
// This throws an exception if the validation fails.
$this -> validateCollaboraDictionaries ( $value );
$this -> set ( 'collabora_dictionaries' , $value );
}
}
2026-01-19 12:21:23 +01:00
/**
* @ throws InvalidSettingConfigurationException
*/
public string $collabora_additional_options {
get => $this -> get ( 'collabora_additional_options' , '' );
set {
// This throws an exception if the validation fails.
$this -> validateCollaboraAdditionalOptions ( $value );
$this -> set ( 'collabora_additional_options' , $value );
}
}
2026-01-19 15:44:22 +01:00
public array $aio_community_containers {
get => explode ( ' ' , $this -> get ( 'aio_community_containers' , '' ));
set { $this -> set ( 'aio_community_containers' , implode ( ' ' , $value )); }
}
2026-01-19 12:23:22 +01:00
public string $turn_domain {
get => $this -> get ( 'turn_domain' , '' );
set { $this -> set ( 'turn_domain' , $value ); }
}
2026-01-19 14:28:15 +01:00
private function GetConfig () : array
2021-11-30 11:20:42 +01:00
{
2026-01-07 17:29:24 +01:00
if ( $this -> config === [] && file_exists ( DataConst :: GetConfigFile ()))
2021-11-30 11:20:42 +01:00
{
2025-11-05 12:58:18 +01:00
$configContent = ( string ) file_get_contents ( DataConst :: GetConfigFile ());
2026-01-07 17:29:24 +01:00
$this -> config = json_decode ( $configContent , true , 512 , JSON_THROW_ON_ERROR );
2021-11-30 11:20:42 +01:00
}
2026-01-07 17:29:24 +01:00
return $this -> config ;
2021-11-30 11:20:42 +01:00
}
2026-01-07 17:29:24 +01:00
private function get ( string $key , mixed $fallbackValue = null ) : mixed {
return $this -> GetConfig ()[ $key ] ? ? $fallbackValue ;
}
private function set ( string $key , mixed $value ) : void {
$this -> GetConfig ();
$this -> config [ $key ] = $value ;
2026-01-23 16:40:45 +01:00
// Only write if this isn't called in between startTransaction() and commitTransaction().
2026-01-07 17:29:24 +01:00
if ( $this -> noWrite !== true ) {
$this -> WriteConfig ();
}
}
/**
2026-01-23 16:40:45 +01:00
* This allows to assign multiple attributes without saving the config to disk in between . It must be
* followed by a call to commitTransaction (), which then writes all changes to disk .
2026-01-07 17:29:24 +01:00
*/
2026-01-23 16:40:45 +01:00
public function startTransaction () : void {
$this -> GetConfig ();
2026-01-07 17:29:24 +01:00
$this -> noWrite = true ;
2026-01-23 16:40:45 +01:00
}
/**
* This allows to assign multiple attributes without saving the config to disk in between .
*/
public function commitTransaction () : void {
2026-01-07 17:29:24 +01:00
try {
$this -> WriteConfig ();
} finally {
$this -> noWrite = false ;
}
}
2022-12-25 02:26:32 +01:00
public function GetAndGenerateSecret ( string $secretId ) : string {
2025-01-23 16:28:07 +01:00
if ( $secretId === '' ) {
return '' ;
}
2026-01-20 18:22:40 +01:00
$secrets = $this -> get ( 'secrets' , []);
if ( ! isset ( $secrets [ $secretId ])) {
$secrets [ $secretId ] = bin2hex ( random_bytes ( 24 ));
$this -> set ( 'secrets' , $secrets );
2021-11-30 11:20:42 +01:00
}
if ( $secretId === 'BORGBACKUP_PASSWORD' && ! file_exists ( DataConst :: GetBackupSecretFile ())) {
2026-01-20 18:22:40 +01:00
$this -> DoubleSafeBackupSecret ( $secrets [ $secretId ]);
2021-11-30 11:20:42 +01:00
}
2026-01-20 18:22:40 +01:00
return $secrets [ $secretId ];
2021-11-30 11:20:42 +01:00
}
2025-09-09 14:44:23 -07:00
public function GetRegisteredSecret ( string $secretId ) : string {
if ( $this -> secrets [ $secretId ]) {
return $this -> GetAndGenerateSecret ( $secretId );
2022-12-25 02:26:32 +01:00
}
2025-09-09 14:44:23 -07:00
throw new \Exception ( " The secret " . $secretId . " was not registered. Please check if it is defined in secrets of containers.json. " );
}
2022-12-25 02:26:32 +01:00
2025-09-09 14:44:23 -07:00
public function RegisterSecret ( string $secretId ) : void {
$this -> secrets [ $secretId ] = true ;
2022-12-25 02:26:32 +01:00
}
2022-03-01 11:44:59 +01:00
private function DoubleSafeBackupSecret ( string $borgBackupPassword ) : void {
2021-11-30 11:20:42 +01:00
file_put_contents ( DataConst :: GetBackupSecretFile (), $borgBackupPassword );
}
public function hasBackupRunOnce () : bool {
if ( ! file_exists ( DataConst :: GetBackupKeyFile ())) {
return false ;
} else {
return true ;
}
}
public function GetLastBackupTime () : string {
if ( ! file_exists ( DataConst :: GetBackupArchivesList ())) {
return '' ;
}
2025-05-30 09:32:51 +02:00
2025-11-05 12:58:18 +01:00
$content = ( string ) file_get_contents ( DataConst :: GetBackupArchivesList ());
2021-11-30 11:20:42 +01:00
$lastBackupLines = explode ( " \n " , $content );
2023-03-10 11:17:25 +01:00
$lastBackupLine = " " ;
if ( count ( $lastBackupLines ) >= 2 ) {
$lastBackupLine = $lastBackupLines [ sizeof ( $lastBackupLines ) - 2 ];
}
2021-11-30 11:20:42 +01:00
if ( $lastBackupLine === " " ) {
return '' ;
}
$lastBackupTimes = explode ( " , " , $lastBackupLine );
$lastBackupTime = $lastBackupTimes [ 1 ];
if ( $lastBackupTime === " " ) {
return '' ;
}
2025-05-30 09:32:51 +02:00
2021-11-30 11:20:42 +01:00
return $lastBackupTime ;
}
2021-12-07 19:10:05 +01:00
public function GetBackupTimes () : array {
if ( ! file_exists ( DataConst :: GetBackupArchivesList ())) {
2022-02-16 15:05:04 +01:00
return [];
2021-12-07 19:10:05 +01:00
}
2025-05-30 09:32:51 +02:00
2025-11-05 12:58:18 +01:00
$content = ( string ) file_get_contents ( DataConst :: GetBackupArchivesList ());
2021-12-07 19:10:05 +01:00
2022-02-16 15:05:04 +01:00
$backupLines = explode ( " \n " , $content );
2022-02-16 14:32:57 +01:00
$backupTimes = [];
2021-12-07 19:10:05 +01:00
foreach ( $backupLines as $lines ) {
2022-02-16 15:05:04 +01:00
if ( $lines !== " " ) {
$backupTimesTemp = explode ( ',' , $lines );
2025-05-30 09:32:51 +02:00
$backupTimes [] = $backupTimesTemp [ 1 ];
2022-02-16 15:05:04 +01:00
}
2021-12-07 19:10:05 +01:00
}
2022-03-15 16:02:43 +01:00
// Reverse the array to list newest backup first
$backupTimes = array_reverse ( $backupTimes );
2021-12-07 19:10:05 +01:00
return $backupTimes ;
}
2025-03-06 16:00:53 +01:00
private function isx64Platform () : bool {
2022-03-15 23:46:58 +01:00
if ( php_uname ( 'm' ) === 'x86_64' ) {
return true ;
} else {
return false ;
}
}
2021-11-30 11:20:42 +01:00
/**
* @ throws InvalidSettingConfigurationException
2026-01-19 12:06:20 +01:00
*
* We can ' t turn this into a private validation method because of the second argument .
2021-11-30 11:20:42 +01:00
*/
2025-11-07 15:05:36 +01:00
public function SetDomain ( string $domain , bool $skipDomainValidation ) : void {
2023-04-01 14:05:40 +02:00
// Validate that at least one dot is contained
2024-06-24 14:32:14 +02:00
if ( ! str_contains ( $domain , '.' )) {
2023-04-01 14:05:40 +02:00
throw new InvalidSettingConfigurationException ( " Domain must contain at least one dot! " );
}
2023-04-19 18:33:34 +02:00
// Validate that no slashes are contained
2024-06-24 14:32:14 +02:00
if ( str_contains ( $domain , '/' )) {
2023-04-19 18:33:34 +02:00
throw new InvalidSettingConfigurationException ( " Domain must not contain slashes! " );
}
// Validate that no colons are contained
2024-06-24 14:32:14 +02:00
if ( str_contains ( $domain , ':' )) {
2023-04-19 18:33:34 +02:00
throw new InvalidSettingConfigurationException ( " Domain must not contain colons! " );
}
2022-03-09 11:59:44 +01:00
// Validate domain
2024-01-24 12:14:08 +01:00
if ( filter_var ( $domain , FILTER_VALIDATE_DOMAIN , FILTER_FLAG_HOSTNAME ) === false ) {
2022-05-20 14:02:04 +02:00
throw new InvalidSettingConfigurationException ( " Domain is not a valid domain! " );
2021-11-30 11:20:42 +01:00
}
2022-02-23 18:01:56 +01:00
// Validate that it is not an IP-address
if ( filter_var ( $domain , FILTER_VALIDATE_IP )) {
throw new InvalidSettingConfigurationException ( " Please enter a domain and not an IP-address! " );
}
2022-06-30 14:34:36 +02:00
// Skip domain validation if opted in to do so
2025-11-13 10:31:29 +01:00
if ( $this -> shouldDomainValidationBeSkipped ( $skipDomainValidation )) {
error_log ( 'Skipping domain validation' );
} else {
2022-06-30 14:34:36 +02:00
$dnsRecordIP = gethostbyname ( $domain );
if ( $dnsRecordIP === $domain ) {
$dnsRecordIP = '' ;
2022-06-30 14:47:37 +02:00
}
2022-06-07 00:15:02 +02:00
2022-06-30 14:34:36 +02:00
if ( empty ( $dnsRecordIP )) {
$record = dns_get_record ( $domain , DNS_AAAA );
2024-05-13 18:07:40 +02:00
if ( isset ( $record [ 0 ][ 'ipv6' ]) && ! empty ( $record [ 0 ][ 'ipv6' ])) {
2022-06-30 14:34:36 +02:00
$dnsRecordIP = $record [ 0 ][ 'ipv6' ];
}
}
2022-06-27 12:20:17 +02:00
2022-06-30 14:34:36 +02:00
// Validate IP
if ( ! filter_var ( $dnsRecordIP , FILTER_VALIDATE_IP )) {
throw new InvalidSettingConfigurationException ( " DNS config is not set for this domain or the domain is not a valid domain! (It was found to be set to ' " . $dnsRecordIP . " ') " );
2022-06-27 12:20:17 +02:00
}
2021-11-30 11:20:42 +01:00
2022-06-30 14:34:36 +02:00
// Get the apache port
2026-01-20 12:47:45 +01:00
$port = $this -> apache_port ;
2021-11-30 11:20:42 +01:00
2022-06-30 14:34:36 +02:00
if ( ! filter_var ( $dnsRecordIP , FILTER_VALIDATE_IP , FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE )) {
if ( $port === '443' ) {
2025-01-10 23:09:12 +01:00
throw new InvalidSettingConfigurationException ( " It seems like the ip-address of the domain is set to an internal or reserved ip-address. This is not supported by the domain validation. (It was found to be set to ' " . $dnsRecordIP . " '). Please set it to a public ip-address so that the domain validation can work or skip the domain validation! " );
2022-06-30 14:34:36 +02:00
} else {
2025-01-10 23:09:12 +01:00
error_log ( " Info: It seems like the ip-address of " . $domain . " is set to an internal or reserved ip-address. (It was found to be set to ' " . $dnsRecordIP . " ') " );
2022-06-30 14:34:36 +02:00
}
}
2021-11-30 11:20:42 +01:00
2022-06-30 14:34:36 +02:00
// Check if port 443 is open
$connection = @ fsockopen ( $domain , 443 , $errno , $errstr , 10 );
if ( $connection ) {
fclose ( $connection );
} else {
2025-11-20 11:06:12 +01:00
throw new InvalidSettingConfigurationException ( " The domain is not reachable on Port 443 from within this container. Have you opened port 443/tcp in your router/firewall? If yes is the problem most likely that the router or firewall forbids local access to your domain. Or in other words: NAT loopback (Hairpinning) does not seem to work in your network. You can work around that by setting up a local DNS server and utilizing Split-Brain-DNS and configuring the daemon.json file of your docker daemon to use the local DNS server. " );
2022-06-30 14:34:36 +02:00
}
// Get Instance ID
2022-12-25 02:26:32 +01:00
$instanceID = $this -> GetAndGenerateSecret ( 'INSTANCE_ID' );
2021-12-08 18:12:56 +01:00
2022-06-30 14:34:36 +02:00
// set protocol
if ( $port !== '443' ) {
$protocol = 'https://' ;
} else {
$protocol = 'http://' ;
}
2021-11-30 11:20:42 +01:00
2022-06-30 14:34:36 +02:00
// Check if response is correct
$ch = curl_init ();
2025-11-24 17:38:50 +01:00
if ( $ch === false ) {
throw new InvalidSettingConfigurationException ( 'Could not init curl! Please check the logs!' );
}
2022-06-30 14:34:36 +02:00
$testUrl = $protocol . $domain . ':443' ;
curl_setopt ( $ch , CURLOPT_URL , $testUrl );
curl_setopt ( $ch , CURLOPT_RETURNTRANSFER , true );
2025-05-30 09:32:51 +02:00
curl_setopt ( $ch , CURLOPT_CONNECTTIMEOUT , 10 );
2023-04-09 01:03:06 +02:00
curl_setopt ( $ch , CURLOPT_TIMEOUT , 10 );
2022-06-30 14:34:36 +02:00
$response = ( string ) curl_exec ( $ch );
# Get rid of trailing \n
$response = str_replace ( " \n " , " " , $response );
if ( $response !== $instanceID ) {
error_log ( 'The response of the connection attempt to "' . $testUrl . '" was: ' . $response );
error_log ( 'Expected was: ' . $instanceID );
error_log ( 'The error message was: ' . curl_error ( $ch ));
2024-01-14 13:32:45 +01:00
$notice = " Domain does not point to this server or the reverse proxy is not configured correctly. See the mastercontainer logs for more details. ('sudo docker logs -f nextcloud-aio-mastercontainer') " ;
if ( $port === '443' ) {
$notice .= " If you should be using Cloudflare, make sure to disable the Cloudflare Proxy feature as it might block the domain validation. Same for any other firewall or service that blocks unencrypted access on port 443. " ;
} else {
2025-08-22 04:43:55 -07:00
error_log ( 'Please follow https://github.com/nextcloud/all-in-one/blob/main/reverse-proxy.md#how-to-debug in order to debug things!' );
2024-01-12 17:07:59 +01:00
}
2024-01-14 13:32:45 +01:00
throw new InvalidSettingConfigurationException ( $notice );
2022-06-30 14:34:36 +02:00
}
2021-11-30 11:20:42 +01:00
}
2026-01-23 16:40:45 +01:00
$this -> startTransaction ();
// Write domain
// Don't set the domain via the attribute, or we create a loop.
$this -> set ( 'domain' , $domain );
// Reset the borg restore password when setting the domain
$this -> borg_restore_password = '' ;
$this -> startTransaction ();
$this -> commitTransaction ();
2021-11-30 11:20:42 +01:00
}
2024-04-04 10:26:42 +02:00
public function GetBaseDN () : string {
2026-01-19 12:06:20 +01:00
$domain = $this -> domain ;
2024-04-04 10:26:42 +02:00
if ( $domain === " " ) {
return " " ;
}
return 'dc=' . implode ( ',dc=' , explode ( '.' , $domain ));
}
2021-11-30 11:20:42 +01:00
/**
* @ throws InvalidSettingConfigurationException
*/
2024-11-07 22:19:56 +01:00
public function SetBorgLocationVars ( string $location , string $repo ) : void {
$this -> ValidateBorgLocationVars ( $location , $repo );
2026-01-23 16:40:45 +01:00
$this -> startTransaction ();
$this -> borg_backup_host_location = $location ;
$this -> borg_remote_repo = $repo ;
$this -> commitTransaction ();
2021-11-30 11:20:42 +01:00
}
2024-11-07 22:19:56 +01:00
private function ValidateBorgLocationVars ( string $location , string $repo ) : void {
if ( $location === '' && $repo === '' ) {
throw new InvalidSettingConfigurationException ( " Please enter a path or a remote repo url! " );
} elseif ( $location !== '' && $repo !== '' ) {
throw new InvalidSettingConfigurationException ( " Location and remote repo url are mutually exclusive! " );
}
2025-05-30 09:32:51 +02:00
2024-11-07 22:19:56 +01:00
if ( $location !== '' ) {
$isValidPath = false ;
if ( str_starts_with ( $location , '/' ) && ! str_ends_with ( $location , '/' )) {
$isValidPath = true ;
} elseif ( $location === 'nextcloud_aio_backupdir' ) {
$isValidPath = true ;
}
if ( ! $isValidPath ) {
2025-07-03 10:00:59 +02:00
throw new InvalidSettingConfigurationException ( " The path must start with '/', and must not end with '/'! Another option is to use the docker volume name 'nextcloud_aio_backupdir'. " );
2024-11-07 22:19:56 +01:00
}
2025-07-03 10:00:59 +02:00
2025-07-03 11:48:22 +02:00
// Prevent backup to be contained in Nextcloud Datadir as this will delete the backup archive upon restore
// See https://github.com/nextcloud/all-in-one/issues/6607
2026-01-20 12:51:30 +01:00
if ( str_starts_with ( $location . '/' , rtrim ( $this -> nextcloud_datadir_mount , '/' ) . '/' )) {
throw new InvalidSettingConfigurationException ( " The path must not be a children of or equal to NEXTCLOUD_DATADIR, which is currently set to " . $this -> nextcloud_datadir_mount );
2025-07-03 10:00:59 +02:00
}
2024-11-07 22:19:56 +01:00
} else {
$this -> ValidateBorgRemoteRepo ( $repo );
}
}
private function ValidateBorgRemoteRepo ( string $repo ) : void {
$commonMsg = " For valid urls, see the remote examples at https://borgbackup.readthedocs.io/en/stable/usage/general.html#repository-urls " ;
if ( $repo === " " ) {
// Ok, remote repo is optional
} elseif ( ! str_contains ( $repo , " @ " )) {
throw new InvalidSettingConfigurationException ( " The remote repo must contain '@'. $commonMsg " );
} elseif ( ! str_contains ( $repo , " : " )) {
throw new InvalidSettingConfigurationException ( " The remote repo must contain ':'. $commonMsg " );
}
}
2025-12-02 12:42:13 +01:00
public function DeleteBorgBackupLocationItems () : void {
// Delete the variables
2026-01-23 16:40:45 +01:00
$this -> startTransaction ();
$this -> borg_backup_host_location = '' ;
$this -> borg_remote_repo = '' ;
$this -> commitTransaction ();
2025-12-02 12:42:13 +01:00
// Also delete the borg config file to be able to start over
if ( file_exists ( DataConst :: GetBackupKeyFile ())) {
if ( unlink ( DataConst :: GetBackupKeyFile ())) {
error_log ( 'borg.config file deleted to be able to start over.' );
}
}
2023-06-10 14:18:05 +02:00
}
2024-11-07 22:19:56 +01:00
/**
2022-03-21 13:23:17 +01:00
* @ throws InvalidSettingConfigurationException
*/
2024-11-07 22:19:56 +01:00
public function SetBorgRestoreLocationVarsAndPassword ( string $location , string $repo , string $password ) : void {
$this -> ValidateBorgLocationVars ( $location , $repo );
2022-03-21 13:23:17 +01:00
if ( $password === '' ) {
throw new InvalidSettingConfigurationException ( " Please enter the password! " );
}
2026-01-23 16:40:45 +01:00
$this -> startTransaction ();
$this -> borg_backup_host_location = $location ;
$this -> borg_remote_repo = $repo ;
$this -> borg_restore_password = $password ;
2026-01-23 17:16:02 +01:00
$this -> instanceRestoreAttempt = true ;
2026-01-23 16:40:45 +01:00
$this -> commitTransaction ();
2022-03-21 13:23:17 +01:00
}
2022-03-08 21:16:33 +01:00
/**
* @ throws InvalidSettingConfigurationException
*/
public function ChangeMasterPassword ( string $currentPassword , string $newPassword ) : void {
if ( $currentPassword === '' ) {
throw new InvalidSettingConfigurationException ( " Please enter your current password. " );
}
2026-01-19 14:55:29 +01:00
if ( $currentPassword !== $this -> password ) {
2022-03-08 21:16:33 +01:00
throw new InvalidSettingConfigurationException ( " The entered current password is not correct. " );
}
if ( $newPassword === '' ) {
throw new InvalidSettingConfigurationException ( " Please enter a new password. " );
}
if ( strlen ( $newPassword ) < 24 ) {
throw new InvalidSettingConfigurationException ( " New passwords must be >= 24 digits. " );
}
if ( ! preg_match ( " #^[a-zA-Z0-9 ]+ $ # " , $newPassword )) {
throw new InvalidSettingConfigurationException ( 'Not allowed characters in the new password.' );
}
// All checks pass so set the password
2026-01-19 14:55:29 +01:00
$this -> set ( 'password' , $newPassword );
2022-03-08 21:16:33 +01:00
}
2026-01-20 12:47:45 +01:00
public string $apache_port {
get => $this -> GetEnvironmentalVariableOrConfig ( 'APACHE_PORT' , 'apache_port' , '443' );
set { $this -> set ( 'apache_port' , $value ); }
2021-12-08 18:12:56 +01:00
}
2026-01-20 12:52:24 +01:00
public string $talk_port {
get => $this -> GetEnvironmentalVariableOrConfig ( 'TALK_PORT' , 'talk_port' , '3478' );
set { $this -> set ( 'talk_port' , $value ); }
2022-06-07 00:43:48 +02:00
}
2021-12-04 11:01:38 +01:00
/**
* @ throws InvalidSettingConfigurationException
*/
2026-01-19 14:28:15 +01:00
private function WriteConfig () : void {
2021-11-30 11:20:42 +01:00
if ( ! is_dir ( DataConst :: GetDataDirectory ())) {
2021-12-04 11:01:38 +01:00
throw new InvalidSettingConfigurationException ( DataConst :: GetDataDirectory () . " does not exist! Something was set up falsely! " );
2021-11-30 11:20:42 +01:00
}
2026-01-07 17:29:24 +01:00
// Shouldn't happen, but as a precaution we won't write an empty config to disk.
if ( $this -> config === []) {
return ;
}
2022-10-06 20:08:46 +02:00
$df = disk_free_space ( DataConst :: GetDataDirectory ());
2026-01-07 17:29:24 +01:00
$content = json_encode ( $this -> config , JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR );
2023-02-13 15:30:38 +01:00
$size = strlen ( $content ) + 10240 ;
if ( $df !== false && ( int ) $df < $size ) {
2022-10-06 20:08:46 +02:00
throw new InvalidSettingConfigurationException ( DataConst :: GetDataDirectory () . " does not have enough space for writing the config file! Not writing it back! " );
}
2023-02-13 15:30:38 +01:00
file_put_contents ( DataConst :: GetConfigFile (), $content );
2026-01-07 17:29:24 +01:00
$this -> config = [];
2021-11-30 11:20:42 +01:00
}
2022-03-11 17:28:55 +01:00
private function GetEnvironmentalVariableOrConfig ( string $envVariableName , string $configName , string $defaultValue ) : string {
$envVariableOutput = getenv ( $envVariableName );
2026-01-19 11:43:43 +01:00
$configValue = $this -> get ( $configName , '' );
2022-03-11 17:28:55 +01:00
if ( $envVariableOutput === false ) {
2026-01-19 11:43:43 +01:00
if ( $configValue === '' ) {
return $defaultValue ;
2022-03-11 17:28:55 +01:00
}
2026-01-19 11:43:43 +01:00
return $configValue ;
2022-03-14 14:59:46 +01:00
}
2026-01-19 11:43:43 +01:00
if ( file_exists ( DataConst :: GetConfigFile ())) {
if ( $envVariableOutput !== $configValue ) {
$this -> set ( $configName , $envVariableOutput );
2022-03-11 17:28:55 +01:00
}
}
2026-01-19 11:43:43 +01:00
2022-03-14 14:59:46 +01:00
return $envVariableOutput ;
2022-03-11 17:28:55 +01:00
}
2024-11-07 22:19:56 +01:00
public function GetBorgPublicKey () : string {
if ( ! file_exists ( DataConst :: GetBackupPublicKey ())) {
return " " ;
}
2025-05-30 09:32:51 +02:00
2025-11-05 12:33:07 +01:00
return trim (( string ) file_get_contents ( DataConst :: GetBackupPublicKey ()));
2024-11-07 22:19:56 +01:00
}
2026-01-20 12:53:22 +01:00
public string $nextcloud_mount {
get => $this -> GetEnvironmentalVariableOrConfig ( 'NEXTCLOUD_MOUNT' , 'nextcloud_mount' , '' );
set { $this -> set ( 'nextcloud_mount' , $value ); }
2022-02-21 17:31:05 +01:00
}
2022-03-08 16:49:13 +01:00
2026-01-20 12:51:30 +01:00
public string $nextcloud_datadir_mount {
get => $this -> GetEnvironmentalVariableOrConfig ( 'NEXTCLOUD_DATADIR' , 'nextcloud_datadir' , 'nextcloud_aio_nextcloud_data' );
set { $this -> set ( 'nextcloud_datadir_mount' , $value ); }
2022-03-08 16:49:13 +01:00
}
2022-04-04 19:12:07 +02:00
2026-01-20 12:54:14 +01:00
public string $nextcloud_upload_limit {
get => $this -> GetEnvironmentalVariableOrConfig ( 'NEXTCLOUD_UPLOAD_LIMIT' , 'nextcloud_upload_limit' , '16G' );
set { $this -> set ( 'nextcloud_upload_limit' , $value ); }
2022-08-17 18:34:02 +02:00
}
2026-01-20 12:54:54 +01:00
public string $nextcloud_memory_limit {
get => $this -> GetEnvironmentalVariableOrConfig ( 'NEXTCLOUD_MEMORY_LIMIT' , 'nextcloud_memory_limit' , '512M' );
set { $this -> set ( 'nextcloud_memory_limit' , $value ); }
2022-11-09 21:25:10 +01:00
}
2022-09-20 18:16:17 +02:00
public function GetApacheMaxSize () : int {
2026-01-20 12:54:14 +01:00
$uploadLimit = ( int ) rtrim ( $this -> nextcloud_upload_limit , 'G' );
2022-09-20 18:16:17 +02:00
return $uploadLimit * 1024 * 1024 * 1024 ;
}
2026-01-20 12:55:58 +01:00
public string $nextcloud_max_time {
get => $this -> GetEnvironmentalVariableOrConfig ( 'NEXTCLOUD_MAX_TIME' , 'nextcloud_max_time' , '3600' );
set { $this -> set ( 'nextcloud_max_time' , $value ); }
2022-08-17 19:43:05 +02:00
}
2023-04-27 16:01:16 +02:00
public function GetBorgRetentionPolicy () : string {
$envVariableName = 'BORG_RETENTION_POLICY' ;
$configName = 'borg_retention_policy' ;
$defaultValue = '--keep-within=7d --keep-weekly=4 --keep-monthly=6' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
2025-02-04 18:05:09 +01:00
public function GetFulltextsearchJavaOptions () : string {
$envVariableName = 'FULLTEXTSEARCH_JAVA_OPTIONS' ;
$configName = 'fulltextsearch_java_options' ;
$defaultValue = '-Xms512M -Xmx512M' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
2022-05-13 18:46:34 +02:00
public function GetDockerSocketPath () : string {
2023-04-16 17:47:15 +02:00
$envVariableName = 'WATCHTOWER_DOCKER_SOCKET_PATH' ;
2022-05-13 18:46:34 +02:00
$configName = 'docker_socket_path' ;
$defaultValue = '/var/run/docker.sock' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
2022-08-31 22:50:19 +02:00
public function GetTrustedCacertsDir () : string {
2022-11-09 20:28:50 +01:00
$envVariableName = 'NEXTCLOUD_TRUSTED_CACERTS_DIR' ;
2022-08-31 22:50:19 +02:00
$configName = 'trusted_cacerts_dir' ;
$defaultValue = '' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
2022-11-08 21:38:31 +01:00
public function GetNextcloudAdditionalApks () : string {
$envVariableName = 'NEXTCLOUD_ADDITIONAL_APKS' ;
$configName = 'nextcloud_additional_apks' ;
2022-11-10 19:16:33 +01:00
$defaultValue = 'imagemagick' ;
2022-11-08 21:38:31 +01:00
return trim ( $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue ));
}
public function GetNextcloudAdditionalPhpExtensions () : string {
$envVariableName = 'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS' ;
$configName = 'nextcloud_additional_php_extensions' ;
$defaultValue = 'imagick' ;
return trim ( $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue ));
}
2022-09-14 11:21:23 +02:00
public function GetCollaboraSeccompPolicy () : string {
$defaultString = '--o:security.seccomp=' ;
2025-11-24 11:01:11 +01:00
if ( ! $this -> isSeccompDisabled ()) {
2022-09-14 11:21:23 +02:00
return $defaultString . 'true' ;
}
return $defaultString . 'false' ;
}
private function GetCollaboraSeccompDisabledState () : string {
$envVariableName = 'COLLABORA_SECCOMP_DISABLED' ;
$configName = 'collabora_seccomp_disabled' ;
$defaultValue = 'false' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
2025-11-24 10:08:12 +01:00
public function isSeccompDisabled () : bool {
2026-01-19 15:18:13 +01:00
return $this -> GetCollaboraSeccompDisabledState () === 'true' ;
2025-11-24 10:08:12 +01:00
}
2022-04-04 19:12:07 +02:00
/**
* @ throws InvalidSettingConfigurationException
*/
2023-10-09 17:18:40 +02:00
public function SetDailyBackupTime ( string $time , bool $enableAutomaticUpdates , bool $successNotification ) : void {
2022-04-04 19:12:07 +02:00
if ( $time === " " ) {
throw new InvalidSettingConfigurationException ( " The daily backup time must not be empty! " );
}
if ( ! preg_match ( " #^[0-1][0-9]:[0-5][0-9] $ # " , $time ) && ! preg_match ( " #^2[0-3]:[0-5][0-9] $ # " , $time )) {
throw new InvalidSettingConfigurationException ( " You did not enter a correct time! One correct example is '04:00'! " );
}
2025-05-30 09:32:51 +02:00
2022-07-10 21:47:25 +02:00
if ( $enableAutomaticUpdates === false ) {
2022-07-17 20:19:11 +02:00
$time .= PHP_EOL . 'automaticUpdatesAreNotEnabled' ;
2023-10-18 14:22:29 +02:00
} else {
$time .= PHP_EOL ;
2022-07-10 21:47:25 +02:00
}
2023-10-09 17:18:40 +02:00
if ( $successNotification === false ) {
$time .= PHP_EOL . 'successNotificationsAreNotEnabled' ;
2023-10-18 14:22:29 +02:00
} else {
$time .= PHP_EOL ;
2023-10-09 17:18:40 +02:00
}
2022-04-04 19:12:07 +02:00
file_put_contents ( DataConst :: GetDailyBackupTimeFile (), $time );
}
public function GetDailyBackupTime () : string {
if ( ! file_exists ( DataConst :: GetDailyBackupTimeFile ())) {
return '' ;
}
2025-11-05 12:58:18 +01:00
$dailyBackupFile = ( string ) file_get_contents ( DataConst :: GetDailyBackupTimeFile ());
2022-07-10 21:47:25 +02:00
$dailyBackupFileArray = explode ( " \n " , $dailyBackupFile );
return $dailyBackupFileArray [ 0 ];
}
public function areAutomaticUpdatesEnabled () : bool {
if ( ! file_exists ( DataConst :: GetDailyBackupTimeFile ())) {
return false ;
}
2025-11-05 12:58:18 +01:00
$dailyBackupFile = ( string ) file_get_contents ( DataConst :: GetDailyBackupTimeFile ());
2022-07-10 21:47:25 +02:00
$dailyBackupFileArray = explode ( " \n " , $dailyBackupFile );
if ( isset ( $dailyBackupFileArray [ 1 ]) && $dailyBackupFileArray [ 1 ] === 'automaticUpdatesAreNotEnabled' ) {
return false ;
} else {
return true ;
}
2022-04-04 19:12:07 +02:00
}
public function DeleteDailyBackupTime () : void {
if ( file_exists ( DataConst :: GetDailyBackupTimeFile ())) {
unlink ( DataConst :: GetDailyBackupTimeFile ());
}
}
2022-08-22 17:35:03 +02:00
/**
* @ throws InvalidSettingConfigurationException
*/
public function SetAdditionalBackupDirectories ( string $additionalBackupDirectories ) : void {
$additionalBackupDirectoriesArray = explode ( " \n " , $additionalBackupDirectories );
$validDirectories = '' ;
foreach ( $additionalBackupDirectoriesArray as $entry ) {
// Trim all unwanted chars on both sites
$entry = trim ( $entry );
if ( $entry !== " " ) {
2024-03-20 08:42:56 +01:00
if ( ! preg_match ( " #^/[.0-9a-zA-Z/_-]+ $ # " , $entry ) && ! preg_match ( " #^[.0-9a-zA-Z_-]+ $ # " , $entry )) {
2022-08-22 17:35:03 +02:00
throw new InvalidSettingConfigurationException ( " You entered unallowed characters! Problematic is " . $entry );
}
$validDirectories .= rtrim ( $entry , '/' ) . PHP_EOL ;
}
}
if ( $validDirectories === '' ) {
unlink ( DataConst :: GetAdditionalBackupDirectoriesFile ());
} else {
file_put_contents ( DataConst :: GetAdditionalBackupDirectoriesFile (), $validDirectories );
}
}
public function GetAdditionalBackupDirectoriesString () : string {
if ( ! file_exists ( DataConst :: GetAdditionalBackupDirectoriesFile ())) {
return '' ;
}
2025-11-05 12:58:18 +01:00
return ( string ) file_get_contents ( DataConst :: GetAdditionalBackupDirectoriesFile ());
2022-08-22 17:35:03 +02:00
}
public function GetAdditionalBackupDirectoriesArray () : array {
$additionalBackupDirectories = $this -> GetAdditionalBackupDirectoriesString ();
$additionalBackupDirectoriesArray = explode ( " \n " , $additionalBackupDirectories );
$additionalBackupDirectoriesArray = array_unique ( $additionalBackupDirectoriesArray , SORT_REGULAR );
return $additionalBackupDirectoriesArray ;
}
2022-04-04 19:12:07 +02:00
public function isDailyBackupRunning () : bool {
2026-01-19 15:18:13 +01:00
return file_exists ( DataConst :: GetDailyBackupBlockFile ());
2022-04-04 19:12:07 +02:00
}
2022-05-18 18:36:51 +02:00
2022-05-23 19:55:50 +02:00
/**
* @ throws InvalidSettingConfigurationException
*/
2026-01-19 12:17:25 +01:00
private function validateTimezone ( string $timezone ) : void {
2022-05-18 18:36:51 +02:00
if ( $timezone === " " ) {
throw new InvalidSettingConfigurationException ( " The timezone must not be empty! " );
}
2022-05-23 19:55:50 +02:00
if ( ! preg_match ( " #^[a-zA-Z0-9_ \ - \ / \ +]+ $ # " , $timezone )) {
2022-05-18 18:36:51 +02:00
throw new InvalidSettingConfigurationException ( " The entered timezone does not seem to be a valid timezone! " );
}
}
2026-01-19 12:17:25 +01:00
/**
* Provide an extra method since our `timezone` attribute setter prevents setting an empty timezone .
*/
public function deleteTimezone () : void {
$this -> set ( 'timezone' , '' );
2022-05-18 18:36:51 +02:00
}
2022-06-30 14:34:36 +02:00
2025-11-07 15:05:36 +01:00
public function shouldDomainValidationBeSkipped ( bool $skipDomainValidation ) : bool {
if ( $skipDomainValidation || getenv ( 'SKIP_DOMAIN_VALIDATION' ) === 'true' ) {
2022-06-30 14:34:36 +02:00
return true ;
}
return false ;
}
2022-06-27 15:29:17 +02:00
2022-09-26 20:27:35 +02:00
public function GetNextcloudStartupApps () : string {
$apps = getenv ( 'NEXTCLOUD_STARTUP_APPS' );
if ( is_string ( $apps )) {
return trim ( $apps );
}
2023-05-26 12:40:13 +02:00
return 'deck twofactor_totp tasks calendar contacts notes' ;
2022-09-26 20:27:35 +02:00
}
2022-06-27 15:29:17 +02:00
/**
* @ throws InvalidSettingConfigurationException
*/
2026-01-19 12:18:45 +01:00
private function validateCollaboraDictionaries ( string $CollaboraDictionaries ) : void {
2022-06-27 15:29:17 +02:00
if ( $CollaboraDictionaries === " " ) {
throw new InvalidSettingConfigurationException ( " The dictionaries must not be empty! " );
}
if ( ! preg_match ( " #^[a-zA-Z_ ]+ $ # " , $CollaboraDictionaries )) {
throw new InvalidSettingConfigurationException ( " The entered dictionaries do not seem to be a valid! " );
}
}
2026-01-19 12:18:45 +01:00
/**
* Provide an extra method since the corresponding attribute setter prevents setting an empty value .
*/
2022-06-27 15:29:17 +02:00
public function DeleteCollaboraDictionaries () : void {
2026-01-19 12:18:45 +01:00
$this -> set ( 'collabora_dictionaries' , '' );
2022-06-27 15:29:17 +02:00
}
2022-07-08 17:11:49 +02:00
2025-02-26 14:30:55 +01:00
/**
* @ throws InvalidSettingConfigurationException
*/
2026-01-19 12:21:23 +01:00
private function validateCollaboraAdditionalOptions ( string $additionalCollaboraOptions ) : void {
2025-02-26 14:30:55 +01:00
if ( $additionalCollaboraOptions === " " ) {
throw new InvalidSettingConfigurationException ( " The additional options must not be empty! " );
}
if ( ! preg_match ( " #^--o:# " , $additionalCollaboraOptions )) {
throw new InvalidSettingConfigurationException ( " The entered options must start with '--o:'. So the config does not seem to be a valid! " );
}
}
2025-11-06 11:21:46 +01:00
public function isCollaboraSubscriptionEnabled () : bool {
2026-01-19 15:18:13 +01:00
return str_contains ( $this -> collabora_additional_options , '--o:support_key=' );
2025-11-06 11:21:46 +01:00
}
2026-01-19 12:21:23 +01:00
/**
* Provide an extra method since the corresponding attribute setter prevents setting an empty value .
*/
public function deleteAdditionalCollaboraOptions () : void {
$this -> set ( 'collabora_additional_options' , '' );
2025-02-26 14:30:55 +01:00
}
2024-11-02 14:11:17 -07:00
public function GetApacheAdditionalNetwork () : string {
$envVariableName = 'APACHE_ADDITIONAL_NETWORK' ;
$configName = 'apache_additional_network' ;
$defaultValue = '' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
2022-07-17 22:07:34 +02:00
private function GetDisableBackupSection () : string {
2023-04-16 17:49:03 +02:00
$envVariableName = 'AIO_DISABLE_BACKUP_SECTION' ;
2022-07-17 22:07:34 +02:00
$configName = 'disable_backup_section' ;
$defaultValue = '' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
public function isBackupSectionEnabled () : bool {
if ( $this -> GetDisableBackupSection () === 'true' ) {
return false ;
} else {
return true ;
}
}
2023-01-03 02:01:03 +01:00
2025-05-30 09:32:51 +02:00
public function listAvailableCommunityContainers () : array {
$cc = [];
$dir = scandir ( DataConst :: GetCommunityContainersDirectory ());
if ( $dir === false ) {
return $cc ;
}
2025-05-30 10:24:24 +02:00
// Get rid of dots from the scandir command
2025-05-30 10:47:55 +02:00
$dir = array_diff ( $dir , array ( '..' , '.' , 'readme.md' ));
2025-05-30 09:32:51 +02:00
foreach ( $dir as $id ) {
$filePath = DataConst :: GetCommunityContainersDirectory () . '/' . $id . '/' . $id . '.json' ;
$fileContents = apcu_fetch ( $filePath );
if ( ! is_string ( $fileContents )) {
$fileContents = file_get_contents ( $filePath );
if ( is_string ( $fileContents )) {
apcu_add ( $filePath , $fileContents );
}
}
2025-11-05 12:33:07 +01:00
$json = is_string ( $fileContents ) ? json_decode ( $fileContents , true , 512 , JSON_THROW_ON_ERROR ) : false ;
2025-05-30 09:32:51 +02:00
if ( is_array ( $json ) && is_array ( $json [ 'aio_services_v1' ])) {
foreach ( $json [ 'aio_services_v1' ] as $service ) {
$documentation = is_string ( $service [ 'documentation' ]) ? $service [ 'documentation' ] : '' ;
if ( is_string ( $service [ 'display_name' ])) {
2025-05-30 12:48:15 +02:00
$cc [ $id ] = [
'id' => $id ,
'name' => $service [ 'display_name' ],
'documentation' => $documentation
];
2025-05-30 09:32:51 +02:00
}
break ;
}
}
}
return $cc ;
}
2023-01-03 02:01:03 +01:00
private function GetEnabledDriDevice () : string {
$envVariableName = 'NEXTCLOUD_ENABLE_DRI_DEVICE' ;
$configName = 'nextcloud_enable_dri_device' ;
$defaultValue = '' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
public function isDriDeviceEnabled () : bool {
if ( $this -> GetEnabledDriDevice () === 'true' ) {
return true ;
} else {
return false ;
}
}
2023-07-19 21:29:24 +02:00
2024-12-20 04:12:59 -06:00
private function GetEnabledNvidiaGpu () : string {
2025-01-06 11:03:41 +01:00
$envVariableName = 'NEXTCLOUD_ENABLE_NVIDIA_GPU' ;
2024-12-20 04:12:59 -06:00
$configName = 'enable_nvidia_gpu' ;
$defaultValue = '' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
public function isNvidiaGpuEnabled () : bool {
return $this -> GetEnabledNvidiaGpu () === 'true' ;
}
2023-07-19 21:29:24 +02:00
private function GetKeepDisabledApps () : string {
$envVariableName = 'NEXTCLOUD_KEEP_DISABLED_APPS' ;
$configName = 'nextcloud_keep_disabled_apps' ;
$defaultValue = '' ;
return $this -> GetEnvironmentalVariableOrConfig ( $envVariableName , $configName , $defaultValue );
}
public function shouldDisabledAppsGetRemoved () : bool {
if ( $this -> GetKeepDisabledApps () === 'true' ) {
return false ;
} else {
return true ;
}
}
2026-01-20 09:27:43 +01:00
public function setAioVariables ( array $input ) : void {
if ( $input === []) {
return ;
}
2026-01-23 16:40:45 +01:00
$this -> startTransaction ();
foreach ( $input as $variable ) {
if ( ! is_string ( $variable ) || ! str_contains ( $variable , '=' )) {
error_log ( " Invalid input: ' $variable ' is not a string or does not contain an equal sign ('=') " );
continue ;
2026-01-20 09:27:43 +01:00
}
2026-01-23 16:40:45 +01:00
$keyWithValue = $confManager -> replaceEnvPlaceholders ( $variable );
// Pad the result with nulls so psalm is happy (and we don't risk to run into warnings in case
// the check for an equal sign from above gets changed).
[ $key , $value ] = explode ( '=' , $keyWithValue , 2 ) + [ null , null ];
if ( $value === null ) {
error_log ( " Invalid input: ' $keyWithValue ' has no value after the equal sign " );
} else if ( ! property_exists ( $confManager , $key )) {
error_log ( " Error: ' $key ' is not a valid configuration key (in ' $keyWithValue ') " );
} else {
$confManager -> $key = $value ;
}
}
$this -> commitTransaction ();
2026-01-20 09:27:43 +01:00
}
//
// Replaces placeholders in $envValue with their values.
// E.g. "%NC_DOMAIN%:%APACHE_PORT" becomes "my.nextcloud.com:11000"
public function replaceEnvPlaceholders ( string $envValue ) : string {
// $pattern breaks down as:
// % - matches a literal percent sign
// ([^%]+) - capture group that matches one or more characters that are NOT percent signs
// % - matches the closing percent sign
//
// Assumes literal percent signs are always matched and there is no
// escaping.
$pattern = '/%([^%]+)%/' ;
$matchCount = preg_match_all ( $pattern , $envValue , $matches );
if ( $matchCount === 0 ) {
return $envValue ;
}
$placeholders = $matches [ 0 ]; // ["%PLACEHOLDER1%", "%PLACEHOLDER2%", ...]
$placeholderNames = $matches [ 1 ]; // ["PLACEHOLDER1", "PLACEHOLDER2", ...]
$placeholderPatterns = array_map ( static fn ( string $p ) => '/' . preg_quote ( $p ) . '/' , $placeholders ); // ["/%PLACEHOLDER1%/", ...]
$placeholderValues = array_map ( $this -> getPlaceholderValue ( ... ), $placeholderNames ); // ["val1", "val2"]
// Guaranteed to be non-null because we found the placeholders in the preg_match_all.
return ( string ) preg_replace ( $placeholderPatterns , $placeholderValues , $envValue );
}
private function getPlaceholderValue ( string $placeholder ) : string {
return match ( $placeholder ) {
'NC_DOMAIN' => $this -> domain ,
'NC_BASE_DN' => $this -> GetBaseDN (),
2026-01-23 17:12:45 +01:00
'AIO_TOKEN' => $this -> aioToken ,
2026-01-20 09:27:43 +01:00
'BORGBACKUP_REMOTE_REPO' => $this -> borg_remote_repo ,
'BORGBACKUP_MODE' => $this -> backupMode ,
2026-01-23 17:17:54 +01:00
'AIO_URL' => $this -> aioUrl ,
2026-01-20 09:27:43 +01:00
'SELECTED_RESTORE_TIME' => $this -> selectedRestoreTime ,
'RESTORE_EXCLUDE_PREVIEWS' => $this -> restoreExcludePreviews ? '1' : '' ,
'APACHE_PORT' => $this -> apache_port ,
'APACHE_IP_BINDING' => $this -> apache_ip_binding ,
'TALK_PORT' => $this -> talk_port ,
'TURN_DOMAIN' => $this -> turn_domain ,
'NEXTCLOUD_MOUNT' => $this -> nextcloud_mount ,
'BACKUP_RESTORE_PASSWORD' => $this -> borg_restore_password ,
'CLAMAV_ENABLED' => $this -> isClamavEnabled ? 'yes' : '' ,
'TALK_RECORDING_ENABLED' => $this -> isTalkRecordingEnabled ? 'yes' : '' ,
'ONLYOFFICE_ENABLED' => $this -> isOnlyofficeEnabled ? 'yes' : '' ,
'COLLABORA_ENABLED' => $this -> isCollaboraEnabled ? 'yes' : '' ,
'TALK_ENABLED' => $this -> isTalkEnabled ? 'yes' : '' ,
'UPDATE_NEXTCLOUD_APPS' => ( $this -> isDailyBackupRunning () && $this -> areAutomaticUpdatesEnabled ()) ? 'yes' : '' ,
'TIMEZONE' => $this -> timezone === '' ? 'Etc/UTC' : $this -> timezone ,
'COLLABORA_DICTIONARIES' => $this -> collabora_dictionaries === '' ? 'de_DE en_GB en_US es_ES fr_FR it nl pt_BR pt_PT ru' : $this -> collabora_dictionaries ,
'IMAGINARY_ENABLED' => $this -> isImaginaryEnabled ? 'yes' : '' ,
'FULLTEXTSEARCH_ENABLED' => $this -> isFulltextsearchEnabled ? 'yes' : '' ,
'DOCKER_SOCKET_PROXY_ENABLED' => $this -> isDockerSocketProxyEnabled ? 'yes' : '' ,
'NEXTCLOUD_UPLOAD_LIMIT' => $this -> nextcloud_upload_limit ,
'NEXTCLOUD_MEMORY_LIMIT' => $this -> nextcloud_memory_limit ,
'NEXTCLOUD_MAX_TIME' => $this -> nextcloud_max_time ,
'BORG_RETENTION_POLICY' => $this -> GetBorgRetentionPolicy (),
'FULLTEXTSEARCH_JAVA_OPTIONS' => $this -> GetFulltextsearchJavaOptions (),
'NEXTCLOUD_TRUSTED_CACERTS_DIR' => $this -> GetTrustedCacertsDir (),
'ADDITIONAL_DIRECTORIES_BACKUP' => $this -> GetAdditionalBackupDirectoriesString () !== '' ? 'yes' : '' ,
'BORGBACKUP_HOST_LOCATION' => $this -> borg_backup_host_location ,
'APACHE_MAX_SIZE' => ( string )( $this -> GetApacheMaxSize ()),
'COLLABORA_SECCOMP_POLICY' => $this -> GetCollaboraSeccompPolicy (),
'NEXTCLOUD_STARTUP_APPS' => $this -> GetNextcloudStartupApps (),
'NEXTCLOUD_ADDITIONAL_APKS' => $this -> GetNextcloudAdditionalApks (),
'NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS' => $this -> GetNextcloudAdditionalPhpExtensions (),
2026-01-23 17:10:21 +01:00
'INSTALL_LATEST_MAJOR' => $this -> install_latest_major ,
2026-01-20 09:27:43 +01:00
'REMOVE_DISABLED_APPS' => $this -> shouldDisabledAppsGetRemoved () ? 'yes' : '' ,
// Allow to get local ip-address of database container which allows to talk to it even in host mode (the container that requires this needs to be started first then)
'AIO_DATABASE_HOST' => gethostbyname ( 'nextcloud-aio-database' ),
// Allow to get local ip-address of caddy container and add it to trusted proxies automatically
'CADDY_IP_ADDRESS' => in_array ( 'caddy' , $this -> aio_community_containers , true ) ? gethostbyname ( 'nextcloud-aio-caddy' ) : '' ,
'WHITEBOARD_ENABLED' => $this -> isWhiteboardEnabled ? 'yes' : '' ,
default => $this -> GetRegisteredSecret ( $placeholder ),
};
}
2021-11-30 11:20:42 +01:00
}