По мере распространения Go и Rust появляется всё больше программ, которые состоят из одного бинарника без каких-либо нестандартных зависимостей, и которые мы устанавливаем руками, скачивая релиз с GitHub: либо потому, что данного приложения ещё нет в вашем дистрибутиве, либо потому, что просто хочется всегда иметь актуальную версию, а не ждать, когда её затянут в дистрибутив.

Ставить (а особенно обновлять) такие приложения руками – занятие неблагодарное, особенно когда их количество становится больше одного-двух – и хочется какой-то автоматизации. У меня таких программ около десятка, и довольно долгое время я пользовался различными наколеночными скриптами вроде этого для поддержания их актуальности. Но bash-скрипт – это всё-таки как-то несерьёзно, и поэтому всегда хотелось чего-то более управляемого в виде нормального приложения. Найти что-то готовое, что удовлетворяло бы всем моим потребностям, мне сходу не удалось – поэтому некоторое время назад решил пойти моим излюбленным путём и написать своё приложение под эту конкретную задачу.

binup

Пара недель кодинга по вечерам – и родилась утилита binup. Недавно я зарелизил версию 1.0.0 и полностью перешёл на неё со своих скриптов. Буду рад, если получившаяся тула будет полезна кому-то кроме меня.

Вот как она работает: вы создаёте конфигурационный файл ~/.config/binup/config.yaml с примерно следующим содержимым, в котором описываете конкретное приложение (как его найти на GitHub):

tools:
  binup:
    project: KonishchevDmitry/binup

... запускаете binup install или binup upgrade – и тула устанавливает, либо обновляет указанные вами приложения.

Работает binup довольно просто: она нигде не хранит никакую информацию об установленных приложениях, а вместо этого при запуске смотрит на их текущий статус: если нужного бинарника нет, то устанавливает его; если же есть, то пробует запустить приложение с --version, чтобы определить текущую версию приложения и сравнить её с последним релизом на GitHub. Если же версию определить не удалось (к примеру, программа вовсе может не поддерживать флаг --version), то binup ориентируется на время модификации файла, которое при установке приложения задаёт равным времени модификации релиза.

Конфигурация

Вот пример конфигурационного файла со всеми доступными на данный момент опциями:

# Path where to install the binaries (the default is ~/.local/bin)
path: /usr/local/bin

tools:
  # Binary name
  prometheus:
    # GitHub project name
    project: prometheus/prometheus

    # Changelog URL (will be printed on app upgrade)
    changelog: https://github.com/prometheus/prometheus/blob/main/CHANGELOG.md

    # Release archive pattern:
    # * By default shell-like glob matching is used (https://docs.rs/globset/latest/globset/#syntax)
    # * Pattern started with '~' is treated as regular expression (https://docs.rs/regex/latest/regex/#syntax)
    #
    # If it's not specified, the archive will be chosen automatically according to target platform.
    release_matcher: prometheus-*.linux-amd64.tar.gz

    # Binary path to look for inside the release archive. If it's not specified, the tool will try to find it automatically.
    binary_matcher: "*/prometheus"

    # Post-install script
    post: systemctl restart prometheus

# If you have a lot of tools, you may hit GitHub API rate limits for anonymous requests at some moment.
# So it's recommended to obtain GitHub token (https://github.com/settings/tokens) and specify it here.
# No permissions are required for the token – it's needed just to make API requests non-anonymous.
github:
  token: $token

Последующее развитие

Развивать её в какой-то полноценный пакетный менеджер вроде Homebrew я точно не планирую, но вот в рамках решения вышеописанной задачи – вполне.

Пока что из наиболее явных потенциальных фичей видится разве что поддержка GitLab, если возникнет такая необходимость (лично у меня пока что нет ни одного приложения с него). Прямо сейчас она закрывает все мои потребности, но вполне вероятно, что в процессе использования будут возникать новые – и тогда буду закрывать и их.

