skriptorium 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +124 -0
- data/LICENSE +21 -0
- data/README.md +53 -0
- data/bin/google.rb +99 -0
- data/bin/skriptorium +117 -0
- data/config.yml.example +27 -0
- data/lib/skriptorium/article.rb +114 -0
- data/lib/skriptorium/base_destination.rb +27 -0
- data/lib/skriptorium/base_source.rb +40 -0
- data/lib/skriptorium/blogger/destination.rb +98 -0
- data/lib/skriptorium/config.rb +18 -0
- data/lib/skriptorium/dev_to/destination.rb +62 -0
- data/lib/skriptorium/dev_to/source.rb +71 -0
- data/lib/skriptorium/github_pages/destination.rb +69 -0
- data/lib/skriptorium/hashnode/destination.rb +188 -0
- data/lib/skriptorium/local/destination.rb +34 -0
- data/lib/skriptorium/rss/source.rb +50 -0
- data/lib/skriptorium/version.rb +3 -0
- data/lib/skriptorium.rb +83 -0
- data/test/fixtures/dev_to_me_article_1.json +31 -0
- data/test/fixtures/dev_to_me_article_2.json +38 -0
- data/test/fixtures/dev_to_me_articles.json +1211 -0
- data/test/fixtures/rss_teletype.xml +150 -0
- metadata +230 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type_of": "article",
|
|
3
|
+
"id": 4198697,
|
|
4
|
+
"title": "Syncerman: от bash-скриптов к Go cli для пяти облаков одновременно",
|
|
5
|
+
"description": "Иногда, чтобы решить свою проблему, приходится написать инструмент. А иногда — чтобы написать...",
|
|
6
|
+
"published": true,
|
|
7
|
+
"published_at": "2026-07-21T15:46:22.000Z",
|
|
8
|
+
"slug": "syncerman-ot-bash-skriptov-k-go-cli-dlia-piati-oblakov-odnovriemienno-hn7",
|
|
9
|
+
"path": "/kinnalru/syncerman-ot-bash-skriptov-k-go-cli-dlia-piati-oblakov-odnovriemienno-hn7",
|
|
10
|
+
"url": "https://dev.to/kinnalru/syncerman-ot-bash-skriptov-k-go-cli-dlia-piati-oblakov-odnovriemienno-hn7",
|
|
11
|
+
"comments_count": 0,
|
|
12
|
+
"public_reactions_count": 0,
|
|
13
|
+
"page_views_count": 0,
|
|
14
|
+
"published_timestamp": "2026-07-21T16:24:04Z",
|
|
15
|
+
"body_markdown": "---\ntitle: Syncerman: от bash-скриптов к Go cli для пяти облаков одновременно\npublished: true\ndate: 2026-07-21 15:46:22 UTC\ntags: \ncanonical_url: https://teletype.in/@jerry_ru/NpCFHTA1uZQ?utm_source=teletype&utm_medium=feed_rss&utm_campaign=jerry_ru\n---\n\n\n\nИногда, чтобы решить свою проблему, приходится написать инструмент. А иногда — чтобы написать инструмент, приходится решить свою проблему. Короче, как обычно, всё началось не с идеи сделать утилиту, а с того, что добавилась пара важных документов (по иппотке) к существующему зоопарку данных, которые надо хранить.\n\n### Как я к этому пришел\n\nУ меня давно назрела проблема: синхронизировать разные файлы с разных рабочих мест — по крайней мере дома и на работе. Документы (важные), сохранения из игр, книги для читалки. Ну и надёжный архив всего этого добра, потому что терять даже что-то незначительное это боль 🤕.\n\nПросто положить всё в одно облако — хорошо, но нужен ещё постоянный процесс актуализации. `Яндекс Диск` работает, `Google Drive` может изменить условия, `Dropbox` может внезапно ограничить доступ. Поэтому идея была простая: **хранить копии везде**. `Яндекс Диск`, `Google Drive`, `Mail.ru Cloud`, `Dropbox` — чем больше, тем надёжнее. Только файлы, только хардкор!\n\nРаньше я обходился консольным демоном `yandex-disk` — он работал, но только с Яндексом. Потом перешёл на самописные bash-скрипты, которые вызывали [rclone](https://rclone.org/) напрямую. Это работало… до поры до времени. Конфигурация разрасталась, порядок синхронизации был важен (нельзя синкать из пустого облака в другое до того, как туда прилетели файлы) и всякое подобное. Проблема!\n\nКогда появились нормальные ИИ-ассистенты для кодинга, я решил, что пора пробовать, а мне нужен нормальный инструмент. Declarative configuration, predictable order, automatic error handling и пр. Так родился [Syncerman](https://gitlab.com/kinnalru/syncerman).\n\n### ~~Из говна и палок~~ архитектура и технологии\n\n`Syncerman` написан на **Go** — выбрал его только за простоту распространения (один бинарник).\n\nВ основе лежит [rclone bisync](https://rclone.org/bisync/) — двусторонняя синхронизация с сохранением метаданных. `Syncerman` не пытается переизобрести колесо синхронизации, а выступает оркестратором над `rclone`, предоставляя удобный CLI и YAML-конфигурацию. Никаких серверов и БД.\n\n### YAML-конфигурация\n\nВместо папок с ссылками на bash-скрипты — чёткая структура:\n\n```yaml\njobs:\n important:\n name: \"Важное\"\n priority: 10\n tasks:\n - from: \"local:/home/jerry/cloud/mirror/Documents\"\n to:\n - path: \"gdrive:folders/Documents\"\n - path: \"ydisk:folders/Documents\"\n - from: local:Наследие\n to:\n - path: gdrive:Наследие/\n args: [\"--force\"]\n - path: ydisk:Наследие/\n args: [\"--force\"]\n - path: mailru:Наследие/\n args: [\"--force\"]\n\n games:\n name: \"игрули\"\n priority: 20\n tasks:\n - from: \"local:games_sync\"\n to:\n - path: \"ydisk:folders/games_sync\"\n```\n\nПорядок **tasks** и **to** внутри массивов сохраняется строго. Это критично для цепочек: `local → Google Drive → Yandex Disk`. Без этого — катастрофа.\n\n### Проблемасики\n\n`rclone bisync` требует state-файлы для работы (они лежат где-то в папке пользователя). При первом запуске на новой паре путей он падает с ошибкой _\"cannot find prior Path1 or Path2 listings\"_. Syncerman детектирует это по двум паттернам в выводе и автоматически перезапускает синхронизацию с флагом `--resync`. Пользователь даже не замечает проблемы. Раньше делал руками.\n\nНа ранних этапах обнаружился критический баг: из-за использования map в Go порядок выполнения целей был недетерминированным. Для линейных цепочек это катастрофа: если сначала синкать из пустого `Yandex` в `Google`, а потом из локальной папки в `Google` — быть беде. Проблема! Так появились массивы.\n\n### Результат\n\n[Syncerman](https://gitlab.com/kinnalru/syncerman) версии \\*\\*0.3.6\\*\\* сейчас работает в production — то есть на моих машинах :) Само собой нужны пруфы, и их есть у меня!\n\n- бинарники для Linux и Windows (сборка сразу подо всё)\n- GitLab CI как в лучших домах\n- Покрытие тестами - самому было бы лень писать а тут юнит-тесты для всех ключевых пакетов (config, rclone, sync), интеграционные тесты (не просто интеграционные а целые **bash-сценарии с десятком шагов** ).\n\nПубличные репозитории (это мой фетиш):\n\n- Gitlab [https://gitlab.com/kinnalru/syncerman](https://gitlab.com/kinnalru/syncerman)\n- Github [https://github.com/kinnalru/syncerman](https://github.com/kinnalru/syncerman)\n- Gitflic [https://gitflic.ru/project/kinnalru/syncerman](https://gitflic.ru/project/kinnalru/syncerman)\n- Gitverse [https://gitverse.ru/kinnalru/syncerman](https://gitverse.ru/kinnalru/syncerman)\n\n### Использование\n\nМой `crontab` выглядит так:\n\n```shell\n# Синхронизация документов и сохранений 2 рааз в час и лог для ручной проверки\n10,30 * * * * /bin/sh -lc \"cd /home/jerry/cloud/mirror && syncerman sync 2>&1 | tee ./lastrun.log\"\n```\n\nКонфигурация зеркалирует локальные папки во все облака (настроенные в `rclone`) одновременно, а затем синхронизирует облака между собой. Если одно облако отвалится — данные остаются в остальных. Если `rclone` выдаст `first-run` ошибку — `Syncerman` сам всё починит.\n\n### Вместо вывода\n\nПоявление ИИ-ассистентов снизило порог входа для написания таких инструментов. Раньше мне было лень писать то, что и так работает с помощью пары строк на bash. А сейчас это делаю не я :) — разбор выхлопов `rclone`, обработка edge cases и тестирование делаются очень легко. Я могу заняться решением проблем (архитектурой и требованиями), а рутина \"делается сама\".\n\n* * *\n\nВторой фетиш это ссылочки:\n\n- [rclone bisync](https://rclone.org/bisync/) - это база\n- [unison](https://github.com/bcpierce00/unison) - еще одна классная штука по теме синхронизации, она прочно заняла место в моём сердце, но об это в другой раз",
|
|
16
|
+
"positive_reactions_count": 0,
|
|
17
|
+
"cover_image": null,
|
|
18
|
+
"tag_list": [],
|
|
19
|
+
"canonical_url": "https://teletype.in/@jerry_ru/NpCFHTA1uZQ?utm_source=teletype&utm_medium=feed_rss&utm_campaign=jerry_ru",
|
|
20
|
+
"reading_time_minutes": 1,
|
|
21
|
+
"user": {
|
|
22
|
+
"name": "Samoilenko Yuri",
|
|
23
|
+
"username": "kinnalru",
|
|
24
|
+
"twitter_username": null,
|
|
25
|
+
"github_username": "kinnalru",
|
|
26
|
+
"user_id": 559891,
|
|
27
|
+
"website_url": "https://teletype.in/@jerry_ru",
|
|
28
|
+
"profile_image": "https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F559891%2Fe6a6dcca-2504-4ab8-ab71-d4b4cb6f492c.png",
|
|
29
|
+
"profile_image_90": "https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F559891%2Fe6a6dcca-2504-4ab8-ab71-d4b4cb6f492c.png"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type_of": "article",
|
|
3
|
+
"id": 2275835,
|
|
4
|
+
"title": "DevSecOps подкрался незаметно, хотя заметен был издалека…",
|
|
5
|
+
"description": "DevSecOps уверенно шагает по нашей индустрии, и горе тому, кто попадёт под его поступь… Эта статья...",
|
|
6
|
+
"published": true,
|
|
7
|
+
"published_at": "2025-02-11T12:34:47.000Z",
|
|
8
|
+
"slug": "devsecops-podkralsia-niezamietno-khotia-zamietien-byl-izdalieka-3gjj",
|
|
9
|
+
"path": "/rnds/devsecops-podkralsia-niezamietno-khotia-zamietien-byl-izdalieka-3gjj",
|
|
10
|
+
"url": "https://dev.to/rnds/devsecops-podkralsia-niezamietno-khotia-zamietien-byl-izdalieka-3gjj",
|
|
11
|
+
"comments_count": 0,
|
|
12
|
+
"public_reactions_count": 0,
|
|
13
|
+
"page_views_count": 20,
|
|
14
|
+
"published_timestamp": "2026-07-21T07:45:15Z",
|
|
15
|
+
"body_markdown": "---\ntitle: DevSecOps подкрался незаметно, хотя заметен был издалека…\npublished: true\ndate: 2025-02-11 12:34:47 UTC\ntags: \ncanonical_url: https://blog.rnds.pro/064-devsecops1?utm_source=teletype&utm_medium=feed_rss&utm_campaign=rnds\n---\n\n\n\nDevSecOps уверенно шагает по нашей индустрии, и горе тому, кто попадёт под его поступь… Эта статья про ~~ультимативную~~ сборку базовых образов для Ruby для удовлетворения самых параноидальных потребностей ИБ. Да, именно об этом мы и расскажем - что такое \"инсталляция ruby\", где, что, почему лежит и как с этим жить нашему пайплайну сборки и самому приложению.\n\n<section style=\"background-color:hsl(hsl(24, 24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);\">\n <p id=\"CaKt\">Статья подойдёт тем, кто хочет более глубоко понимать, какие процессы происходят в системе, когда вызывается <code>gem install</code>, <code>bundle install</code> или (не дай Бог) <code>gem update –system</code></p>\n </section>\n## Как было до?\n\nС самого начала появления docker мы в RNDSOFT использовали парадигму \"всё включено\". Для нас это означало, что в образ мы включаем всё, что нам надо не только для продуктовой эксплуатации, но и для проведения всех тестов. При прохождении CI пайплайна на следующие стадии продвигался образ целиком и, в конце концов, выкатывался на прод.\n\nЭто было очень удобно и позволяло быть максимально (насколько это вообще возможно) уверенным в работоспособности, однако имело ряд серьёзных минусов, с которыми мы прекрасно жили достаточно долгое время:\n\n- размер образа был большим - от 1 ГБ;\n- образ включал большое количество неиспользуемого (в проде) ПО.\n\n## Сканеры сканировали, сканировали, да не высканировали…\n\nИ вот однажды один (на данный момент уже далеко не один) наш клиент захотел устранения всех замечаний, которые смог выявить сканнер [Trivy](https://github.com/aquasecurity/trivy). А их, как не трудно догадаться, было достаточно много. И главная проблема заключается в том, что большая часть замечаний никак не связана с непосредственными зависимостями вашего приложения (теми, которые фиксируются в Gemfile.lock). \nТак откуда же они берутся?\n\nЕсли коротко отвечать - отовсюду :) При сборке проекта необходимо поставить его зависимости - это план минимум. Кроме того, часто появляется необходимость установить bundle определенной версии или даже обновить ruby целиком, выполнив команду [gem update --system](https://guides.rubygems.org/command-reference/#gem-update). В результате этих действий в ваш образ в разнообразные папки ставятся разнообразные гемы, но\n\n<section style=\"background-color:hsl(hsl(323, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);\">\n <p id=\"gt1O\">старые версии гемов и сама базовая \"инсталляция ruby\" остаётся в системе со всеми своими \"устаревшими\" и \"уязвимыми\" версиями. И эти версии очень нравятся сканерам для того, чтобы поднять тревогу. </p>\n </section>\n\nИ еще два слова о том, что же именно сканирует Trivy (касательно ruby конечно):\n\n- сканирует [.gemspec файлы](https://guides.rubygems.org/specification-reference/) на всей файловой системе и не важно, установлен гем или нет - если в .gemspec указана версия \"с уязвимостью\" - алярм;\n- сканирует .gem файлы на всей файловой системе и не важно, установлен гем, лежит просто в папке cache, используется или нет в вашем приложении.\n\nЗначит для удовлетворения хотелок сканера, надо сделать так, чтоб нигде не лежало ничего лишнего или не используемого, **включая** [stdlib](https://docs.ruby-lang.org/en/3.2/standard_library_rdoc.html) - стандартную библиотеку ruby.\n\nПроблема ясна, задача очевидна - можно приступать к решению и начинать надо с базовых образов. Их надо собрать так, чтобы сами базовые образы уже не содержали никаких уязвимостей.\n\n\n_Немного удручающее зрелище, особенно для ИБ клиента_\n\n## Что такое инсталляция ruby?\n\nЕсли мы возьмём любой дистрибутив с установленным там ruby, то увидим, что само ruby будет находиться где-то в районе:\n\n- /usr/lib/ruby - например в alpine после `apk add ruby`\n- /usr/local/lib/ruby - например в `ruby:alpine`, где руби собирается отдельно от apk\n\nА дальше начинается интересное. Если посмотреть в финальный образ, в котором сделано много различных операций (обновление ruby, установка гемов через `gem install`, установка через `bundle install`), то в корневой папке ruby обнаружится несколько папок, по которым тем или иным способом будут распределены установленные вами гемы:\n\n```plaintext\n/usr/local/lib/ruby\n├── 3.2.0\n├── gems/3.2.0\n├── site_ruby/3.2.0\n└── vendor_ruby/3.2.0\n```\n\nСразу добавим к этому списку другие папки (которые можно посмотреть в выводе команды `gem env`):\n\n```yaml\n - INSTALLATION DIRECTORY: /usr/local/bundle\n - USER INSTALLATION DIRECTORY: /root/.local/share/gem/ruby/3.3.0\n - SPEC CACHE DIRECTORY: /root/.cache/gem/specs\n - GEM PATHS:\n - /usr/lib/ruby/gems/3.3.0\n - /root/.local/share/gem/ruby/3.3.0\n```\n\nИ вишенкой на торте будет настройка вашего bundle, если вы используете кеширование сборки ([bundle cache](https://bundler.io/man/bundle-cache.1.html) или `bundle config set cache_path vendor/cache`), и используемые в этом зоопарке переменные окружения `GEM_HOME`, `BUNDLE_CACHE_PATH`, `BUNDLE_PATH` и скорее всего еще какие-то скрытые в глубинах экосистемы ruby.\n\nНемного путано и в результате беспорядочных ~~связей~~ установок и обновлений во всех этих папках могут (и будут) появляться гемы. Надо исправить!\n\nПостараюсь дать верхнеуровневое описание, что же именно это за папочки, не углубляясь в подробности и исключения:\n\n### /usr/local/lib/ruby/3.3.0\n\nИменно в этой папке установлены основные файлы ruby и stdlib, а также компилируемые расширения в папке x86\\_64-linux-musl (в нашем случае собранные alpine для x86\\_64). Эти файлы принадлежат условной \"системе\" и никак не будут изменяться при дальнейших модификациях, например при установке или обновлении гемов через `gem install`. Вместо этого библиотеки будут ставиться в папку `/usr/local/lib/ruby/gems/3.3.0`\n\n### /usr/local/lib/ruby/gems/3.3.0\n\nТут собрано всё, что вы ставите (без модификации `GEM_PATH`) командами `gem install,` включая компилируемые расширения.\n\n### /usr/local/lib/ruby/vendor\\_ruby/3.3.0\n\nСюда **должны** ставиться дополнительные гемы и/или патчи от команды мейнтейнеров дистрибутива. Об этом сложно найти информацию, но несколько слов есть в книге `The Ruby Programming Language` или, что интереснее, в changelog для [NEWS for Ruby 1.8.7](https://docs.ruby-lang.org/en/2.3.0/NEWS-1_8_7.html) (да, окаменелое…):\n\n<section style=\"background-color:hsl(hsl(24, 24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);\">\n <p id=\"VAZR\"><strong>vendor_ruby</strong> directory<br>A new library directory named <code>vendor_ruby</code> is introduced in addition to <code>site_ruby</code>. The idea is to separate libraries installed by the package system (<code>vendor</code>) from manually (<code>site</code>) installed libraries preventing the former from getting overwritten by the latter, while preserving the user option to override vendor libraries with site libraries. (<code>site_ruby</code> takes precedence over <code>vendor_ruby</code>)<br>If you are a package maintainer, make each library package configure the library passing the <code>--vendor</code> option to <code>extconf.rb</code> so that the library files will get installed under <code>vendor_ruby</code>.<br>You can change the directory locations using configure options such as<code> --with-sitedir=DIR</code> and <code>--with-vendordir=DIR</code>.</p>\n </section>\n### /usr/local/lib/ruby/site\\_ruby/3.3.0\n\nСюда будут ставиться \"системные\" файлы, но не от OS, а от самого ruby, например после выполнения `gem update –system`:\n\n```plaintext\nsite_ruby/\n└── 3.4.0\n ├── bundler\n ├── rubygems\n └── x86_64-linux-musl\n```\n\n### /usr/local/lib/ruby/gems/3.3.0/specifications/\n\n…а также INSTALLATION DIRECTORY `/usr/local/bundle/specifications`… \n…а также USER INSTALLATION DIRECTORY: `/root/.local/share/gem/ruby/3.3.0`… \n…а также SPEC CACHE DIRECTORY `/root/.cache/gem/specs`… \n…а также BUNDLE\\_CACHE\\_PATH …\n\nСюда ставятся .gemspec файлы, которые собственно и говорят пакетному менеджеру ruby (gem и bundle), какие именно версии каких гемов установлены.\n\n### /usr/local/lib/ruby/gems/3.3.0/specifications/default\n\nDefault, Карл… Default - это особое состояние гема, и эти гемы нельзя удалить. Если вы обновили гем до новой версии, то старая всё равно останется, и Trivy вам этого не простит. Весьма вредная папка с точки зрения сканирования.\n\n### /usr/local/lib/ruby/gems/3.3.0/cache/\n\n…а также INSTALLATION DIRECTORY `/usr/local/bundle/cache`… \n…а также BUNDLE\\_PATH …\n\nСюда пакетный менеджер ruby (gem или bundle) скачивает гемы (допустим вы ставите faraday.gem) перед установкой. Этот кеш очень часто используется для ускорения сборки, например [в вашем Gitlab](https://docs.gitlab.com/ee/ci/caching/#cache-ruby-dependencies).\n\n## Что здесь у вас происходит?!\n\nПосле того как мы в процессе исследования детально разобрались и увидели всё это многообразие мест, где могут находиться файлы, вызывающие панические атаки у Trivy, мы решили радикально решить эту проблему: свести все файлы, все гемы и все спеки (.gemspec) в одно место. Это позволит легко следить за всеми (всеми!) фактическими зависимостями и эффективно пользоваться командами `gem cleanup` и `bundle clean`. Тут надо отметить, что для вашей рабочей OS (системы общего назначения) такое решение приведёт к поломке системного пакетного менеджера ([Gentoo Portage](https://ru.wikipedia.org/wiki/Portage), [dpkg/apt](https://ru.wikipedia.org/wiki/Dpkg), [rpm/zypper](https://ru.wikipedia.org/wiki/RPM) и пр.), но мы ведь говорим о конкретной сборке ruby под ваш конкретный проект - и тут никаких проблем не будет.\n\n<section style=\"background-color:hsl(hsl(24, 24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);\">\n <p id=\"ma0i\">Для того чтобы узнать что куда, когда и зачем ставится, мы использовали git прямо на корне файловой системы внутри контейнера (ruby:alpine, просто alpine, ruby:debian и другие образы для сравнения) и фиксировали изменения после различных команд ⏳</p>\n </section>\n\nНо это еще не всё. Остаётся еще проблема с default gemspec, но её относительно легко решить:\n\n```shell\nmv /usr/local/lib/ruby/gems/3.3.0/specifications/default/* /usr/local/lib/ruby/gems/3.3.0/specifications/\n```\n\nТеперь мы можем сформулировать План:\n\n- Все дороги ведут в Рим - делаем ссылочки для `vendor_ruby`, `site_ruby` и пр. Также явно прописываем системные переменные `GEM_HOME` и `BUNDLE_APP_CONFIG.`\n- Разбираемся с default gems.\n- Обновляем ruby (имеется в виду stdlib) до последней требуемой версии.\n- Обновляем bundle до нужной версии.\n- Удаляем лишние гемы (например rdoc).\n- Переставляем (!) системные (на текущий момент сборки базового образа - **все** ) гемы, потому что, как оказалось, `mv` для default gemspec имеет не очень хорошие последствия.\n- profit!\n\nСказано - сделано, и добро пожаловать под кат!\n\n```docker\nARG BASE_RUBY=3.2\nARG BASE_ALPINE=alpine3.16\nARG BASE_IMAGE=ruby:${BASE_RUBY}-${BASE_ALPINE}\n\nFROM ${BASE_IMAGE}\n\nARG BASE_RUBY=3.2\n\nARG RUBYGEMS_VERSION=3.5.20\nARG BUNDLER_VERSION=2.5.20\n\n# эта переменная есть в старом alpine но нет в debian и новом\n# добавляем потому что она очень нужна для работы с папочками\nENV RUBY_MAJOR=${BASE_RUBY}\n\nENV RUBYGEMS_VERSION=${RUBYGEMS_VERSION} \\\n BUNDLER_VERSION=${BUNDLER_VERSION}\n\nRUN apk update && apk upgrade\n\n# Это наш костыльный скрипт который удаляет всякие лишние кеши,\n# man-файлы и прочий мусор. \nCOPY common/scripts/cleanallbuilds.sh /usr/bin/\n\n# dumb-init всегда используем как PID-1 но это немного другая история\nRUN set -ex \\\n && apk add dumb-init \\\n && cleanallbuilds.sh\n\n###### \n# Надругиваемся над диструбутивом, чтоб иметь строго\n# одну версию руби в систему и управлять ею целиком через gem/bundle\n\n# все пути ведут в Рим\nENV GEM_HOME=/usr/local/lib/ruby/gems/${RUBY_MAJOR}.0/\nENV BUNDLE_APP_CONFIG=/usr/local/lib/ruby/gems/${RUBY_MAJOR}.0/\n\n# Сводим vendor_ruby, site_ruby и GEM_HOME в одно место\nRUN set -ex \\\n && rm -rf /usr/local/lib/ruby/site_ruby /usr/local/lib/ruby/vendor_ruby \\\n && ln -sf /usr/local/lib/ruby /usr/local/lib/ruby/site_ruby \\\n && ln -sf /usr/local/lib/ruby /usr/local/lib/ruby/vendor_ruby \\\n && mkdir -p /root/.local/share/gem/ruby/ \\\n && ln -sf ${GEM_HOME} /root/.local/share/gem/ruby/${RUBY_MAJOR}.0\n\n# Чутка тюним bundle config чтоб в дальнейшем не забыть\nRUN set -ex \\\n && bundle config --local disable_version_check true \\\n && bundle config --local clean false \\\n && bundle config --local no_prune false \\\n && bundle config --local disable_local_branch_check true \\\n && bundle config --local jobs 2 \\\n && bundle config --local allow_offline_install true\n\n# настраиваем .gemrc чтоб не было ничего лишнего, вклюячая rdoc\nRUN set -ex \\\n && echo 'gem: --no-document' > /usr/local/etc/gemrc \\\n && echo 'update_sources: false' >> /usr/local/etc/gemrc \\\n && echo 'verbose: false' >> /usr/local/etc/gemrc \\\n && echo 'update: --no-suggestions' >> /usr/local/etc/gemrc \\\n && echo 'install: --no-suggestions --conservative' >> /usr/local/etc/gemrc\n\n# пытаемся удалить ненужные гемы с самого начала - попытка не пытка\nRUN set -ex \\\n && gem uninstall -a -x --quiet --force `gem list | cut -f 1 -d \" \"` \\\n && gem cleanup\n\nRUN set -ex \\\n && apk add --virtual .build-deps \\\n autoconf \\\n bison \\\n bzip2 \\\n bzip2-dev \\\n coreutils \\\n curl-dev \\\n dpkg-dev dpkg \\\n g++ \\\n gcc \\\n gdbm-dev \\\n git \\\n glib-dev \\\n libc-dev \\\n libffi-dev \\\n libxml2-dev \\\n libxslt-dev \\\n linux-headers \\\n make \\\n ncurses-dev \\\n procps \\\n readline-dev \\\n tar \\\n xz \\\n yaml-dev \\\n zlib-dev \\\n shared-mime-info \\\n && mv /usr/local/lib/ruby/gems/${RUBY_MAJOR}.0/specifications/default/* /usr/local/lib/ruby/gems/${RUBY_MAJOR}.0/specifications/ \\\n && gem cleanup \\\n && gem update --system \"${RUBYGEMS_VERSION}\" \\\n && gem uninstall bundler --all --silent || true \\\n && gem install bundler -v \"${BUNDLER_VERSION}\" \\\n && gem uninstall rdoc --all --silent || true \\\n && GMS=`gem list | sed s/default:\\ // | sed -E 's/\\ \\((.*)\\)/:\\1/' | sort` \\\n && DEBUG_FLAGS=\"-Wno-calloc-transposed-args\" gem pristine --all --extensions \\\n && commands=$(for x in $GMS; do \\\n g=$(echo \"$x\" | cut -f 1 -d \":\"); \\\n v=$(echo \"$x\" | cut -f 2 -d \":\"); \\\n echo gem pristine $g --version \"$v\"; \\\n done) \\\n && echo -e \"$commands\" | xargs -I CMD -P 3 bash -c CMD\\\n && gem cleanup \\\n && rm -rf root/.local/share/gem/specs \\\n && apk del .build-deps \\\n && cleanallbuilds.sh\n\nWORKDIR /home/app\nRUN set -ex \\\n && adduser -D -s /sbin/nologin app \\\n && chown -R app:app /home/app\n\nENTRYPOINT [\"/usr/bin/entrypoint.sh\"]\nSHELL [\"/bin/sh\", \"-c\"] \n```\n\nСамое интересное, конечно, находится в одном слое самого пухленького RUN, и по некоторым командам надо дать пояснения:\n\n- `GMS=`gem list | sed s/default:\\ // | sed -E 's/\\ \\((.*)\\)/:\\1/' | sort`` - получаем список установленных гемов с версиями в формате \"yaml:0.4.0 zlib:3.2.1\". Это нам потребуется дальше из-за странного поведения `gem pristine`\n- `gem pristine --all --extensions` - должен для всех установленных гемов сделать \"чистовую\" установку, включая скачивание и перекомпиляцию расширений. Но нет. На самом деле он делает не для всех и не всё :(\n- `DEBUG_FLAGS=\"-Wno-calloc-transposed-args\"` - мы используем один Dockerfile для сборки базовых образов ruby 2.7, 3.0, 3.1, 3.2, и по умолчанию все расширения должны компилироваться без единого ~~разрыва~~ ворнинга компиляции, но для некоторых версий руби (кажется 3.0) это не так, и именно этот ворнинг всё портит, поэтому его подавляем без затей.\n- `commands=$(for x in $GMS; do …` - а вы знали, что в bash тоже есть пул потоков? Строго говоря, он [пул процессов](https://stackoverflow.com/questions/6441509/how-to-write-a-process-pool-bash-shell), и не в баше, а в xargs, но это не важно. В общем тут список `$GMS` в виде \"yaml:0.4.0 zlib:3.2.1\" трансформируется в список `$commands`, потому что в такой форме `gem pristine` работает именно так как надо:\n\n```shell\ngem pristine yaml --version \"0.4.0\"\ngem pristine zlib --version \"3.2.1\"\n...\n```\n\n- `echo -e \"$commands\" | xargs -I CMD -P 3 bash -c CMD` - отправляем `$commands` в пул из трех процессов для одновременной установки гемов. \n- `cleanallbuilds.sh` - зовём в самом конце кустарный скрипт, который удаляет всякий мусор из системы:\n\n```shell\n#!/bin/sh\n\n# ruby (alpine + debian)\n# Скрипт единый идля debian и для alpine, поэтому ошибки,\n# возникающие из-за разницы дистрибутивов просто игнорируем\nrm -rf /usr/src/ruby/ || true\nrm -rf /root/.local/share/gem/specs/ || true\nrm -rf /root/.local/state/ || true\nrm -rf /root/.cache/gem/ || true\nrm -rf /usr/local/lib/ruby/gems/3.2.0/cache/ || true\nrm -rf /root/.bundle/cache/ || true\ngem cleanup || true\n\n# alpine\napk cache clean || true\n\n# debian\napt-get clean autoclean || true\napt-get autoclean --yes || true\napt-get autoremove --yes || true\n\nfind /var/log/ -type f -delete || true\nfind /var/lib/log/ -type f -delete || true\nfind /usr/share/doc/ -type f -delete || true\n```\n\n## Результат\n\nСтарый образ занимал 900MB, новый - 152MB (не спрашивайте...)\n\n\n_Значительно лучше. Особенно для базового образа_\n\n## Что дальше?\n\nТеперь эти базовые образы надо начать использовать непосредственно в CI пайплайнах боевых сервисов и, конечно, процедуру сборки и тестирования придётся поменять. Точнее, мы уже перешли на новые образы и сборку уже пару недель назад - дело теперь только за статьёй. А в качестве приманки приведу результаты перехода для одного из самых \"толстых\" наших сервисов:\n\n```plaintext\nimage:old size:1.49GB vulns:255\nimage:new-prod size:259MB vulns:1 (Вчерашняя!! СVE-2025-25186)\nimage:new-test size:269MB vulns:1 (СVE-2025-25186)\n```\n\nЕсли взглянуть ретроспективно, то совсем не весело - так что:\n\n<section style=\"background-color:hsl(hsl(24, 24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);\">\n <p id=\"tf2v\">профилируйте чаще не только ваш код и инфраструктуру сборки.</p>\n </section><tt-tags id=\"HMo0\">\n <tt-tag name=\"docker\">#docker</tt-tag>\n <tt-tag name=\"devsecops\">#devsecops</tt-tag>\n <tt-tag name=\"ruby\">#ruby</tt-tag>\n <tt-tag name=\"cicd\">#cicd</tt-tag>\n </tt-tags>",
|
|
16
|
+
"positive_reactions_count": 0,
|
|
17
|
+
"cover_image": null,
|
|
18
|
+
"tag_list": [],
|
|
19
|
+
"canonical_url": "https://blog.rnds.pro/064-devsecops1?utm_source=teletype&utm_medium=feed_rss&utm_campaign=rnds",
|
|
20
|
+
"reading_time_minutes": 6,
|
|
21
|
+
"user": {
|
|
22
|
+
"name": "Samoilenko Yuri",
|
|
23
|
+
"username": "kinnalru",
|
|
24
|
+
"twitter_username": null,
|
|
25
|
+
"github_username": "kinnalru",
|
|
26
|
+
"user_id": 559891,
|
|
27
|
+
"website_url": "https://teletype.in/@jerry_ru",
|
|
28
|
+
"profile_image": "https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F559891%2Fe6a6dcca-2504-4ab8-ab71-d4b4cb6f492c.png",
|
|
29
|
+
"profile_image_90": "https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F559891%2Fe6a6dcca-2504-4ab8-ab71-d4b4cb6f492c.png"
|
|
30
|
+
},
|
|
31
|
+
"organization": {
|
|
32
|
+
"name": "RNDSOFT",
|
|
33
|
+
"username": "rnds",
|
|
34
|
+
"slug": "rnds",
|
|
35
|
+
"profile_image": "https://media2.dev.to/dynamic/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F3591%2Fdbaf4648-1791-4e01-aa3b-aa6059717d48.png",
|
|
36
|
+
"profile_image_90": "https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F3591%2Fdbaf4648-1791-4e01-aa3b-aa6059717d48.png"
|
|
37
|
+
}
|
|
38
|
+
}
|