November 30, 2021

Библиотека модулей Raku: Зеркалируем репозитории | Raku Module Library: Mirroring repositories

Популярность языка программирования зависит не только (и не столько) от его дизайна и реализации, но и от огромного числа сторонних вещей. Таких, как качество и количество документации, удобная среда разработки, отзывчивое сообщество единомышленников, библиотека модулей на все случаи жизни и прочее. Сегодня как раз про библиотеку модулей языка Raku. А точнее, про систему их хранения и распространения.

Что есть в природе

Если посмотреть в миры Java, Python, JavaScript, Perl — всё примерно одинаково. Есть де-факто центральный публичный репозиторий (Nexus, PyPI, npm-registry, CPAN), есть сколько-то альтернатив и зеркал, есть возможность поднять свой.

Что есть в мире Raku

Основной менеджер пакетов для Raku называется zef. Сейчас он работает следующим образом: берёт с какого-то сервера индекс, анализирует его и скачивает необходимые перечисленные в нём модули. Так можно делать с несколькими серверами.

Изначально, в качестве сервера для распространения модулей служил GitHub. Это дешёвое и сердитое решение. Индексы считают на сторонней машине про cron и выкладывают в отдельный git репозиторий. Сейчас в этом ‘сервере’ хранятся индексы о более чем восьмистах версий различных модулей. Эта схема получила название p6с. Минусы её очевидны — завязывание на одну проприетарную платформу, которая изначально спроектирована для других целей.

Второй попыткой стала проба использовать CPAN для нужд Raku. Если без подробностей, то модули в Perl и Raku немного отличаются по структуре, и CPAN пришлось грубо допиливать. В конечном итоге он стал просто хранилищем, неким FTP сервером. Индексы так же считают на сторонней машине про cron и выкладывают в отдельный git репозиторий. На данный момент это самая большая библиотека модулей — их там больше 2700 версий.

Третьей попыткой, почти год назад, стал проект fez. Если CPAN предоставлял только возможности FTP сервера, то fez решил идти дальше и добавил несколько полезных вещей. Таких, как улучшенная система авторизации, правильная работа с номерами версий модулей, мгновенная индексация на стороне репозитория. Плюс, так как это специальный проект, созданный именно для нужд Raku, то он имеет отличные возможности для качественного развития.

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

Что хочется ещё

Для успокоения души мне не хватает зеркал сервера fez. Для чего нужно зеркало вообще?

Дело в том, что мы живём во время, когда информационные сети стали неимоверно сложными, а желания их контролировать слишком высокими. Где-то специально, где-то случайно закрывается доступ к очень большим, важным и привычным ресурсам. Нет уверенности в том, что завтра утром ты сможешь получить доступ к своим данным, хранящимся где-то там. Благо это или нет, но это стало нашей сегодняшней реальностью. От сюда есть вполне естественное желание диверсифицировать риски. Один из способов — иметь зеркало библиотеки модулей.

Хочешь сделать что-то хорошо - сделай сам

Хорошо сделать не обещаю, но так как альтернатив никаких, то сойдёт любое решение. Ниже я покажу как можно локально развернуть зеркало для модулей, хранящихся в CPAN и fez (p6c используется малоактивно, и зеркалировать git хостинг это отдельная задача). Нам понадобится curl, grep, awk, sed, sh и docker.

Исходя из логики работы zef, принципиально нужно:

  1. Скачать индексный файл;
  2. Скачать все перечисленные в нём архивы с модулями;
  3. Повторить 1 и 2 для каждого репозитория;
  4. Поднять и настроить nginx, чтобы он умел отдавать скачанные индексы и архивы;
  5. Сказать zef по какому адресу лежит зеркало.

Скачиваем индексный файл

Все настройки zef лежат в файле config.json. В документации к zef сказано, чтобы узнать с каким дефолтным конфигом запускается zef, нужно выполнить zef --help:

> zef --help
[..]
CONFIGURATION ~/.rakubrew/versions/moar-2021.10/install/share/perl6/site/
resources/250C9FFF9756350CAA0F60C1873CDD80F2EB7A77.json
[..]

> cat ~/.rakubrew/versions/moar-2021.10/install/share/perl6/site/
resources/250C9FFF9756350CAA0F60C1873CDD80F2EB7A77.json
[..]
{
  "short-name": "fez",
  "enabled": 1,
  "module": "Zef::Repository::Ecosystems",
  "options": {
    "name": "fez",
    "auto-update": 1,
    "uses-path": true,
    "mirrors": [
      "http://360.zef.pm/"
    ]
  }
},

В разделе mirrors конфигурации для fez видим URI — то что нам нужно.

> mkdir -p ~/local-zef/local-fez && cd ~/local-zef/local-fez
local-fez> curl https://360.zef.pm -o index.json

Скачиваем архивы модулей

В индексе fez относительный путь архиву модуля лежит в поле path. Например:

{
[..]
	"name": "fez",
	"path": "F/EZ/FEZ/259a4732c97520938ce7310b3a1330db75bf91d0.tar.gz",
	"perl": "6.c",
[..]
	"source-url": "https://github.com/tony-o/raku-fez.git",
	"version": "17"
}

Сейчас нужно вытащить все значения path и составить один большой curl запрос. Немного sed-awk-магии:

