gemlog/public/uk/rust-crates-mirroring-with-kellnr.gmi
2026-02-27 18:31:01 +02:00

323 lines
No EOL
18 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Організація локального дзеркала залежностей Cargo з Kellnr
Давно задумуюсь про організацію локального дзеркала залежностей crates.io, щоб не ходити за ними в Інтернет. При чому, я не маю достатньо простору для хостингу повної копії репозиторію, утім готовий ділитися тими крейтами, якими користуюся сам.
Трішки прозондувавши тему, віднайшов проєкт Kellnr:
=> https://kellnr.io
Це рішення позиціонується як Self Hosted Solution для хостингу крейтів, та головне - має фічу вбудованого кешуючого проксі, що власне під мої задачі підходить.
Дане рішення постачається з Веб-інтерфейсом для адміністрування: зокрема, керування правами доступу.
=> rust-crates-mirroring-with-kellnr/index-page.png Головна сторінка каталогу (скріншот)
Сторінка пошуку містить відповідну перемичку фільтрації:
=> rust-crates-mirroring-with-kellnr/proxy-filter.png Фільтрація кешованих пакунків в Kellnr (скріншот)
Отже, має бути зручно, особливо для візуалізації результатів CLI-експериментів. Нижче занотую процес встановлення і налаштування публічної ноди (бо варіант з Docker мені не підходить як і автоматичне встановлення скриптом `.sh` - якщо пускатиму в контейнері, то робитиму це руками в LXC)
## Встановлення
``` bash
git clone https://github.com/kellnr/kellnr.git
cd kellnr
```
Якщо планується компіляція кореневого `Cargo.toml` то потрібно спочатку зібрати компонент `ui`, інакше буде помилка на етапі `crates/embedded-resources`:
``` bash
cd ui
npm install
npm run build
```
На цьому етапі, важливо зкопіювати результат білду до теки `crates/embedded-resources/static`, вміст якої статично додається до бінарного пакету `kellnr`:
``` bash
cp -r dist/. ../crates/embedded-resources/static/
```
Тепер збираємо корінь .rs:
``` bash
cd ..
cargo build --release
```
## Запуск
Перш, як почепити налаштований демон на systemd, службу можна запустити на локалхост:
``` bash
target/release/kellnr start --registry-data-dir /path/to/kellnr \
--proxy-enabled true --local-ip 127.0.0.1 -l debug
```
Після запуску, зайшов до адмінки, вказавши логін `admin` і пароль `admin`:
=> http://127.0.0.1:8000/login?redirect=settings
* на віддаленому сервері потрібно цей пароль змінити (в моїй версії була помилка не співпадіння - проігноруйте її, бо пароль насправді змінюється). Див. також `--setup-admin-pwd`.
База даних тут стандартно SQLite, вона буде розташована за вказаним шляхом `/path/to/kellnr`.
## Підключення клієнта
Щоб почати наповнення індексу запланованим мною способом (тобто через кешування проксі) додаю наступні рядки до клієнтського файлу `~/.cargo/config.toml`:
``` ~/.cargo/config.toml
# Оголошуємо параметри підключення до сервера
[registries.kellnr]
index = "sparse+http://127.0.0.1:8000/api/v1/crates/"
# Вказуємо Cargo замінити стандартне джерело crates.io на Kellnr
[source.crates-io]
replace-with = "kellnr"
# Варто збільшити час очікування, оскільки трафік "блукатиме" значно довше
[http]
timeout = 180
```
Тепер, якщо відкрити будь який проєкт Rust і виконати в ньому оновлення індексу, то отримаємо наступне:
``` bash
$ cargo update
Updating `kellnr` index
error: no matching package named `libadwaita` found
```
Ця помилка відбувається тому, що ми "залінкувались" на статичне сховище, яке наразі порожнє. Тому в даному випадку, повертаємось до `~/.cargo/config.toml` і змінюємо `index` на URL з суфіксом `cratesio`:
``` bash
index = "sparse+http://127.0.0.1:8000/api/v1/cratesio/"
```
Повторюємо оновлення і бачимо, що залежності почали відбудовуватись, а кеш - наповнюватись:
=> rust-crates-mirroring-with-kellnr/proxy-index-update.png Оновлення кешованого індексу проксі на головній сторінці Kellnr (скріншот)
Інші способи глобального і локального оголошення джерела пакунків описані в документації, зупинятись окремо на кожному не будемо:
=> https://kellnr.io/documentation#configure-cargo
## Спільний сервер
За схожою логікою, можна розгорнути суспільний сервіс, замінивши хост з `127.0.0.1` на `0.0.0.0` або `::`.
Такий сервіс буде зручно запускати від окремого користувача `kellnr` через systemd:
``` bash
useradd -mr kellnr
```
* в домашній теці - будуть зберігатись журнали і власне дані сервера
Системний сервіс у мене виглядає наступним чином:
``` /etc/systemd/system/kellnr.service
[Unit]
After=network.target
Wants=network.target
[Service]
Type=simple
User=kellnr
Group=kellnr
ExecStart=/usr/local/bin/kellnr start --registry-data-dir /home/kellnr \
--proxy-enabled true \
# кешувати файли крейтів локально (до теки cratesio)
# --proxy-download-on-update true \
--local-ip :: \
--local-port 8180 \
# в мене використовується багатомережний origin
# --origin-hostname .ua.srv \
# --origin-port 8180 \
# деталізація журналів
-l warn
Environment="NO_COLOR=1"
StandardOutput=file:///home/kellnr/debug.log
StandardError=file:///home/kellnr/error.log
[Install]
WantedBy=multi-user.target
```
* для Tor і I2P достатньо лише перекинути на будь-який IP "прихований сервіс" або "тунель"
* теоретично, можна оголоситись на `::1` і перекинути проксі Nginx, але не знаю чи це працюватиме в контексті `sparse`
Після цього відкривається порт:
``` bash
ufw allow from 0200::/7 to 202:68d0:f0d5:b88d:1d1a:555e:2f6b:3148 port 8180 proto tcp
ufw allow from 0400::/7 to 505:6847:c778:61a1:5c6d:e802:d291:8191 port 8180 proto tcp
```
* у прикладі використовуються дозволи для мереж Yggdrasil і Mycelium
Нагадаю, що на серверах Fedora, потрібно окремо оголосити наступну політику SELinux:
``` bash
setsebool -P httpd_can_network_connect 1
semanage port -a -t http_port_t -p tcp 8180
```
* а дані профілю - розмістити в канонічних теках `/var/lib/kellnr` і `/var/log/kellnr` відповідно (з правами `kellnr:kellnr`)
Керування сервісом відбувається класично:
* `systemctl restart kellnr` - (пере) запуск
* `systemctl enable kellnr` - авто-старт з системою
* `systemctl status kellnr` - перевірка статусу
---
Варто враховувати, що простір може швидко заповнитись, якщо проксі-сервер активно використовується.
Стосовно перманентного хостингу проєктів - як правило, використовуються облікові записи і згенеровані для них токени, утім це вже інша тема.
### Налаштування вихідного проксі
На моєму сервері немає Інтернет-інтерфейсу і я не можу забрати пакунки з crates.io напряму. Kellnr також поки не підтримує обгортку сокетів програмно, тому я створив відповідний тікет і згодом отримав відповідь з порадою використовувати змінні оточення (які підтримуються імплементацією https://crates.io/crates/reqwest)
=> https://github.com/kellnr/kellnr/issues/1096#issuecomment-3968553238
Змінні оточення звичайно вказуються через `export` або префіксом команди `kellnr`:
``` bash
HTTP_PROXY=http://[::1]:8118 \
HTTPS_PROXY=http://[::1]:8118 \
NO_PROXY=localhost,127.0.0.1,::1,202:68d0:f0d5:b88d:1d1a:555e:2f6b:3148,505:6847:c778:61a1:5c6d:e802:d291:8191 \
/usr/local/bin/kellnr
```
Я користуюсь systemd і його фрагмент `[Service]` виглядає так:
``` /etc/systemd/system/kellnr.service
[Service]
Environment="HTTP_PROXY=http://[::1]:8118"
Environment="HTTPS_PROXY=http://[::1]:8118"
Environment="NO_PROXY=localhost,127.0.0.1,::1,202:68d0:f0d5:b88d:1d1a:555e:2f6b:3148,505:6847:c778:61a1:5c6d:e802:d291:8191"
```
* таким чином, будь-який запит здійснений процесом `kellnr` - буде перенаправлено через відповідний проксі, окрім запитів на мережі, вказані в `NO_PROXY` (у мене там адреси сервера Yggdrasil і Mycelium бо процес направлятиме на них деякі службові запити API)
#### Проксування засобами Nginx
Рішення нижче виявилось робочим лише частково: наприклад, завантажити крейт (`cargo install`) не вийде, тільки підтягнути до нього залежності (`cargo update`). Також цей сценарій чомусь не зберігає кешовані файли, тільки наповнює індекс в БД.
Тому від описаного нижче способу я згодом відмовився бо він потребує доопрацювання вибіркових шляхів API через `/etc/hosts` і додаткових правил для config.json:
=> https://github.com/rust-lang/crates.io-index/blob/master/config.json
Коротше, цей спосіб - костиль, при чому кривий, але я лишу його для історії, якщо вам з якихось причин не підходить спосіб зі змінними оточення або потрібен тюнінг окремих URI.
``` /etc/nginx/sites-available/default
server {
listen [::1]:8200;
access_log /var/log/nginx/kellnr.access.log;
error_log /var/log/nginx/kellnr.error.log warn;
location / {
proxy_pass http://[::1]:8118;
proxy_set_header X-Forwarded-Proto "https";
proxy_set_header Host "index.crates.io";
}
location /crates {
proxy_pass http://[::1]:8118;
proxy_set_header X-Forwarded-Proto "https";
proxy_set_header Host "static.crates.io";
}
}
```
* `[::1]:8118` - це будь-який локальний проксі (типу privoxy або gost) який вміє обробляти HTTP
* теоретично, можна пускати трафік через Tor, без проксі-посередника, але в моїй редакції Arti підтримується тільки SOCKS, а той - не підтримується Nginx
Окремо, маю ще такий конфіг, який кидає запити напряму без маршрутизуючого проксі:
```
server {
listen [::1]:8200;
access_log /var/log/nginx/kellnr.access.log;
error_log /var/log/nginx/kellnr.error.log warn;
location / {
proxy_set_header X-Forwarded-Proto "https";
proxy_set_header Host "index.crates.io";
proxy_ssl_server_name on;
proxy_ssl_name "index.crates.io";
# dig index.crates.io
proxy_pass https://151.101.238.137;
}
location /crates {
proxy_set_header X-Forwarded-Proto "https";
proxy_set_header Host "static.crates.io";
proxy_ssl_server_name on;
proxy_ssl_name "static.crates.io";
# dig static.crates.io
proxy_pass https://151.101.238.137;
}
}
```
* на ньому я резольвлю руками `.crates.io` бо піймав якісь глюки Nginx: коли він слухає на IPv6, то резольвить на те ж сімейство адрес, інтерфейсу якого в мене немає і опції типу `resolver 8.8.8.8 8.8.4.4 ipv6=off valid=300s;` - не допомагають
* можливо, деякі поля можна прибрати, але це остання заготовка яка працювала, тому додав "як є"
Відповідно до нашого прикладу Nginx, при запуску сервера Kellnr, потрібно додати два аргументи:
``` bash
kellnr start .. \
--proxy-index="http://[::1]:8200" \
--proxy-url="http://[::1]:8200/crates"
```
* тут я також пробував різні аналоги proxychains і "магічні" URL типу `http://[::1]:8118/index.crates.io/` - але цей фокус не пройшов, тому ось такий костиль з Nginx, який працює.
### Вирішення проблем
Якщо на стороні клієнта бачите помилку:
``` bash
$ cargo build --locked
warning: spurious network error (3 tries remaining): [7] Could not connect to server (Failed to connect to 127.0.0.1 port 8000 after 1 ms: Could not connect to server)
```
то не плутайте її з локальними налаштуваннями `Cargo.toml` і вкажіть на сервері актуальний `--origin-*`, вирішивши спочатку проблему його підключення до вихідного проксі.
Якщо помилки:
``` bash
$ cargo build --locked
warning: spurious network error (3 tries remaining): [28] Timeout was reached (Operation timed out after 30000 milliseconds with 0 bytes received)
```
збільшіть значення `http.timeout` в `~/.cargo/config.toml`
## Зауваження щодо безпеки
Хоча в Cargo передбачається декілька рівнів перевірки цілісності пакетів, буде не зайвим переконатись в наявності оригінального файлу `Cargo.lock` для вашого проєкту, що містить хеші залежностей. Ці хеш-суми стандартно не доступні в реєстрах crates.io, утім вони звичайно зберігаються в репозиторіях Git.
При використанні будь яких проксі, рекомендую збиратись з використанням аргументу `--locked`:
``` bash
cargo build --locked
```
Додатково, для запобігання компрометації пакунків, варто створити окремий апстрім для індексів з хешами: наприклад на базі офіційного дзеркала GitHub, опціонально розгорнувши мульти-мережне дзеркало Radicle:
=> https://github.com/rust-lang/crates.io-index
```
crates.io > -|- out proxy > kellnr server > kellnr client > cargo
crates.io-index > _↑ |
↑_________________[--proxy-index]__________________↓
```
## Посилання
Наш експериментальний вузол в мережах Yggdrasil і Mycelium, побачимо що з того вийде:
* `http://[202:68d0:f0d5:b88d:1d1a:555e:2f6b:3148]:8180` | http://ygg.ua.srv:8180
* `http://[505:6847:c778:61a1:5c6d:e802:d291:8191]:8180` | http://myc.ua.srv:8180
Не офіційне дзеркало Kellnr в [Radicle](https://devzone.org.ua/post/radicle-detsentralizovanyy-p2p-khostynh-gitdvcs), знімок репозиторію на момент написання матеріалу:
```
rad:z4VnEyS5YXnFpEgY1iheHneRPeSX6
```