Комментарии (13)


  1. zorn-v100500
    08.08.2024 06:17

    Бинарник для скачивания бинарников без зависимостей, сам имеет зависимости )

    $ binup

    binup: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory

    $ lsb_release -a

    No LSB modules are available.
    Distributor ID: Ubuntu
    Description:    Ubuntu 24.04 LTS
    Release:        24.04
    Codename:       noble


    $ apt show openssl

    Package: openssl
    Version: 3.0.13-0ubuntu3.2

    ...


    1. KonishchevDmitry Автор
      08.08.2024 06:17
      +1

      Да – имеет, но вполне себе стандартные:

      $ ldd /usr/local/bin/binup
              linux-vdso.so.1 (0x00007ffc269ce000)
              liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x0000778f07092000)
              libssl.so.1.1 => /lib/x86_64-linux-gnu/libssl.so.1.1 (0x0000778f06762000)
              libcrypto.so.1.1 => /lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x0000778f06400000)
              libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x0000778f06732000)
              libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x0000778f0672a000)
              libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x0000778f06312000)
              libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x0000778f06722000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000778f06000000)
              /lib64/ld-linux-x86-64.so.2 (0x0000778f070da000)

      У меня были мысли делать статическую сборку, но пока решил обойтись без этого:

      • liblzma вроде должен быть на любой системе. К примеру, та же Ubuntu мне не даёт его удалить, т. к. на него завязано слишком много всего.

      • libssl – да, вы правы – та версия, с которой слинкован мой бинарник, довольно старая (релиз специально собирался на старом LTS, чтобы не было проблем с glibc на новых дистрибутивах), и на моём сервере эта версия установлена как зависимость пакетов, которые не установлены из коробки: The following packages will be REMOVED: libdns1104 libisc1100 libpython3.7 libpython3.7-minimal libpython3.7-stdlib libssl1.1 mongo-tools mongodb mongodb-clients mongodb-server mongodb-server-core. Тут я не прав.

      • Все остальные зависимости довольно стандартные – с ними проблем вроде бы быть не должно (или я ошибаюсь?) – релиз специально собирается на относительно древней 20.04, а у glibc, насколько я знаю, в этом месте с совместимостью всё хорошо.

      В общем, решил, что в первом релизе можно пока с этим не мудрить. Но спасибо, замечание валидное – посмотрю, как тут лучше сделать. То, что оно у вас из коробки не запустилось – действительно нехорошо, это надо исправлять.


    1. KonishchevDmitry Автор
      08.08.2024 06:17
      +1

      Поправил. Теперь вот так:

      $ ldd /usr/local/bin/binup
              linux-vdso.so.1 (0x00007ffc0ea76000)
              liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x000077e82861a000)
              libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x000077e8285ea000)
              libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x000077e8285e2000)
              libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x000077e827712000)
              libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x000077e8285da000)
              libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000077e827400000)
              /lib64/ld-linux-x86-64.so.2 (0x000077e828662000)

      На выходных напишу тесты на это дело и попробую разобраться с liblzma + подумаю – может всё-таки перейду на musl и буду линковаться с ним – тогда будет вообще 100% статика. Но я к нему отношусь несколько настороженно – поэтому изначально и не стал идти в сторону статической линковки.


  1. OleSv
    08.08.2024 06:17
    +2

    Посмотрите на eget https://github.com/zyedidia/eget


    1. KonishchevDmitry Автор
      08.08.2024 06:17

      Спасибо, но, судя по README, он, как минимум, не поддерживает post-install-скрипты. Для меня это прямо важно – начиная с того, чтобы рестартануть обновлённый сервис и заканчивая чем-то более сложным вроде:

      set -eu
      
      prometheus-node-exporter --help > /etc/prometheus/help.dump
      systemctl restart prometheus-node-exporter
      
      curl -s http://localhost:9100/metrics | \
        sed -r '/^#/ d; s/^([a-z_]+)(.*)/\1/' | \
        sort -u > /etc/prometheus/metrics.dump
      
      cd /etc/prometheus && git diff help.dump metrics.dump >&2

      – к примеру, вот такой post-install скрипт у меня настроен для Prometheus Node Exporter, чтобы отслеживать, какие новые интересные метрики появились в новом релизе.

      Ну и ещё всякие мелочи – к примеру, binup при обновлении выводит ссылку на changelog – мелочь, но очень удобно. :)


      1. Revertis
        08.08.2024 06:17

        В код пока не смотрел, но как вы подменяете бинарник без остановки сервиса?


        1. selivanov_pavel
          08.08.2024 06:17

          В Linux имя файла - это просто название для конкретного inode в каталоге. Можно его менять или удалять, даже если из файла запущен процесс.


          1. Revertis
            08.08.2024 06:17

            У меня как будто не всегда это получается, поэтому и спрашиваю :-/


            1. selivanov_pavel
              08.08.2024 06:17

              cp `which htop` .

              ./htop

              rm ./htop # другая консоль

              всё работает


            1. KonishchevDmitry Автор
              08.08.2024 06:17
              +1

              Вы видимо пытались открыть именно этот файл и поправить – так да, нельзя. Но так никто никогда и не делает – вдруг вы по какой-то причине не допишете его до конца и тогда только всё запорете. Поэтому всегда в этой же директории создаётся новый файл с другим именем, туда пишутся данные + делается fsync(), чтобы они гарантированно записались на диск, а затем уже делается rename() – и новый файл атомарно заменяет старый.

              Если погуглите, то даже сможете найти интересный хак, как можно восстановить удалённый файл через /proc, если есть хотя бы один процесс, у которого он ещё открыт. ;)


  1. 13werwolf13
    08.08.2024 06:17
    +3

    простите но установку софта (а так же различного рантайма/библиотек/зависимостей/etc) в систему в обход штатного пакетного менеджера иначе как замусориванием и вредными советами я назвать не могу.

    гораздо правильнее и полезнее потратить 5 секунд времени и помочь мейнтейнерам своего дистрибутива опакетить недостающее ПО, тем более что в случае go и rust это делается проще простого (пример spec для rpm проекта на rust) и даже не потребует тащить себе в систему ничего что требуется для сборки, это можно сделать вообще с телефона в браузере.


  1. zorn-v100500
    08.08.2024 06:17
    +2

    гораздо правильнее и полезнее потратить 5 секунд времени и помочь мейнтейнерам своего дистрибутива опакетить недостающее ПО

    Удачи с дебианом за 5 секунд сделать )

    А установку пакетов в обход стандартных репозиториев я считаю еще большим злом (как минимум нужен рут, а в пакет можно запихать скриптов на что фантазии хватит). И тем более добавление каких то левых реп в систему.


    1. eyeDM
      08.08.2024 06:17

      Удачи с дебианом

      Для таких случаев есть Docker и аналоги