Yandex Metrika
sanches.free

Монтирование SMB/CIFS из PHP: mount.cifs, файл учётных данных и размонтирование

Зачем монтировать из PHP

Иногда нужно прочитать или разложить файлы на сетевой шаре без постоянного «ручного» mount на сервере: разовый импорт, синхронизация каталога из SMB, служебный скрипт на том же хосте, что и 1С‑Битрикс. Обертка в классе позволяет создать точку монтирования под конкретный UNC‑путь, подставить логин и пароль через файл опций и по завершении работы снять монтирование, чтобы не оставлять лишние записи в /proc/mounts.

Что должно быть на стороне ОС

На Linux обычно нужен пакет с mount.cifs (семейство cifs-utils). Пользователь, от которого крутится PHP‑процесс, должен иметь право вызывать монтирование — часто это root или настроенный sudo на конкретную команду; без этого PHP получит ошибку даже при корректном коде.

Схема работы

UNC вида //server/share/path превращается в локальную папку под выбранным базовым каталогом. Имя подкаталога можно привязать к хэшу строки шары, чтобы не пересекаться с другими задачами. Учётные данные пишутся во временный файл в формате ключ=значение построчно; в опциях монтирования указывается credentials=…. Для сценария «только чтение» разумно добавить ro. После работы — umount, удаление пустой точки и файла с паролем.

Пример класса

Ниже — самодостаточный каркас: конструктор принимает путь к шаре и массив строк для credentials, метод attach() создаёт монтирование, detach() снимает его. Деструктор вызывает detach(), чтобы не забыть размонтировать при unset или выходе из блока.

<?php

class NetworkShareMount {
  private string $uncPath;
  /** @var string[] */
  private array $credentialLines;
  private bool $verbose;
  private ?string $baseDir = null;
  private ?string $localMount = null;
  private ?string $secretFile = null;

  public function __construct(string $uncPath, array $credentialLines, bool $verbose = false) {
    $this->uncPath = $uncPath;
    $this->credentialLines = $credentialLines;
    $this->verbose = $verbose;
  }

  public function __destruct() {
    $this->detach();
  }

  /** @throws \Exception */
  public function attach(string $baseDir): self {
    $this->baseDir = rtrim($baseDir, '/');
    $token = md5($this->uncPath);
    $this->localMount = $this->baseDir . '/' . $token;
    $this->secretFile = $this->baseDir . '/' . $token . '.cred';

    if ($this->isActive()) {
      $msg = 'Уже смонтировано в ' . $this->localMount;
      $this->log($msg);
      throw new \Exception($msg, 1);
    }

    $this->performMount();
    $this->log('Смонтировано: ' . $this->localMount);
    return $this;
  }

  public function getLocalPath(): string {
    return (string) $this->localMount;
  }

  public function detach(): self {
    if (!$this->isActive()) {
      return $this;
    }
    exec(sprintf('umount %s', escapeshellarg($this->localMount)));
    @rmdir($this->localMount);
    if (is_file($this->secretFile)) {
      unlink($this->secretFile);
    }
    $this->log('Размонтировано: ' . $this->localMount);
    return $this;
  }

  private function isActive(): bool {
    return $this->localMount !== null && is_dir($this->localMount);
  }

  /** @throws \Exception */
  private function performMount(): void {
    $body = implode("\n", $this->credentialLines) . "\n";
    if (file_put_contents($this->secretFile, $body) === false) {
      throw new \Exception('Не удалось записать credentials: ' . $this->secretFile);
    }
    chmod($this->secretFile, 0600);
    mkdir($this->localMount, 0700, true);

    $cmd = sprintf(
      '/sbin/mount.cifs %s %s -o ro,credentials=%s && echo OK',
      escapeshellarg($this->uncPath),
      escapeshellarg($this->localMount),
      escapeshellarg($this->secretFile)
    );
    $out = exec($cmd);
    if (trim((string) $out) !== 'OK') {
      throw new \Exception('Ошибка mount.cifs: ' . $this->uncPath);
    }
  }

  private function log(string $text): void {
    if ($this->verbose) {
      echo $text . PHP_EOL;
    }
  }
}

$share = new NetworkShareMount(
  '//file-host/projects/',
  ['username=backup_reader', 'password=********', 'domain=WORKGROUP'],
  true
);

$mountPath = $share->attach(__DIR__)->getLocalPath();
// … работа с файлами в $mountPath …
$share->detach();

Безопасность и ограничения

Файл с паролем держите с узкими правами (0600), каталог для монтирования не публикуйте в веб. Проверка «смонтировано ли» только через is_dir — упрощение; в ответственных сценариях имеет смысл сверяться с /proc/mounts или выводом findmnt. Не храните боевые пароли в репозитории: подставляйте их из окружения или секрет-хранилища хостинга.

Не хотите копаться сами?

Починю за 1-3 дня. Без предоплаты — оплата по результату.

15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии