From 0a54ad5485bb8b3d7aa4dbae5e7c4c078488ee66 Mon Sep 17 00:00:00 2001 From: ildyria Date: Mon, 22 Jun 2026 12:28:21 +0200 Subject: [PATCH] Fix shared hosting tmp issue --- .env.example | 4 ++++ .../Pipes/Checks/IniSettingsCheck.php | 6 +++--- app/Actions/InstallUpdate/DefaultConfig.php | 4 ++++ app/Assets/Helpers.php | 21 +++++++++++++++++++ app/Facades/Helpers.php | 1 + app/Image/Files/TemporaryLocalFile.php | 3 ++- app/Image/Handlers/ImagickHandler.php | 3 ++- config/features.php | 11 ++++++++++ 8 files changed, 48 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index a3c3e3dc142..401704630ab 100644 --- a/.env.example +++ b/.env.example @@ -321,6 +321,10 @@ VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" # DISABLE_IMPORT_FROM_SERVER=false +# On shared hosting where sys_get_temp_dir() is not readable/writable, +# set to false to use storage/tmp/uploads_parts instead. +# USE_SYSTEM_TEMP_DIR=true + # When enabled, the request caching settings (cache_enabled, cache_ttl, # cache_event_logging) become visible in the admin settings panel. # ENABLE_REQUEST_CACHING=false diff --git a/app/Actions/Diagnostics/Pipes/Checks/IniSettingsCheck.php b/app/Actions/Diagnostics/Pipes/Checks/IniSettingsCheck.php index 07f86676d8c..1ced6c3d4d3 100644 --- a/app/Actions/Diagnostics/Pipes/Checks/IniSettingsCheck.php +++ b/app/Actions/Diagnostics/Pipes/Checks/IniSettingsCheck.php @@ -133,15 +133,15 @@ public function handle(array &$data, \Closure $next): array } // @codeCoverageIgnoreEnd - $path = sys_get_temp_dir(); + $path = Helpers::getTmpDir(); if (!is_writable($path)) { // @codeCoverageIgnoreStart - $data[] = DiagnosticData::error('sys_get_temp_dir() is not writable, this will prevent you from uploading pictures.', self::class); + $data[] = DiagnosticData::error('The temp directory (' . $path . ') is not writable, this will prevent you from uploading pictures. Set USE_SYSTEM_TEMP_DIR=false in .env to use storage/tmp/uploads_parts instead.', self::class); // @codeCoverageIgnoreEnd } if (!is_readable($path)) { // @codeCoverageIgnoreStart - $data[] = DiagnosticData::error('sys_get_temp_dir() is not readable, this will prevent you from uploading pictures.', self::class); + $data[] = DiagnosticData::error('The temp directory (' . $path . ') is not readable, this will prevent you from uploading pictures. Set USE_SYSTEM_TEMP_DIR=false in .env to use storage/tmp/uploads_parts instead.', self::class); // @codeCoverageIgnoreEnd } diff --git a/app/Actions/InstallUpdate/DefaultConfig.php b/app/Actions/InstallUpdate/DefaultConfig.php index 0d5eaa0278d..51258ff869a 100644 --- a/app/Actions/InstallUpdate/DefaultConfig.php +++ b/app/Actions/InstallUpdate/DefaultConfig.php @@ -138,6 +138,10 @@ public function __construct() $this->config['requirements']['php'][] = $db_possibility[1]; } } + + if (config('features.use-system-temp-dir') === false) { + $this->config['permissions']['storage/tmp/uploads_parts/'] = 'file_exists|is_readable|is_writable|is_executable'; + } // @codeCoverageIgnoreStart } catch (BindingResolutionException|ContainerExceptionInterface $e) { throw new FrameworkException('Laravel\'s container component', $e); diff --git a/app/Assets/Helpers.php b/app/Assets/Helpers.php index b5f31556c97..348f5728dfc 100644 --- a/app/Assets/Helpers.php +++ b/app/Assets/Helpers.php @@ -14,6 +14,7 @@ use Safe\Exceptions\FilesystemException; use Safe\Exceptions\InfoException; use function Safe\ini_get; +use function Safe\mkdir; use function Safe\rmdir; use function Safe\unlink; @@ -42,6 +43,26 @@ public function trancateIf32(string $id, int $prev_short_id = 0, int $php_max = return (string) $short_id; } + /** + * Return the temporary directory path used for uploads. + * + * When features.use-system-temp-dir is true (default), returns sys_get_temp_dir(). + * Otherwise returns storage/tmp/uploads_parts, creating it if needed. + */ + public function getTmpDir(): string + { + if (config('features.use-system-temp-dir', true) === true) { + return sys_get_temp_dir(); + } + + $path = storage_path('tmp/uploads_parts'); + if (!file_exists($path)) { + mkdir($path, 0755, true); + } + + return $path; + } + /** * Check if $path has readable and writable permissions. */ diff --git a/app/Facades/Helpers.php b/app/Facades/Helpers.php index ae2798b0ab2..e2557ba0b8b 100644 --- a/app/Facades/Helpers.php +++ b/app/Facades/Helpers.php @@ -17,6 +17,7 @@ * * Keep the list of documented method in sync with {@link \App\Assets\Helpers}. * + * @method static string getTmpDir() * @method static string trancateIf32(string $id, int $prevShortId = 0, int $php_max) * @method static string getExtension(string $filename, bool $isURI = false) * @method static bool hasPermissions(string $path) diff --git a/app/Image/Files/TemporaryLocalFile.php b/app/Image/Files/TemporaryLocalFile.php index a0dd2785e87..ceebfc9b322 100644 --- a/app/Image/Files/TemporaryLocalFile.php +++ b/app/Image/Files/TemporaryLocalFile.php @@ -9,6 +9,7 @@ namespace App\Image\Files; use App\Exceptions\MediaFileOperationException; +use App\Facades\Helpers; /** * Class TemporaryLocalFile. @@ -53,7 +54,7 @@ public function __construct( */ protected function getFileBasePath(): string { - return sys_get_temp_dir(); + return Helpers::getTmpDir(); } /** diff --git a/app/Image/Handlers/ImagickHandler.php b/app/Image/Handlers/ImagickHandler.php index 5f08d3aaaf7..c05d38e3671 100644 --- a/app/Image/Handlers/ImagickHandler.php +++ b/app/Image/Handlers/ImagickHandler.php @@ -14,6 +14,7 @@ use App\DTO\ImageDimension; use App\Exceptions\ImageProcessingException; use App\Exceptions\MediaFileOperationException; +use App\Facades\Helpers; use App\Image\Files\FlysystemFile; use App\Image\Files\InMemoryBuffer; use App\Image\Files\NativeLocalFile; @@ -105,7 +106,7 @@ private function loadPdf(MediaFile $file): void // .pdf path so Ghostscript recognises the format by extension. The outer // try/finally guarantees the .pdf temp file is cleaned up even if fopen(), // stream_copy_to_stream(), or readImage() throws. - $tmp_base = tempnam(sys_get_temp_dir(), 'lychee_'); + $tmp_base = tempnam(Helpers::getTmpDir(), 'lychee_'); $tmp_path = $tmp_base . '.pdf'; rename($tmp_base, $tmp_path); $pdf_path = $tmp_path; diff --git a/config/features.php b/config/features.php index 6d2fc0e8f65..2a330d3d1fd 100644 --- a/config/features.php +++ b/config/features.php @@ -268,6 +268,17 @@ */ 'use_fopen_for_url_imports' => (bool) env('USE_FOPEN_FOR_URL_IMPORTS', false), + /* + |-------------------------------------------------------------------------- + | Use system temp directory for uploads + |-------------------------------------------------------------------------- + | + | When enabled, Lychee uses PHP's sys_get_temp_dir() for temporary upload + | files. On shared hosting where that directory is not readable/writable, + | set USE_SYSTEM_TEMP_DIR=false to use storage/tmp/uploads_parts instead. + */ + 'use-system-temp-dir' => (bool) env('USE_SYSTEM_TEMP_DIR', true), + /* |-------------------------------------------------------------------------- | Enable Request Caching