env('FILESYSTEM_DISK', 'local'), /* |-------------------------------------------------------------------------- | Filesystem Disks |-------------------------------------------------------------------------- | | Below you may configure as many filesystem disks as necessary, and you | may even configure multiple disks for the same driver. Examples for | most supported storage drivers are configured here for reference. | | Supported drivers: "local", "ftp", "sftp", "s3" | */ 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app/private'), 'serve' => true, 'throw' => false, 'report' => false, ], 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage', 'visibility' => 'public', 'throw' => false, 'report' => false, ], /* * Записи, которые клиент отдаёт нам для обучения робота обзвона * (задача З-2.3). 🔴 Это голоса живых людей, которые об этом не знают и * согласия нам не давали. * * Отдельный диск, а не папка внутри 'local', ровно по трём причинам: * • 'serve' => false — у 'local' стоит true, то есть Laravel отдаёт его * содержимое своим маршрутом. Чужим голосам такого маршрута не нужно; * • ключа 'url' здесь НЕТ намеренно; * • корень — под storage/app/private, наружу веб-сервером не отдаётся. * * 🔴 ИСПРАВЛЕНО 06.08.2026 (круг З-2.1). Здесь стояло «Storage::disk(...) * ->url() бросит исключение, и публичной ссылки на чужой голос не * появится даже по ошибке». Это НЕВЕРНО, и проверено живым запуском: * у местного драйвера `->url()` не бросает никогда, а молча возвращает * `/storage/7/golos.wav` (Laravel 13, FilesystemAdapter::getLocalUrl). * Ссылка ведёт в папку ПУБЛИЧНОГО диска, где материала нет, — она не * играет и ничего не выдаёт, — но обещанной защиты здесь не было. * Настоящая защита в том, что `->url()` не зовёт никто: единственная * дверь — MaterialyKlientaService::otkryt со следом в журнале ПДн. * * Внутри диска раскладка `/.<расширение>`: имя файла * неугадываемо, а принадлежность видна прямо в пути. * * 🪤 Диск НЕ заменяет защиту по строкам: путь — всего лишь строка. Кто * вправе открыть материал, решают RLS на obzvon_materialy_klienta и * App\Services\Obzvon\MaterialyKlientaService, а не файловая система. */ 'obzvon_materialy' => [ 'driver' => 'local', 'root' => storage_path('app/private/obzvon-materialy'), 'serve' => false, 'visibility' => 'private', 'throw' => false, 'report' => false, ], /* * Записи НАШИХ разговоров: робот позвонил человеку, разговор записан * (задача З-2.1). 🔴 Это голос живого человека, и портал дал про него два * обещания — «живёт месяц» и «потребуешь удалить — удалим». * * 🔴 ДО ЭТОЙ СТРОЧКИ ОБА ОБЕЩАНИЯ БЫЛИ БУМАЖНЫМИ. Чистка по сроку (З-2.2) * и стирание по требованию человека (З-2.5) были построены и обе ходили в * хранилище с именем `obzvon_zapisi`, которого здесь НЕ БЫЛО. Врать они не * врали — честно отвечали «не смог удалить файл», — но удалять им было * нечего. Имя менять нельзя: на него опираются оба и около двадцати мест в * проверках. * * 🔴 ДРАЙВЕР ПЕРЕКЛЮЧАЕТСЯ НАСТРОЙКОЙ, А НЕ ПРАВКОЙ КОДА. В чертеже * записано, что звук лежит в объектном хранилище с шифрованием: при трёхстах * разговорах в день это порядка 290 МБ в сутки на клиента и около 50 ГБ за * полгода. Но объектное хранилище — деньги владельца, и решения он не * принимал. Поэтому сегодня драйвер local, а в день покупки меняется ОДНА * переменная окружения OBZVON_ZAPISI_DRIVER=s3 и заполняются ключи ниже. * Код `App\Services\Obzvon\ObzvonZapisHranilishche` не трогается вовсе — он * не знает ни одного пути машины и ходит только через Storage. * * Закрытость — тремя вещами сразу, как у диска obzvon_materialy: * • 'serve' => false — Laravel НЕ регистрирует для диска маршрут отдачи * и НЕ умеет для него ссылок со сроком (замерено: * providesTemporaryUrls() = false). Ссылка со сроком в час появится * сама, когда драйвер станет s3: объектное хранилище это умеет; * • ключа 'url' НЕТ намеренно; * • корень под storage/app/private, наружу веб-сервером не отдаётся. * * 🔴 ЧЕГО ОТСУТСТВИЕ 'url' НЕ ДАЁТ — и это замерено, а не предположено. * Ждать, что `->url()` БРОСИТ, нельзя: у местного драйвера он не бросает * никогда, а молча возвращает `/storage/<путь>` (Laravel 13, * FilesystemAdapter::getLocalUrl). Ссылка при этом ведёт в папку ПУБЛИЧНОГО * диска, где записи нет, — то есть не играет и ничего не выдаёт, — но * защитой это не является. Защита в том, что `->url()` не зовёт НИКТО: * единственная дверь к звуку — ObzvonZapisHranilishche, и она пишет след в * журнал ПДн. * * Внутри раскладка `/<звонок>-.<расширение>`: имя * неугадываемо, принадлежность видна прямо в пути. * * 🪤 Диск НЕ заменяет защиту по строкам: путь — всего лишь строка, и * заполнять её будет чужая машина (отчёт робота, З-0.6). Кто вправе * открыть запись, решают RLS на obzvon_calls и ObzvonZapisHranilishche, * который сверяет и клиента, и сам путь. */ 'obzvon_zapisi' => [ 'driver' => env('OBZVON_ZAPISI_DRIVER', 'local'), // local 'root' => storage_path('app/private/obzvon-zapisi'), 'serve' => false, 'visibility' => 'private', // объектное хранилище — заполняется в день покупки, код не меняется 'key' => env('OBZVON_ZAPISI_KEY'), 'secret' => env('OBZVON_ZAPISI_SECRET'), 'region' => env('OBZVON_ZAPISI_REGION', 'ru-central1'), 'bucket' => env('OBZVON_ZAPISI_BUCKET'), 'endpoint' => env('OBZVON_ZAPISI_ENDPOINT'), 'use_path_style_endpoint' => env('OBZVON_ZAPISI_PATH_STYLE', false), 'throw' => false, 'report' => false, ], 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'throw' => false, 'report' => false, ], ], /* |-------------------------------------------------------------------------- | Symbolic Links |-------------------------------------------------------------------------- | | Here you may configure the symbolic links that will be created when the | `storage:link` Artisan command is executed. The array keys should be | the locations of the links and the values should be their targets. | */ 'links' => [ public_path('storage') => storage_path('app/public'), ], ];