<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>@kright</title><generator>teletype.in</generator><description><![CDATA[программист]]></description><link>https://teletype.in/@kright?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/kright?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/kright?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Tue, 07 Apr 2026 22:33:30 GMT</pubDate><lastBuildDate>Tue, 07 Apr 2026 22:33:30 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@kright/FH4</guid><link>https://teletype.in/@kright/FH4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright</link><comments>https://teletype.in/@kright/FH4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright#comments</comments><dc:creator>kright</dc:creator><title>Впечатления от Forza Horizon 4 на руле</title><pubDate>Sun, 05 Jun 2022 22:29:04 GMT</pubDate><description><![CDATA[TLDR: игра создана под клавиатуру/джойстик, лучше играть на них.]]></description><content:encoded><![CDATA[
  <p id="firstHeading"><strong>TLDR</strong>: игра создана под клавиатуру/джойстик, лучше играть на них.</p>
  <h3 id="OL6m">Суть проблемы</h3>
  <p id="jzsF">Я когда-то играл на клавиатуре в разные игры и горя не знал, а потом купил руль, и с ним впечатления от игр кардинально поменялись. Например, симуляторы типа Dirt Rally 2.0 или Asseto Corsa при игре на клавиатуре не впечатляют, но буквально расцветают при подключении руля и педалей. А вот с форзой, к моему сожалению, получилось ровно наоборот. Обратные реакции на руле кажутся искусственными и вообще складывается впечатление, что руль сделан &quot;для галочки&quot;.</p>
  <p id="uL0Y">Ниже я по пунктам распишу, что же мне не понравилось.</p>
  <h3 id="prPR">Кривые дефолтные настройки руля.</h3>
  <p id="QepW">По умолчанию руль нелинейный. Повернули наполовину - колёса поворачиваются намного слабее, а вот в конце поворота - очень даже сильно. Мой руль поворачивается от упора до упора на 1080 градусов, и с нелинейной настройкой играть мягко говоря проблематично - из положения &quot;около нуля&quot; надо очень далеко крутить руль, чтобы машина хоть как-то начала поворачивать. Итак, лезем в &quot;продвинутые&quot; настройки руля и делаем его снова линейным. После такого force feedback становится чуточку адекватнее, а руль начинает влиять на угол поворота колёс как на нормальном автомобиле.  Ещё можно в настройках сложности отключить traction control, abs и прочие &quot;улучшения&quot;, чтобы получить максимум ощущений. Но это нас не спасёт :(</p>
  <h2 id="aUHE">Физика, которую мы потеряли</h2>
  <p id="QFNH">Игры forza motorsport и forza horizon используют один и тот же физический движок. Окно тюнинга автомобиля создаёт обманчивое ощущения, что модель поведения автомобиля глубоко проработана и приближена к реальности. К сожалению, эксперименты с рулём показали, что это не так. Во имя аркадности в физику воткнуты очень большие костыли, радикально упрощающие геймплей и сводящие на нет удовольствие от игры на руле. Дальше я перечислю различные отдельные эффекты, которые упрощены или отсутствуют. </p>
  <p id="t0o6">Не то чтобы я был радикально против и хотел сделать из форзы симулятор в открытом мире, но эти особенности больше всего чувствуются в игре на руле. Хотя вру - очень даже хотел бы симулятор с графикой форзы и открытым миром.</p>
  <h3 id="olN9">Машина после столкновения не вращается</h3>
  <p id="Gr3f">Например, если влететь в дерево левой фарой, машина мгновенно остановится. Она не будет красиво кувыркаться, вращаться и лететь куда-то дальше. Собственно, при игровых 200+ км/ч машина может очень далеко лететь. Я не против такого введения, но это очень хорошая иллюстрация того, как можно вмешаться в физику и сделать её менее честной.</p>
  <h3 id="Oogs">Езда по разным покрытиям</h3>
  <p id="akdg">В assetto corsa при заезде парой колёс на траву очень легко улететь с трассы. А если нажать на газ или тормоз - тем более. В реальной жизни, впрочем, так же - не стоит &quot;наполовину&quot; съезжать с дороги и резко нажимать на тормоз. Те колёса, которые на асфальте - затормозят, а другие - нет, автомобиль начнёт разворачивать носом в сторону асфальта.</p>
  <p id="etMz">В форзе этого эффекта вообще не чувствуется - я специально вставал парой колёс на асфальт и парой на обочину, пробовал укоряться-замедляться с отключенными трекшеном и абс - ничего подобного, машина едет прямо. Кроме того, я пробовал на переднеприводной машине с полностью разблокированным дифференциалом в таких условиях разгоняться и смотрел телеметрию. Оба передних колеса начинали проскальзывать одновременно.</p>
  <p id="HtFU">В реальном мире при таком разгоне колесо на обочине начнёт проскальзывать, дифференциал направит движение на него, а второе ведущее колесо будет без проскальзывания катиться по асфальту.</p>
  <p id="kVqa">У меня сложилось впечатление, что для машины просто берут центральную точку, смотрят покрытие под ней - асфальт/трава/гравий - и при дальнейших рассчётах предполагают, что под всеми колёсами это покрытие. В forza motorsport скорее всего не так.</p>
  <p id="qvVs">Зачем это может быть сделано? А как раз ради аркадности, чтобы не вписавшийся в поворот игрок спокойно ехал дальше и не боялся развернуться при съезде с асфальта.</p>
  <p id="hkmy">Но, на мой взгляд, это упрощение фатально портит игру. В ней есть разные времена года и попадаются дождь, снег и т.п. В них важно покрытие под каждым колесом и то, как оно меняется. Но разработчики заруинили этот момент, поведение на них скучное и не совпадающее с картинкой.</p>
  <p id="XbEs">В качестве примера &quot;честной&quot; реализации приведу Dirt Rally 2.0 на трассе в Монте-Карло. На <a href="https://www.youtube.com/watch?v=n7AS-6CLMBQ" target="_blank">видео</a> есть смысл смотреть третью минуту - чередуются асфальт со снегом и льдом, на последних машина едет и ощущается совсем по-другому. Приходится творчески перестраивать траекторию поворотов, чтобы избежать пятен снега - на них машина скользит и не особо управляется. И да - если наехать одной стороной на лёд и нажать на газ, то можно очень хорошо почувствовать настройку дифференциала: машина со свободным диффом шлифует лёд одним колесом и не ускоряется, машина с заблокированным дифференциалом толкается колесом от асфальта и её уводит в сторону. </p>
  <p id="aPio">А ещё на льду руль становится очень лёгким - впрочем, как и на реальном автомобиле.</p>
  <p id="RX0f">В форзе всего этого нет.</p>
  <h3 id="60MF">Езда на трёх колёсах</h3>
  <p id="EjTo">Если посмотреть, как ездят в реальном спорте, то там есть неочевидные решения. </p>
  <p id="berX">Например, в картинге задний привод и нет дифференциала. В повороте карт едет на трёх колёсах - двух передних и внешнем заднем. Заднее внутреннее висит в воздухе и свободно крутится. Благодаря этому вся мощность двигателя идёт на заднее внешнее колесо, а заднее внутреннее не мешает карту ехать. Кроме того, при нажатии на газ заднее внешнее колесо толкает карт вперёд и помогает ему поворачивать.</p>
  <p id="Dd2f">В форзе этого эффекта толком нет. Технически можно в машину поставить жёсткий задний стабилизатор, заблокировать дифференциал и т.п., но оно ощущается совсем не так и доворачивать машину газом толком не получится.</p>
  <p id="MspR">Кроме того, в реальном спорте для переднеприводных автомобилей встречается и другая схема - заблокированный дифференциал, жёсткие стабилизаторы спереди и автомобиль, который в повороте под газом поднимает переднее внутреннее колесо в воздух. Вся мощность двигателя идёт на передне внешнее колесо и позволяет максимально ускориться на выходе из поворота.</p>
  <p id="5fSf">Я попробовал сделать такое в форзе. На клавиатуре или джойстике без шансов - при нажатии в повороте на газ игра решает, что отрыв внутреннего колеса - что-то экстраординарное и при зажатой кнопке поворота машина начинает ехать более прямо. Там эта схема в принципе не рабочая.</p>
  <p id="l1Pk">Попробовал на руле - технически, оно работает, на некоторых машинах даже удавалось поднять переднее колесо в воздух. Вот только машина от этого лучше поворачивать не стала и на добавление газа в повороте толком никак не реагировала. Она ускорялась, но не было чувства, что внешнее колесо тянет машину вперёд и заодно её поворачивает.</p>
  <p id="c05r">У меня сложилось впечатление, что такие &quot;закручивающие&quot; силы в игре зачем-то убрали. А жаль, на руле их реально не хватает.</p>
  <p id="JmUp">По факту для переднего привода в форзе хорошо работает только одна схема - разблокировать дифференциал, убрать передний стабилизатор и поставить максимально жёсткий задний. </p>
  <h3 id="coTq">Баланс тормозов и увод колёс</h3>
  <p id="Govo">Резиновые шина под нагрузками гнётся и может ехать на совсем прямо. Для сликов в формуле 1 &quot;не совсем прямо&quot; может быть отклонено на 10 градусов в сторону. На резине с протектором эффект слабее, но он тоже есть. Он зависит от давления в колесе, высоты профиля, мягкости резины и прочих характеристик. Вот тут <a href="https://youtu.be/ZZ1uCH9ay1k?t=58" target="_blank">показано</a>, как сильно может деформироваться резина. </p>
  <p id="FEnD">При росте нагрузки шины не сразу начинают скользить. Есть переходные состояния, когда шины гнутся, деформируются, отклоняются от прямого направления, но всё ещё держатся за асфальт. </p>
  <p id="P27h">К сожалению, в форзе я этого не чувствую. Машина либо едет, либо скользит. Нету плавного соскальзывания под нагрузкой.</p>
  <p id="N2VB">Как это можно было бы использовать в гонках? </p>
  <ul id="mgVX">
    <li id="UdCy">баланс тормозов смещён назад: В повороте можно немножно нажать на тормоз и добавить машине избыточной поворачиваемости. Задние колёса начнёт плавно уводить, машина охотнее ввинтится в поворот.</li>
    <li id="cu4N">баланс тормозов смещён вперёд: Добавление тормоза в повороте приведёт к недостаточной поворачиваемости и машина будет выпрямляться. Поведение не самое интересное, но зато позволяет стабильно тормозить и не бояться развернуться.</li>
  </ul>
  <p id="9cvV">Чем лучше сцепление с дорогой, тем сильнее надо тормозить передними колёсами и слабее задними, так как при торможении больше веса уйдёт вперёд. Если брать ралли с разнообразными участками, то выбор баланса тормозов добавляет ещё один &quot;слой&quot; планирования и вышеописанные эффекты разнообразят геймплей. Но не в форзе(</p>
  <p id="YfHY">Кроме того, на переднем приводе есть интересный приём по смещению баланса - можно одновременно нажать газ и тормоз. На передних колёсах тормоза будут бороться с двигателем, а задние будут тормозить в полную силу. Баланс тормозов сместится назад и машина очень охотно зарулит в поворот. В Dirt Rally 2.0 я этот приём как-то интуитивно начал использовать, в форзе этого эффекта в принципе не чувствуется. Жаль :(</p>
  <h3 id="cDPJ">Скучный оффроад</h3>
  <p id="MDMP">При езде по пересечённой местности трясётся руль и машина капельку хуже разгоняется. И всё, можно на lamborgini катить по полю 200 км/ч, почему нет? </p>
  <p id="MENe">На самом деле в игре нет маленьких неровностей. Они даже не нарисованы! У просёлочной дороги в лучшем случае будет обочина на другой высоте. Никаких колей, ухабов, уклонов дороги. А значит - никакого микроконтроля автомобиля, никаких приёмов типа зацепления колёсами за колею и т.п. </p>
  <h3 id="0rEO">Жёсткая задемпфировання подвеска</h3>
  <p id="Dnjl">Управление клавиатурой или джойстиком состоит из очень резких нажатий. Какие-нибудь раскачивания кузова или большие ходы подвески сделают игру неудобной, машина будет болтаться и не поворачивать. </p>
  <p id="EJy2">Поэтому в форзе подвеска жёсткая и задемпфированная, никаких раскачиваний. </p>
  <p id="SdeO">А на руле хочется чувствовать машину! В том, что подвеска работает и в разных условиях машина рулится по-разному - самый кайф! Нажать на тормоз, загрузить передние колёса, повернуть руль одновременно с отпусканием тормоза, добавить газу. Реакции на всё это должны быть не мгновенными, а чуть-чуть размазанными по времени.</p>
  <p id="a1vZ">Частично проблему можно решить, если специально сделать машине мягкую подвеску. Управляемость станет чуточку интереснее, но всё равно очень далёкой от симуляторов.</p>
  <h3 id="oktF">Быстрее, выше, сильнее</h3>
  <p id="IOQe">В форзе доступны гиперкары под 1000+л.с., скорости под 400 км/ч, широкие дороги...  А нужно ли это всё для получения удовольствия? Нет! </p>
  <p id="Ci58">В Dirt Rally 2.0 езда на старом миникупере по узкой скользкой дороге со скоростью 60 км/ч оказывается куда более увлекательной. Как так получается?</p>
  <p id="xwB0">Здесь особенности сплетаются в взаимосвязанный клубок, и в идеале надо бы менять сразу все аспекты.</p>
  <p id="kBlM">Во-первых, в игре есть куча очень быстрых и мощных машин и игра всеми силами предлагает кататься именно на них.</p>
  <p id="vYlt">Но есть нюанс: длина тормозного пути пропорциональна квадрату скорости. На 100 км/ч - около 30 метров на асфальте, на 200 - 120 метров, на 300 - 270м, на 400 - 480м. Ой, как-то неинтересно за полкилометра до поворота тормозить, что же нам делать? </p>
  <p id="wtzP">А давайте накрутим коэффициент трения с поверхностью, пускай машина тормозит с ускорением не 1g, а все 2-3! Ух, заживём! И перед поворотами станет можно тормозить не так сильно. И для дождя/земли/снега тоже, мы же не хотим зимой 100 км/ч тащиться, хотим сразу 200-300.</p>
  <p id="xrK5">Только есть нюанс. Машина в поворотах быстрая и дерзкая, теперь сложно по прямой ехать. Нажмёшь клавишу поворота на мгновение, и машина уже на пару метров вбок отклоняется. Надо бы дорогу пошире сделать. Нарисуем две полосы, а по ширине сделаем как все 4, чтобы игрок в них как-нибудь поместился.</p>
  <p id="i1vI">И ещё нюанс. Проблему с тормозным путём решили, но 360 км/ч - это 100 метров в секунду. Даже если игрок затормозит на 0.2 секунды позже, чем требуется, он пролетит мимо поворота на 20 метров. Что же делать? А не беда, дороги и так широкие, добавим ещё возможность ехать по обочине и срезать, вот 20 метров и компенсируем.</p>
  <p id="Hrx6">Впрочем, 100 м/с от нас всё никак не отстанут. Например, чтобы думать вперёд на 3-5 секунд, неплохо бы видеть трассу вперёд на 300-500 метров. Ну ничего, камеру от третьего лица повесим повыше, будет видно далеко и скорость визуально будет поменьше казаться.</p>
  <p id="D6Ux">И да, если на дороге каждые 100 метров будет новый поворот, придётся каждую секунду маневрировать. Вряд ли игроки обрадуются, так что сделаем дороги более прямыми, а повороты - плавными.</p>
  <p id="HB03">Ой, наш &quot;огромный&quot; мир можно проехать за несколько минут. Как же так получилось? Ну ничё, в следующей игре сделаем её раза в полтора больше и максимальную скорость автомобилей до 400-500 км/ч поднимем. Прогресс!</p>
  <p id="RY5z">На мой взгляд, форза пошла куда-то не туда. Всякими &quot;космолётами&quot; на руле играть неинтересно и малореально. На более медленных машинах оказывается, что карта не такая уж и насыщенная, повороты и элементы рельефа крупные и расставлены далеко друг от друга. Техничных извилистых трасс толком нет. Дороги слишком широкие. Машина не живая, на движения руля реагирует очень резко и неестественно, подвески как будто нет.</p>
  <h2 id="OVkU">Выводы</h2>
  <p id="0Ejs">В форзу лучше играть на джойстике с видом от третьего лица. Вся игра сбалансирована и настроена под это.</p>
  <p id="WVEy">В игре есть какая-то физика, но разработчики её сознательно упростили в пользу аркадности. </p>
  <p id="A1h5">Играть на руле совершенно неинтересно, для этого есть совсем другие игры.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@kright/UXCTBmq4xsm</guid><link>https://teletype.in/@kright/UXCTBmq4xsm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright</link><comments>https://teletype.in/@kright/UXCTBmq4xsm?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright#comments</comments><dc:creator>kright</dc:creator><title>Input lag в мониторе</title><pubDate>Mon, 30 May 2022 13:11:26 GMT</pubDate><description><![CDATA[В моём мониторе есть настройки - fast и normal, и я решил померять разницу. ]]></description><content:encoded><![CDATA[
  <p id="yO4A">В моём мониторе есть настройки - fast и normal, и я решил померять разницу. </p>
  <p id="flQy">Способ измерения - подключить два монитора в режиме mirror, чтобы на них отправлялась одинаковая картинка. Открыть что-то типа отображения часов с миллисекундной точностью и сфотографировать оба экрана.</p>
  <p id="OSkj">Такие часы можно открыть прям в браузере: <a href="https://cdpn.io/robertschulze/fullpage/yLOgryr" target="_blank">https://cdpn.io/robertschulze/fullpage/yLOgryr</a> </p>
  <h3 id="adtu">Объективный результат</h3>
  <p id="cWWK">Между fast и normal режимами отображения разница около 16 миллисекунд. Монитор Dell P2418D. </p>
  <p id="yPRT">Мне кажется, это вполне сочетается с частотой обновления экрана в 60 гц, изображение рисуется на один кадр позже. Видимо, монитор для какого-то улучшения картинки ждёт её целиком и только потом рисует.</p>
  <h3 id="BsLm">Субъективные ощущения</h3>
  <p id="fuxp">Курсор мышки более чётко двигается по экрану. Разница небольшая, на грани ощущений.</p>
  <p id="JG0X">Я попросил второго человека включить какой-то режим, дать мне подвигать мышкой, потом поменять режим на другой и снова дать подвигать мышкой. Я смог почувствовать разницу и правильно назвать режим. Но есть нюанс - после первого &quot;измерения&quot; я не мог почувствовать, в каком из режимов нахожусь, разница чувствовалась только на контрасте.</p>
  <p id="VHIe">Разницы в &quot;красивости картинки&quot; я не заметил.</p>
  <h3 id="bp5w">Выводы</h3>
  <p id="k5Bq">16 мс - вроде бы мелочь, но я включил быстрый режим и сижу довольный.</p>
  <p id="nfsi">У типичного пользователя &quot;нормальный&quot; режим монитора, &quot;двойная&quot; или даже &quot;тройная буферизация&quot; на стороне видеокарты, включена вертикальная синхронизация, как-то подтормаживающий компьютер, какая-то задержка при действиях клавиатурой/мышкой - и вот на мониторе 60 гц получатся запаздывание картинки на 2-4 кадра. Ну т.е., реакция запаздывает на 1/30 - 1/15 секунды и складывается из нескольких факторов. Один из них я улучшил)</p>
  <p id="a3mT">И да - я сейчас не про игры, а просто про комфорт работы за компьютером.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@kright/scala3_transparent_inline_with_dynamic</guid><link>https://teletype.in/@kright/scala3_transparent_inline_with_dynamic?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright</link><comments>https://teletype.in/@kright/scala3_transparent_inline_with_dynamic?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright#comments</comments><dc:creator>kright</dc:creator><title>Scala 3: transparent inline with Dynamic</title><pubDate>Sun, 08 May 2022 13:38:56 GMT</pubDate><description><![CDATA[В скале есть интерфейс Dynamic. От него можно унаследоваться и получить динамическую типизацию - при отсутствии поля в классе компилятор подставит вызов специального метода.]]></description><content:encoded><![CDATA[
  <h2 id="wfES">динамическая типизация внутри статической</h2>
  <p id="bp9p">В скале есть интерфейс Dynamic. От него можно унаследоваться и получить динамическую типизацию - при отсутствии поля в классе компилятор подставит вызов специального метода.</p>
  <pre id="XuLa" data-lang="scala">import scala 
import scala.language.dynamics

class MyClass extends Dynamic

val a = MyClass
a.x 
// a.selectDynamic(&quot;x&quot;)
a.x = 2
// a.updateDynamic(&quot;x&quot;)(2)</pre>
  <p id="eXKP">Класс MyClass может быть любым, например таким:</p>
  <pre id="7wjz" data-lang="scala">class MyClass extends Dynamic {
  private var x: Int = 0
	
  def selectDynamic(fieldName: String): Int = {
	fieldName match 
	  case &quot;x&quot; =&gt; return x 
  }
	
  def updateDynamic(fieldName: String)(value: Int): Unit = {
	fieldName match 
	  case &quot;x&quot; =&gt; x = value
  }
}</pre>
  <p id="8SRv">Конкретно такой класс получился не особо полезным - по поведению он похож на </p>
  <pre id="EVUV" data-lang="scala">class MyClass(){var x: Int = 0}</pre>
  <p id="r5ut">только хуже - во время компиляции мы не увидим ошибок, если обратимся к несуществующему полю, и код упадёт в рантайме. Впрочем, этот код нужен для иллюстрации возожностей языка.</p>
  <p id="1pFL">Что, если нам захочется сделать класс с полями разных типов?</p>
  <pre id="ZXLe" data-lang="scala">class MyClass() {
    var x: Int = 0 
	var b: Boolean = false
}</pre>
  <p id="6liI">Если попробуем написать альтернативу этому классу с помощью Dynamic, в качестве принимаемых и возвращаемых типов придётся указывать Any и кастовать к какому-то типу.<br />Впрочем, у нас тут Scala 3, в которой появились Union types, можно указать в качестве типа поля Int | Boolean и что-то иное типа строки компилятор не даст присвоить.</p>
  <pre id="8EzS" data-lang="scala">class MyClass extends Dynamic {  
  private var x: Int = 0  
  private var b: Boolean = false  
  
  def selectDynamic(field: String): Int | Boolean = {  
    field match  
      case &quot;x&quot; =&gt; x  
      case &quot;b&quot; =&gt; b  
  }  
  
  def updateDynamic(field: String)(value: Int | Boolean): Unit = {  
    field match  
      case &quot;x&quot; =&gt;  
        value match  
          case v: Int =&gt; x = v  
      case &quot;y&quot; =&gt;  
        value match  
          case v: Boolean =&gt; b = v  
  }  
}</pre>
  <p id="DBc0"></p>
  <p id="20C3">Но появилась ещё проблема - мы всё равно можем попробовать присвоить Boolean в x или Int в b, а потом упадём в рантайме. Такова участь динамической типизации, ничего не поделать.</p>
  <p id="Z0ks">И тут c ноги врывается transparent inline! Перепишем метод selectDynamic:</p>
  <pre id="juUV" data-lang="scala">import scala.compiletime.error

MyClass { 
    ...
	transparent inline def selectDynamic(inline field: String): Int | Boolean = {  
	  inline field match  
		case &quot;x&quot; =&gt; x
		case &quot;b&quot; =&gt; b
		case _ =&gt; error(&quot;unknown field in MyClass&quot;)
	}
}</pre>
  <p id="xhmJ">Что будет дальше? В месте вызова</p>
  <pre id="Pghl" data-lang="scala">a.x</pre>
  <p id="E3vG">компилятор подставит вызов функции selectDynamic, пойдёт внутрь, в inline match, найдёт там подходящую строку &quot;x&quot; и заменит всё-всё-всё на простое обращение к полю x с типом Int.</p>
  <p id="ciql">Если быть точным и посмотреть байткод - у MyClass появится метод - геттер</p>
  <pre id="GvJ8" data-lang="scala">def inline$x(): Int</pre>
  <p id="qY81"> который будет вызываться напрямую.</p>
  <p id="VrTZ">Ну либо на это можно смотреть так</p>
  <pre id="cQSQ" data-lang="scala">val x: Int = a.selectDynamic(&quot;x&quot;)
val b: Boolean = a.selectDynamic(&quot;b&quot;)</pre>
  <p id="niBi">Возвращаемый тип зависит от аргумента!</p>
  <p id="XWV6">Кроме того, если обратиться к несуществующему полю, прямо во время компиляции подставится compiletime.error и компилятор покажет ошибку.</p>
  <p id="YMD9">Поздравляю, мы снова вернулись к статической типизации. Какие выводы можно сделать?</p>
  <ul id="soyT">
    <li id="3WJr"> transparent inline - очень мощный инструмент, позволяющий гибко работать с типами. В scala 2.0 такое невозможно.</li>
    <li id="ZtKn">Нет строго дуализма между runtime и compiletime преобразованиями, гибкость scala позволяет в какой-то мере заменить первое на второе. В С++ похожая ситуация.</li>
  </ul>
  <p id="tGtS">Для полноты картины покажу, как можно переписать второй метод:</p>
  <pre id="IkXP" data-lang="scala">transparent inline def updateDynamic(inline field: String)(inline value: Int | Boolean): Unit = { 
  inline field match  
    case &quot;x&quot; =&gt;  
      inline value match  
        case v: Int =&gt; x = v  
        case _ =&gt; error(&quot;should be int&quot;)  
    case &quot;y&quot; =&gt;  
      inline value match  
        case v: Boolean =&gt; b = v  
        case _ =&gt; error(&quot;should be boolean&quot;)  
    case _ =&gt; error(&quot;unknown field&quot;)  
}</pre>
  <p id="hm1r">Посмотрите на переход от динамической типизации обратно к статической при помощи более &quot;умного&quot; компилятора! Что-то подобное можно попробовать в динамических языках типа Python, чтобы ловить ошибки как можно раньше. Не все, но какую-то часть.</p>
  <p id="oFpp">Пример кода выше не очень практичен - можно сделать обычный класс с полями x: Int, b:Boolean и он будет прекрасно работать. В классе c Dynamic уже известные поля можно заменить на реальные поля и это тоже будет прекрасно работать, компилятор будет обращаться напрямую к ним вместо вызова selectDynamic:</p>
  <p id="l5dh"></p>
  <pre id="oaf3" data-lang="scala">class MyDynamic extends Dynamic{
    var x: Double = 0
	var b: Boolean = 0
	def selectDynamic(...) ...
}</pre>
  <p id="Ybsq">В общем, код выше дублирует базовые возможности языка, бесполезен сам по себе, но, надеюсь, полезен для иллюстрации.</p>
  <p id="pAZY">Для чего использовать transparent inline в реальном коде - не знаю. Люди как-то жили без этой возможности и прекрасно решали свои задачи. Но я точно уверен, что часть этих задач теперь можно решить более гибко и красиво.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@kright/raspberry-pi-4b-nas</guid><link>https://teletype.in/@kright/raspberry-pi-4b-nas?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright</link><comments>https://teletype.in/@kright/raspberry-pi-4b-nas?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=kright#comments</comments><dc:creator>kright</dc:creator><title>Моё домашнее сетевое хранилище из raspberry pi 4b</title><pubDate>Mon, 24 Jan 2022 17:43:17 GMT</pubDate><description><![CDATA[Не уверен, что мой способ оптимальный по надёжности или расходам, но для истории запишу его здесь, включая особенности и подводные камни.]]></description><content:encoded><![CDATA[
  <p id="cUNo">Не уверен, что мой способ оптимальный по надёжности или расходам, но для истории запишу его здесь, включая особенности и подводные камни.</p>
  <p id="bcke">P.S. Время от времени пробую сделать с малинкой что-нибудь новое и дополняю статью.</p>
  <h3 id="qbQO">Установка ОС</h3>
  <p id="ZSnL">Есть готовая штука - raspberry pi imager, в которой можно выбрать желаемую ОС и залить её на sd-карту. В случае с малинкой на 8 гб оперативной памяти стоит обратить внимание на битность ОС. Якобы видеодрайвера в 64битной ОС работают плохо, но в дальнейшем мне они и не нужны. Я выбрал 64-битную версию Ubuntu server.</p>
  <p id="yfSO">Можно установить ОС, не подключая монитор/клавиатуру/мышь. Чтобы она подключилась к сети, где-то в файлике на sd-карточке с ОС придётся прописать имя и пароль от wifi сети. Но ещё лучше (и на мой взгляд проще) подключить сеть через кабель к роутеру. В малинке гигабитный порт, для нужд домашнего хранилища самое то. Вышеописанные шаги с паролем не нужны, зато неплохо бы иметь доступ на роутер, чтобы узнать, по какому адресу доступна малинка.</p>
  <p id="Ncah">Либо можно просканировать локальную сеть и так узнать адрес:</p>
  <pre id="lsYn" data-lang="bash">sudo nmap -sn 192.168.1.0/24</pre>
  <p id="RP1j">При самом первом запуске ОС грузится несколько минут, а я поначалу думал что что-то делаю не так. Вся дальнейшая работа и настройка делается через ssh</p>
  <h3 id="3xLP">Внешний жёсткий диск</h3>
  <p id="TKac">Я купил внешний жёсткий диск на 4Тб: HDD WD Elements Desktop WDBWLG0040HBK-EESN. Из особенностей - у него есть отдельный провод для питания от розетки и он не должен тащить энергию из малинки. Из минусов - в розетку придётся втыкать два девайса. У малинки синие разъёмы - usb 3.0, диск лучше втыкать в них.</p>
  <p id="GVbQ">Дальше начинаются приключения: raspberry pi imager при установке ОС размечает сд-карточку в MBR. Можно вместо карточки подключить жёсткий диск, но MBR позволит использовать только первые 2 Тб диска. А ещё хочется отказаться от sd карточки. Почему-то полностью переселить ОС на диск c GPT разметкой у меня не получилось, ОС не грузилась. В качестве полумеры я оставил загрузочный раздел на sd карточке и раздел с ОС перенёс на диск. Количество записей на карточку будет минимальным, и в случае потери я смогу вставить новую карточку памяти с загрузчиком и работать дальше. </p>
  <p id="qgA3">Самое странное, что в сдругим жёстким диском toshiba на 2 гб с gpt внезапно загрузиться получилось. Я потратил около дня, но так и не понял, почему с одного диска грузится, а с другого - нет.</p>
  <p id="2Ki7">Можно взять раздел с ОС прямо с сд карточки (зря что ли на неё ОС ставили и настраивали) и скопировать на диск. Дальше понадобится узнать айдишники для разделов. </p>
  <pre id="b1NP" data-lang="bash">sudo blkid</pre>
  <p id="PXqh">Примерное описание тут: <a href="https://linuxtut.com/en/7af17f1272ca0c558d47/" target="_blank">https://linuxtut.com/en/7af17f1272ca0c558d47/</a></p>
  <p id="mkNM">Эти айдишники надо будет обновить в двух местах:</p>
  <ol id="MqCg">
    <li id="JHWW">В загрузчике boot/cmdline.txt указывает, откуда грузить ОС.</li>
    <li id="2XoT">В самой ОС в еtc/fstab монтируются и загрузчик, и раздел с ОС</li>
  </ol>
  <p id="g4hr">Как я потом узнал - не обязательно использовать PARTUUID, можно вместо него указать PARTLABEL. Название раздела можно сделать любое, и мне это показалось более удобным. Но надо проследить, чтобы не было двух разделов с одинаковым названием.</p>
  <p id="S1o2">В малинке целых 2 usb порта, поэтому можно включить рядышком второй диск и сделать софтовый RAID1. На двух дисках будут лежать идентичные копии данных, чтение теоретически может ускориться в два раза, запись будет со скоростью самого медленного и некоторыми приколами дисков с черепичной записью. К сожалению, о современных дисках производители эту информацию не особо публикуют, в одной &quot;модели&quot; может оказаться как одно, так и другое, и я RAID делать не стал. В планах организовать резервное копирование с домашнего ПК на малинку - тогда отказ одного из дисков или устройств не будет фатальным.</p>
  <h2 id="bZlU">Применение</h2>
  <ol id="GSSN">
    <li id="LQOM">sshfs для доступа из Линукса для тех, кто не осилил нормальную настройку самбы. (это я)</li>
    <li id="UpIW">Samba для доступа к папкам из локальной сети не только из линукса</li>
    <li id="lxQi">QBittorrent с возможностью управлять им из браузера</li>
    <li id="oUWW">24/7 крутить своего телеграм бота. (это тоже я)</li>
    <li id="OCyr">Хранить git-репозитории</li>
    <li id="Atna">Подключить второй массив и организовать raid</li>
    <li id="9KEU">Настроить резервное копирование с помощью rsync и cron</li>
    <li id="kzri">Раздобыть вторую малинку и организовать загрузку по сети, вообще без sd- карты</li>
    <li id="pQBx">Добавить пассивное охлаждение</li>
    <li id="BQNL">Настроить LUKS шифрование для дисков</li>
  </ol>
  <p id="yf98">Скорость копирования по проводу через sshfs получилась 44-47 мегабайт в секунду. Почти полгигабита! У меня есть подозрение, что сеть на малинке быстрее, а это тормозит жёсткий диск или роутер, но пруфов не будет.</p>
  <h3 id="Ppaf">Автозапуск бота и торрентов</h3>
  <p id="S80U">Ниже будет упрощённый пересказ того, что я узнал по этой ссылке: <a href="https://obu4alka.ru/ustanovka-qbittorrent-na-ubuntu-server-20-04-lts.html" target="_blank">https://obu4alka.ru/ustanovka-qbittorrent-na-ubuntu-server-20-04-lts.html</a></p>
  <p id="JscX">Кстати, qbittorrent в браузере выглядит практически так же, как и приложение. И ещё он качает/раздаёт круглые сутки, так что можно оживить полузабытые раздачи.</p>
  <p id="kb84">В ubuntu есть systemd, которая запускает сервисы при старте ОС. Чтобы бот и торрент тоже запускались, сделаем текстовые файлики:</p>
  <p id="U2Bs">etc/systemd/system/qbittorrent-nox.service</p>
  <pre id="Bqez">[Unit]
Description=qBittorrent Command Line Client
After=network.target

[Service]
Type=forking
User=qbittorrent-nox
Group=qbittorrent-nox
UMask=007
ExecStart=/usr/bin/qbittorrent-nox -d --webui-port=8080
Restart=on-failure

[Install]
WantedBy=multi-user.target</pre>
  <p id="iB7s">и ещё один для моего бота:</p>
  <pre id="cFuL">[Unit]
Description = Runs my bot

Wants=network-online.target
After=network-online.target nss-lookup.target

[Service]
Type=simple
ExecStart=/home/ubuntu/bot/run.sh

[Install]
WantedBy=multi-user.target</pre>
  <p id="8RAb">В случае qbittorrent я создал отдельного пользователя, но если честно не особо понял смысл. Ну да, так вроде безопаснее. А потом самба не cможет открыть файл, т.к. у неё нет прав на чтение и вас ждут красота и изучение того как в линуксе работают пользователи, группы и разрешения.</p>
  <p id="G3Y9">В настройках qbittorrent (прямо в веб-интерфейсе) есть смысл поменять дефолтную папку для хранения торрентов, а так же залезть во вкладку webUI и убрать там галочку &quot;Use UPnP / NAT-PMP to forward the port from my router&quot;, чтобы запретить вход в веб-интерфейс из сети снаружи роутера. Ещё там есть опция &quot;Bypass authentication for clients on localhost&quot;, её наоборот удобнее включить для доступа из локальной сети без пароля. </p>
  <p id="gICq"><strong>Самба</strong></p>
  <p id="GkAF">В windows 10 отключили старую версию протокола smb, который позволял заходить без авторизации. Но с самбой всё как-то криво складывается, я сделал readonly шару и забил, потому что из линукса достаточно sshfs.</p>
  <p id="cP22">Для того, чтобы самба переходила по символьным ссылкам, придётся ещё какие-то опции указывать. Их тут нет. /etc/samba/smb.conf</p>
  <pre id="Kmn8">[share]
  path = /mnt/data/share/
  read only = yes
  guest ok = yes
  guest only = yes</pre>
  <h3 id="IiXh">sshfs</h3>
  <p id="YaVq">Очень удобный вариант, можно удалённую папку примонтировать как папку у себя. Я даже alias завёл: </p>
  <pre id="AgQR">alias mount-pi8=&quot;sshfs -o reconnect,ServerAliveInterval=15,ServerAliveCountMax=3 ubuntu@192.168.1.120:/ /home/me/Desktop/pi8&quot;
alias umount-pi8=&quot;umount /home/me/Desktop/pi8&quot;</pre>
  <p id="pJj6">Единственное - несмотря на параметры, оно как-то криво работает при покидании домашней сети. Ноутбук был не рад, пришлось перезагружать. По тем же причинам в автозапуск такое ставить не надо. Руками каждый раз монтировать - чуточку лень. Возможно, я всё-таки донастрою самбу или ещё что-то.</p>
  <p id="0Xnd">В остальном - шикарно, скорость работы близка к скорости жёсткого диска (возможно, не современного, а десятилетней давности, но всё же).</p>
  <h3 id="Dbx3">Хранение git-репозиториев</h3>
  <p id="zfAx">Я не стал использовать что-то типа gitlab и пошёл по максимально простому пути. В git сервер и клиент равноправны, на клиенте хранится своя копия данных. Между ними нет большой разницы, и я под сервером подразумеваю круглосуточно работающий ПК, и предполагаю что клиент запускается время от времени и закидывает на сервер свои изменения.</p>
  <p id="bUZn">Адресов для скачивания/заливания репозитория может быть несколько. Обычно он один и называется origin. Но никто не запретит добавить несколько адресов, выкачать ветку с одного и потом её влить по другому адресу. И даже в совершенно другой репозиторий так можно, лишь бы имена веток не пересекались. Гибкость впечатляет, хоть про неё и не все знают.</p>
  <p id="Vd6q">Если есть ssh доступ к серверу, то добавить туда репозиторий очень просто:</p>
  <p id="KoQF">На сервере: создать папку, в ней вызвать</p>
  <pre id="RbBG">git init --bare</pre>
  <p id="p4P5">На клиенте:</p>
  <pre id="1Ytd">git remote add pi8 ssh://user@ip/path-to-repo
git push pi8 master</pre>
  <p id="eLjl">В идеале ещё можно создать отдельного пользователя для git и т.п., но для локального домашнего применения не вижу смысла. </p>
  <p id="U0jz">Подробнее можно прочитать тут: <a href="https://git-scm.com/book/en/v2/Git-on-the-Server-Setting-Up-the-Server" target="_blank">https://git-scm.com/book/en/v2/Git-on-the-Server-Setting-Up-the-Server</a></p>
  <h3 id="r25n">RAID массив</h3>
  <p id="izyF">В итоге я отказался от этой идеи.<br />С помощью mdadm можно сделать raid1 массив, в котором данные будут лежать в двух копиях. Одна на одном диске и другая на другом. Т.к. диски обычно неравных размеров, лучше вместо указания дисков (типа /dev/sda) использовать разделы: например /dev/sda1. Само собой, разделы должны быть на разных дисках. В случае отказа одного из дисков массив с данными продолжит работать как ни в чём ни бывало, пока не откажет второй диск. В общем, за статусом raid массива надо будет следить (и мне кажется, это неудобно).</p>
  <p id="ljVg">Вдобавок, я сделал раздел с raid только для важных данных, и при отказе основного диска ОС похерится и малинка всё равно не запустится. Я подумал над этим безобразием и решил вместо этого сделать бэкапы.</p>
  <h3 id="SyH7">Бэкапы</h3>
  <p id="0S5t">В отличие от raid массива, у бэкапов есть очень полезное свойство - при случайном удалении файлов их можно будет восстановить.</p>
  <p id="MEyU">Я подумал над возможными вариантами:</p>
  <ul id="JM36">
    <li id="8lkr">есть специальное ПО типа <a href="https://ru.wikipedia.org/wiki/Bacula" target="_blank">https://ru.wikipedia.org/wiki/Bacula</a>. Инкрементальные бэкапы хранятся в каком-то бинарном формате, само ПО надо ставить и настраивать.</li>
    <li id="HGVy">rsync + cron. Внезапно, rsync умеет делать инкрементальные бэкапы. Чтобы не хранить один файл по многу раз, используюся жёсткие ссылки. С точки зрения меня как пользователя получатся папки типа backup1, backup2, и т.д, в каждой из которых лежат все файлы на момент копирования. </li>
  </ul>
  <p id="cyAJ">На мой взгляд, с rsync самый удобный вариант  - я смогу вставить диск с бэкапами в любой комп и вытащить данные обратно. </p>
  <p id="BRCy">Я вдохновлялся вот этой статьёй: <a href="https://linuxconfig.org/how-to-create-incremental-backups-using-rsync-on-linux" target="_blank">https://linuxconfig.org/how-to-create-incremental-backups-using-rsync-on-linux</a>, но сделал немножко по-другому:</p>
  <pre id="dxlH">readonly DATETIME=&quot;$(date &#x27;+%Y-%m-%d_%H:%M:%S&#x27;)&quot;
readonly LOG_FILE=&quot;/mnt/backup/logs/backup_safedata_${DATETIME}.txt&quot;

printf &quot;start at ${DATETIME}\n&quot; &gt;&gt; $LOG_FILE

rsync -av --delete /mnt/safedata --link-dest ../latest --exclude &quot;lost+found&quot; /mnt/backup/safedata/${DATETIME} &gt;&gt; $LOG_FILE

df &gt;&gt; $LOG_FILE
echo &quot;finished at $(date &#x27;+%Y-%m-%d_%H:%M:%S&#x27;)&quot; &gt;&gt; $LOG_FILE

cd /mnt/backup/safedata
rm latest
ln -s ${DATETIME} latest

python3 -c &#x27;
import os
import shutil
listdir = os.listdir(&quot;.&quot;)
files = sorted([f for f in listdir if f.startswith(&quot;20&quot;)])
to_preserve = set(files[-31:])
monthly = {f[:7]: f for f in reversed(files)}
to_preserve.update(monthly.values())
for f in sorted(to_preserve):
	print(f&quot;keep &#x27;{f}&#x27;&quot;)
for f in set(files) - to_preserve:
    print(f&quot;remove &#x27;{f}&#x27;&quot;)
    shutil.rmtree(f)
&#x27; &gt;&gt; $LOG_FILE
</pre>
  <p id="bxkE">latest - мягкая ссылка на последний бэкап. В --link-dest путь должен быть абсолютный или относительный для папки с бэкапом, поэтому надо выскочить на директорию вверх: ../latest</p>
  <p id="VyB4">если ссылки нет, то rsync всё ещё работает.</p>
  <p id="8CFS">Логи я решил каждый раз писать в отдельный файл, так как иначе при добавлении большого количества файлов он превращается в многомегабайтный лог и в нём сложно найти, что в какой день происходило. Папка для логов сама не создастся, надо добавить её вручную</p>
  <p id="MNMs">скрипт на питоне хранит последние 30 версий бэкапа и первую версию каждого месяца, остальные удаляет.</p>
  <p id="vGp9">Для отладки есть полезные опции <code>--dry-run</code> для запуска без реальных изменений, а так же можно опцию <code>--verbose</code> указать дважды и получить более подробный вывод: <code>-vv</code></p>
  <p id="gSt0">Опция -a раскрывается в кучу настроек, которые пытаются перетащить время, пользователя файла, права доступа и т.п. Я попробовал запускать rsync между компом и малинкой и несмотря на <code>--link-dest</code> создавались копии файлов. Я потратил кучу времени на то, чтобы понять, что все эти опции мешают и в итоге между машинами копировал с опциями <code>-rl --size-only</code></p>
  <p id="1pVs">В этом случае время игнорируется, а файл с тем же размером считается не требующим копирования. Можно ещё указать --checksum, но тогда и передающая и принимающая стороны будут считать md5 чексуммы для файлов, что потребует их полного прочтения. Я решил забить и ограничиться проверкой размера.</p>
  <p id="29vR">C помощью cron можно будет настроить бэкапы на каждый день или каждую неделю:</p>
  <pre id="RKHP">crontab -e</pre>
  <p id="O2Th">и там написать</p>
  <pre id="aP0d">20 4 * * * /mnt/backup/backup.sh</pre>
  <p id="4Hxg">Чтобы каждую ночь в 4:20 делать бэкап.</p>
  <h2 id="DGEW">Загрузка по сети*</h2>
  <p id="YIPK">*Задание со звёздочкой, будут приключения.</p>
  <p id="rr40">Заливать образ на флешку, вставлять в малинку, ждать пока загрузится - не самое весёлое занятие. После того, как перевая малинка стала работающим 24/7 полноценным сервером, я раздобыл вторую и решил попробовать загрузку по сети.</p>
  <p id="Ydto">Глобально это состоит из двух шагов:</p>
  <ol id="9Zfw">
    <li id="JDe5">включить на клиентской малинке загрузку по сети</li>
    <li id="oApB">настроить на сервере dhcp и nfs, чтобы они работали</li>
  </ol>
  <p id="ZWJl">Из принципиальных ограничений - клиентская малинка должна быть подключена к сети по проводу, а не через wifi.</p>
  <p id="9oSG">Список команд и т.п. несложный, но в процессе я нашёл кучу подводных камней, и настройка заняла у меня целый вечер.</p>
  <p id="6MMR"><a href="https://www.raspberrypi.com/documentation/computers/remote-access.html#network-boot-your-raspberry-pi" target="_blank">https://www.raspberrypi.com/documentation/computers/remote-access.html#network-boot-your-raspberry-pi</a></p>
  <p id="mYHu">Вот ссылка на документацию, но делать надо далеко не всё, что в ней написано.</p>
  <h3 id="2hMC">Настройка клиента</h3>
  <p id="wtoa">На 4B, загрузку включать надо, причём процесс отличается от предыдущих версий.</p>
  <p id="PZDa">Во-первых, понадобится поставить именно raspbian os. В принципе, можно обойтись и без подключения мышки-клавиатуры, если в rpi Imager нажать ctrl+shift+x (или какое-то ещё неочевидное сочетание клавиш), оказаться в настройках и там указать ключ для ssh. Экран я советую всё-таки подключить, он потом пригодится.</p>
  <p id="7P0N">Запускаем штуку для настройки (прям из консоли)</p>
  <pre id="m47G">sudo raspi-config</pre>
  <p id="bPjV">В ней в менюшках надо выбрать <code>Advanced Options</code>, <code>Boot Order</code>, <code>Network Boot</code>и в ней выбрать загрузку по сети (что странно - с загрузкой по сети вариант ровно один - малинка попытается загрузиться с sd карты, если не получится - то по сети).</p>
  <p id="mPau">Чтобы изменения применились, придётся перезагрузиться.</p>
  <p id="c3sx">И потом командой </p>
  <pre id="6FKR">vcgencmd bootloader_config</pre>
  <p id="UK51">можно посмотреть, что в BOOT_ORDER. В идеале там должно быть 0xf21. Тут можно посмотреть подробнее, что же число значит:</p>
  <p id="Zapr"><a href="https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-4-bootloader-configuration" target="_blank">https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#raspberry-pi-4-bootloader-configuration</a></p>
  <p id="G6ES">Я так понял, они справа налево идут.</p>
  <p id="ZuFa">После этого малинку можно выключить, выдернуть из неё sd-карту и снова включить. Выше я советовал включить экран - малинка на нём покажет текстом свой ip адрес и потихоньку растущее количество неудачных попыток загрузки по сети.</p>
  <h3 id="ixri">Настройка сервера</h3>
  <p id="k4QE">Дальше инструкции с официального сайта следовать не обязательно.</p>
  <p id="W623">Во-первых, можно просто взять и на на домашнем роутере во вкладочке с настройками DHCP указать статические ip адреса для сервера (давно уже так сделал) и для клиента (сделал сейчас).</p>
  <p id="9bxc">Во вторых, у меня клиент - raspbian os, а сервер ubuntu. На сайте происходит кунг-фу по включению той же самой sd-карты в сервер, загрузке с неё и дальнейшей модификации для того, чтобы клиент и сервер различались. Спасибо, но я воздержусь.</p>
  <p id="W4c2">Итак, можно смело листать сайт до инструкции </p>
  <pre id="yrYk">sudo apt install tcpdump dnsmasq
sudo systemctl enable dnsmasq
sudo tcpdump -i eth0 port bootpc</pre>
  <p id="wDam">Но dnsmasq ставить пока не надо.</p>
  <p id="nS9U">С помощью tcpdump можно убедиться, что малинка при загрузке действительно посылает запросы всем в локальной сети.</p>
  <p id="YId5">С dnsmasq меня ждал сюрприз. Это в одном лице и dns сервер, и нужные нам dhcp и tftp сервера. В ubuntu уже есть свой dns-резолвер, systemd-resolved, и он, как нетрудно догадаться, тоже dns сервер, который слушает порт 53 и не даёт слушать его другим. </p>
  <p id="g0VB">После установки dnsmasq полезет на 53 порт и не сможет - тот уже занят. Всё что надо сделать - залезть в /etc/dnsmasq.conf и указать там port=0, чтобы dns-составляющая не запускалась и не мешалась.</p>
  <p id="s7ud"><a href="https://askubuntu.com/questions/191226/dnsmasq-failed-to-create-listening-socket-for-port-53-address-already-in-use" target="_blank">https://askubuntu.com/questions/191226/dnsmasq-failed-to-create-listening-socket-for-port-53-address-already-in-use</a></p>
  <p id="WubI">Я вместо этого воспользовался первым нагугленным советом и отключил systemd-resolved. В итоге dns работать перестал, я узнал, что команде sudo без dns работается плохо и потом возвращал всё обратно.</p>
  <p id="PIxB">так вот, в dnsmasq.conf должно в итоге быть</p>
  <pre id="ItJr">port=0
dhcp-range=192.168.1.255,proxy
log-dhcp
enable-tftp
tftp-root=/tftpboot
pxe-service=0,&quot;Raspberry Pi Boot&quot;</pre>
  <p id="501o">tftp-root - путь к папке с загрузчиком на сервере</p>
  <h3 id="LAfv">как скопировать системный раздел на сервер</h3>
  <p id="ye9z">В оригинальной статье они обходятся одной sd-картой, но я по понятным причинам включил sd-карту в ноутбук и задумался, как же скопировать её на сервер. Суть проблемы в том, что </p>
  <pre id="Y7sI">rsync from to</pre>
  <p id="Bm1i">не может скопировать файлы, принадлежащие root и запрещённые другим для чтения. А поскольку мы копируем системный раздел, там такие файлы будут.</p>
  <p id="68ib">Можно написать так:</p>
  <pre id="HPZm">sudo rsync from to</pre>
  <p id="d6sm">Тогда получится скопировать файлы с владельцем root, но не получится их записать по двум причинам:</p>
  <ol id="zO6R">
    <li id="dD9C">у рута своя папка .ssh и там может не быть ключа для ssh</li>
    <li id="M3mm">на системе, куда копируем, rsync работает не из под-рута</li>
  </ol>
  <p id="uNjz">Красота и удобство. Указать root@ip:/path у меня тоже не получилось. Первая проблема решается копированием ~/.ssh/id_rsa, вторая - хитрой опцией. Для хитрой опции требуется, чтобы на другой машине команда sudo не спрашивала пароль. Рабочая команда выглядит так:</p>
  <pre id="qpeM">sudo rsync --progress -ax -e &quot;ssh&quot; --rsync-path=&quot;sudo rsync&quot; rootfs/ ubuntu@192.168.1.142:/nfs/client1</pre>
  <p id="Gxml">Таким незамысловатым образом можно скопировать загрузчик в /nfs/nfsboot, а системный раздел в /nfs/client1</p>
  <p id="2chM">Идею и команду взял отсюда: <a href="https://superuser.com/questions/605425/rsync-root-files-between-systems-without-specifying-password" target="_blank">https://superuser.com/questions/605425/rsync-root-files-between-systems-without-specifying-password</a></p>
  <p id="HBUd">После этого надо будет в nfsboot/cmdline.txt заменить указание на root: у меня получилось так:</p>
  <pre id="gr7g">console=serial0,115200 console=tty1 root=/dev/nfs nfsroot=192.168.1.142:/nfs/client1,vers=4.1,proto=tcp rw ip=dhcp rootwait</pre>
  <p id="FxxZ">и поправить etc/fstab</p>
  <pre id="wmEy">192.168.1.142:/nfs/tftpboot	/boot	nfs	defaults,vers=4.1,proto=tcp 0 0</pre>
  <p id="npPF">То, что есть только boot без корневой системы - нормально, так и должно быть</p>
  <p id="uumZ">Кроме того, надо ещё включить rpcbind, настроить nfs-kernel-server и в /etc/exports указать пути для nfs:</p>
  <pre id="IlSx">/nfs/client1 *(rw,sync,no_subtree_check,no_root_squash)
/nfs/tftpboot *(rw,sync,no_subtree_check,no_root_squash)</pre>
  <p id="hoUE">В результате малинка без SD-карты загружается по сети из файлов в папке. При желании ту папку можно будет копировать, хранить несколько разных версий ОС и т.п.</p>
  <h3 id="W2jL">Пассивное охлаждение</h3>
  <p id="wYen">Температуру можно узнать так:</p>
  <pre id="BkTV">vcgencmd measure_temp</pre>
  <p id="MQXT">У меня она была 64 градуса (с нагрузкой из описанного выше софта), после - 57. Я купил корпус Qumo Aluminum RS001, который заодно является радиатором. Охлаждение пассивное, вентилятора нет и ломаться нечему. И шуметь тоже. Сам корпус, кажется, сделан из аллюминия. Смотрится красиво и солидно. В комплекте шли два маленьких кусочка термопрокладки, болтики и шестигранный ключик - в общём, всё необходимое. </p>
  <p id="812W">Эффектом немного разочарован, думал будет градусов 10-15 разницы, а получилось около 7. Сам корпус ощутимо горячий во всех местах</p>
  <h3 id="38Fs">LUKS шифрование раздела</h3>
  <p id="Vmkh">Опциональная часть, надо чётко понимать, нужно ли шифрование. Цель - чтобы содержимое диска было невозможно прочитать при включении в посторонний пк.</p>
  <p id="bHSm">Если пароль от раздела будет храниться где-то на малинке, то затея бессмысленная. Я планирую заходить на малинку по ssh и руками подключать зашифрованный раздел. Кроме того - для самой малинки, пока она работает, пароль известен. Если сама малинка скомпроментирована - то злоумышленник и имеет доступ к диску, и может утащить к себе пароль.</p>
  <p id="oxf6">Шифрование спасает только от сценария, когда постороний забирает диск и пытается прочитать с него данные. Всё, ничего больше.</p>
  <p id="t0qL">Я где-то видел, чтобы на малине как-то хитро шифровали прям диск с системой, но я так извращаться не буду. В малинке негде хранить пароль, и я хочу, чтобы она могла сама включиться без моего участия. Шифрую только отдельный раздел с данными.</p>
  <p id="F01J">Итак, если шифрование всё-таки нужно, начнём:</p>
  <p id="TaEl">Скорее всего, cryptsetup уже установлен, если нет, ставим:</p>
  <pre id="anca">sudo apt install cryptsetup</pre>
  <p id="0qAC">Особенности малинки - нет хардварной поддержки aes, скорость шифровки и дешифровки будет медленная, советуют вместо него использовать adiantum. </p>
  <p id="TLu9">Проверить скорость шифрования (без записи на диск, всё в RAM):</p>
  <pre id="Z3ng">cryptsetup benchmark
# Tests are approximate using memory only (no storage IO).
PBKDF2-sha1       386643 iterations per second for 256-bit key
PBKDF2-sha256     630912 iterations per second for 256-bit key
PBKDF2-sha512     510007 iterations per second for 256-bit key
PBKDF2-ripemd160  324034 iterations per second for 256-bit key
PBKDF2-whirlpool  142935 iterations per second for 256-bit key
argon2i       4 iterations, 333516 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time)
argon2id      4 iterations, 335222 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time)
#     Algorithm |       Key |      Encryption |      Decryption
        aes-cbc        128b        85.6 MiB/s        98.5 MiB/s
    serpent-cbc        128b        42.7 MiB/s        44.4 MiB/s
    twofish-cbc        128b        63.3 MiB/s        69.1 MiB/s
        aes-cbc        256b        76.0 MiB/s        77.5 MiB/s
    serpent-cbc        256b        43.8 MiB/s        44.4 MiB/s
    twofish-cbc        256b        68.1 MiB/s        69.2 MiB/s
        aes-xts        256b        84.4 MiB/s       101.8 MiB/s
    serpent-xts        256b        43.1 MiB/s        44.8 MiB/s
    twofish-xts        256b        67.9 MiB/s        70.7 MiB/s
        aes-xts        512b        78.6 MiB/s        79.8 MiB/s
    serpent-xts        512b        44.9 MiB/s        44.7 MiB/s
    twofish-xts        512b        70.5 MiB/s        70.6 MiB/s
</pre>
  <p id="FIrA">И померять aes-adiantum, который должен быть быстрее</p>
  <pre id="6qa8">cryptsetup benchmark -c xchacha12,aes-adiantum
# Tests are approximate using memory only (no storage IO).
#            Algorithm |       Key |      Encryption |      Decryption
xchacha12,aes-adiantum        256b       188.1 MiB/s       189.2 MiB/s


cryptsetup benchmark -c xchacha20,aes-adiantum
# Tests are approximate using memory only (no storage IO).
#            Algorithm |       Key |      Encryption |      Decryption
xchacha20,aes-adiantum        256b       161.0 MiB/s       162.2 MiB/s

</pre>
  <p id="qQmr"></p>
  <p id="syTj">Ссылки, которыми я руководствовался:</p>
  <p id="pLPI"><a href="https://gist.github.com/palopezv/792b9f0100484186c3f74cbee7b07630" target="_blank">https://gist.github.com/palopezv/792b9f0100484186c3f74cbee7b07630</a></p>
  <p id="EKny"><a href="https://wiki.davidl.me/index.php?title=LUKS&mobileaction=toggle_view_desktop" target="_blank">https://wiki.davidl.me/index.php?title=LUKS&amp;mobileaction=toggle_view_desktop</a></p>
  <p id="Ddnt">Я выбрал xchacha20, т.к. разница в скорости не критичная, а оно вроде как надёжнее. </p>
  <p id="Dfi8">xchacha20 отличается в xchacha12 количеством раундов шифрования - 20 вместо 12.</p>
  <p id="fEvh">Где почитать: <a href="https://www.reddit.com/r/cryptography/comments/p3dflu/fulldiskencryption_xchacha12_vs_xchacha20/" target="_blank">https://www.reddit.com/r/cryptography/comments/p3dflu/fulldiskencryption_xchacha12_vs_xchacha20/</a></p>
  <p id="f5BJ">Создадим раздел:</p>
  <pre id="mZQS">sudo cryptsetup luksFormat --type=luks2 --sector-size=4096 -c xchacha20,aes-adiantum-plain64 -s 256 -h sha512 --use-urandom /dev/sdXX</pre>
  <p id="trwy">Тут вместо sdXX написать реальный раздел как он показывается в lsblk</p>
  <p id="WN2y">Дальше был забавный момент - после всего программа требует написать YES большими буковками. Просто чтобы убедиться, что человек внимательно посмотрел на команду и случайно не похерит нужный раздел.</p>
  <p id="qLht">Дальше надо подключить раздел и отформатировать</p>
  <pre id="Ptnd">sudo cryptsetup open /dev/sdXX NAME
sudo mkfs.ext4 /dev/mapper/NAME</pre>
  <p id="K8iX">Примонтировать раздел:</p>
  <pre id="s1fz"># Open the encrypted drive
sudo cryptsetup open /dev/sdXX NAME
# Mount your partition
mount -t ext4 /dev/mapper/NAME MOUNT_LOCATION</pre>
  <p id="MMi3">Размонтировать:</p>
  <pre id="0c77"># Unmount your partition
umount MOUNT_LOCATION
# Close the decrypted drive
cryptsetup close NAME</pre>
  <h2 id="dJaY">Выводы</h2>
  <p id="VIDG">Если хочется только домашнее хранилище - проще и дешевле подключить диск напрямую к роутеру (некоторые роутеры так умеют). Но мне хотелось иметь полноценный сервер, хоть и маленький. Своей цели я достиг и очень доволен.</p>

]]></content:encoded></item></channel></rss>