Давненько я тебя годнотой не радовал, чёт максимально увлекся прокачкой LF и сложно остановиться, а пока ты в потоке, лучше из него не выходить. Потому, что если выйти, то тебе пизда, сразу превращаешься в ленивое животное.

Сегодня я вроде основное закрыл, поэтому как говорится — если у тебя в детстве не было компьютера, то шанс, что ты вернешься домой живым, был 50 на 50.

Поехали чё! Сегодня про пиздатый и неочевидный Install.

Команда install универсальная и заменяет cp, chmod, chown и кусок bash-скрипта.

Есть команды, которыми ты пользуешься каждый день, а есть команды, о которых ты даже не знаешь. Но они тихо лежат как заброшенные, детские игрушки в твоём шкафу и ждут своего часа.

Обычно на install случайно натыкаешься в чужом Makefile и потом ты уже никогда не станешь прежним. Глаза открываются, сознание расширяется.

Пакеты эта штука не устанавливает. Для этого у нас с тобой есть apt, dnf, pacman, хуйня-муйня и другие способы испортить себе вечер.

Команда install гораздо скучнее, но очень полезная в автоматизации, скриптах. Она копирует файлы в нужное место и сразу выставляет им владельца, группу и права.

То есть такой привычный набор:

mkdir -p /usr/local/bin
cp ./my-app /usr/local/bin/my-app
chown root:root /usr/local/bin/my-app
chmod 0755 /usr/local/bin/my-app

можно заменить одной командой:

sudo install -D -o root -g root -m 0755 \
  ./my-app /usr/local/bin/my-app

Магии здесь немного, но точек, в которых можно проебаться, стало меньше. Путь создан, файл скопирован, владелец и права выставлены явно. Заебись? Да!

Поехали дальше, давай посмотрим что ещё умеет install и где оно удобнее. А самое главное посмотрим где можно наступить на хорошие такие грабли.

В GNU/Linux команда обычно приезжает в составе coreutils. Базовый синтаксис выглядит так:

install [опции] источник назначение

Самый простой пример

install ./backup.sh /usr/local/bin/backup

Файл backup.sh будет скопирован в /usr/local/bin/backup.

Вот здесь можем словить первый сюрприз и жопа-боль, бля пиздец!

Роман Шубин
Роман Шубин
CEO & CTO, Главред в «Цифровой улей»
Задать вопрос
Если не указать права явно, GNU install выставит режим 0755. Для скрипта или бинарника это почти всегда годится, а для конфига с паролем получится уже не деплой, а маленький внутренний пентест.

Поэтому сразу пишем с ключом -m, даже когда нужные права совпадают со значением по умолчанию:

# Исполняемый файл
install -m 0755 ./backup.sh /usr/local/bin/backup

# Обычный конфиг
install -m 0644 ./my-app.conf /etc/my-app/my-app.conf

# Файл с секретами
install -m 0600 ./my-app.env /etc/my-app/my-app.env

Через полгода не придётся вспоминать, почему один файл получился исполняемым, а другой внезапно читается всеми пользователями системы. Короче соблюдай стандарты и порядок, тогда не будешь бегать как выебанная макака и искать концы.

Владелец и группа

Для владельца есть -o, для группы -g, логично вроде…

sudo install \
  -o root \
  -g my-app \
  -m 0640 \
  ./my-app.conf \
  /etc/my-app/my-app.conf

На выходе получим примерно это:

-rw-r----- 1 root my-app ... /etc/my-app/my-app.conf
Роман Шубин
Роман Шубин
CEO & CTO, Главред в «Цифровой улей»
Задать вопрос
Произвольного владельца может назначить только привилегированный процесс, поэтому -o root под обычным пользователем закончится ошибкой. Если владелец и группа тебе важны, лучше задавать их явно, особенно в скриптах, которые запускаются через sudo, из systemd или в сборочном окружении.

Создание каталогов

Отдельный режим -d создаёт каталоги вместе с недостающими родителями, удобная хуйня!

sudo install -d \
  -o my-app \
  -g my-app \
  -m 0750 \
  /var/lib/my-app/cache

Выглядит как mkdir -p, к которому сразу прикрутили chown и chmod.

Но есть неочевидный момент. В GNU install режим из -m применяется к конечному каталогу, а недостающие родительские каталоги создаются с правами 0755, независимо от текущего umask и переданного -m. Везде блядь подводные камни, вечно усложняют простые вещи.

Например:

install -d -m 0700 /tmp/one/two/three

Каталог three получит 0700, но автоматически созданные one и two будут с 0755.

Если вся цепочка должна быть закрыта, создавай уровни отдельно и назначай права явно:

install -d -m 0700 /tmp/one
install -d -m 0700 /tmp/one/two
install -d -m 0700 /tmp/one/two/three

Для /usr/local/bin это обычно не имеет значения, а при подготовке каталогов с секретами уже имеет. Конечно если к ИИшке это подключить, оно само разберется и сделает правильно (при условии если это не локальная модель на 4кк).

Чем -D отличается от -d

Ха, да, такое тоже бывает. Эти ключи легко перепутать и въебаться в стену.

-d говорит, что все переданные аргументы являются каталогами и их нужно создать

install -d -m 0750 /etc/my-app

GNU-ключ -D используется при копировании файла. Он сначала создаёт недостающие каталоги в пути назначения, а затем устанавливает сам файл

install -D -m 0644 \
  ./my-app.conf \
  /etc/my-app/conf.d/my-app.conf

Без -D команда упадёт, если /etc/my-app/conf.d ещё не существует. С -D вся цепочка будет создана автоматически.

Для коротких linux-скриптов удобно. Для переносимых скриптов чуть ниже будет неприятный сюрприз, короче можно знатно обосраться.

Установка нескольких файлов

Если каталог назначения уже существует, можно передать сразу несколько источников:

install -m 0644 \
  ./configs/app.conf \
  ./configs/logging.conf \
  /etc/my-app/

Ещё мне нравится форма с -t, потому что в длинной команде сразу видно, где находится каталог назначения:

install \
  -t /etc/my-app/ \
  -m 0644 \
  ./configs/app.conf \
  ./configs/logging.conf

Форма с -t особенно хорошо читается, когда список файлов генерируется массивом

configs=(
  ./configs/app.conf
  ./configs/logging.conf
  ./configs/limits.conf
)

install -t /etc/my-app -m 0644 -- "${configs[@]}"

-- завершает разбор опций, если имя файла внезапно начинается с дефиса, команда не попытается принять его за очередной ключ. В своих скриптах я стараюсь добавлять -- везде, где путь приходит из переменной или массива.

Защита от неоднозначного назначения

Представим, что мы хотим установить конфиг именно как файл /etc/my-app/app.conf

install -m 0644 ./app.conf /etc/my-app/app.conf

Если /etc/my-app/app.conf внезапно уже является каталогом, обычная команда положит файл внутрь него:

/etc/my-app/app.conf/app.conf

Формально всё успешно, код возврата нулевой, а приложение продолжает читать старый конфиг или вообще не запускается. Очень удобная ошибка, можно этот кейс прям на собесах давать и смотреть чё будет.

GNU-ключ -T запрещает воспринимать назначение как каталог:

install -T -m 0644 \
  ./app.conf \
  /etc/my-app/app.conf

Теперь при неожиданном каталоге команда завершится ошибкой. Если назначение является символической ссылкой на каталог, GNU install -T не пойдёт по ссылке, а заменит саму ссылку обычным файлом. Это тоже важно учитывать, но случайно сложить конфиг внутрь каталога команда уже не сможет.