From 9c464bcf1d8874129c76d4c0e5d770defb3b48ef Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 20 Jan 2026 13:28:35 -0500 Subject: [PATCH 1/2] refactor(app/public): tidy up bootstrapper/main endpoint Signed-off-by: Josh --- php/public/index.php | 244 ++++++++++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 96 deletions(-) diff --git a/php/public/index.php b/php/public/index.php index b57f65a5..10ad662f 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -1,148 +1,205 @@ get(\AIO\Data\DataConst::class); ini_set('session.save_path', $dataConst->GetSessionDirectory()); -// Auto logout on browser close -ini_set('session.cookie_lifetime', '0'); - -# Keep session for 24h max -ini_set('session.gc_maxlifetime', '86400'); - -// Create app -AppFactory::setContainer($container); +//------------------------------------------------- +// Application Creation and Core Middleware +//------------------------------------------------- $app = AppFactory::create(); $responseFactory = $app->getResponseFactory(); -// Register Middleware On Container -$container->set(Guard::class, function () use ($responseFactory) { +$container->set(Guard::class, function () use ($responseFactory): Guard { $guard = new Guard($responseFactory); $guard->setPersistentTokenMode(true); return $guard; }); -// Register Middleware To Be Executed On All Routes session_start(); $app->add(Guard::class); -// Create Twig -$twig = Twig::create(__DIR__ . '/../templates/', ['cache' => false]); +$twig = Twig::create(__DIR__ . '/../templates/', + [ 'cache' => AIO_TWIG_CACHE_PATH ] +); $app->add(TwigMiddleware::create($app, $twig)); + $twig->addExtension(new \AIO\Twig\CsrfExtension($container->get(Guard::class))); -// Auth Middleware $app->add(new \AIO\Middleware\AuthMiddleware($container->get(\AIO\Auth\AuthManager::class))); -// API -$app->post('/api/docker/watchtower', AIO\Controller\DockerController::class . ':StartWatchtowerContainer'); -$app->get('/api/docker/getwatchtower', AIO\Controller\DockerController::class . ':StartWatchtowerContainer'); -$app->post('/api/docker/start', AIO\Controller\DockerController::class . ':StartContainer'); -$app->post('/api/docker/backup', AIO\Controller\DockerController::class . ':StartBackupContainerBackup'); -$app->post('/api/docker/backup-check', AIO\Controller\DockerController::class . ':StartBackupContainerCheck'); -$app->post('/api/docker/backup-list', AIO\Controller\DockerController::class . ':StartBackupContainerList'); -$app->post('/api/docker/backup-check-repair', AIO\Controller\DockerController::class . ':StartBackupContainerCheckRepair'); -$app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . ':StartBackupContainerTest'); -$app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore'); -$app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer'); -$app->get('/api/docker/logs', AIO\Controller\DockerController::class . ':GetLogs'); -$app->post('/api/auth/login', AIO\Controller\LoginController::class . ':TryLogin'); -$app->get('/api/auth/getlogin', AIO\Controller\LoginController::class . ':GetTryLogin'); -$app->post('/api/auth/logout', AIO\Controller\LoginController::class . ':Logout'); -$app->post('/api/configuration', \AIO\Controller\ConfigurationController::class . ':SetConfig'); +//------------------------------------------------- +// API Routes +//------------------------------------------------- +$app->group('/api/docker', function (RouteCollectorProxy $group): void { + // Docker Container management + $group->post('/watchtower', AIO\Controller\DockerController::class . ':StartWatchtowerContainer'); + $group->get('/getwatchtower', AIO\Controller\DockerController::class . ':StartWatchtowerContainer'); + $group->post('/start', AIO\Controller\DockerController::class . ':StartContainer'); + $group->post('/stop', AIO\Controller\DockerController::class . ':StopContainer'); + $group->get('/logs', AIO\Controller\DockerController::class . ':GetLogs'); + // Backups + $group->post('/backup', AIO\Controller\DockerController::class . ':StartBackupContainerBackup'); + $group->post('/backup-check', AIO\Controller\DockerController::class . ':StartBackupContainerCheck'); + $group->post('/backup-list', AIO\Controller\DockerController::class . ':StartBackupContainerList'); + $group->post('/backup-check-repair', AIO\Controller\DockerController::class . ':StartBackupContainerCheckRepair'); + $group->post('/backup-test', AIO\Controller\DockerController::class . ':StartBackupContainerTest'); + $group->post('/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore'); +}); -// Views -$app->get('/containers', function (Request $request, Response $response, array $args) use ($container) { +// Auth-related +$app->group('/api/auth', function (RouteCollectorProxy $group): void { + $group->post('/login', AIO\Controller\LoginController::class . ':TryLogin'); + $group->get('/getlogin', AIO\Controller\LoginController::class . ':GetTryLogin'); + $group->post('/logout', AIO\Controller\LoginController::class . ':Logout'); +}); + +// Configuration endpoints +$app->post('/api/configuration', AIO\Controller\ConfigurationController::class . ':SetConfig'); + +//------------------------------------------------- +// Views Routes +//------------------------------------------------- + +// Containers +$app->get('/containers', function (Request $request, Response $response, array $args) use ($container): Response { $view = Twig::fromRequest($request); $view->addExtension(new \AIO\Twig\ClassExtension()); + /** @var \AIO\Data\ConfigurationManager $configurationManager */ $configurationManager = $container->get(\AIO\Data\ConfigurationManager::class); /** @var \AIO\Docker\DockerActionManager $dockerActionManager */ $dockerActionManager = $container->get(\AIO\Docker\DockerActionManager::class); /** @var \AIO\Controller\DockerController $dockerController */ $dockerController = $container->get(\AIO\Controller\DockerController::class); + + // Ensure master container is attached to the required Docker network $dockerActionManager->ConnectMasterContainerToNetwork(); + // Ensure the domaincheck container is started for domain configuration validation $dockerController->StartDomaincheckContainer(); - // Check if bypass_mastercontainer_update is provided on the URL, a special developer mode to bypass a mastercontainer update and use local image. + // URL parameters $params = $request->getQueryParams(); - $bypass_mastercontainer_update = isset($params['bypass_mastercontainer_update']); - $bypass_container_update = isset($params['bypass_container_update']); - $skip_domain_validation = isset($params['skip_domain_validation']); + $bypassMastercontainerUpdate = isset($params['bypass_mastercontainer_update']); + $bypassContainerUpdate = isset($params['bypass_container_update']); + $skipDomainValidation = isset($params['skip_domain_validation']); return $view->render($response, 'containers.twig', [ + // === Instance & Domain Information === 'domain' => $configurationManager->GetDomain(), + 'timezone' => $configurationManager->GetTimezone(), + 'current_channel' => $dockerActionManager->GetCurrentChannel(), + + // ---- Ports & Networking ---- 'apache_port' => $configurationManager->GetApachePort(), + 'talk_port' => $configurationManager->GetTalkPort(), + + // ---- Containers & Status ---- + 'containers' => (new \AIO\ContainerDefinitionFetcher($configurationManager, $container))->FetchDefinition(), + 'is_mastercontainer_update_available' => $bypassMastercontainerUpdate ? false : $dockerActionManager->IsMastercontainerUpdateAvailable(), + 'was_start_button_clicked' => $configurationManager->wasStartButtonClicked(), + 'has_update_available' => $dockerActionManager->isAnyUpdateAvailable(), + 'automatic_updates' => $configurationManager->areAutomaticUpdatesEnabled(), + + // ---- Backups ---- 'borg_backup_host_location' => $configurationManager->GetBorgBackupHostLocation(), 'borg_remote_repo' => $configurationManager->GetBorgRemoteRepo(), 'borg_public_key' => $configurationManager->GetBorgPublicKey(), - 'nextcloud_password' => $configurationManager->GetAndGenerateSecret('NEXTCLOUD_PASSWORD'), - 'containers' => (new \AIO\ContainerDefinitionFetcher($container->get(\AIO\Data\ConfigurationManager::class), $container))->FetchDefinition(), 'borgbackup_password' => $configurationManager->GetAndGenerateSecret('BORGBACKUP_PASSWORD'), - 'is_mastercontainer_update_available' => ( $bypass_mastercontainer_update ? false : $dockerActionManager->IsMastercontainerUpdateAvailable() ), 'has_backup_run_once' => $configurationManager->hasBackupRunOnce(), 'is_backup_container_running' => $dockerActionManager->isBackupContainerRunning(), 'backup_exit_code' => $dockerActionManager->GetBackupcontainerExitCode(), 'is_instance_restore_attempt' => $configurationManager->isInstanceRestoreAttempt(), 'borg_backup_mode' => $configurationManager->GetBackupMode(), - 'was_start_button_clicked' => $configurationManager->wasStartButtonClicked(), - 'has_update_available' => $dockerActionManager->isAnyUpdateAvailable(), 'last_backup_time' => $configurationManager->GetLastBackupTime(), 'backup_times' => $configurationManager->GetBackupTimes(), - 'current_channel' => $dockerActionManager->GetCurrentChannel(), - 'is_clamav_enabled' => $configurationManager->isClamavEnabled(), - 'is_onlyoffice_enabled' => $configurationManager->isOnlyofficeEnabled(), - 'is_collabora_enabled' => $configurationManager->isCollaboraEnabled(), - 'is_talk_enabled' => $configurationManager->isTalkEnabled(), 'borg_restore_password' => $configurationManager->GetBorgRestorePassword(), 'daily_backup_time' => $configurationManager->GetDailyBackupTime(), 'is_daily_backup_running' => $configurationManager->isDailyBackupRunning(), - 'timezone' => $configurationManager->GetTimezone(), - 'skip_domain_validation' => $configurationManager->shouldDomainValidationBeSkipped($skip_domain_validation), - 'talk_port' => $configurationManager->GetTalkPort(), - 'collabora_dictionaries' => $configurationManager->GetCollaboraDictionaries(), - 'collabora_additional_options' => $configurationManager->GetAdditionalCollaboraOptions(), - 'automatic_updates' => $configurationManager->areAutomaticUpdatesEnabled(), - 'is_backup_section_enabled' => $configurationManager->isBackupSectionEnabled(), - 'is_imaginary_enabled' => $configurationManager->isImaginaryEnabled(), - 'is_fulltextsearch_enabled' => $configurationManager->isFulltextsearchEnabled(), 'additional_backup_directories' => $configurationManager->GetAdditionalBackupDirectoriesString(), + + // ---- Nextcloud and PHP Config ---- + 'nextcloud_password' => $configurationManager->GetAndGenerateSecret('NEXTCLOUD_PASSWORD'), 'nextcloud_datadir' => $configurationManager->GetNextcloudDatadirMount(), 'nextcloud_mount' => $configurationManager->GetNextcloudMount(), 'nextcloud_upload_limit' => $configurationManager->GetNextcloudUploadLimit(), 'nextcloud_max_time' => $configurationManager->GetNextcloudMaxTime(), 'nextcloud_memory_limit' => $configurationManager->GetNextcloudMemoryLimit(), + + // ---- Feature/App Enablement ---- + 'is_clamav_enabled' => $configurationManager->isClamavEnabled(), + 'is_onlyoffice_enabled' => $configurationManager->isOnlyofficeEnabled(), + 'is_collabora_enabled' => $configurationManager->isCollaboraEnabled(), + 'is_talk_enabled' => $configurationManager->isTalkEnabled(), + 'is_backup_section_enabled' => $configurationManager->isBackupSectionEnabled(), + 'is_imaginary_enabled' => $configurationManager->isImaginaryEnabled(), + 'is_fulltextsearch_enabled' => $configurationManager->isFulltextsearchEnabled(), 'is_dri_device_enabled' => $configurationManager->isDriDeviceEnabled(), 'is_nvidia_gpu_enabled' => $configurationManager->isNvidiaGpuEnabled(), 'is_talk_recording_enabled' => $configurationManager->isTalkRecordingEnabled(), 'is_docker_socket_proxy_enabled' => $configurationManager->isDockerSocketProxyEnabled(), 'is_whiteboard_enabled' => $configurationManager->isWhiteboardEnabled(), + + // ---- Integration-specific Customization ---- + 'collabora_dictionaries' => $configurationManager->GetCollaboraDictionaries(), + 'collabora_additional_options' => $configurationManager->GetAdditionalCollaboraOptions(), + + // ---- Community Containers ---- 'community_containers' => $configurationManager->listAvailableCommunityContainers(), 'community_containers_enabled' => $configurationManager->GetEnabledCommunityContainers(), - 'bypass_container_update' => $bypass_container_update, + + // ---- Misc Flags ---- + 'skip_domain_validation' => $configurationManager->shouldDomainValidationBeSkipped($skipDomainValidation), + 'bypass_container_update' => $bypassContainerUpdate, ]); -})->setName('profile'); -$app->get('/login', function (Request $request, Response $response, array $args) use ($container) { +}); + +// Login +$app->get('/login', function (Request $request, Response $response, array $args) use ($container): Response { $view = Twig::fromRequest($request); /** @var \AIO\Docker\DockerActionManager $dockerActionManager */ $dockerActionManager = $container->get(\AIO\Docker\DockerActionManager::class); @@ -150,51 +207,46 @@ $app->get('/login', function (Request $request, Response $response, array $args) 'is_login_allowed' => $dockerActionManager->isLoginAllowed(), ]); }); -$app->get('/setup', function (Request $request, Response $response, array $args) use ($container) { + +// Setup +$app->get('/setup', function (Request $request, Response $response, array $args) use ($container): Response { $view = Twig::fromRequest($request); /** @var \AIO\Data\Setup $setup */ $setup = $container->get(\AIO\Data\Setup::class); - if(!$setup->CanBeInstalled()) { - return $view->render( - $response, - 'already-installed.twig' - ); + if (!$setup->CanBeInstalled()) { + return $view->render($response, 'already-installed.twig'); } - - return $view->render( - $response, - 'setup.twig', - [ + return $view->render($response, 'setup.twig', [ 'password' => $setup->Setup(), - ] - ); + ]); }); -// Auth Redirector -$app->get('/', function (\Psr\Http\Message\RequestInterface $request, Response $response, array $args) use ($container) { +//------------------------------------------------- +// Root Redirector +//------------------------------------------------- +$app->get('/', function (Request $request, Response $response, array $args) use ($container): Response { /** @var \AIO\Auth\AuthManager $authManager */ $authManager = $container->get(\AIO\Auth\AuthManager::class); - /** @var \AIO\Data\Setup $setup */ $setup = $container->get(\AIO\Data\Setup::class); - if($setup->CanBeInstalled()) { - return $response - ->withHeader('Location', 'setup') - ->withStatus(302); - } - if($authManager->IsAuthenticated()) { - return $response - ->withHeader('Location', 'containers') - ->withStatus(302); - } else { - return $response - ->withHeader('Location', 'login') - ->withStatus(302); + if ($setup->CanBeInstalled()) { + return $response->withHeader('Location', 'setup')->withStatus(302); } + if ($authManager->IsAuthenticated()) { + return $response->withHeader('Location', 'containers')->withStatus(302); + } + return $response->withHeader('Location', 'login')->withStatus(302); }); -$errorMiddleware = $app->addErrorMiddleware(false, true, true); +//------------------------------------------------- +// Error Middleware +//------------------------------------------------- + +// TODO: Figure out why the default plain text renderer is being used by logging +// TODO: Change logging to not generate stack traces for 404s +// TODO: Change logging to log the path +$errorMiddleware = $app->addErrorMiddleware(AIO_DISPLAY_ERRORS, true, true); $app->run(); From 4a2d5bbfda047ca9d4466c5c6cfb4520755af89e Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 20 Jan 2026 13:50:05 -0500 Subject: [PATCH 2/2] chore: some additional refvisions Signed-off-by: Josh --- php/public/index.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/php/public/index.php b/php/public/index.php index 10ad662f..102a9fd9 100644 --- a/php/public/index.php +++ b/php/public/index.php @@ -25,7 +25,6 @@ const AIO_MAX_EXECUTION_TIME = '7200'; // (2h) in case of a very slow inter const AIO_SESSION_MAX_LIFETIME = '86400'; // (24h) const AIO_COOKIE_LIFETIME = '0'; // Auto logout on browser close const AIO_LOG_ERRORS_MAX_LEN = '0'; // Log whole log messages - const AIO_TWIG_CACHE_PATH = false; // e.g., __DIR__ . '/../var/twig-cache' const AIO_DISPLAY_ERRORS = false; @@ -54,6 +53,7 @@ ini_set('session.save_path', $dataConst->GetSessionDirectory()); $app = AppFactory::create(); $responseFactory = $app->getResponseFactory(); +// Register CSRF middleware (container-only) $container->set(Guard::class, function () use ($responseFactory): Guard { $guard = new Guard($responseFactory); $guard->setPersistentTokenMode(true); @@ -61,15 +61,20 @@ $container->set(Guard::class, function () use ($responseFactory): Guard { }); session_start(); + +// Activate CSRF middleware for all routes $app->add(Guard::class); +// Setup and activate Twig middleware $twig = Twig::create(__DIR__ . '/../templates/', [ 'cache' => AIO_TWIG_CACHE_PATH ] ); $app->add(TwigMiddleware::create($app, $twig)); +// Add CSRF extension to Twig so templates can access CSRF tokens $twig->addExtension(new \AIO\Twig\CsrfExtension($container->get(Guard::class))); +// Establish and activate authentication middleware for all routes $app->add(new \AIO\Middleware\AuthMiddleware($container->get(\AIO\Auth\AuthManager::class))); //-------------------------------------------------