Add missing type definitions for psalm

Signed-off-by: Simon L. <szaimen@e.mail.de>
This commit is contained in:
Simon L. 2026-02-03 17:53:39 +01:00
parent 9c0334d3f0
commit 87e005be4b
9 changed files with 97 additions and 21 deletions

View file

@ -20,7 +20,9 @@ use Psr\Http\Message\ServerRequestInterface as Request;
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
/** @var \DI\Container */
$container = \AIO\DependencyInjection::GetContainer(); $container = \AIO\DependencyInjection::GetContainer();
/** @var \AIO\Data\DataConst */
$dataConst = $container->get(\AIO\Data\DataConst::class); $dataConst = $container->get(\AIO\Data\DataConst::class);
ini_set('session.save_path', $dataConst->GetSessionDirectory()); ini_set('session.save_path', $dataConst->GetSessionDirectory());

View file

@ -22,8 +22,10 @@ readonly class ContainerDefinitionFetcher {
public function GetContainerById(string $id): Container public function GetContainerById(string $id): Container
{ {
/** @var array */
$containers = $this->FetchDefinition(); $containers = $this->FetchDefinition();
/** @psalm-var \AIO\Container\Container $container */
foreach ($containers as $container) { foreach ($containers as $container) {
if ($container->identifier === $id) { if ($container->identifier === $id) {
return $container; return $container;
@ -38,22 +40,26 @@ readonly class ContainerDefinitionFetcher {
*/ */
private function GetDefinition(): array private function GetDefinition(): array
{ {
/** @psalm-var array $data */
$data = json_decode((string)file_get_contents(DataConst::GetContainersDefinitionPath()), true, 512, JSON_THROW_ON_ERROR); $data = json_decode((string)file_get_contents(DataConst::GetContainersDefinitionPath()), true, 512, JSON_THROW_ON_ERROR);
$additionalContainerNames = []; $additionalContainerNames = [];
/** @psalm-var string $communityContainer */
foreach ($this->configurationManager->aioCommunityContainers as $communityContainer) { foreach ($this->configurationManager->aioCommunityContainers as $communityContainer) {
if ($communityContainer !== '') { if ($communityContainer !== '') {
$path = DataConst::GetCommunityContainersDirectory() . '/' . $communityContainer . '/' . $communityContainer . '.json'; $path = DataConst::GetCommunityContainersDirectory() . '/' . $communityContainer . '/' . $communityContainer . '.json';
/** @psalm-var array $additionalData */
$additionalData = json_decode((string)file_get_contents($path), true, 512, JSON_THROW_ON_ERROR); $additionalData = json_decode((string)file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$data = array_merge_recursive($data, $additionalData); $data = array_merge_recursive($data, $additionalData);
if (isset($additionalData['aio_services_v1'][0]['display_name']) && $additionalData['aio_services_v1'][0]['display_name'] !== '') { if (isset($additionalData['aio_services_v1'][0]['display_name']) && $additionalData['aio_services_v1'][0]['display_name'] !== '') {
// Store container_name of community containers in variable for later // Store container_name of community containers in variable for later
$additionalContainerNames[] = $additionalData['aio_services_v1'][0]['container_name']; $additionalContainerNames[] = (string) $additionalData['aio_services_v1'][0]['container_name'];
} }
} }
} }
$containers = []; $containers = [];
/** @psalm-var array $entry */
foreach ($data['aio_services_v1'] as $entry) { foreach ($data['aio_services_v1'] as $entry) {
if ($entry['container_name'] === 'nextcloud-aio-clamav') { if ($entry['container_name'] === 'nextcloud-aio-clamav') {
if (!$this->configurationManager->isClamavEnabled) { if (!$this->configurationManager->isClamavEnabled) {
@ -98,6 +104,7 @@ readonly class ContainerDefinitionFetcher {
$ports = new ContainerPorts(); $ports = new ContainerPorts();
if (isset($entry['ports'])) { if (isset($entry['ports'])) {
/** @psalm-var array $value */
foreach ($entry['ports'] as $value) { foreach ($entry['ports'] as $value) {
$ports->AddPort( $ports->AddPort(
new ContainerPort( new ContainerPort(
@ -111,6 +118,7 @@ readonly class ContainerDefinitionFetcher {
$volumes = new ContainerVolumes(); $volumes = new ContainerVolumes();
if (isset($entry['volumes'])) { if (isset($entry['volumes'])) {
/** @psalm-var array $value */
foreach ($entry['volumes'] as $value) { foreach ($entry['volumes'] as $value) {
if($value['source'] === '%BORGBACKUP_HOST_LOCATION%') { if($value['source'] === '%BORGBACKUP_HOST_LOCATION%') {
$value['source'] = $this->configurationManager->borgBackupHostLocation; $value['source'] = $this->configurationManager->borgBackupHostLocation;
@ -157,15 +165,18 @@ readonly class ContainerDefinitionFetcher {
$dependsOn = []; $dependsOn = [];
if (isset($entry['depends_on'])) { if (isset($entry['depends_on'])) {
/** @var array */
$valueDependsOn = $entry['depends_on']; $valueDependsOn = $entry['depends_on'];
if ($entry['container_name'] === 'nextcloud-aio-apache') { if ($entry['container_name'] === 'nextcloud-aio-apache') {
// Add community containers first and default ones last so that aio_variables works correctly // Add community containers first and default ones last so that aio_variables works correctly
$valueDependsOnTemp = []; $valueDependsOnTemp = [];
/** @psalm-var string $containerName */
foreach ($additionalContainerNames as $containerName) { foreach ($additionalContainerNames as $containerName) {
$valueDependsOnTemp[] = $containerName; $valueDependsOnTemp[] = $containerName;
} }
$valueDependsOn = array_merge_recursive($valueDependsOnTemp, $valueDependsOn); $valueDependsOn = array_merge_recursive($valueDependsOnTemp, $valueDependsOn);
} }
/** @psalm-var string $value */
foreach ($valueDependsOn as $value) { foreach ($valueDependsOn as $value) {
if ($value === 'nextcloud-aio-clamav') { if ($value === 'nextcloud-aio-clamav') {
if (!$this->configurationManager->isClamavEnabled) { if (!$this->configurationManager->isClamavEnabled) {
@ -210,6 +221,7 @@ readonly class ContainerDefinitionFetcher {
$variables = new ContainerEnvironmentVariables(); $variables = new ContainerEnvironmentVariables();
if (isset($entry['environment'])) { if (isset($entry['environment'])) {
/** @psalm-var string $value */
foreach ($entry['environment'] as $value) { foreach ($entry['environment'] as $value) {
$variables->AddVariable($value); $variables->AddVariable($value);
} }
@ -217,6 +229,7 @@ readonly class ContainerDefinitionFetcher {
$aioVariables = new AioVariables(); $aioVariables = new AioVariables();
if (isset($entry['aio_variables'])) { if (isset($entry['aio_variables'])) {
/** @psalm-var string $value */
foreach ($entry['aio_variables'] as $value) { foreach ($entry['aio_variables'] as $value) {
$aioVariables->AddVariable($value); $aioVariables->AddVariable($value);
} }
@ -224,27 +237,28 @@ readonly class ContainerDefinitionFetcher {
$displayName = ''; $displayName = '';
if (isset($entry['display_name'])) { if (isset($entry['display_name'])) {
$displayName = $entry['display_name']; $displayName = (string) $entry['display_name'];
} }
$restartPolicy = ''; $restartPolicy = '';
if (isset($entry['restart'])) { if (isset($entry['restart'])) {
$restartPolicy = $entry['restart']; $restartPolicy = (string) $entry['restart'];
} }
$maxShutdownTime = 10; $maxShutdownTime = 10;
if (isset($entry['stop_grace_period'])) { if (isset($entry['stop_grace_period'])) {
$maxShutdownTime = $entry['stop_grace_period']; $maxShutdownTime = (int) $entry['stop_grace_period'];
} }
$internalPort = ''; $internalPort = '';
if (isset($entry['internal_port'])) { if (isset($entry['internal_port'])) {
$internalPort = $entry['internal_port']; $internalPort = (string) $entry['internal_port'];
} }
if (isset($entry['secrets'])) { if (isset($entry['secrets'])) {
// All secrets are registered with the configuration when they // All secrets are registered with the configuration when they
// are discovered so they can be later generated at time-of-use. // are discovered so they can be later generated at time-of-use.
/** @psalm-var string $secret */
foreach ($entry['secrets'] as $secret) { foreach ($entry['secrets'] as $secret) {
$this->configurationManager->registerSecret($secret); $this->configurationManager->registerSecret($secret);
} }
@ -252,67 +266,72 @@ readonly class ContainerDefinitionFetcher {
$uiSecret = ''; $uiSecret = '';
if (isset($entry['ui_secret'])) { if (isset($entry['ui_secret'])) {
$uiSecret = $entry['ui_secret']; $uiSecret = (string) $entry['ui_secret'];
} }
$devices = []; $devices = [];
if (isset($entry['devices'])) { if (isset($entry['devices'])) {
/** @var array */
$devices = $entry['devices']; $devices = $entry['devices'];
} }
$enableNvidiaGpu = false; $enableNvidiaGpu = false;
if (isset($entry['enable_nvidia_gpu'])) { if (isset($entry['enable_nvidia_gpu'])) {
$enableNvidiaGpu = $entry['enable_nvidia_gpu']; $enableNvidiaGpu = (bool) $entry['enable_nvidia_gpu'];
} }
$capAdd = []; $capAdd = [];
if (isset($entry['cap_add'])) { if (isset($entry['cap_add'])) {
/** @var array */
$capAdd = $entry['cap_add']; $capAdd = $entry['cap_add'];
} }
$shmSize = -1; $shmSize = -1;
if (isset($entry['shm_size'])) { if (isset($entry['shm_size'])) {
$shmSize = $entry['shm_size']; $shmSize = (int) $entry['shm_size'];
} }
$apparmorUnconfined = false; $apparmorUnconfined = false;
if (isset($entry['apparmor_unconfined'])) { if (isset($entry['apparmor_unconfined'])) {
$apparmorUnconfined = $entry['apparmor_unconfined']; $apparmorUnconfined = (bool) $entry['apparmor_unconfined'];
} }
$backupVolumes = []; $backupVolumes = [];
if (isset($entry['backup_volumes'])) { if (isset($entry['backup_volumes'])) {
/** @var array */
$backupVolumes = $entry['backup_volumes']; $backupVolumes = $entry['backup_volumes'];
} }
$nextcloudExecCommands = []; $nextcloudExecCommands = [];
if (isset($entry['nextcloud_exec_commands'])) { if (isset($entry['nextcloud_exec_commands'])) {
/** @var array */
$nextcloudExecCommands = $entry['nextcloud_exec_commands']; $nextcloudExecCommands = $entry['nextcloud_exec_commands'];
} }
$readOnlyRootFs = false; $readOnlyRootFs = false;
if (isset($entry['read_only'])) { if (isset($entry['read_only'])) {
$readOnlyRootFs = $entry['read_only']; $readOnlyRootFs = (bool) $entry['read_only'];
} }
$tmpfs = []; $tmpfs = [];
if (isset($entry['tmpfs'])) { if (isset($entry['tmpfs'])) {
/** @var array */
$tmpfs = $entry['tmpfs']; $tmpfs = $entry['tmpfs'];
} }
$init = true; $init = true;
if (isset($entry['init'])) { if (isset($entry['init'])) {
$init = $entry['init']; $init = (bool) $entry['init'];
} }
$imageTag = '%AIO_CHANNEL%'; $imageTag = '%AIO_CHANNEL%';
if (isset($entry['image_tag'])) { if (isset($entry['image_tag'])) {
$imageTag = $entry['image_tag']; $imageTag = (string) $entry['image_tag'];
} }
$documentation = ''; $documentation = '';
if (isset($entry['documentation'])) { if (isset($entry['documentation'])) {
$documentation = $entry['documentation']; $documentation = (string) $entry['documentation'];
} }
$containers[] = new Container( $containers[] = new Container(

View file

@ -19,26 +19,34 @@ readonly class ConfigurationController {
try { try {
$this->configurationManager->startTransaction(); $this->configurationManager->startTransaction();
if (isset($request->getParsedBody()['domain'])) { if (isset($request->getParsedBody()['domain'])) {
/** @var string */
$domain = $request->getParsedBody()['domain'] ?? ''; $domain = $request->getParsedBody()['domain'] ?? '';
$skipDomainValidation = isset($request->getParsedBody()['skip_domain_validation']); $skipDomainValidation = isset($request->getParsedBody()['skip_domain_validation']);
$this->configurationManager->setDomain($domain, $skipDomainValidation); $this->configurationManager->setDomain($domain, $skipDomainValidation);
} }
if (isset($request->getParsedBody()['current-master-password']) || isset($request->getParsedBody()['new-master-password'])) { if (isset($request->getParsedBody()['current-master-password']) || isset($request->getParsedBody()['new-master-password'])) {
/** @var string */
$currentMasterPassword = $request->getParsedBody()['current-master-password'] ?? ''; $currentMasterPassword = $request->getParsedBody()['current-master-password'] ?? '';
/** @var string */
$newMasterPassword = $request->getParsedBody()['new-master-password'] ?? ''; $newMasterPassword = $request->getParsedBody()['new-master-password'] ?? '';
$this->configurationManager->changeMasterPassword($currentMasterPassword, $newMasterPassword); $this->configurationManager->changeMasterPassword($currentMasterPassword, $newMasterPassword);
} }
if (isset($request->getParsedBody()['borg_backup_host_location']) || isset($request->getParsedBody()['borg_remote_repo'])) { if (isset($request->getParsedBody()['borg_backup_host_location']) || isset($request->getParsedBody()['borg_remote_repo'])) {
/** @var string */
$location = $request->getParsedBody()['borg_backup_host_location'] ?? ''; $location = $request->getParsedBody()['borg_backup_host_location'] ?? '';
/** @var string */
$borgRemoteRepo = $request->getParsedBody()['borg_remote_repo'] ?? ''; $borgRemoteRepo = $request->getParsedBody()['borg_remote_repo'] ?? '';
$this->configurationManager->setBorgLocationVars($location, $borgRemoteRepo); $this->configurationManager->setBorgLocationVars($location, $borgRemoteRepo);
} }
if (isset($request->getParsedBody()['borg_restore_host_location']) || isset($request->getParsedBody()['borg_restore_remote_repo']) || isset($request->getParsedBody()['borg_restore_password'])) { if (isset($request->getParsedBody()['borg_restore_host_location']) || isset($request->getParsedBody()['borg_restore_remote_repo']) || isset($request->getParsedBody()['borg_restore_password'])) {
/** @var string */
$restoreLocation = $request->getParsedBody()['borg_restore_host_location'] ?? ''; $restoreLocation = $request->getParsedBody()['borg_restore_host_location'] ?? '';
/** @var string */
$borgRemoteRepo = $request->getParsedBody()['borg_restore_remote_repo'] ?? ''; $borgRemoteRepo = $request->getParsedBody()['borg_restore_remote_repo'] ?? '';
/** @var string */
$borgPassword = $request->getParsedBody()['borg_restore_password'] ?? ''; $borgPassword = $request->getParsedBody()['borg_restore_password'] ?? '';
$this->configurationManager->setBorgRestoreLocationVarsAndPassword($restoreLocation, $borgRemoteRepo, $borgPassword); $this->configurationManager->setBorgRestoreLocationVarsAndPassword($restoreLocation, $borgRemoteRepo, $borgPassword);
} }
@ -54,6 +62,7 @@ readonly class ConfigurationController {
} else { } else {
$successNotification = false; $successNotification = false;
} }
/** @var string */
$dailyBackupTime = $request->getParsedBody()['daily_backup_time'] ?? ''; $dailyBackupTime = $request->getParsedBody()['daily_backup_time'] ?? '';
$this->configurationManager->setDailyBackupTime($dailyBackupTime, $enableAutomaticUpdates, $successNotification); $this->configurationManager->setDailyBackupTime($dailyBackupTime, $enableAutomaticUpdates, $successNotification);
} }
@ -63,6 +72,7 @@ readonly class ConfigurationController {
} }
if (isset($request->getParsedBody()['additional_backup_directories'])) { if (isset($request->getParsedBody()['additional_backup_directories'])) {
/** @var string */
$additionalBackupDirectories = $request->getParsedBody()['additional_backup_directories'] ?? ''; $additionalBackupDirectories = $request->getParsedBody()['additional_backup_directories'] ?? '';
$this->configurationManager->setAdditionalBackupDirectories($additionalBackupDirectories); $this->configurationManager->setAdditionalBackupDirectories($additionalBackupDirectories);
} }
@ -72,11 +82,13 @@ readonly class ConfigurationController {
} }
if (isset($request->getParsedBody()['timezone'])) { if (isset($request->getParsedBody()['timezone'])) {
/** @var string */
$timezone = $request->getParsedBody()['timezone'] ?? ''; $timezone = $request->getParsedBody()['timezone'] ?? '';
$this->configurationManager->timezone = $timezone; $this->configurationManager->timezone = $timezone;
} }
if (isset($request->getParsedBody()['options-form'])) { if (isset($request->getParsedBody()['options-form'])) {
/** @var string */
$officeSuiteChoice = $request->getParsedBody()['office_suite_choice'] ?? ''; $officeSuiteChoice = $request->getParsedBody()['office_suite_choice'] ?? '';
if ($officeSuiteChoice === 'collabora') { if ($officeSuiteChoice === 'collabora') {
@ -103,6 +115,7 @@ readonly class ConfigurationController {
$enabledCC = []; $enabledCC = [];
/** /**
* @psalm-suppress PossiblyNullIterator * @psalm-suppress PossiblyNullIterator
* @psalm-var string $item
*/ */
foreach ($request->getParsedBody() as $item) { foreach ($request->getParsedBody() as $item) {
if (array_key_exists($item , $cc)) { if (array_key_exists($item , $cc)) {
@ -117,6 +130,7 @@ readonly class ConfigurationController {
} }
if (isset($request->getParsedBody()['collabora_dictionaries'])) { if (isset($request->getParsedBody()['collabora_dictionaries'])) {
/** @var string */
$collaboraDictionaries = $request->getParsedBody()['collabora_dictionaries'] ?? ''; $collaboraDictionaries = $request->getParsedBody()['collabora_dictionaries'] ?? '';
$this->configurationManager->collaboraDictionaries = $collaboraDictionaries; $this->configurationManager->collaboraDictionaries = $collaboraDictionaries;
} }
@ -126,6 +140,7 @@ readonly class ConfigurationController {
} }
if (isset($request->getParsedBody()['collabora_additional_options'])) { if (isset($request->getParsedBody()['collabora_additional_options'])) {
/** @var string */
$additionalCollaboraOptions = $request->getParsedBody()['collabora_additional_options'] ?? ''; $additionalCollaboraOptions = $request->getParsedBody()['collabora_additional_options'] ?? '';
$this->configurationManager->collaboraAdditionalOptions = $additionalCollaboraOptions; $this->configurationManager->collaboraAdditionalOptions = $additionalCollaboraOptions;
} }

View file

@ -125,6 +125,7 @@ readonly class DockerController {
public function StartBackupContainerRestore(Request $request, Response $response, array $args) : Response { public function StartBackupContainerRestore(Request $request, Response $response, array $args) : Response {
$this->configurationManager->startTransaction(); $this->configurationManager->startTransaction();
$this->configurationManager->backupMode = 'restore'; $this->configurationManager->backupMode = 'restore';
/** @var string */
$this->configurationManager->selectedRestoreTime = $request->getParsedBody()['selected_restore_time'] ?? ''; $this->configurationManager->selectedRestoreTime = $request->getParsedBody()['selected_restore_time'] ?? '';
$this->configurationManager->restoreExcludePreviews = isset($request->getParsedBody()['restore-exclude-previews']); $this->configurationManager->restoreExcludePreviews = isset($request->getParsedBody()['restore-exclude-previews']);
$this->configurationManager->commitTransaction(); $this->configurationManager->commitTransaction();
@ -171,6 +172,7 @@ readonly class DockerController {
$uri = $request->getUri(); $uri = $request->getUri();
$host = $uri->getHost(); $host = $uri->getHost();
$port = $uri->getPort(); $port = $uri->getPort();
/** @var string */
$path = $request->getParsedBody()['base_path'] ?? ''; $path = $request->getParsedBody()['base_path'] ?? '';
if ($port === 8000) { if ($port === 8000) {
error_log('The AIO_URL-port was discovered to be 8000 which is not expected. It is now set to 443.'); error_log('The AIO_URL-port was discovered to be 8000 which is not expected. It is now set to 443.');
@ -284,6 +286,7 @@ readonly class DockerController {
return; return;
// Don't start if domaincheck is already running // Don't start if domaincheck is already running
} elseif ($domaincheckContainer->GetRunningState() === ContainerState::Running) { } elseif ($domaincheckContainer->GetRunningState() === ContainerState::Running) {
/** @psalm-var mixed $domaincheckWasStarted */
$domaincheckWasStarted = apcu_fetch($cacheKey); $domaincheckWasStarted = apcu_fetch($cacheKey);
// Start domaincheck again when 10 minutes are over by not returning here // Start domaincheck again when 10 minutes are over by not returning here
if($domaincheckWasStarted !== false && is_string($domaincheckWasStarted)) { if($domaincheckWasStarted !== false && is_string($domaincheckWasStarted)) {

View file

@ -21,6 +21,7 @@ readonly class LoginController {
$response->getBody()->write("The login is blocked since Nextcloud is running."); $response->getBody()->write("The login is blocked since Nextcloud is running.");
return $response->withHeader('Location', '.')->withStatus(422); return $response->withHeader('Location', '.')->withStatus(422);
} }
/** @var string */
$password = $request->getParsedBody()['password'] ?? ''; $password = $request->getParsedBody()['password'] ?? '';
if($this->authManager->CheckCredentials($password)) { if($this->authManager->CheckCredentials($password)) {
$this->authManager->SetAuthState(true); $this->authManager->SetAuthState(true);
@ -32,6 +33,7 @@ readonly class LoginController {
} }
public function GetTryLogin(Request $request, Response $response, array $args) : Response { public function GetTryLogin(Request $request, Response $response, array $args) : Response {
/** @var string */
$token = $request->getQueryParams()['token'] ?? ''; $token = $request->getQueryParams()['token'] ?? '';
if($this->authManager->CheckToken($token)) { if($this->authManager->CheckToken($token)) {
$this->authManager->SetAuthState(true); $this->authManager->SetAuthState(true);

View file

@ -288,6 +288,7 @@ class ConfigurationManager
if ($this->config === [] && file_exists(DataConst::GetConfigFile())) if ($this->config === [] && file_exists(DataConst::GetConfigFile()))
{ {
$configContent = (string)file_get_contents(DataConst::GetConfigFile()); $configContent = (string)file_get_contents(DataConst::GetConfigFile());
/** @var array */
$this->config = json_decode($configContent, true, 512, JSON_THROW_ON_ERROR); $this->config = json_decode($configContent, true, 512, JSON_THROW_ON_ERROR);
} }
@ -332,6 +333,7 @@ class ConfigurationManager
return ''; return '';
} }
/** @var array */
$secrets = $this->get('secrets', []); $secrets = $this->get('secrets', []);
if (!isset($secrets[$secretId])) { if (!isset($secrets[$secretId])) {
$secrets[$secretId] = bin2hex(random_bytes(24)); $secrets[$secretId] = bin2hex(random_bytes(24));
@ -466,15 +468,17 @@ class ConfigurationManager
if ($this->shouldDomainValidationBeSkipped($skipDomainValidation)) { if ($this->shouldDomainValidationBeSkipped($skipDomainValidation)) {
error_log('Skipping domain validation'); error_log('Skipping domain validation');
} else { } else {
/** @var string */
$dnsRecordIP = gethostbyname($domain); $dnsRecordIP = gethostbyname($domain);
if ($dnsRecordIP === $domain) { if ($dnsRecordIP === $domain) {
$dnsRecordIP = ''; $dnsRecordIP = '';
} }
if (empty($dnsRecordIP)) { if (empty($dnsRecordIP)) {
/** @var array */
$record = dns_get_record($domain, DNS_AAAA); $record = dns_get_record($domain, DNS_AAAA);
if (isset($record[0]['ipv6']) && !empty($record[0]['ipv6'])) { if (isset($record[0]['ipv6']) && !empty($record[0]['ipv6'])) {
$dnsRecordIP = $record[0]['ipv6']; $dnsRecordIP = (string) $record[0]['ipv6'];
} }
} }
@ -694,6 +698,7 @@ class ConfigurationManager
private function getEnvironmentalVariableOrConfig(string $envVariableName, string $configName, string $defaultValue) : string { private function getEnvironmentalVariableOrConfig(string $envVariableName, string $configName, string $defaultValue) : string {
$envVariableOutput = getenv($envVariableName); $envVariableOutput = getenv($envVariableName);
/** @var mixed */
$configValue = $this->get($configName, ''); $configValue = $this->get($configName, '');
if ($envVariableOutput === false) { if ($envVariableOutput === false) {
if ($configValue === '') { if ($configValue === '') {
@ -919,6 +924,7 @@ class ConfigurationManager
$dir = array_diff($dir, array('..', '.', 'readme.md')); $dir = array_diff($dir, array('..', '.', 'readme.md'));
foreach ($dir as $id) { foreach ($dir as $id) {
$filePath = DataConst::GetCommunityContainersDirectory() . '/' . $id . '/' . $id . '.json'; $filePath = DataConst::GetCommunityContainersDirectory() . '/' . $id . '/' . $id . '.json';
/** @psalm-var mixed $fileContents */
$fileContents = apcu_fetch($filePath); $fileContents = apcu_fetch($filePath);
if (!is_string($fileContents)) { if (!is_string($fileContents)) {
$fileContents = file_get_contents($filePath); $fileContents = file_get_contents($filePath);
@ -926,8 +932,11 @@ class ConfigurationManager
apcu_add($filePath, $fileContents); apcu_add($filePath, $fileContents);
} }
} }
/** @psalm-var array $json */
$json = is_string($fileContents) ? json_decode($fileContents, true, 512, JSON_THROW_ON_ERROR) : false; $json = is_string($fileContents) ? json_decode($fileContents, true, 512, JSON_THROW_ON_ERROR) : false;
if(is_array($json) && is_array($json['aio_services_v1'])) { if(isset($json['aio_services_v1']) && is_array($json['aio_services_v1'])) {
/** @psalm-var array $service */
foreach ($json['aio_services_v1'] as $service) { foreach ($json['aio_services_v1'] as $service) {
$documentation = is_string($service['documentation']) ? $service['documentation'] : ''; $documentation = is_string($service['documentation']) ? $service['documentation'] : '';
if (is_string($service['display_name'])) { if (is_string($service['display_name'])) {
@ -960,8 +969,9 @@ class ConfigurationManager
return; return;
} }
$this->startTransaction(); $this->startTransaction();
/** @psalm-var string $variable */
foreach ($input as $variable) { foreach ($input as $variable) {
if (!is_string($variable) || !str_contains($variable, '=')) { if (!str_contains($variable, '=')) {
error_log("Invalid input: '$variable' is not a string or does not contain an equal sign ('=')"); error_log("Invalid input: '$variable' is not a string or does not contain an equal sign ('=')");
continue; continue;
} }

View file

@ -54,6 +54,7 @@ readonly class DockerActionManager {
throw $e; throw $e;
} }
/** @var array */
$responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); $responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR);
if ($responseBody['State']['Running'] === true) { if ($responseBody['State']['Running'] === true) {
@ -74,6 +75,7 @@ readonly class DockerActionManager {
throw $e; throw $e;
} }
/** @var array */
$responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); $responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR);
if ($responseBody['State']['Restarting'] === true) { if ($responseBody['State']['Restarting'] === true) {
@ -98,6 +100,7 @@ readonly class DockerActionManager {
return VersionState::Equal; return VersionState::Equal;
} }
/** @psalm-var string $runningDigest */
foreach ($runningDigests as $runningDigest) { foreach ($runningDigests as $runningDigest) {
if ($runningDigest === $remoteDigest) { if ($runningDigest === $remoteDigest) {
return VersionState::Equal; return VersionState::Equal;
@ -334,6 +337,7 @@ readonly class DockerActionManager {
} }
$tmpfs = []; $tmpfs = [];
/** @psalm-var string $tmp */
foreach ($container->tmpfs as $tmp) { foreach ($container->tmpfs as $tmp) {
$mode = ""; $mode = "";
if (str_contains($tmp, ':')) { if (str_contains($tmp, ':')) {
@ -374,6 +378,7 @@ readonly class DockerActionManager {
// Special things for the backup container which should not be exposed in the containers.json // Special things for the backup container which should not be exposed in the containers.json
if (str_starts_with($container->identifier, 'nextcloud-aio-borgbackup')) { if (str_starts_with($container->identifier, 'nextcloud-aio-borgbackup')) {
// Additional backup directories // Additional backup directories
/** @psalm-var string $additionalBackupVolumes */
foreach ($this->getAllBackupVolumes() as $additionalBackupVolumes) { foreach ($this->getAllBackupVolumes() as $additionalBackupVolumes) {
if ($additionalBackupVolumes !== '') { if ($additionalBackupVolumes !== '') {
$mounts[] = ["Type" => "volume", "Source" => $additionalBackupVolumes, "Target" => "/nextcloud_aio_volumes/" . $additionalBackupVolumes, "ReadOnly" => false]; $mounts[] = ["Type" => "volume", "Source" => $additionalBackupVolumes, "Target" => "/nextcloud_aio_volumes/" . $additionalBackupVolumes, "ReadOnly" => false];
@ -383,6 +388,7 @@ readonly class DockerActionManager {
// Make volumes read only in case of borgbackup container. The viewer makes them writeable // Make volumes read only in case of borgbackup container. The viewer makes them writeable
$isReadOnly = $container->identifier === 'nextcloud-aio-borgbackup'; $isReadOnly = $container->identifier === 'nextcloud-aio-borgbackup';
/** @psalm-var string $additionalBackupDirectories */
foreach ($this->configurationManager->getAdditionalBackupDirectoriesArray() as $additionalBackupDirectories) { foreach ($this->configurationManager->getAdditionalBackupDirectoriesArray() as $additionalBackupDirectories) {
if ($additionalBackupDirectories !== '') { if ($additionalBackupDirectories !== '') {
if (!str_starts_with($additionalBackupDirectories, '/')) { if (!str_starts_with($additionalBackupDirectories, '/')) {
@ -578,6 +584,7 @@ readonly class DockerActionManager {
$container = $this->containerDefinitionFetcher->GetContainerById($id); $container = $this->containerDefinitionFetcher->GetContainerById($id);
$nextcloudExecCommands = ''; $nextcloudExecCommands = '';
/** @psalm-var string $execCommand */
foreach ($container->nextcloudExecCommands as $execCommand) { foreach ($container->nextcloudExecCommands as $execCommand) {
$nextcloudExecCommands .= $execCommand . PHP_EOL; $nextcloudExecCommands .= $execCommand . PHP_EOL;
} }
@ -595,10 +602,12 @@ readonly class DockerActionManager {
private function GetRepoDigestsOfContainer(string $containerName): ?array { private function GetRepoDigestsOfContainer(string $containerName): ?array {
try { try {
$containerUrl = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); $containerUrl = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
/** @var array */
$containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $containerOutput = json_decode($this->guzzleClient->get($containerUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
$imageName = $containerOutput['Image']; $imageName = (string) $containerOutput['Image'];
$imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName)); $imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName));
/** @var array */
$imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
if (!isset($imageOutput['RepoDigests'])) { if (!isset($imageOutput['RepoDigests'])) {
@ -613,6 +622,7 @@ readonly class DockerActionManager {
$repoDigestArray = []; $repoDigestArray = [];
$oneDigestGiven = false; $oneDigestGiven = false;
/** @psalm-var string $repoDigest */
foreach ($imageOutput['RepoDigests'] as $repoDigest) { foreach ($imageOutput['RepoDigests'] as $repoDigest) {
$digestPosition = strpos($repoDigest, '@'); $digestPosition = strpos($repoDigest, '@');
if ($digestPosition === false) { if ($digestPosition === false) {
@ -635,6 +645,7 @@ readonly class DockerActionManager {
private function GetCurrentImageName(): string { private function GetCurrentImageName(): string {
$cacheKey = 'aio-image-name'; $cacheKey = 'aio-image-name';
/** @psalm-var mixed $imageName */
$imageName = apcu_fetch($cacheKey); $imageName = apcu_fetch($cacheKey);
if ($imageName !== false && is_string($imageName)) { if ($imageName !== false && is_string($imageName)) {
return $imageName; return $imageName;
@ -643,13 +654,14 @@ readonly class DockerActionManager {
$containerName = 'nextcloud-aio-mastercontainer'; $containerName = 'nextcloud-aio-mastercontainer';
$url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); $url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
try { try {
/** @var array */
$output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
$imageNameArray = explode(':', $output['Config']['Image']); $imageNameArray = explode(':', $output['Config']['Image']);
if (count($imageNameArray) === 2) { if (count($imageNameArray) === 2) {
$imageName = $imageNameArray[0]; $imageName = $imageNameArray[0];
} else { } else {
error_log("No tag was found when getting the current channel. You probably did not follow the documentation correctly. Changing the imageName to the default " . $output['Config']['Image']); error_log("No tag was found when getting the current channel. You probably did not follow the documentation correctly. Changing the imageName to the default " . $output['Config']['Image']);
$imageName = $output['Config']['Image']; $imageName = (string) $output['Config']['Image'];
} }
apcu_add($cacheKey, $imageName); apcu_add($cacheKey, $imageName);
return $imageName; return $imageName;
@ -662,6 +674,7 @@ readonly class DockerActionManager {
public function GetCurrentChannel(): string { public function GetCurrentChannel(): string {
$cacheKey = 'aio-ChannelName'; $cacheKey = 'aio-ChannelName';
/** @psalm-var mixed $channelName */
$channelName = apcu_fetch($cacheKey); $channelName = apcu_fetch($cacheKey);
if ($channelName !== false && is_string($channelName)) { if ($channelName !== false && is_string($channelName)) {
return $channelName; return $channelName;
@ -670,6 +683,7 @@ readonly class DockerActionManager {
$containerName = 'nextcloud-aio-mastercontainer'; $containerName = 'nextcloud-aio-mastercontainer';
$url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName)); $url = $this->BuildApiUrl(sprintf('containers/%s/json', $containerName));
try { try {
/** @var array */
$output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $output = json_decode($this->guzzleClient->get($url)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
$tagArray = explode(':', $output['Config']['Image']); $tagArray = explode(':', $output['Config']['Image']);
if (count($tagArray) === 2) { if (count($tagArray) === 2) {
@ -702,6 +716,7 @@ readonly class DockerActionManager {
return false; return false;
} }
/** @psalm-var string $runningDigest */
foreach ($runningDigests as $runningDigest) { foreach ($runningDigests as $runningDigest) {
if ($remoteDigest === $runningDigest) { if ($remoteDigest === $runningDigest) {
return false; return false;
@ -717,6 +732,7 @@ readonly class DockerActionManager {
// schedule the exec // schedule the exec
$url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName))); $url = $this->BuildApiUrl(sprintf('containers/%s/exec', urlencode($containerName)));
/** @var array */
$response = json_decode( $response = json_decode(
$this->guzzleClient->request( $this->guzzleClient->request(
'POST', 'POST',
@ -739,7 +755,7 @@ readonly class DockerActionManager {
JSON_THROW_ON_ERROR, JSON_THROW_ON_ERROR,
); );
$id = $response['Id']; $id = (string) $response['Id'];
// start the exec // start the exec
$url = $this->BuildApiUrl(sprintf('exec/%s/start', $id)); $url = $this->BuildApiUrl(sprintf('exec/%s/start', $id));
@ -878,8 +894,10 @@ readonly class DockerActionManager {
throw $e; throw $e;
} }
/** @var array */
$responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); $responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR);
/** @var null|int */
$exitCode = $responseBody['State']['ExitCode']; $exitCode = $responseBody['State']['ExitCode'];
if (is_int($exitCode)) { if (is_int($exitCode)) {
return $exitCode; return $exitCode;
@ -900,8 +918,10 @@ readonly class DockerActionManager {
throw $e; throw $e;
} }
/** @var array */
$responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR); $responseBody = json_decode((string)$response->getBody(), true, 512, JSON_THROW_ON_ERROR);
/** @var null|int */
$exitCode = $responseBody['State']['ExitCode']; $exitCode = $responseBody['State']['ExitCode'];
if (is_int($exitCode)) { if (is_int($exitCode)) {
return $exitCode; return $exitCode;
@ -932,6 +952,7 @@ readonly class DockerActionManager {
$imageName = $imageName . ':' . $this->GetCurrentChannel(); $imageName = $imageName . ':' . $this->GetCurrentChannel();
try { try {
$imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName)); $imageUrl = $this->BuildApiUrl(sprintf('images/%s/json', $imageName));
/** @var array */
$imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); $imageOutput = json_decode($this->guzzleClient->get($imageUrl)->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
if (!isset($imageOutput['Created'])) { if (!isset($imageOutput['Created'])) {

View file

@ -17,6 +17,7 @@ readonly class DockerHubManager {
public function GetLatestDigestOfTag(string $name, string $tag) : ?string { public function GetLatestDigestOfTag(string $name, string $tag) : ?string {
$cacheKey = 'dockerhub-manifest-' . $name . $tag; $cacheKey = 'dockerhub-manifest-' . $name . $tag;
/** @psalm-var mixed $cachedVersion */
$cachedVersion = apcu_fetch($cacheKey); $cachedVersion = apcu_fetch($cacheKey);
if($cachedVersion !== false && is_string($cachedVersion)) { if($cachedVersion !== false && is_string($cachedVersion)) {
return $cachedVersion; return $cachedVersion;
@ -30,9 +31,10 @@ readonly class DockerHubManager {
'https://auth.docker.io/token?service=registry.docker.io&scope=repository:' . $name . ':pull' 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:' . $name . ':pull'
); );
$body = $authTokenRequest->getBody()->getContents(); $body = $authTokenRequest->getBody()->getContents();
/** @var array */
$decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR); $decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if(isset($decodedBody['token'])) { if(isset($decodedBody['token'])) {
$authToken = $decodedBody['token']; $authToken = (string) $decodedBody['token'];
$manifestRequest = $this->guzzleClient->request( $manifestRequest = $this->guzzleClient->request(
'HEAD', 'HEAD',
'https://registry-1.docker.io/v2/'.$name.'/manifests/' . $tag, 'https://registry-1.docker.io/v2/'.$name.'/manifests/' . $tag,

View file

@ -18,6 +18,7 @@ readonly class GitHubContainerRegistryManager
{ {
$cacheKey = 'ghcr-manifest-' . $name . $tag; $cacheKey = 'ghcr-manifest-' . $name . $tag;
/** @psalm-var mixed $cachedVersion */
$cachedVersion = apcu_fetch($cacheKey); $cachedVersion = apcu_fetch($cacheKey);
if ($cachedVersion !== false && is_string($cachedVersion)) { if ($cachedVersion !== false && is_string($cachedVersion)) {
return $cachedVersion; return $cachedVersion;
@ -31,9 +32,10 @@ readonly class GitHubContainerRegistryManager
'https://ghcr.io/token?scope=repository:' . $name . ':pull' 'https://ghcr.io/token?scope=repository:' . $name . ':pull'
); );
$body = $authTokenRequest->getBody()->getContents(); $body = $authTokenRequest->getBody()->getContents();
/** @var array */
$decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR); $decodedBody = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if (isset($decodedBody['token'])) { if (isset($decodedBody['token'])) {
$authToken = $decodedBody['token']; $authToken = (string) $decodedBody['token'];
$manifestRequest = $this->guzzleClient->request( $manifestRequest = $this->guzzleClient->request(
'HEAD', 'HEAD',
'https://ghcr.io/v2/' . $name . '/manifests/' . $tag, 'https://ghcr.io/v2/' . $name . '/manifests/' . $tag,