local-fez> grep -o '"path":"[^"]*"' index.json | \
awk -F\" '{print $4}' | \
sed 's|\(.*\)| -o \1 https://360.zef.pm/\1|g' | \
awk -v d="" '{s=(NR==1?s:s d)$0}END{print s}' | \
sed 's/\(.*\)/curl -Z --create-dirs \1/' | \
sh

DL% UL%  Dled  Uled  Xfers  Live   Qd Total     Current  Left    Speed
100 --  36.6M     0   623     0     0  0:00:06  0:00:11 --:--:-- 5440k

local-fez> ls

A  D  G  J  M  P  T  W
B  E  H  K  N  R  U  Y
C  F  I  L  O  S  V  index.json

Повторяем для CPAN

Таким же образом можно скачать индекс и весь архив для CPAN:

> mdkir -p ~/local-zef/local-cpan && cd ~/local-zef/local-cpan
local-cpan> curl https://raw.githubusercontent.com/ugexe/Perl6-ecosystems/master/cpan1.json -o index.json
local-cpan> grep -o '"source-url": "[^"]*"' index.json | \
awk -F\" '{print $4}' | \
sed 's|http://www.cpan.org/authors/id/\(.*\)|\1|g' | \
sed 's|\(.*\)| -o \1 http://www.cpan.org/authors/id/\1|g' | \
awk -v d="" '{s=(NR==1?s:s d)$0}END{print s}' | \
sed 's|\(.*\)|curl -Z --create-dirs \1|' | \
sh

DL% UL%  Dled  Uled  Xfers  Live   Qd Total     Current  Left    Speed
100 --   337M     0  2767     0     0  0:00:54  0:00:41 --:--:-- 6308k

local-cpan> rm -rf ./git:

local-cpan> sed 's|"source-url": "http://www.cpan.org/authors/id|"source-url": "http://localhost/local-cpan|g' index.json > index-fix-uri.json

local-cpan> ls

A    G    N    V
B    H    P    W
C    J    R    Y
D    K    S    index-fix-uri.json
E    L    T    index.json
F    M    U

Различия в ситуации со CPAN следующие:

  • Путь к архиву абсолютный и лежит в поле source-url. Соответственно, нам нужно подправить индексный файл, чтобы путь вёл не на cpan.org, а на наш localhost;
  • В индексе упоминаются несколько (около шести) устаревших модуля с неверными source-url. Они порождают папку git:, которая нам совершенно не нужна.

Поднимаем и настраиваем nginx

Для начала нужно написать конфигурационный файл для nginx:

cd ~/local-zef
local-zef> vim nginx.conf
[..]
local-zef> cat nginx.conf
events { worker_connections 1024; }
http {
  server {
    listen 80;
    root	/usr/share/nginx/html;
    location /local-fez { index /local-fez/index.json; }
    location /local-cpan { index /local-cpan/index-fix-uri.json; }
  }
}

Затем поднимаем nginx в docker, монтируем папку с архивами и индексами, и подкладываем нашу конфигурацию:

cd ~/local-zef
local-zef> docker run -it -p 80:80 \
-v /$PWD://usr/share/nginx/html:ro \
-v /$PWD/nginx.conf://etc/nginx/nginx.conf:ro \
--name=local-zef \
nginx:alpine

После этих манипуляций у нас имеется поднятый web-сервер, который отдаст индексы для fez и cpan по адресам http://localhost/local-zef и http://localhost/local-cpan соответственно. Кроме того, с сервера можно скачать архивы по ссылкам из индекса.

Настраиваем zef

Я предпочитаю завести отдельный конфиг для zef. Относительно оригинального в нём нужно поменять пути до хранилища и временной папки, а так же имена и uri в разделах про fez и cpan репозитории:

cd ~/local-zef
local-zef> cp ~/.rakubrew/versions/moar-2021.10/install/share/perl6/site/resources/250C9FFF9756350CAA0F60C1873CDD80F2EB7A77.json zef.json
local-zef> vim zef.json
[..]
local-zef> cat zef.json
[..]
"StoreDir" : "$*HOME/.zef/store_local",
"TempDir"  : "$*HOME/.zef/tmp_local",
[..]
{
  "short-name": "local-fez",
  "enabled": 1,
  "module": "Zef::Repository::Ecosystems",
  "options": {
    "name": "local-fez",
    "auto-update": 1,
    "uses-path": true,
    "mirrors": [
      "http://127.0.0.1/local-fez/"
    ]
  }
}
[..]
{
  "short-name" : "local-cpan",
  "enabled" : 1,
  "module" : "Zef::Repository::Ecosystems",
  "options" : {
    "name" : "local-cpan",
    "auto-update" : 1,
    "mirrors" : [
      "http://127.0.0.1/local-cpan/"
    ]
  }
}

Теперь мы можем установить модуль из локального зеркала таким образом:

zef --config=~/local-zef/zef.json install LogP6

Централизованный архив всех модулей Raku

Существует проект REA (The Raku Programming Language Ecosystem Archive), в который регулярно собираются все когда-либо опубликованные модули. Кроме того, есть специальный модуль Raku, который позволяет иметь похожий архив самостоятельно. Пока что zef не умеет работать с этим архивом как источником данных, но есть смысл написать отдельный плагин.

Заключение

Я очень надеюсь, что проект fez будет продолжать активно развиваться. По словам автора, сейчас fez это набор из AWS-ламбда, S3 и Сloudfront, но никак не код. Думаю, есть смысл написать реализацию fez в коде. Тогда можно будет легко разворачивать реальные альтернативные сервера, например для корпоративных целей. Пожелаем удачи!

English version