<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[blog4code]]></title><description><![CDATA[blog4code]]></description><link>https://alexey.detr.us</link><generator>metalsmith-feed</generator><lastBuildDate>Sat, 27 Apr 2019 19:53:01 GMT</lastBuildDate><atom:link href="https://alexey.detr.us/feed.xml" rel="self" type="application/rss+xml"/><item><title><![CDATA[Тривиальный случай: nginx для статики в docker-compose]]></title><description><![CDATA[<p>С момента моей прошлой <a href="/posts/2017/2017-09-17-docker-mongo/">заметки про MongoDB в Docker</a> прошло довольно много времени. С тех пор я уже не запускаю руками напрямую docker в CLI с указанием всех его аргументов, так как я нашёл способ поудобнее. О нём я и поведаю в этой заметке на примере запуска веб-сервера nginx для отладки статического сайта. Кстати, при отладке этого блога используется как раз такой метод.</p>
<h2 id="docker-compose">docker-compose</h2>
<p>Итак, главный герой, docker-compose. Эта утилита помогает специфицировать контейнеры в декларативном виде, то есть запуск, остановка и другие задачи выполняются посредством конфигурационного файла (по умолчанию <code class="codespan-short">docker-compose.yml</code>). Давайте взглянем, каким образом нужно его оформить, чтобы запустить официальный контейнер веб-сервера nginx, который будет отдавать статику из указанной директории. В моём случае я располагаю файл <code class="codespan-short">docker-compose.yml</code> в корневой директории проекта.</p>
<pre><code class="lang-yml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3'</span>
<span class="hljs-attr">services:</span>
<span class="hljs-attr">  nginx:</span>
<span class="hljs-attr">    image:</span> <span class="hljs-string">"nginx:latest"</span>
<span class="hljs-attr">    ports:</span>
<span class="hljs-bullet">      -</span> <span class="hljs-string">"127.0.0.1:8080:80"</span>
<span class="hljs-attr">    volumes:</span>
<span class="hljs-bullet">      -</span> <span class="hljs-string">"./build:/usr/share/nginx/html:ro"</span>
</code></pre>
<p>Выглядит просто, но давайте разберём всё по порядку.</p>
<h3 id="version">version</h3>
<p><code class="codespan-short">version: &#39;3&#39;</code> указывает версию конфигурационного файла, на момент написания заметки, 3-я версия является наиболее актуальной.</p>
<h3 id="services">services</h3>
<p>Каждый элемент раздела <code class="codespan-short">services</code> отвечает за конкретный сервис. В нашем случае он один, где <code class="codespan-short">nginx</code> это просто имя сервиса, ничего более.</p>
<h3 id="image">image</h3>
<p><code class="codespan-long">image: &quot;nginx:latest&quot;</code> специфицирует, из какого образа будет создан контейнер, в данном случае это официальный образ веб-сервера nginx, а <code class="codespan-short">:latest</code> это тег образа, который чаще всего отвечает за версию.</p>
<h3 id="ports">ports</h3>
<p><code class="codespan-long">- &quot;127.0.0.1:8080:80&quot;</code> отвечает за маппинг 80 порта контейнера на интерфейс 127.0.0.1 и порт 8080 хостовой машины. Иными словами, nginx, слушающий 80-й порт внутри контейнера, позволит нам работать с ним, открывая в браузере адрес <code class="codespan-short">127.0.0.1:8080</code>, или, даже проще, <code class="codespan-short">localhost:8080</code>.</p>
<h3 id="volumes">volumes</h3>
<p><code class="codespan-long">- &quot;./build:/usr/share/nginx/html:ro&quot;</code> определяет маппинг разделов.</p>
<ul>
<li><code class="codespan-short">./build</code> — директория нашего проекта, в которой лежит собранная версия сайта, то есть статика, которую и необходимо отдавать через веб-сервер. </li>
<li><code class="codespan-short">/usr/share/nginx/html</code> — директория внутри контейнера, в которую будет примонтирован раздел с содержимым <code class="codespan-short">./build</code>. Если задаётесь вопросом, почему именно <code class="codespan-short">/usr/share/nginx/html</code>, то всё просто, эта директория указана в конфигурации <code class="codespan-short">nginx</code>, а конфигурация в свою очередь зашита в базовом образе <code class="codespan-short">nginx:latest</code>. Чтобы не собирать руками свой образ со своей конфигурацией, мы просто опираемся на официальный, конфигурация которого уже готова отдавать статику именно из этой директории.</li>
<li><code class="codespan-short">ro</code> в конце декларации отвечает за то, что раздел будет работать в режиме read-only для нашего контейнера.</li>
</ul>
<h2 id="-">Запуск контейнера</h2>
<p>Итак, файл конфигурации готов, осталось только запустить. Заходим в корневую директорию проекта и выполняем:</p>
<pre><code class="lang-bash">docker-compose up
</code></pre>
<p>При первом запуске docker-compose скачает образ nginx, а затем на его основе создаст новый контейнер. При последующих запусках скачивание больше не потребуется, запуск контейнера будет происходить весьма быстро.</p>
<h2 id="-">Что дальше</h2>
<p>Если продолжать развивать тему, то можно увидеть, что <code class="codespan-short">docker-compose.yml</code> вполне может отвечать не за один сервис, а за несколько. Это крайне удобно, когда у вас есть проект, требующий сразу несколько серверов (например, веб-сервер и сервер БД). В таком случае, при помощи простой команды <code class="codespan-short">docker-compose up</code> достаточно легко подготовить комплексный рантайм зависимостей вашего проекта.</p>
<p>Итак, в итоге мы разобрались, как пользоваться docker-compose в примитивном случае отдачи статического сайта через nginx. Благодарен за ваше внимание, за сим откланиваюсь.</p>
<h2 id="-">Ссылки</h2>
<ol>
<li><a href="https://docs.docker.com/compose/compose-file/">Документация по файлу docker-compose.yml</a></li>
<li><a href="https://hub.docker.com/_/nginx/">Официальный образ nginx в репозитории докера, стоит обратить внимание на примеры</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2018/2018-01-17-nginx-docker-static</link><guid isPermaLink="false">/lang/ru/posts/2018/2018-01-17-nginx-docker-static/index.md</guid><pubDate>Wed, 17 Jan 2018 11:54:38 GMT</pubDate></item><item><title><![CDATA[Сессии, универсальность и простота: миддлвар auzy]]></title><description><![CDATA[<p>Сегодня я расскажу историю одного небольшого проекта, причину создания которого можно описать одним словом «психанул». Нет, на деле, конечно, я не психанул, всего лишь был немного раздосадован тем, как обстоят дела с сессиями в Node.js и тем, как все поголовно используют <a href="http://www.passportjs.org/">Passport</a>, который с одной стороны берёт на себя неплохой объём работы, а с другой переусложнён и обладает устаревшим и запутанным API.</p>
<h2 id="-">Проблематика</h2>
<p>Началось всё довольно давно, с моего знакомства с Node.js в 2014-м. Тогда я поддерживал RESTful API, написанный на фреймворке <a href="http://expressjs.com/">Express</a>, сессии в котором были реализованы с помощью Passport. Это вряд ли можно было назвать правильными инструментами для тех задач, но тогда я впервые столкнулся с избыточностью Passport и недостаточной гибкостью его сессионных миддлвар. В моём проекте требовалось передавать идентификаторы сессий через кастомный HTTP-заголовок, а не через cookie. Чтобы добиться этого, я делал несколько подходов, <a href="https://github.com/expressjs/session/pull/79">пытался законтрибьютить</a>, но в итоге всё закончилось банальным форком, в котором была реализована нужная мне функциональность.</p>
<h2 id="-">Последняя капля</h2>
<p>Время шло, на дворе был уже 2017-й год, я занимался реализацией одного API для своего домашнего проекта на том же Node.js (в этот раз у меня на руках уже был фреймворк <a href="http://restify.com/">Restify</a>). Но, так вышло, что индустриальный стандарт в виде Passport добрался и до туда. Нет, на самом деле, не очень-то он и добирался, просто так вышло, что API его миддлвары подходил к в том числе и к Restify. И, в какой-то прекрасный момент, отлаживая некоторую странность в поведении авторизации, со мной и случилось то самое «психанул». Я увяз в коллбэках, и меня вконец достал неявный нейминг абстракций Passport, который был сделан, мягко говоря, совсем не для людей.</p>
<p>Какое-то время поискав альтернативы Passport’у, я понял, что ничего хорошего в обозримом пространстве нет. В голове пронеслась мысль, многим наверное уже набившая оскомину: «если хочешь сделать что-то хорошо, сделай это сам».</p>
<p>Тогда-то, засучив рукава, я сел и начал писать свой первый серьёзный проект с открытым исходным кодом, который и получил имя <a href="https://github.com/alexey-detr/auzy">auzy</a>, как вы могли уже догадаться.</p>
<h2 id="-">Проект и его концепция</h2>
<p>Насмотревшись и натерпевшись от старых подходов, я твёрдо решил, что у проекта будет современная кодовая база с поддержкой последней стабильной версии Node.js, на тот момент 8-й. Благо, у меня уже был неплохой опыт работы с промисами, async/await, классами и многими другими прелестями ES6+.</p>
<p>Вторым важным моментом должно было стать достаточное покрытие тестами. Согласитесь, зачастую проект без тестов выглядит как-то несерьёзно, да и вообще, я бы опасался использовать такой проект в продакшен среде. Здесь я решил писать как юнит-тесты, так и функциональные, взяв современный фреймворк Jest.</p>
<p>И ещё один важный момент, документация. Её нехватка или отсутствие примеров могут запросто свести на нет пользу от любого проекта, ведь если у пользователей не будет чёткого понимания, как применять библиотеку, никто не станет ей пользоваться. Я пообещал себе ни в коем случае не допускать такого в своём проекте.</p>
<p>Помимо всего обозначенного выше, само собой, мне хотелось достичь разумной гибкости, поддерживаемости, совместимости с популярными фреймворками из коробки, минимальной зависимости от других пакетов. В общем, добиться множества различных мелочей, которые в сумме помогли бы достичь высокого качества проекта.</p>
<h2 id="-">Разработка</h2>
<p>За все свои годы работы с вебом, мне не раз доводилось иметь дело с сессионной логикой. Несколько раз приходилось реализовывать её с нуля. Для меня в этой логике не было ничего особенного, в голове она выстраивалась как на ладони, а пережитый опыт лишь добавлял уверенности и подливал масло в огонь.</p>
<p>Вечерами около месяца я активно работал над проектом, и он наконец начал обретать желаемый вид. Мне удалось реализовать поддержку <a href="http://restify.com/">Restify</a>, <a href="https://github.com/senchalabs/connect">Connect</a>, <a href="http://expressjs.com/">Express</a> и <a href="http://koajs.com/">koa.js</a>, покрыть проект функциональными и юнит-тестами. Иными словами, минимальный результат был достигнут.</p>
<p>В конце концов, настал момент истины, и захотелось понять, а какой же проект вышел лучше. Нет, конечно же я понимал, что auzy не решал всех тех задач, которые решал Passport, но и наоборот, auzy с лёгкостью делал то, что Passport делал с большим скрипом. И вышло так, что некоторое пересечение у них есть, а значит на этом пересечении есть и возможность продемонстрировать, каким же вышел auzy.</p>
<h3 id="-">Подключение в проект</h3>
<p>Несколько слов о технологическом стеке проекта, которому требовались сессии. Фреймворк Restify, база данных MongoDB, хранилище сессий Redis. Идентификатор сессии требовалось передавать в заголовке (к слову, auzy пока не поддерживает передачу сессий в куках). В данном случае <code class="codespan-short">express-session-header</code> это <a href="https://github.com/alexey-detr/session-old">форк</a>, как раз позволяющий передавать сессию в заголовках.</p>
<h4 id="passport">Passport</h4>
<p>Давайте взглянем, как выглядело раньше подключение Passport в проекте:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> passport = <span class="hljs-built_in">require</span>(<span class="hljs-string">'passport'</span>);
<span class="hljs-keyword">const</span> session = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express-session-header'</span>);
<span class="hljs-keyword">const</span> RedisStore = <span class="hljs-built_in">require</span>(<span class="hljs-string">'connect-redis'</span>)(session);
<span class="hljs-keyword">const</span> redisStore = <span class="hljs-keyword">new</span> RedisStore({
    <span class="hljs-attr">ttl</span>: <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span> * <span class="hljs-number">30</span> * <span class="hljs-number">6</span>,
});
server.use(session({
    <span class="hljs-attr">secret</span>: config.session.cookie.secret,
    <span class="hljs-attr">store</span>: redisStore,
    <span class="hljs-attr">resave</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">saveUninitialized</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">header</span>: <span class="hljs-string">'X-Session-Token'</span>,
}));
server.use(passport.initialize());
server.use(passport.session());
<span class="hljs-keyword">const</span> LocalStrategy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'passport-local'</span>).Strategy;
passport.use(<span class="hljs-keyword">new</span> LocalStrategy({
        <span class="hljs-attr">usernameField</span>: <span class="hljs-string">'user[email]'</span>,
        <span class="hljs-attr">passwordField</span>: <span class="hljs-string">'user[password]'</span>,
    },
    <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">email, password, done</span>) </span>{
        <span class="hljs-comment">// логика поиска пользователя в БД и возврат через done</span>
    },
));

passport.serializeUser(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">user, done</span>) </span>{
    <span class="hljs-keyword">return</span> done(<span class="hljs-literal">null</span>, user.id);
});

passport.deserializeUser(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">id, done</span>) </span>{
    User.Model.findOne({<span class="hljs-attr">_id</span>: <span class="hljs-keyword">new</span> ObjectId(id), <span class="hljs-attr">status</span>: User.statuses.ACTIVE}, done);
});
</code></pre>
<p>Громоздко, не правда ли? Что здесь у нас? <code class="codespan-short">require</code> 4-х модулей,  3 миддлвары через <code class="codespan-short">use</code>, логика <code class="codespan-short">serialize</code> и <code class="codespan-short">deserialize</code>. Ну а локальная стратегия (LocalStrategy) неподготовленному человеку вообще покажется лёгким безумством, это объект, который в свою очередь является миддлваром для Passport, у которого ещё и формат полей задан в виде <code class="codespan-short">user[email]</code>, хотя на деле это не обязательно urlencoded, это также может быть путём в объекте.</p>
<p>Кому как, а на мой взгляд всё это выглядит чрезмерно запутанным. Я тратил немало нервов, когда доходил до этих кусков кода, особенно когда нужно было что-то поменять. С auzy от этих ощущений не должно было остаться и следа.</p>
<h4 id="auzy">auzy</h4>
<p>Итак, когда я начал подключать auzy к своему домашнему проекту, это для меня стало глотком свежего воздуха:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> auzy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'auzy'</span>);
<span class="hljs-keyword">const</span> config = {
    <span class="hljs-attr">session</span>: {
        <span class="hljs-attr">sessionName</span>: <span class="hljs-string">'X-Session-Token'</span>,
        <span class="hljs-attr">ttl</span>: <span class="hljs-number">60</span> * <span class="hljs-number">60</span> * <span class="hljs-number">24</span> * <span class="hljs-number">30</span> * <span class="hljs-number">6</span> * <span class="hljs-number">1000</span>,
        <span class="hljs-attr">alwaysSend</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">loadUser</span>: <span class="hljs-function"><span class="hljs-params">sessionData</span> =&gt;</span> {
            <span class="hljs-comment">// логика поиска пользователя в БД и возврат через промис</span>
        },
    },
};
<span class="hljs-keyword">const</span> environment = {
    <span class="hljs-attr">framework</span>: <span class="hljs-string">'restify'</span>,
    <span class="hljs-attr">storage</span>: <span class="hljs-string">'redis'</span>,
    <span class="hljs-attr">transport</span>: <span class="hljs-string">'header'</span>,
};
server.use(auzy(config, environment));
</code></pre>
<p>Благодаря тому, что auzy знает о популярных фреймворках, подключение выглядит заметно проще, оно занимает примерно в 2 раза меньше строк, где выполняется один <code class="codespan-short">require</code> и <code class="codespan-short">use</code> всего 1-го миддлвара. И это всё при нулевых зависимостях (в будущем, возможно, добавится библиотека <a href="https://www.npmjs.com/package/cookie">cookie</a>, но пока ноль).</p>
<p>Что же с API? Если кратко, то почти всё задаётся через конфигурацию. Здесь стоит выделить основные сущности, которыми оперирует auzy:</p>
<ul>
<li><code class="codespan-short">framework</code> специфицирует, каким образом будет выполняться работа с объектами запросов/ответов, как будет складываться и удаляться из них пользователь.</li>
<li><code class="codespan-short">storage</code> определяет хранилище данных для сессий.</li>
<li><code class="codespan-short">transport</code> реализует способы передачи идентификаторов сессий, например, в заголовке.</li>
</ul>
<p>Десериализация и локальная стратегия Passport заменены на функцию <code class="codespan-short">loadUser</code>, а от сериализации и аутентификации осталась только аутентификация, которая выставляет в сессию данные настоящего пользователя. В данном случае под аутентификацией я конечно имею ввиду лишь последний этап фиксации успешности аутентификации.</p>
<h2 id="-">Планы</h2>
<p>Конечно, есть ещё много всего, что было бы неплохо сделать в проекте. Но тут как с ремонтом, его нельзя закончить, его можно только прекратить. И пока я переключился обратно на свой домашний проект, у auzy <a href="https://github.com/alexey-detr/auzy#todos-and-ideas">томится TODO-список</a>. Надеюсь, в скором будущем у меня найдётся время продолжить разработку.</p>
<p>На этом я завершаю свое немалое повествование о том, как я двинулся в СПО. Спасибо всем, кто дочитал до этих строк.</p>
<h2 id="-">Ссылки</h2>
<ol>
<li>Само собой, главный герой заметки <a href="https://github.com/alexey-detr/auzy">auzy</a>.</li>
<li>Если вам нужна особая гибкость, пока ещё имеет смысл взглянуть на <a href="http://www.passportjs.org/">Passport</a>.</li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2018/2018-01-06-auzy-middleware</link><guid isPermaLink="false">/lang/ru/posts/2018/2018-01-06-auzy-middleware/index.md</guid><pubDate>Sat, 06 Jan 2018 02:45:51 GMT</pubDate></item><item><title><![CDATA[Node REPL с библиотеками]]></title><description><![CDATA[<p>В работе мне довольно часто приходится проверять какие-то небольшие сниппеты кода на различных входных данных. Такое возникает, например, когда я занимаюсь код ревью, и возникает желание убедиться в правильной обработке краевых условий. Либо же просто, когда я использую при разработке какую-то внешнюю библиотеку, и мне нужно быть уверенным, что использованный метод будет работать ожидаемо.</p>
<p>Для таких проверок и приходит на помощь CLI-среда <a href="https://ru.wikipedia.org/wiki/REPL">REPL (read-eval-print-loop)</a>, которая по сути является неким инлайн-интерпретатором, в нём вы можете написать строку кода и тут же увидеть результат.</p>
<h2 id="node-js-repl">Node.js REPL</h2>
<p>Чтобы запустить REPL Node.js, достаточно просто набрать в терминале:</p>
<pre><code><span class="hljs-keyword">node</span><span class="hljs-title"></span>
</code></pre><p>В нём легко выполнять какие-то простые вещи и сразу видеть результат:</p>
<pre><code>&gt; [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>].join(<span class="hljs-string">':'</span>)
<span class="hljs-string">'1:2:3'</span>
</code></pre><h2 id="repl-">REPL и библиотеки</h2>
<p>Давайте представим, что мы попали в следующую ситуацию: нам понадобилось посмотреть, как именно ведёт себя функция библиотеки <a href="https://lodash.com/docs/4.17.4#keyBy">lodash keyBy</a>. Мы наивно пробуем:</p>
<pre><code>&gt; _.keyBy([{ <span class="hljs-string">id:</span> <span class="hljs-number">1</span>, <span class="hljs-string">name:</span> <span class="hljs-string">'Justin'</span> }, { <span class="hljs-string">id:</span> <span class="hljs-number">2</span>, <span class="hljs-string">name:</span> <span class="hljs-string">'Bob'</span> }], <span class="hljs-string">'id'</span>)
<span class="hljs-string">TypeError:</span> Cannot read property <span class="hljs-string">'keyBy'</span> of undefined
</code></pre><p>Естественно, никакой магии нет, мы никак не обозначили, что собираемся использовать lodash в REPL, да и более того, lodash у нас не установлена глобально, и REPL просто не может её увидеть. Что ж, ставим:</p>
<pre><code>yarn <span class="hljs-built_in">global</span> add lodash
<span class="hljs-comment"># или</span>
<span class="hljs-built_in">npm</span> i -g lodash
</code></pre><p>И пробуем запустить REPL, но в этот раз сделаем это несколько хитрее:</p>
<pre><code><span class="hljs-keyword">node</span> <span class="hljs-title">-i</span> -e <span class="hljs-string">"const _ = require('lodash')"</span>
</code></pre><p>И видим <code class="codespan-long">Error: Cannot find module &#39;lodash&#39;</code>. Давайте пока отложим эту неприятность и посмотрим, какими ключами мы воспользовались:</p>
<ul>
<li><code class="codespan-long">-e &quot;const _ = require(&#39;lodash&#39;)&quot;</code> выполняет указанный код, в данном случае импортирует модуль lodash в константу <code class="codespan-short">_</code>.</li>
<li><code class="codespan-short">-i</code> форсирует интерактивный режим, нам это необходимо, так как предыдущий ключ <code class="codespan-short">-e</code> подразумевает под собой только выполнение кода, без интерактивного режима.</li>
</ul>
<p>С ключами разобрались, но почему же не видна библиотека, мы же поставили её глобально? Тут стоит проверить, а верно ли указана переменная окружения <code class="codespan-short">NODE_PATH</code>. В моём случае она не была установлена вообще, и пришлось её добавить в конфиги шелла (в моём случае это был <a href="https://fishshell.com/">fish</a>), указав в ней путь до глобальных модулей:</p>
<pre><code class="lang-fish">set -x NODE_PATH ~/.config/fnm/lib/node_modules
</code></pre>
<p>Эта переменная может задаваться по-разному, если у вас, например, bash или zsh, и в зависимости от того, пользуетесь ли вы nvm или fnm. <em>Если вдруг у вас возникнут трудности на этом этапе, не стесняйтесь задавать вопросы, будем разбираться вместе!</em></p>
<p>Итак, пробуем снова:</p>
<pre><code><span class="hljs-keyword">node</span> <span class="hljs-title">-i</span> -e <span class="hljs-string">"const _ = require('lodash')"</span>
</code></pre><p>И, наконец, попадаем в REPL без ошибок, с надеждой снова пробуем функцию из lodash:</p>
<pre><code>&gt; _.keyBy([{ <span class="hljs-string">id:</span> <span class="hljs-number">1</span>, <span class="hljs-string">name:</span> <span class="hljs-string">'Justin'</span> }, { <span class="hljs-string">id:</span> <span class="hljs-number">2</span>, <span class="hljs-string">name:</span> <span class="hljs-string">'Bob'</span> }], <span class="hljs-string">'id'</span>)
{ <span class="hljs-string">'1'</span>: { <span class="hljs-string">id:</span> <span class="hljs-number">1</span>, <span class="hljs-string">name:</span> <span class="hljs-string">'Justin'</span> }, <span class="hljs-string">'2'</span>: { <span class="hljs-string">id:</span> <span class="hljs-number">2</span>, <span class="hljs-string">name:</span> <span class="hljs-string">'Bob'</span> } }
</code></pre><p>И, всё заработало! Мы добились того, чтобы при запуске нашей песочницы нам подключалась библиотека и мы могли свободно ей пользоваться.</p>
<p>Также хотелось бы отметить, что это далеко не единственный и даже не самый простой способ использовать внешние библиотеки в REPL. Например, есть <a href="https://github.com/VictorBjelkholm/trymodule">trymodule</a>, который довольно просто позволяет открывать REPL с импортированными библиотеками. Но честно говоря, в моём случае я столкнулся с небольшими субъективным недостатками, поэтому пока остаюсь на нативном REPL.</p>
<p>Итак, подытожим, мы с вами научились из Node.js создавать свою песочницу для различных проб и экспериментов.</p>
<h2 id="-">Ссылки</h2>
<ol>
<li><a href="https://nodejs.org/api/repl.html">Документация к REPL-режиму Node.js</a></li>
<li><a href="https://github.com/VictorBjelkholm/trymodule">trymodule</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2017/2017-12-31-node-repl-libraries</link><guid isPermaLink="false">/lang/ru/posts/2017/2017-12-31-node-repl-libraries/index.md</guid><pubDate>Sun, 31 Dec 2017 16:35:22 GMT</pubDate></item><item><title><![CDATA[Запускаем mongodb в docker]]></title><description><![CDATA[<p>Занимаясь очередным домашним проектом, мне вдруг понадобилась <code class="codespan-short">mongodb</code>. Как правило, раньше я всегда ставил <code class="codespan-short">mongodb</code> через <code class="codespan-short">brew</code>. Но захотелось уже научиться поднимать её при помощи докера, чтобы можно было легко разделять инстансы между различными проектами и не засорять основную систему.</p>
<p>В целом, заметка будет небольшой, я лишь рассмотрю несколько простых команд, которые позволят поднять сервер <code class="codespan-short">mongodb</code> в <code class="codespan-short">docker</code> и продолжить работу с ним.</p>
<h2 id="-">Инициализация</h2>
<p>Для инициализации запускаем контейнер из официального образа со следующими параметрами:</p>
<pre><code class="lang-bash">docker run -d -p 127.0.0.1:27017:27017 --name mongo-exp-project mongo
</code></pre>
<ul>
<li><code class="codespan-short">-d</code> выполнить детач от текущего терминала и вывести ID контейнера.</li>
<li><code class="codespan-short">-p</code> публикует порт контейнера для хоста (это нужно, чтобы сервер <code class="codespan-short">mongodb</code>, запущенный внутри контейнера, было видно извне, и к нему можно было подключиться, например, из Node.js).</li>
<li><code class="codespan-short">--name</code> просто имя контейнера, чтобы было проще ориентироваться в последствии.</li>
</ul>
<p><code class="codespan-short">mongo</code> — это образ, на основе которого будет запускаться контейнер. В данном случае это наиболее свежий официальный образ <code class="codespan-short">mongodb</code>.</p>
<h2 id="-">Запуск контейнера</h2>
<p>Допустим, наш контейнер оказался остановлен. Это могло произойти, например, из-за банальной перезагрузки операционной системы.</p>
<p>Для начала, выясним ID нашего контейнера:</p>
<pre><code class="lang-bash">docker ps -a
</code></pre>
<pre><code class="lang-text">CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                          PORTS                        NAMES
a7bec3375bdc        mongo:latest        "docker-entrypoint..."   7 weeks ago         Exited (0) 3 seconds ago        127.0.0.1:27017-&gt;27017/tcp   mongo-exp-project
</code></pre>
<p>Ключ <code class="codespan-short">-a</code> позволяет нам увидеть весь список контейнеров, в том числе и остановленных. Затем просто запускаем наш контейнер при помощи <code class="codespan-short">start</code>, указав ID:</p>
<pre><code class="lang-bash">docker start a7bec3375bdc
</code></pre>
<h2 id="-">Запуск команды внутри контейнера</h2>
<p>Иногда бывает нужно что-то сделать в рамках контейнера, выполнить какую-то операцию или, к примеру, запустить оболочку <code class="codespan-short">mongodb</code>. Это можно сделать при помощи команды <code class="codespan-short">exec</code>.</p>
<pre><code class="lang-bash">docker <span class="hljs-built_in">exec</span> -it a7bec3375bdc mongo
</code></pre>
<ul>
<li><code class="codespan-short">-i</code> интерактивный режим, оставляет STDIN открытым, таким образом, контейнер остаётся доступным на получение данных.</li>
<li><code class="codespan-short">-t</code> выделяет псевдо-TTY (иными словами, некий инстанс текстового терминала).</li>
</ul>
<p>Ключи <code class="codespan-short">-i</code> и <code class="codespan-short">-t</code> выглядят несколько непонятными, не находите? Вообще, если говорить человеческим языком, оба эти ключа позволяют обеспечить интерактивность оболочки <code class="codespan-short">mongo</code>. То есть, мы можем писать в ней команды, а она, соответственно, может отдавать нам результат.</p>
<p>Когда <code class="codespan-short">-i</code> и <code class="codespan-short">-t</code> будут лишними? Например, когда нам не нужно «общаться» с командой докера: <code class="codespan-long">docker exec a7bec3375bdc ls /var/log</code> В данном случае нам интересен только ответ в виде списка файлов, мы не планируем что-то делать интерактивно.</p>
<p>Совершенно аналогично можно запустить оболочку <code class="codespan-short">bash</code> внутри контейнера и что-то в нём поделать:</p>
<pre><code class="lang-bash">docker <span class="hljs-built_in">exec</span> -it a7bec3375bdc bash
</code></pre>
<p>Вроде бы самое полезное я теперь упомянул. Полагаю, этих базовых команд будет достаточно для того, чтобы пользоваться сервером <code class="codespan-short">mongodb</code>. На этом буду завершать свою заметку. Буду рад, если она поможет вам начать использовать <code class="codespan-short">docker</code>.</p>
<h2 id="-">Ссылки</h2>
<ol>
<li><a href="https://docs.docker.com/">Официальная документация Docker</a></li>
<li><a href="https://www.mongodb.com/">Сайт mongodb</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2017/2017-09-17-docker-mongo</link><guid isPermaLink="false">/lang/ru/posts/2017/2017-09-17-docker-mongo/index.md</guid><pubDate>Sun, 17 Sep 2017 15:50:19 GMT</pubDate></item><item><title><![CDATA[Быстро, просто и со вкусом делаем base64 URL из файла]]></title><description><![CDATA[<p>Тут на днях понадобилось мне заменить ссылки на фоновые изображения в CSS на соответствующие встроенные base64 закодированные изображения. Да, на дворе шёл 2017-й, можно было без испуга использовать прекрасный векторный SVG, а его до безобразия небольшие размеры (особенно после прогона через <a href="https://github.com/svg/svgo">SVGO</a>) просто напрашивались на base64. Да, наверное существует множество различных онлайн-инструментов для таких штук, но это для меня выглядело как-то неспортивно.</p>
<p>Итак, не буду долго томить, рецепт довольно прост (ну, если учесть, что у вас уже есть под руками <a href="npmjs.com">NPM</a>).</p>
<p>Сначала ставим себе в глобальные зависимости пакет <a href="https://www.npmjs.com/package/mime">mime</a>:</p>
<pre><code class="lang-bash">npm i -g mime
</code></pre>
<p>Затем для ваших любимых шеллов добавляем себе функции.</p>
<h2 id="fish">fish</h2>
<p>Просто вставляем это в файл <code class="codespan-long">~/.config/fish/functions/base64urldata.fish</code>:</p>
<pre><code class="lang-fish">function base64urldata
  set filename $argv[1]
  echo &quot;data:&quot;(mime $filename)&quot;;base64,&quot;(cat $filename | openssl enc -base64 | tr -d &#39;\n&#39;)
end
</code></pre>
<h2 id="zsh-bash">zsh/bash</h2>
<p>В конец <code class="codespan-short">~/.zshrc</code> или в конец <code class="codespan-short">~/.bashrc</code>, соответственно:</p>
<pre><code class="lang-bash"><span class="hljs-function"><span class="hljs-title">base64urldata</span></span>() {
    FILENAME=<span class="hljs-variable">$1</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"data:<span class="hljs-variable">$(mime $FILENAME)</span>;base64,<span class="hljs-variable">$(cat $FILENAME | openssl enc -base64 | tr -d '\n')</span>"</span>
}
</code></pre>
<p>Пользоваться всем этим добром везде одинаково просто:</p>
<pre><code class="lang-bash">base64urldata ~/path/to/file.svg
</code></pre>
<p>Собственно, на этом всё, гладкой вам вёрстки!</p>
<h2 id="-">Ссылки</h2>
<ol>
<li>Если ещё не приходилось, <a href="https://github.com/svg/svgo">обязательно уделите время знакомству с SVGO</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2017/2017-03-08-base64urldata</link><guid isPermaLink="false">/lang/ru/posts/2017/2017-03-08-base64urldata/index.md</guid><pubDate>Wed, 08 Mar 2017 00:03:42 GMT</pubDate></item><item><title><![CDATA[Автообновление сертификатов Let's Encrypt]]></title><description><![CDATA[<p>И очередной раз я посыпаю голову пеплом, так как вот уже около года на одном из моих пет-прожектов <a href="https://spaceismine.org/">spaceismine.org</a> протух Let’s Encrypt сертификат, а я даже и не думал шевелиться. Что ж, рано или поздно всё равно нужно исправляться. Приступаю.</p>
<p>Бегло заглянув на сайт этого замечательного проекта с бесплатными сертификатами, я отыскал нужный себе раздел и примерно понял, что нужно делать. Год развития проекта дал о себе знать, теперь существует прокачанная утилита, распространяемая через deb-пакет. Ставим её.</p>
<pre><code class="lang-bash">sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install letsencrypt
</code></pre>
<p>Да, приходится подключать отдельный PPA, так как в Ubuntu 16.04 (которая у меня) этот пакет довольно старый, и в нём нет поддержки ключа <code class="codespan-short">--post-hook</code>, который нам понадобится ниже.</p>
<p>Судя по всему, написано там всё на питоне. Итак, поставив certbot, я сделал для проверки успешности своих намерений dry-run запуск (так называемый запуск вхолостую, который по факту ничего не делает, а только показывает отчёт), как это предлагалось в документации certbot.</p>
<pre><code class="lang-bash">sudo letsencrypt renew --dry-run --agree-tos

Processing /etc/letsencrypt/renewal/spaceismine.org.conf
** DRY RUN: simulating <span class="hljs-string">'letsencrypt renew'</span> close to cert expiry
**          (The <span class="hljs-built_in">test</span> certificates below have not been saved.)

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/spaceismine.org/fullchain.pem (success)
** DRY RUN: simulating <span class="hljs-string">'letsencrypt renew'</span> close to cert expiry
**          (The <span class="hljs-built_in">test</span> certificates above have not been saved.)
</code></pre>
<p>Success-success, успех-успех! Ну, дальше можно было уже запустить и полноценное обновление сертификата.</p>
<pre><code class="lang-bash">sudo letsencrypt renew

Processing /etc/letsencrypt/renewal/spaceismine.org.conf
new certificate deployed without reload, fullchain is /etc/letsencrypt/live/spaceismine.org/fullchain.pem

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/spaceismine.org/fullchain.pem (success)
</code></pre>
<p>Замечательно, он говорит, что всё нам обновил. Делаем релоад нашему <code class="codespan-short">nginx</code>.</p>
<pre><code class="lang-bash">sudo service nginx reload
</code></pre>
<p>И вот всё, сертификат обновлён, всё замечательно. Но, не буду же я раз в 3 месяца заходить обновлять его руками, поэтому я поискал информацию, как лучше всего это прикрутить на <code class="codespan-short">cron</code>. Оказывается, ничего особо сложного с этим тоже не было.</p>
<pre><code class="lang-bash">sudo crontab -e
</code></pre>
<p>и добавляем там</p>
<pre><code class="lang-bash">42 0,12 * * * letsencrypt renew --post-hook <span class="hljs-string">"service nginx reload"</span>
</code></pre>
<p>“Зачем делать два раза в день?” спросите вы. Да, это довольно часто, но я столкнулся с мнениями, что в этом нет ничего такого, так как certbot ничего не делает, если сертификат по факту пока обновлять не требуется. Момент необходимости обновления сертификата наступает за 30 дней до его истечения.</p>
<p>Убеждаемся напоследок, что команда <code class="codespan-short">cron</code> работает. Многие годы работы с <code class="codespan-short">cron</code> научили меня это делать в обязательном порядке.</p>
<pre><code class="lang-bash">sudo letsencrypt renew --post-hook <span class="hljs-string">"service nginx reload"</span>

Saving debug <span class="hljs-built_in">log</span> to /var/<span class="hljs-built_in">log</span>/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/spaceismine.org.conf
-------------------------------------------------------------------------------
Cert not yet due <span class="hljs-keyword">for</span> renewal

The following certs are not due <span class="hljs-keyword">for</span> renewal yet:
  /etc/letsencrypt/live/spaceismine.org/fullchain.pem (skipped)
No renewals were attempted.
No hooks were run.
</code></pre>
<p>Итак, похоже всё хорошо и всё работает. На этом завершаю свою заметку про автоматическое обновление сертификатов Let’s Ecnrypt.</p>
<h2 id="-">Ссылки</h2>
<ol>
<li><a href="/posts/2016/2016-01-11-https-http2-letsencypt-nginx/">Моя прошлогодняя статья про первоначальную настройку Let’s Encrypt</a></li>
<li><a href="https://certbot.eff.org/">Сайт certbot</a></li>
<li><a href="https://letsencrypt.org/">Сайт проекта Let’s Encrypt</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2017/2017-03-05-letsencrypt-renewal</link><guid isPermaLink="false">/lang/ru/posts/2017/2017-03-05-letsencrypt-renewal/index.md</guid><pubDate>Sun, 05 Mar 2017 15:03:15 GMT</pubDate></item><item><title><![CDATA[Upfox бот для Telegram]]></title><description><![CDATA[<p>Эта статья не носит никакого образовательного характера. Но возникло желание отметиться, и вот, я снова здесь. Итак, поехали.</p>
<p>Да, я и правда давно сюда не заглядывал, а меж тем, шёл уже 3-й месяц 2017-го. Просто тут у меня появилась причина поведать возможно что-то интересное. У меня наконец-то дошли руки до моей затеи открыть исходники <a href="https://telegram.me/upfox_bot">телеграм-бота Upfox</a>. Он предназначен для людей, вроде меня, <del>у которых шило в ж</del> которым не терпится узнавать о свежих версиях программ как можно быстрее. Нет, не как в RSS-лентах, а прямо-вот-сейчас-сразу-же! Поэтому и пришла в голову такая мысль: “А было бы круто слать себе нотификации, как только появляется что-то новенькое”. Так и пришла идея написать бота, принцип работы которого крайне прост: каждые N минут он стучится на целевые страницы (на которых можно найти информацию о версии), регуляркой вытаскивает самую свежую версию и, если она обновилась, уведомляет своих пользователей об этом.</p>
<p><a class="swipebox thumbnail" href="/content_images/lang/ru/posts/2017/2017-03-02-upfox-bot/upfox_messages.png" title="Upfox бот для Telegram"><img src="/content_images/lang/ru/posts/2017/2017-03-02-upfox-bot/upfox_messages_small.png" alt="Upfox бот для Telegram" /></a></p>
<p>Это всё просто и банально, само собой, но основной интерес, как мне кажется, представляет простота добавления новых программ на отслеживание. Достаточно небольшого конфига, описывающего мета-данные и регулярное выражение для поиска версии. На текущий момент <a href="https://github.com/alexey-detr/upfox-bot/tree/master/polls">список приложений невелик</a>, но, если у проекта появятся пользователи, расти этому перечню будет довольно просто. Хотя конечно, с большим количеством пользователей появятся и другие проблемы, но о них пока рано.</p>
<p>В любом случае, буду рад, если бот кому-то пригодится. Пишите в комментарии свои пожелания и полезна ли вам его функциональность.</p>
<h2 id="-">Ссылки</h2>
<ol>
<li><a href="https://telegram.me/upfox_bot">Ссылка на бота</a></li>
<li><a href="https://github.com/alexey-detr/upfox-bot">Ссылка на GitHub-репозиторий</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2017/2017-03-02-upfox-bot</link><guid isPermaLink="false">/lang/ru/posts/2017/2017-03-02-upfox-bot/index.md</guid><pubDate>Thu, 02 Mar 2017 00:03:56 GMT</pubDate></item><item><title><![CDATA[HTTP/2 в nginx при помощи Let's Encrypt]]></title><description><![CDATA[<p>Итак, был конец 2015 года, я смотрел на свой давний проект <a href="https://spaceismine.org">spaceismine.org</a>, на котором всякие картинки-статика, и тут в голову ударило: “Вообще-то всю статику можно грузить по одному TCP-соединению гораздо эффективнее! Какого чёрта у меня до сих пор не поддерживается модный и красивый <code class="codespan-short">HTTP/2</code>?!” Что ж, вопрос требовал скорее действий, чем ответа, поэтому недолго думая, я начал копать, как же там всё у <code class="codespan-short">nginx</code> с этим обстоит. Я в течение всего года наблюдал, как постепенно в этом веб-сервере пилится поддержка <code class="codespan-short">HTTP/2</code> в версиях 1.9.x. И действительно, поддержка уже есть, но она всё ещё обкатывается, о чём свидетельствует название PPA, который мне пришлось добавить:</p>
<pre><code class="lang-bash">sudo add-apt-repository ppa:nginx/development
</code></pre>
<p>В <code class="codespan-short">ppa:nginx/stable</code> всё ещё версия 1.8.0. Но, <del>как я всегда говорю, жизнь меня ничему не учит</del> мои пет-проекты вовсе никакие не супер-важные, поэтому я решил обновиться. Почему нет. Итак, поддержка <code class="codespan-short">HTTP/2</code> у меня была в кармане.</p>
<p>Немного проглядев информацию про сам протокол, стало понятно, что он может работать только в шифрованном режиме. Получать через пень-колоду бесплатный долгосрочный сертификат через китайцев, как я это <a href="/posts/2015/2015-05-10-audiocoding-https/">делал прошлой весной</a>, вовсе не хотелось. Хотелось попробовать что-то новое. И тут очень кстати мне вспомнился проект <a href="https://letsencrypt.org">Let’s Encrypt</a>.</p>
<h2 id="let-s-encrypt-https-">Let’s Encrypt и бесплатный HTTPS всем</h2>
<p>Этот амбициозный проект был начат двумя сотрудниками Mozilla, но теперь он уже существенно вырос и обзавёлся поддержкой многих гигантов (таких как Akamai, Cisco, Facebook). Центр сертификации Let’s Encrypt вошёл в стадию открытой беты в начале декабря 2015 года и с тех пор раздаёт бесплатные сертификаты. Сертификаты выдаются всего на 3 месяца, но процесс их получения заключается в запуске всего лишь одного скрипта с указанием e-mail’а и доменов. Мне сходу понравилась эта простота после китайцев, да и бесплатность была тоже очень кстати.</p>
<p>Этот скрипт, который вам придётся поставить на сервер, проверяет, что вы являетесь владельцем домена. Это возможно в нескольких режимах:</p>
<ul>
<li><code class="codespan-short">apache</code> – его я не пробовал, у меня же <code class="codespan-short">nginx</code>, но, поговаривают, этот режим весь такой красивый и полностью автоматизированный</li>
<li><code class="codespan-short">standalone</code> – через запуск встроенного веб-сервера на 80-м порту, что мне тоже не подошло, у меня же <code class="codespan-short">nginx</code> на нём висит и работает в любой момент времени</li>
<li><code class="codespan-short">webroot</code> – выбрал именно этот вариант, проверка осуществляется созданием специального файла в корневой директории сайта, которую вы укажете в параметре <code class="codespan-short">--webroot</code></li>
</ul>
<p>Я склонировал репозиторий себе:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/letsencrypt/letsencrypt
<span class="hljs-built_in">cd</span> letsencrypt
</code></pre>
<p>Разобравшись с параметрами, у меня в итоге получилась примерно такая команда для получения сертификата:</p>
<pre><code class="lang-bash">./letsencrypt-auto certonly --webroot -w /var/www/spaceismine/html/ --email xlz@ya.ru -d spaceismine.org -d www.spaceismine.org
</code></pre>
<p>Вероятно, понадобится sudo-пароль, потому что скрипт будет записывать сертификаты в <code class="codespan-short">/etc</code>. Но у меня <code class="codespan-short">sudo</code> уже торчал в сессии, не помню, чтобы мне приходилось вводить пароль. После того, как команда успешно отработала, в директории <code class="codespan-long">/etc/letsencrypt/live/spaceismine.org</code> аккуратным образом появились симлинки на ключ, сертификат, цепочки.</p>
<h2 id="nginx-http-2-https">nginx ~ HTTP/2 ~ HTTPS</h2>
<p>Далее нужно было настроить <code class="codespan-short">nginx</code>. Прикладываю свой конфиг:</p>
<pre><code><span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
    <span class="hljs-attribute">server_name</span> spaceismine.org www.spaceismine.org;
    <span class="hljs-attribute">rewrite</span><span class="hljs-regexp"> ^(.*)</span> https://spaceismine.org<span class="hljs-variable">$1</span> <span class="hljs-literal">permanent</span>;
}

<span class="hljs-section">server</span> {
    <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;

    <span class="hljs-attribute">server_name</span> spaceismine.org;
    <span class="hljs-attribute">root</span> /var/www/spaceismine/html;
    <span class="hljs-attribute">index</span> index.html;

    <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/spaceismine.org/fullchain.pem;
    <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/spaceismine.org/privkey.pem;
    <span class="hljs-attribute">add_header</span> Strict-Transport-Security <span class="hljs-string">'max-age=604800'</span>;
}
</code></pre><p>Суть первых строк в том, что мы <code class="codespan-short">HTTP</code> будем всегда кидать на <code class="codespan-short">HTTPS</code>. Ну и для верности добавим хидер <code class="codespan-long">add_header Strict-Transport-Security &#39;max-age=604800&#39;;</code>, чтобы браузерам было попроще и попонятнее перекидывать пользователя на нужный вариант протокола. Как видите, конфиг вышел на удивление простой, что не может не радовать.</p>
<h2 id="-a-ssllabs">Подвохи и рейтинг A на ssllabs</h2>
<p>Затем у меня были некоторые проблемы с тем, что сайт открывался только в Safari. Я долго пытался понять, с чем это может быть связано. Но в итоге вспомнил, что слабые алгоритмы в последнее время активно выпиливаются браузерами, поэтому нашёл способ, как можно было настроить более “качественный” <code class="codespan-short">HTTPS</code> в <code class="codespan-short">nginx</code>.</p>
<p>Прикладываю конфиг секции <code class="codespan-short">http</code> главного конфига <code class="codespan-short">nginx.conf</code>, который позволил добиться мне A на <a href="https://www.ssllabs.com/ssltest/">тесте ssllabs</a>:</p>
<pre><code>ssl_prefer_server_ciphers on<span class="hljs-comment">;
</span>ssl_session_cache shared:SSL:<span class="hljs-number">10</span>m<span class="hljs-comment">;
</span>ssl_session_timeout <span class="hljs-number">10</span>m<span class="hljs-comment">;
</span>ssl_stapling on<span class="hljs-comment">;
</span>ssl_dhparam ssl/dhparam.pem<span class="hljs-comment">;
</span>ssl_ciphers 'ECDHE-RSA-AES<span class="hljs-number">128</span>-GCM-SHA<span class="hljs-number">256</span>:ECDHE-ECDSA-AES<span class="hljs-number">128</span>-GCM-SHA<span class="hljs-number">256</span>:ECDHE-RSA-AES<span class="hljs-number">256</span>-GCM-SHA<span class="hljs-number">384</span>:ECDHE-ECDSA-AES<span class="hljs-number">256</span>-GCM-SHA<span class="hljs-number">384</span>:DHE-RSA-AES<span class="hljs-number">128</span>-GCM-SHA<span class="hljs-number">256</span>:DHE-DSS-AES<span class="hljs-number">128</span>-GCM-SHA<span class="hljs-number">256</span>:kEDH+AESGCM:ECDHE-RSA-AES<span class="hljs-number">128</span>-SHA<span class="hljs-number">256</span>:ECDHE-ECDSA-AES<span class="hljs-number">128</span>-SHA<span class="hljs-number">256</span>:ECDHE-RSA-AES<span class="hljs-number">128</span>-SHA:ECDHE-ECDSA-AES<span class="hljs-number">128</span>-SHA:ECDHE-RSA-AES<span class="hljs-number">256</span>-SHA<span class="hljs-number">384</span>:ECDHE-ECDSA-AES<span class="hljs-number">256</span>-SHA<span class="hljs-number">384</span>:ECDHE-RSA-AES<span class="hljs-number">256</span>-SHA:ECDHE-ECDSA-AES<span class="hljs-number">256</span>-SHA:DHE-RSA-AES<span class="hljs-number">128</span>-SHA<span class="hljs-number">256</span>:DHE-RSA-AES<span class="hljs-number">128</span>-SHA:DHE-DSS-AES<span class="hljs-number">128</span>-SHA<span class="hljs-number">256</span>:DHE-RSA-AES<span class="hljs-number">256</span>-SHA<span class="hljs-number">256</span>:DHE-DSS-AES<span class="hljs-number">256</span>-SHA:DHE-RSA-AES<span class="hljs-number">256</span>-SHA:AES<span class="hljs-number">128</span>-GCM-SHA<span class="hljs-number">256</span>:AES<span class="hljs-number">256</span>-GCM-SHA<span class="hljs-number">384</span>:AES<span class="hljs-number">128</span>-SHA<span class="hljs-number">256</span>:AES<span class="hljs-number">256</span>-SHA<span class="hljs-number">256</span>:AES<span class="hljs-number">128</span>-SHA:AES<span class="hljs-number">256</span>-SHA:AES:CAMELLIA:DES-CBC<span class="hljs-number">3</span>-SHA:<span class="hljs-title">!aNULL</span>:<span class="hljs-title">!eNULL</span>:<span class="hljs-title">!EXPORT</span>:<span class="hljs-title">!DES</span>:<span class="hljs-title">!RC4</span>:<span class="hljs-title">!MD5</span>:<span class="hljs-title">!PSK</span>:<span class="hljs-title">!aECDH</span>:<span class="hljs-title">!EDH-DSS-DES-CBC3-SHA</span>:<span class="hljs-title">!EDH-RSA-DES-CBC3-SHA</span>:<span class="hljs-title">!KRB5-DES-CBC3-SHA</span>'<span class="hljs-comment">;</span>
</code></pre><p>Здесь один важный момент <code class="codespan-long">ssl_dhparam ssl/dhparam.pem;</code>. Да, пришлось сгенерить свой <code class="codespan-short">dhparam</code> на 2048 бит:</p>
<pre><code class="lang-bash">openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
</code></pre>
<p>Учтите, что директория <code class="codespan-short">/etc/nginx/ssl</code> должна существовать, иначе <code class="codespan-short">openssl</code> ничего не сделает.</p>
<p>Далее <code class="codespan-short">service nginx restart</code> и всё стало хорошо во всех браузерах, а в ssllabs начала красоваться A:</p>
<p><a class="swipebox thumbnail" href="/content_images/lang/ru/posts/2016/2016-01-11-https-http2-letsencypt-nginx/ssllabs_a.png" title="HTTP/2 в nginx при помощи Let&#39;s Encrypt"><img src="/content_images/lang/ru/posts/2016/2016-01-11-https-http2-letsencypt-nginx/ssllabs_a_small.png" alt="HTTP/2 в nginx при помощи Let&#39;s Encrypt" /></a></p>
<p>Что ж, на этом всё, таков был мой опыт настройки <code class="codespan-short">HTTP/2</code> на своём проекте. Так что если вам это интересно, конечно берите и пробуйте, всё можно использовать уже сейчас.</p>
<h2 id="-">Ссылки</h2>
<ol>
<li><a href="http://habrahabr.ru/post/269853/">Хорошее пояснение/обзор/бенчмарк на тему HTTP/2 на хабре</a></li>
<li><a href="https://github.com/letsencrypt/letsencrypt">Проект Let’s Encrypt на github</a></li>
<li><a href="http://nginx.org/ru/docs/http/configuring_https_servers.html">Неплохая статья о конфигурировании HTTPS на сайте nginx</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2016/2016-01-11-https-http2-letsencypt-nginx</link><guid isPermaLink="false">/lang/ru/posts/2016/2016-01-11-https-http2-letsencypt-nginx/index.md</guid><pubDate>Mon, 11 Jan 2016 00:01:54 GMT</pubDate></item><item><title><![CDATA[Быстрый старт tmux + iTerm2]]></title><description><![CDATA[<p><code class="codespan-short">tmux</code> есть мултиплексор терминалов. Не понятно? Да мне тоже не понятно. Короче, человеческим языком, <code class="codespan-short">tmux</code> используется для того, чтобы внутри одного терминала вы могли пользоваться несколькими терминалами (что-то вроде эмуляции). <em>Мы поставили тебе терминал внутрь терминала, чтобы ты мог пользоваться терминалом, пока пользуешься терминалом.</em> Простите.</p>
<p>Когда такое бывает полезно, спросите вы? Например, когда вы коннектитесь к удаленной машине по SSH, с мультиплексором можно вполне так эмулировать многозадачность, утилизируя только одно SSH-соединение.</p>
<p>Но, это еще не все. <code class="codespan-short">tmux</code> внутри каждого своего терминала полностью помнит состояние. И если у вас вдруг произошел разрыв <del><em>с ноября прошлого года</em></del>, то с <code class="codespan-short">tmux</code> вам не придется подключаться снова к серверу в 10 вкладках и в 10 вкладках снова запускать прибитые процессы, вроде <code class="codespan-short">mc</code>, <code class="codespan-short">less</code>, какого-нибудь консольного клиента <code class="codespan-short">mongo</code>… да что угодно. При разрыве все так и останется крутиться в <code class="codespan-short">tmux</code>.</p>
<p>Особо матерые батьки вспомнят тут <code class="codespan-short">screen</code>, да, была такая штука. Но <code class="codespan-short">tmux</code> посовременнее и обладает более богатой функциональностью, поэтому лучше трогать будем его.</p>
<h2 id="-">Ставим-пробуем</h2>
<p>Давайте сходим на свой любимый сервер и поставим там <code class="codespan-short">tmux</code>:</p>
<pre><code class="lang-bash">ssh detr.us
sudo apt-get install tmux
</code></pre>
<p>Далее для тренировки давайте запустим <code class="codespan-short">tmux</code>, сделаем в нем что-то, просимулируем разрыв, перезайдем на сервер и подцепимся к <code class="codespan-short">tmux</code> обратно. Начнем:</p>
<pre><code class="lang-bash">tmux
</code></pre>
<p><a class="swipebox thumbnail" href="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/tmux_works.png" title="Быстрый старт tmux + iTerm2"><img src="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/tmux_works_small.png" alt="Быстрый старт tmux + iTerm2" /></a></p>
<p>Мы видим нечто, что называется <code class="codespan-short">tmux</code>. Давайте запустим что-то бесполезное.</p>
<pre><code class="lang-bash">watch date
</code></pre>
<p>Итак, мы смотрим время (<em>бред какой</em>). Теперь закроем терминал, будто у нас все пропало, открываем новый терминал и делаем следующее:</p>
<pre><code class="lang-bash">ssh detr.us
tmux attach
</code></pre>
<p><a class="swipebox thumbnail" href="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/tmux_watch.png" title="Быстрый старт tmux + iTerm2"><img src="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/tmux_watch_small.png" alt="Быстрый старт tmux + iTerm2" /></a></p>
<p>И вот, мы подцепились к своей существующей <code class="codespan-short">tmux</code>-сессии и снова наблюдаем время… То есть буквально полностью восстановили свое рабочее окружение, бесцеремонно закрытое нами же выше.</p>
<p>В общем, самым простым вещам мы научились. Добавлю лишь, что детач от текущего <code class="codespan-short">tmux</code> можно еще делать комбинацией <kbd>Ctrl</kbd> + <kbd>b</kbd>, <kbd>d</kbd>. Дальше – больше.</p>
<h2 id="-iterm2-">Оооо, iTerm2, великий!</h2>
<p>Да, iTerm2 в этой заметке неспроста. Дело в том, что iTerm2 умеет просто ну очень круто интегрироваться с <code class="codespan-short">tmux</code>. Чем же это поможет? Тем, что окна (в терминологии мультиплексора) <code class="codespan-short">tmux</code> можно отображать как вкладки в iTerm2. Помимо этого у вас пропадают проблемы со скроллом и появляется очень легкая возможность создавать новые окна <code class="codespan-short">tmux</code> простым нажатием <kbd>Cmd</kbd> + <kbd>t</kbd> (то есть как вкладки).</p>
<p>Предварительно я бы рекомендовал настроить iTerm2 следующим образом (обратите внимание на раздел <code class="codespan-short">tmux</code> справа внизу):</p>
<p><a class="swipebox thumbnail" href="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/iterm2_config.png" title="Быстрый старт tmux + iTerm2"><img src="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/iterm2_config_small.png" alt="Быстрый старт tmux + iTerm2" /></a></p>
<p>Итак, пробуем интеграцию iTerm2 с <code class="codespan-short">tmux</code> командой:</p>
<pre><code class="lang-bash">tmux -CC attach
</code></pre>
<p><a class="swipebox thumbnail" href="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/iterm2_tmux_watch.png" title="Быстрый старт tmux + iTerm2"><img src="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/iterm2_tmux_watch_small.png" alt="Быстрый старт tmux + iTerm2" /></a></p>
<p>И вот мы снова видим нашу пресловутую дату. Нажмем <kbd>Cmd</kbd> + <kbd>t</kbd> и откроем какой-нибудь мануал в другом <code class="codespan-short">tmux</code>-окне.</p>
<pre><code class="lang-bash">man find
</code></pre>
<p>Повторяем симуляцию разрыва: безжалостно закрываем iTerm2. Снова идем на сервер и коннектимся к существующей сессии <code class="codespan-short">tmux</code>:</p>
<pre><code class="lang-bash">ssh detr.us
tmux -CC attach
</code></pre>
<p>Символ стрелочки, расположенный на вкладках iTerm2 говорит нам о том, что эти вкладки – окна <code class="codespan-short">tmux</code>.</p>
<p><a class="swipebox thumbnail" href="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/iterm2_tmux_reattach.png" title="Быстрый старт tmux + iTerm2"><img src="/content_images/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/iterm2_tmux_reattach_small.png" alt="Быстрый старт tmux + iTerm2" /></a></p>
<p>И видим, что наша рабочая сессия на удаленном сервере полностью восстановлена. По-моему, это просто прекрасно!</p>
<h2 id="tmux-cheat-sheet-">tmux cheat sheet и горячие клавиши</h2>
<p>На случай, если у вас все же нет под рукой iTerm2 и вам приходится довольствоваться обычным терминалом, прикладываю шпаргалку для <code class="codespan-short">tmux</code></p>
<h3 id="-">Сессии</h3>
<table>
<thead>
<tr>
<th>Действие</th>
<th>Команда</th>
</tr>
</thead>
<tbody>
<tr>
<td>Новая сессия</td>
<td><code class="codespan-short">tmux</code></td>
</tr>
<tr>
<td>Список сессий</td>
<td><code class="codespan-short">tmux ls</code></td>
</tr>
<tr>
<td>Приаттачиться к сессии</td>
<td><code class="codespan-short">tmux attach</code></td>
</tr>
<tr>
<td>Приаттачиться к сессии с детачем других коннектов</td>
<td><code class="codespan-short">tmux attach -d</code></td>
</tr>
<tr>
<td>Детач от сессии</td>
<td><kbd>Ctrl</kbd> + <kbd>b</kbd>, <kbd>d</kbd></td>
</tr>
</tbody>
</table>
<h3 id="-">Окна</h3>
<table>
<thead>
<tr>
<th>Действие</th>
<th>Команда</th>
</tr>
</thead>
<tbody>
<tr>
<td>Новое окно</td>
<td><kbd>Ctrl</kbd> + <kbd>b</kbd>, <kbd>c</kbd></td>
</tr>
<tr>
<td>Список окон</td>
<td><kbd>Ctrl</kbd> + <kbd>b</kbd>, <kbd>w</kbd></td>
</tr>
<tr>
<td>Перейти к окну номер 0-9</td>
<td><kbd>Ctrl</kbd> + <kbd>b</kbd>, <kbd>0-9</kbd></td>
</tr>
<tr>
<td>Переименовать окно</td>
<td><kbd>Ctrl</kbd> + <kbd>b</kbd>, <kbd>,</kbd></td>
</tr>
<tr>
<td>Закрыть окно</td>
<td><kbd>Ctrl</kbd> + <kbd>b</kbd>, <kbd>x</kbd></td>
</tr>
</tbody>
</table>
<p>Также хотел бы отметить, что в <code class="codespan-short">tmux</code> имеются еще и панели, но, насколько я понял, у них нет интеграции с iTerm2. Да и управлять ими через горячие клавиши – сущий ад. Поэтому рассказ про панели я опущу.</p>
<p>На этом, наверное, все. Дай бог вам ни единого разрыва!</p>
<h2 id="-">Ссылки</h2>
<ol>
<li><a href="http://tmux.github.io/">Сайт tmux</a></li>
<li><a href="http://iterm2.com/">Сайт iTerm2</a></li>
<li><a href="https://gitlab.com/gnachman/iterm2/wikis/TmuxIntegration">Страница о состоянии интеграции iTerm с tmux</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2015/2015-06-07-tmux-iterm-quick-start</link><guid isPermaLink="false">/lang/ru/posts/2015/2015-06-07-tmux-iterm-quick-start/index.md</guid><pubDate>Sun, 07 Jun 2015 16:06:43 GMT</pubDate></item><item><title><![CDATA[Динамика в статике, Parse.com и актуальность BaaS]]></title><description><![CDATA[<h2 id="-baas-">Плюшки BaaS’а?</h2>
<p>Стоит наверное начать с небольшого введения, что же такое BaaS и зачем вот это вота все. BaaS (Backend as a Service, как <a href="https://en.wikipedia.org/wiki/Mobile_Backend_as_a_service">гласит Википедия</a>) отчасти можно назвать очередным баззвордом, но тем не менее небольшая ниша у таких сервисов все же есть. Довольно тяжело сходу въехать, как можно написать полноценное мобильное приложение с аккаунтами, регистрацией, профилями и прочими плюшками, не пользуясь при этом услугами разработчиков серверных приложений (будь то PHP, Python, Ruby, Node.js и так далее и тому подобное). На самом деле BaaS именно для этого и был придуман. То есть, имея свое мобильное приложение, вы можете управлять различными данными с клиентской стороны, все производится через API и (как правило) готовые SDK.</p>
<p>Одним из наиболее популярных (и к тому же, допускающим неплохие лимиты на API-запросы при бесплатном аккаунте) BaaS является <a href="https://www.parse.com/">Parse.com</a>. Именно об интеграции с ним я буду сегодня говорить.</p>
<h2 id="-">Динамика в статике, опять?!</h2>
<p>Да, мне кажется я о подобном уже говорил. Что-то подобное вы уже слышали, но тем не менее, факт остается фактом. Добавить динамичности статическому контенту можно, благодаря именно BaaS. Что же тут хорошего, почему бы не взять в таком случае нормальный язык и не написать на нем адекватный бэкенд, спросите вы. Ответ у меня будет довольно простой: скорость работы статического сайта даже на сервере за $5 в месяц (в том же DigitalOcean) будет очень и очень хорошей. Иными словами, это все обусловлено развитием облачных сервисов, предоставляющих VPS буквально за копейки. В итоге BaaS дает нам возможность иметь неплохой информационный сайт с минимальными денежными затратами на содержание. По-моему, это довольно круто и стоит того, чтобы повозиться и разобраться с этим.</p>
<p>Особо пытливые умы в этом месте уже смекнут, что если в этом подходе все задачи ложатся на плечи клиентской стороны, то это будет не так уж и безопасно. Ну то есть, никто не помешает злоумышленнику стащить у вас API-ключ и подпортить вам жизнь. Это вполне реально, если данные не привязаны к аккаунту, будь то количество просмотров или лайки. Против такого Parse.com может противопоставить хранимые на их сервере функции (хотя это уже попахивает тем же полноценным бэкендом), испортить данные с помощью которых будет несколько сложнее. В общем, все это не супер-надежно при некоторых сценариях взаимодействия, это правда. И если вам нужно действительно что-то серьезное, BaaS я бы вам рекомендовать не стал. По крайней мере тот, который существует на нынешнем этапе развития технологий.</p>
<h2 id="-">Пробуем, интегрируемся, ловим грабли</h2>
<p>В роли подопытного кролика я выбрал проект <a href="http://spaceismine.org/">Space is Mine</a>, который не так давно перешел на статику. В качестве динамики я реализовал там счетчик просмотров на страницах статей. Вообще, с Parse.com все оказалось довольно просто и сложно одновременно. Я добавил себе инициализационную строчку в проект, написал сохранение объекта и встрял. Дело в том, что само собой мне хотелось бы иметь upsert, как это сделано, например, в MongoDB, с атомарным инкрементом. Проблема была в том, что в Parse.com не предусмотрена поддержка upsert. Это нонсенс! Бэкенд-сервис, который по сути-то должен экономить количество запросов, насколько это возможно (так как расположен довольно далеко от клиента), не позволяет делать upsert. Ну что ж, ладно, терпим.</p>
<p>На самом деле, в этот момент я задумался, поглядел вокруг повнимательнее, но так понял, что Parse.com – пожалуй, лучшее, что можно найти в этом сегменте (вспоминаем особую лояльность к бесплатным аккаунтам). Делать было нечего, пришлось немного разветвить и написать немного больше кода:</p>
<pre><code class="lang-js"><span class="hljs-keyword">var</span> ArticleObject = Parse.Object.extend(<span class="hljs-string">"Article"</span>);
<span class="hljs-keyword">var</span> path = <span class="hljs-string">'{{ this.path }}'</span>;
<span class="hljs-keyword">var</span> query = <span class="hljs-keyword">new</span> Parse.Query(ArticleObject);
query.equalTo(<span class="hljs-string">'code'</span>, path).first().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">object</span>) </span>{
    <span class="hljs-keyword">if</span> (!object) {
        <span class="hljs-keyword">var</span> articleObject = <span class="hljs-keyword">new</span> ArticleObject();
        articleObject.save({<span class="hljs-attr">code</span>: path, <span class="hljs-attr">viewsAmount</span>: <span class="hljs-number">1</span>});
    } <span class="hljs-keyword">else</span> {
        object.increment(<span class="hljs-string">'viewsAmount'</span>, <span class="hljs-number">1</span>);
        object.save();
    }
});
</code></pre>
<p>Здесь <code class="codespan-short">path</code> – полный путь до страницы, то есть что-то вроде своеобразного <code class="codespan-short">id</code>. Тем не менее, согласитесь, кода было добавлено совсем не много, и это здорово!</p>
<p>На деле браузеру потребуется сделать два запроса, один на получение записи, второй – на добавление/обновление. Ах да, передать свой собственный <code class="codespan-short">id</code> в Parse.com для сохранения новой записи нельзя. Поэтому даже такой хак не прошел. Тем не менее, как показала практика, эти два запроса выполняются довольно быстро и на клиентской стороне все выглядит довольно таки неплохо.</p>
<p>Итак, что мы имеем. Сайт, способный выдавать порядка 120 rps на железе за $10, подсчет просмотров и вообще все очень вкусно. При этом не стоит забывать, что подсчет просмотров – это самый банальный сценарий применения, само собой, можно сделать что-то и посложнее.</p>
<h2 id="-">Ссылки</h2>
<p>Пожалуй, доброй традицией будет приложить несколько ссылок для читателей, которые хотели бы посмотреть на эту тему поподробнее.</p>
<ol>
<li><a href="https://www.parse.com/apps/quickstart">Быстрый старт на Parse.com</a></li>
<li><a href="https://www.parse.com/docs/js/api/">Подробная JavaScript API документация Parse.com</a></li>
<li><a href="http://waracle.net/how-to-choose-the-right-backend-as-a-service-baas-platform/">Довольно свежая статья о выборе правильного BaaS</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2015/2015-05-24-dynamic-in-static-parse</link><guid isPermaLink="false">/lang/ru/posts/2015/2015-05-24-dynamic-in-static-parse.md</guid><pubDate>Sun, 24 May 2015 15:23:26 GMT</pubDate></item><item><title><![CDATA[Прикручиваем бесплатный китайский HTTPS на nginx]]></title><description><![CDATA[<p>Почему-то на днях взбрело мне в голову сделать HTTPS на своем старом-древнем проекте <a href="https://audiocoding.ru/">audiocoding.ru</a>. Не могу сказать, что он там хоть сколько-то необходим. Да он по сути там и не нужен. Но, несколько поводов для этого странного действия у меня все же было. Во-первых, я никогда не касался установки и настройки HTTPS, очень хотелось заполнить этот пробел. Во-вторых, являясь большим почитателем Mozilla, я прознал, что у них есть некоторые туманные <a href="http://www.opennet.ru/opennews/art.shtml?num=42142">планы по постепенному отказу от HTTP</a>. В общем, в любом случае, введение опционального HTTPS на моем информационном сайте хоть пользы бы и не принесло, но и вряд ли бы помешало. Таким образом, я решил поделиться своей небольшой историей. Попытаюсь не вдаваться в глубокие технические детали, а лучше приведу несколько ссылок в конце заметки.</p>
<h2 id="wosign-3-">WoSign бесплатный сертификат на 3 года</h2>
<p>Началось все с того, что я узнал про возможность получения бесплатного сертификата на <a href="https://buy.wosign.com/free/">WoSign</a> на 3 года. Меня это заинтересовало, потому что раньше я слышал только про платные и достаточно дорогие сертификаты. Пришлось столкнуться с трудностями в лице китайского языка, но за халяву можно было и потерпеть, правда? Какое-то время поковырявшись, я смог оставить заявку на получение. На следующий день мне на почту пришло письмо, что я могу его забирать. Пройдя по ссылке в письме, мне предоставили возможность загрузить сертификат в запароленном архиве (пароль вы вводите в момент создания заявки). В общем, все отлично сертификат был на руках.</p>
<h2 id="nginx-">nginx и небольшие подвохи</h2>
<p>Естественно нужно было загрузить сертификаты на сервер и что-то с ними сделать. Перечитав немного информации в Интернете, действия сложными не были. Прикладываю вырезки конфигурации nginx-сервера:</p>
<pre><code><span class="hljs-attr">http</span> <span class="hljs-string">{</span>
    <span class="hljs-attr">...</span>
<span class="hljs-comment">    ##</span>
<span class="hljs-comment">    # SSL Settings</span>
<span class="hljs-comment">    ##</span>
    <span class="hljs-attr">ssl_protocols</span> <span class="hljs-string">TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE</span>
    <span class="hljs-attr">ssl_prefer_server_ciphers</span> <span class="hljs-string">on;</span>
    <span class="hljs-attr">ssl_session_cache</span> <span class="hljs-string">shared:SSL:10m;</span>
    <span class="hljs-attr">ssl_session_timeout</span> <span class="hljs-string">10m;</span>
    <span class="hljs-attr">ssl_stapling</span> <span class="hljs-string">on;</span>
    <span class="hljs-attr">resolver</span> <span class="hljs-string">8.8.8.8 8.8.4.4;</span>
    <span class="hljs-attr">...</span>
    <span class="hljs-attr">server</span> <span class="hljs-string">{</span>
        <span class="hljs-attr">listen</span> <span class="hljs-string">80;</span>
        <span class="hljs-attr">listen</span> <span class="hljs-string">443 ssl;</span>
        <span class="hljs-attr">server_name</span> <span class="hljs-string">audiocoding.ru;</span>
        <span class="hljs-attr">ssl_certificate</span> <span class="hljs-string">path/to/cretificate.crt;</span>
        <span class="hljs-attr">ssl_certificate_key</span> <span class="hljs-string">path/to/key.key;</span>
<span class="hljs-comment">
        # Этот заголовок заставляет браузер использовать HTTPS принудительно в течении недели,</span>
<span class="hljs-comment">        # но чтобы это сработало, пользователь хоть раз должен зайти на HTTPS-версию сайта.</span>
        <span class="hljs-attr">add_header</span> <span class="hljs-string">Strict-Transport-Security 'max-age=604800';</span>
        <span class="hljs-attr">...</span>
    <span class="hljs-attr">}</span>
    <span class="hljs-attr">...</span>
<span class="hljs-attr">}</span>
</code></pre><p>Собственно, все казалось бы хорошо, <code class="codespan-short">configtest</code> показывает <code class="codespan-short">[ OK ]</code>, сервер рестартится успешно, но сайт не открывается. Я уже было начал ломать голову, как вдруг какой-то ответ на stackoverflow.com заставил меня сделать фейспалм. Дело в том, что в какие-то времена фанатичности я закрыл все порты в <code class="codespan-short">iptables</code>, кроме 80 и 22. Ну так, недолго думая, открыл еще и 443. Все заработало, сертификат всеми браузерами воспринимается как доверенный. Что ж, поживем – увидим. Быть может, позднее будут какие-то проблемы с Chrome. Китайский сертификат и Google все таки вещи слабосовместимые.</p>
<p><code class="codespan-short">add_header</code> я все же закомментарил после этих размышлений…</p>
<h2 id="-http-https">Производительность HTTP против HTTPS</h2>
<p>Ну конечно же, мне не могло не прийти в голову, что наверное HTTPS теперь будет медленнее HTTP. Решил прогнать свою главную страницу через <code class="codespan-short">siege</code>, команду выбрал достаточно простую:</p>
<pre><code>siege -<span class="hljs-selector-tag">b</span> -t10s -c50 http:<span class="hljs-comment">//audiocoding.ru/</span>
</code></pre><p>Для HTTPS аналогично.</p>
<p>Итак, тесты для HTTP показали:</p>
<pre><code>Transactions:                <span class="hljs-number">823</span> hits
Availability:             <span class="hljs-number">100.00</span> %
Elapsed time:               <span class="hljs-number">9.57</span> secs
Data transferred:          <span class="hljs-number">15.18</span> MB
Response time:              <span class="hljs-number">0.56</span> secs
Transaction rate:          <span class="hljs-number">86.00</span> trans/sec
Throughput:                 <span class="hljs-number">1.59</span> MB/sec
Concurrency:               <span class="hljs-number">48.56</span>
Successful transactions:     <span class="hljs-number">823</span>
Failed transactions:           <span class="hljs-number">0</span>
Longest transaction:        <span class="hljs-number">1.01</span>
Shortest transaction:       <span class="hljs-number">0.17</span>
</code></pre><p>Для HTTPS:</p>
<pre><code>Transactions:                <span class="hljs-number">622</span> hits
Availability:             <span class="hljs-number">100.00</span> %
Elapsed time:               <span class="hljs-number">9.68</span> secs
Data transferred:          <span class="hljs-number">11.47</span> MB
Response time:              <span class="hljs-number">0.75</span> secs
Transaction rate:          <span class="hljs-number">64.26</span> trans/sec
Throughput:                 <span class="hljs-number">1.19</span> MB/sec
Concurrency:               <span class="hljs-number">48.24</span>
Successful transactions:     <span class="hljs-number">622</span>
Failed transactions:           <span class="hljs-number">0</span>
Longest transaction:        <span class="hljs-number">1.26</span>
Shortest transaction:       <span class="hljs-number">0.29</span>
</code></pre><p>Таким образом, делаем вывод, что HTTPS замедлил работу сайта на 25%. Да, это на самом деле много в относительном значении. В абсолюнтых показателях, взяв кратчайшее время, 170 мс против 290 мс – разница вроде бы и не велика, но все равно ощутима. В общем-то отчасти еще и поэтому, поддержку HTTPS я оставлю опциональной.</p>
<p>Ах да, чуть не забыл, тесты выполнялись на виртуальной машине за $10 в облаке DigitalOcean (1GB RAM, 1 ядро CPU, 30GB SSD). Географически <code class="codespan-short">siege</code> запускался в Москве, сервер при этом находился в Амстердаме.</p>
<h2 id="-">Ссылки</h2>
<p>Как и обещал, прикладываю несколько тематических ссылок, чтобы вам было проще разобраться с материалом:</p>
<ol>
<li><a href="http://nginx.org/ru/docs/http/configuring_https_servers.html">Официальная страница по настройке HTTPS в nginx</a></li>
<li><a href="http://habrahabr.ru/post/195808/">Неплохая статья на хабре по той же теме</a></li>
<li><a href="http://habrahabr.ru/post/257207/">Статья на хабре о том, как осилить WoSign</a></li>
</ol>
]]></description><link>https://alexey.detr.us/posts/2015/2015-05-10-audiocoding-https</link><guid isPermaLink="false">/lang/ru/posts/2015/2015-05-10-audiocoding-https.md</guid><pubDate>Sun, 10 May 2015 14:05:49 GMT</pubDate></item><item><title><![CDATA[Статика с динамикой, как так вышло?]]></title><description><![CDATA[<p>Как я и хотел уже ранее поведать о том, почему и каким образом я перетащил audiocoding.ru на статический движок – так тому и быть. Статья будет представлять собой некое рассуждение о принципах создания контента для веба и их истории.</p>
<h2 id="-">Чуть философии</h2>
<p>Вряд ли я кого-то удивлю, что нынче в тренде всякие статические движки для блогов. А где блоги, там и простейшие сайты. Вообще, философски, я считаю, что все имеет спиралевидное развитие. И зачастую так и происходит, например, давайте обратимся к базам данных. Как все было раньше? Все писали в файлики, и всем было хорошо. Что произошло дальше? Дальше люди захотели работать с файлами немного умнее, чем просто с массивами данных. Появились реляционные базы данных. Да, сложные, да, мощные. Но затем реалии современного мира продиктовали свои условия. Объемы данных возросли настолько, что пришлось экономить на логичности представления данных и мы вновь вернулись к более простым структурам, вроде документоориентированных баз данных. Да, все улучшилось, все стало умнее, но просто замкнулся один из витков спирали.</p>
<h2 id="-">Вебсайты</h2>
<p>Что же происходило с вебом все это время? Вспомните, начиналось все с простых HTML-страниц, которые практически ничего не умели, которые были просто файлы, отдававшиеся серверами по HTTP-протоколу. Потом люди сказали “это не круто”. Ну нет, не совсем так конечно. Они сказали, что HTML почти ничего не умеет, а хотелось бы уметь хранить какие-то данные о пользователе, чтобы он мог логиниться и у него был свой аккаунт. Так появились динамические языки вроде PHP, заточенные под эти цели.</p>
<p>И вроде бы все шло хорошо, мощности компьютеров росли, и сайты на PHP начинали работать все лучше и лучше. Тем не менее, все это оставалось уделом избранных, которым не жалко было платить по 1000 и более рублей за сервер в месяц. Параллельно развивались и технологии, в частности виртуализация. С этим росло предложение приватных виртуальных серверов со стороны всяких сервисов, которые со временем буквально за копейки начали предлагать услуги по предоставлению слабых, но тем не менее полностью выделенных серверов (это не те сервера, когда вы сидите в разных папочках с другими людьми на одной ОС, про этот ужас я даже упоминать не хочу). Так появилось желание платить поменьше и при этом иметь свои сайты, которые быстро открываются. PHP здесь и прочие языки плохо подходили, так как требовали гораздо больше ресурсов, чем просто отдача HTML-файликов. В этот момент и начали активно развиваться движки для генерации статического контента (именно таким и сгенерирован этот сайт). Благодаря современным языкам и технологиям, внезапно стало удобно генерировать статику и раздавать ее с невероятной скоростью на слабых машинах.</p>
<h2 id="audiocoding-ru">Audiocoding.ru</h2>
<p>Думаю, уже примерно понятно, что сайт audiocoding.ru за свою вот уже почти 10-летнюю историю прошел примерно такой путь, как и было рассказано выше. Да, кстати, летом будет юбилей, и мы обязательно с вами это отметим. Теперь <a href="http://audiocoding.ru/">audiocoding.ru</a> представляет собой статически сгенерированные HTML-страницы. Но, как показала практика, этого оказалось недостаточно. Например, раньше у меня был поиск по сайту и довольно богатый файловый архив, который хотелось бы представить в новом представлении, более удобном, более быстром. Что же было решено сделать в этом месте? Это было с одной стороны странное и неожиданное решение. Я предпочел оставить статику, добавив при этом динамику на стороне браузера. Иными словами, весь поиск выполняется без участия серверной стороны, на браузере. Возможным это стало благодаря развитию JavaScript в браузерах. Сказать, что за все это время он стал быстрым – ничего не сказать. Он стал просто молниеносным! Можете сами <a href="http://audiocoding.ru/files/">попробовать</a>, поиск выполняется быстро даже на мобильных устройствах. И да, вот так все внезапно преображается. То, что раньше казалось полным бредом, преобретает вполне адекватные очертания.</p>
<h2 id="-">Заключение</h2>
<p>Таким образом, мы опять находимся на очередном витке все той же спирали. Для веба этот виток прошел за 10 лет, именно столько времени понадобилось для возврата к истокам, для того, чтобы снова начать использовать HTML. Нет, конечно я прекрасно понимаю, что область применения динамических сайтов на стороне сервера никуда не делась. Просто частично мы вернулись к тому, с чего начинали, но в новой реинкарнации. Мы частично вернулись, просто все стало немного лучше.</p>
<p>Что ж, на этом я хотел бы попрощаться с вами до будущих встреч. И я заранее прошу прощения, если вам сегодняшняя заметка показалась несколько сумбурной, но буду очень рад, если мне удалось донести свое видение происходящего вокруг нас.</p>
]]></description><link>https://alexey.detr.us/posts/2015/2015-04-17-static-vs-dynamic</link><guid isPermaLink="false">/lang/ru/posts/2015/2015-04-17-static-vs-dynamic.md</guid><pubDate>Fri, 17 Apr 2015 23:34:52 GMT</pubDate></item><item><title><![CDATA[Медиа-клавиши, Alfred и OS X]]></title><description><![CDATA[<p>Сегодня будет не очень программистская тема, но все же весьма и весьма гиковая.</p>
<p>Скажите, вы любите слушать музыку? Я вот очень, я слушаю ее весьма часто, если не сказать постоянно. В разные моменты своего жизненного пути я пользовался разными сервисами. Пока что я остановился на <a href="https://music.yandex.ru/">Яндекс.Музыке</a> и довольно часто мне бывает нужно поставить ее на паузу или переключиться на следующий трек. На клавиатуре красуются клавиши “Previous”, “Play/Pause” и “Next”, но использовать мы не их можем. Ах, беда, печаль, огорчение. Так, куда это меня понесло… Давайте попорядку.</p>
<h2 id="alfred">Alfred</h2>
<p>Как правило, современные сервисы не имеют нативных клиентов и плохо дружат с системными фишками. Все они работают в браузерах, и это немного усложняет задачу. Сразу обмолвлюсь, что для таких целей я использую браузер <strong>Safari</strong>, он является пожалуй наиболее быстрым и ресурсоэкономным браузером для системы <strong>OS X</strong>. Собственно, именно в нем мы и будем учиться эмулировать нажатия на элементы интерфейса, отвечающие за остановку воспроизведения и переключение треков. Итак, как можно выполнять что-то “свое” в браузере, да при этом повесив это “свое” на горячие клавиши? Тут сразу на ум приходит прекрасный инструмент автоматизации <strong>Alfred</strong>, в некотором роде являющийся стандартом де факто для такого рода задач.</p>
<p>Какое-то время назад я написал для <strong>Alfred</strong> свой workflow, который позволяет взаимодействовать с <strong>Яндекс.Музыкой</strong> в браузере <strong>Safari</strong>. Я не буду скромничать и порекомендую вам воспользоваться именно им: <a href="https://github.com/alexey-detr/yandex-music-hotkeys">https://github.com/alexey-detr/yandex-music-hotkeys</a>.</p>
<p>Установив его в свой <strong>Alfred</strong> (должен сразу предупредить, что для этого нужна именно платная версия) и, попробовав навесить горячие клавиши на основные действия, вы столкнетесь с небольшой неприятностью. Дело в том, что <strong>Alfred</strong> не умеет вешать медиа-клавиши на действия.</p>
<p>Здравой идеей было бы предположить, что медиа-клавиши являются некими системными клавишами и для их “отлова” у <strong>Alfred</strong> просто не хватает привелегий. Ну не очень верится, что разработчик <strong>Alfred</strong> за столь долгий срок разработки ни разу не догадался, что медиа-клавиши тоже можно было бы навешивать на действия.</p>
<p>Что ж, давайте немного забежим вперед и навесим на действия комбинации, которые ну точно никогда и нигде не будут использованы (позднее я объясню, зачем так надо). Пусть это будет что-то вроде:</p>
<pre><code>Shift + Control + Alt + Command + <span class="hljs-function"><span class="hljs-params">F7</span> =&gt;</span> Previous track
Shift + Control + Alt + Command + <span class="hljs-function"><span class="hljs-params">F8</span> =&gt;</span> Play/pause
Shift + Control + Alt + Command + <span class="hljs-function"><span class="hljs-params">F9</span> =&gt;</span> Next track
</code></pre><p>Итак, ладно, у нас есть теперь такие извращенные комбинации на действия в <strong>Alfred</strong>. Но хотелось бы как-то изловить именно медиа-клавиши. Далее повествование переключается на довольно низкоуровневый инструмент, который умеет переназначать клавиши и называется он…</p>
<h2 id="karabiner">Karabiner</h2>
<p>Итак, <strong>Karabiner</strong> можно скачать и установить с официального сайта <a href="https://pqrs.org/osx/karabiner/">https://pqrs.org/osx/karabiner/</a>. Не буду вас долго мучать и объяснять его конфигурирование, тем более вы и сами можете почитать <a href="https://pqrs.org/osx/karabiner/xml.html.en">мануалы к нему</a>. Сразу открою все карты:</p>
<p>{% highlight xml %}
&lt;?xml version=”1.0”?&gt;</p>
<p><root>
  <item>
    <name>‘Media Prev,Play,Pause’ to ‘All modifiers + F7,F8,F9’</name>
    <identifier>private.modify_media</identifier>
    <autogen>
      <strong>KeyToKey</strong>
      ConsumerKeyCode::MUSIC_PREV,
      KeyCode::F7, ModifierFlag::CONTROL_L | ModifierFlag::SHIFT_L | ModifierFlag::OPTION_L | ModifierFlag::COMMAND_L
    </autogen>
    <autogen>
      <strong>KeyToKey</strong>
      ConsumerKeyCode::MUSIC_PLAY,
      KeyCode::F8, ModifierFlag::CONTROL_L | ModifierFlag::SHIFT_L | ModifierFlag::OPTION_L | ModifierFlag::COMMAND_L
    </autogen>
    <autogen>
      <strong>KeyToKey</strong>
      ConsumerKeyCode::MUSIC_NEXT,
      KeyCode::F9, ModifierFlag::CONTROL_L | ModifierFlag::SHIFT_L | ModifierFlag::OPTION_L | ModifierFlag::COMMAND_L
    </autogen>
  </item>
</root>
{% endhighlight %}</p>
<p>Что мы здесь можем видеть? А здесь как раз и есть заветное переназначение медиа-клавиш на комбинации, которые мы навесили в <strong>Alfred</strong> на действия.</p>
<p>Собственно, вы можете включить это у себя, скопировав узел <code class="codespan-short">&lt;item&gt;</code> в свой файл <code class="codespan-short">private.xml</code>. Последний вы можете обнаружить в настройках во вкладке <code class="codespan-short">Misc &amp; Uninstall</code> в пункте <code class="codespan-short">Custom setting</code>. После этого важно будет зайти на вкладку <code class="codespan-short">Change Key</code> и нажать <code class="codespan-short">Reload XML</code>, затем у вас в списке <code class="codespan-short">remapping</code> появится пункт <code class="codespan-long">&#39;Media Prev,Play,Pause&#39; to &#39;All modifiers + F7,F8,F9&#39;</code>, смело ставим напротив него галочку.</p>
<h2 id="-">Что же дальше?</h2>
<p>А вы знаете, это все. Теперь ваши медиа-клавиши будут эмулировать те, с которыми может работать <strong>Alfred</strong>, а он в свою очередь уже будет выполнять действия в <strong>Safari</strong>, таким вот немного костыльным образом можно заставить работать медиа-клавиши в <strong>Яндекс.Музыке</strong>.</p>
<p><strong>P.S.</strong> А столь страшные комбинации были использованы как раз для того, чтобы избежать пересечения с какой-либо другой программой, сделав вид, что медиа-клавиши работают напрямую.</p>
]]></description><link>https://alexey.detr.us/posts/2015/2015-04-16-media-keys-os-x</link><guid isPermaLink="false">/lang/ru/posts/2015/2015-04-16-media-keys-os-x.md</guid><pubDate>Thu, 16 Apr 2015 22:04:16 GMT</pubDate></item><item><title><![CDATA[Python и ошибка по глупости]]></title><description><![CDATA[<p>Хотелось бы написать небольшую заметку о том, что отъело у меня сегодня около получаса времени в моем OS X окружении с Python, поставленным через <a href="http://brew.sh/">brew</a>.</p>
<p>В последнее время я периодически разбираюсь с Ansible, и руки дошли до написания модуля для процессного менеджера <a href="https://github.com/Unitech/pm2">PM2</a>. Нужны были максимально мягкие старт- и стоп-действия для рабочего node.js приложения, реализующего HTTP API. Я решил отслеживать отзывчивость API с помощью довольно популярной библиотеки для Python <a href="http://docs.python-requests.org/en/latest/">requests</a>. Все казалось бы замечательно, но при тестировании модуля у меня постоянно вылетала эта ошибка:</p>
<pre><code>Traceback (most recent <span class="hljs-keyword">call</span> <span class="hljs-keyword">last</span>):
  <span class="hljs-keyword">File</span> <span class="hljs-string">"/Users/alexey_detr/.ansible_module_generated"</span>, line <span class="hljs-number">4</span>, <span class="hljs-keyword">in</span> &lt;<span class="hljs-keyword">module</span>&gt;
    <span class="hljs-keyword">import</span> requests
ImportError: <span class="hljs-keyword">No</span> <span class="hljs-keyword">module</span> named requests
</code></pre><p>Несмотря на все это, пакет <code class="codespan-short">requests</code> у меня был установлен:</p>
<pre><code>$ pip install requests
Requirement already satisfied (use --upgrade to upgrade): requests in /usr/local/<span class="hljs-class"><span class="hljs-keyword">lib</span>/<span class="hljs-title">python2</span>.7/<span class="hljs-title">site</span>-<span class="hljs-title">packages</span></span>
</code></pre><p>Причина крылась в следующем: мой <code class="codespan-short">.py</code> файл Ansible-модуля содержал в первой строке путь до интерпретатора <code class="codespan-short">#!/usr/bin/python</code> (стандартный Python в поставке OS X)</p>
<pre><code>$ /usr/bin/python -V
Python <span class="hljs-number">2.7</span><span class="hljs-number">.6</span>
</code></pre><p>А <code class="codespan-short">pip</code> в свою очередь ставил модули для интерпретатора из <code class="codespan-short">#!/usr/local/bin/python</code> (это уже brew-версия)</p>
<pre><code>$ /usr/local/bin/python -V
Python <span class="hljs-number">2.7</span><span class="hljs-number">.9</span>
</code></pre><p>Таким образом понадобилось всего лишь поменять интерпретатор в <code class="codespan-short">.py</code> файле на тот, что был установлен с brew и все стало хорошо. Мораль: быть более внимательным.</p>
]]></description><link>https://alexey.detr.us/posts/2015/2015-02-17-python-requests</link><guid isPermaLink="false">/lang/ru/posts/2015/2015-02-17-python-requests.md</guid><pubDate>Tue, 17 Feb 2015 22:22:00 GMT</pubDate></item><item><title><![CDATA[Трогаем Docker в OS X]]></title><description><![CDATA[<p>Появилось у меня немного свободного времени и я решил начать писать блог о всяких пробах различных технологий.</p>
<p>Заметка будет о контейнеризации и о том, как начать с ней работать на OS X.</p>
<p>Начну сначала. Итак, что-то дернуло меня попробовать Haskell, но, прежде чем его пробовать, я подумал, что наверное плохо будет захламлять систему всякими платформами, компиляторами и прочим. Тут, возможно, я не прав, и ничего там не захламляется, но тем не менее, появилось желание начать свои эксперименты именно в контейнере.</p>
<h2 id="-docker">Установка Docker</h2>
<p>Так как я люблю brew, устанавливал я через него.</p>
<pre><code><span class="hljs-keyword">brew </span><span class="hljs-keyword">install </span>docker
<span class="hljs-keyword">brew </span><span class="hljs-keyword">install </span><span class="hljs-keyword">boot2docker</span>
</code></pre><p>Вообще, сначала я не сразу понял как работает Docker у себя в потрохах, поэтому осознание, что нужен еще и boot2docker пришло далеко не сразу.</p>
<p>В двух словах, Docker – это тоже виртуализация, но контейнерная. Она легче, чем традиционные VM (гипервизоры), в которых эмулируется все, от процессора до памяти.</p>
<p>Имеем следующее, <code class="codespan-short">boot2docker</code> – это виртуальная машина, под которой работают контейнеры, а <code class="codespan-short">docker</code> – это клиент для нее.</p>
<p>Инициализируем и запускаем <code class="codespan-short">boot2docker</code>:</p>
<pre><code><span class="hljs-keyword">boot2docker </span>init
<span class="hljs-keyword">boot2docker </span>up
$(<span class="hljs-keyword">boot2docker </span>shellinit)
</code></pre><p>В последней строке <code class="codespan-short">boot2docker</code> установит переменные окружения, необходимые для работы docker. В принципе, можно вполне подсмотреть эти переменные (с префиксом <code class="codespan-short">DOCKER_</code>) командой <code class="codespan-short">env</code> и добавить их в какой-нибудь <code class="codespan-short">.zshrc</code> или в <code class="codespan-short">.bashrc</code>, чтобы не запускать <code class="codespan-short">$(boot2docker shellinit)</code> для каждой открытой оболочки. У меня в <code class="codespan-short">.zshrc</code> эти строки выглядят так:</p>
<pre><code><span class="hljs-builtin-name">export</span> <span class="hljs-attribute">DOCKER_HOST</span>=tcp://192.168.59.103:2376
<span class="hljs-builtin-name">export</span> <span class="hljs-attribute">DOCKER_CERT_PATH</span>=/Users/alexey_detr/.boot2docker/certs/boot2docker-vm
<span class="hljs-builtin-name">export</span> <span class="hljs-attribute">DOCKER_TLS_VERIFY</span>=1
</code></pre><p>Небольшая справка: контейнерами называются непосредственно запущенные инстансы. А образами – отправная точная для старта контейнера.</p>
<h2 id="-ubuntu">Запускаем и пользуемся Ubuntu</h2>
<p>Чтобы таки пощупать что-то в контейнере, давайте возьмем Ubuntu. Это официальный готовый образ с Ubuntu. Чтобы его установить и запустить, делаем следующее</p>
<pre><code>docker <span class="hljs-built_in">run</span> -<span class="hljs-keyword">it</span> ubuntu
</code></pre><p>Тут придется немного подождать, когда контейнер скачается и будет готов к использованию. Когда процедура завершится, вы попадете в терминал докеризированной системы, там можно выполнить например <code class="codespan-long">apt-get update &amp;&amp; apt-get install mc</code>.</p>
<p>Тут нужно иметь ввиду, что состояние контейнера само по себе не сохранится. Как только вы выйдете из него командой <code class="codespan-short">exit</code>, его изменения потеряются. Чтобы сохранить изменение контейнера, нужно сделать следующее. Для начала смотрим список текущих запущенных контейнеров командой</p>
<pre><code><span class="hljs-attribute">docker ps</span>
</code></pre><p>Находим наш контейнер, смотрим его NAME и выполняем следующую команду:</p>
<pre><code>docker <span class="hljs-keyword">commit</span> &lt;<span class="hljs-type">name</span>&gt;
</code></pre><p>Таким образом у нас сохранится новый готовый образ Ubuntu с <code class="codespan-short">mc</code> для запуска под Docker в любом окружении. Далее этот образ можно довольно просто поднимать с сохраненной отметки, достаточно открыть список образов</p>
<pre><code><span class="hljs-attribute">docker images</span>
</code></pre><p>найти в нем необходимый образ и воспользоваться например IMAGE ID (намеренно не стал рассказывать о repository или tag, чтобы сократить объем заметки) для запуска</p>
<pre><code>docker <span class="hljs-built_in">run</span> -<span class="hljs-keyword">it</span> <span class="hljs-number">01</span>dbf62fda7b
</code></pre><p>В этом случае вы уже обнаружите установленный <code class="codespan-short">mc</code>.</p>
<p>Рассматривать то, как переносить образы и вдаваться в другие детали, я в этой заметке не планировал, поэтому на этом закончу. В конце-то концов основной целью было опробовать Haskell.</p>
]]></description><link>https://alexey.detr.us/posts/2015/2015-02-11-first-time-docker</link><guid isPermaLink="false">/lang/ru/posts/2015/2015-02-11-first-time-docker.md</guid><pubDate>Wed, 11 Feb 2015 13:40:28 GMT</pubDate></item><item><title><![CDATA[Создание базы данных и пользователя СУБД MySQL]]></title><description><![CDATA[<p>Давно меня здесь не было. Вряд ли я поделюсь с читателем сегодня чем-то новым. Это просто короткая заметка, как создать БД и пользователя в MySQL из консоли.</p>
<p>Создание новой БД (в данном случае <code class="codespan-short">amarok</code>) с кодировкой UTF-8, создание нового пользователя и выдача ему новых прав на базу данных делается всего в 4 команды:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> amarokdb <span class="hljs-built_in">CHARACTER</span> <span class="hljs-keyword">SET</span> utf8 <span class="hljs-keyword">COLLATE</span> utf8_unicode_ci;
<span class="hljs-keyword">GRANT</span> <span class="hljs-keyword">USAGE</span> <span class="hljs-keyword">ON</span> *.* <span class="hljs-keyword">TO</span> amarokuser@localhost <span class="hljs-keyword">IDENTIFIED</span> <span class="hljs-keyword">BY</span> <span class="hljs-string">'amarokpasswd'</span>;
<span class="hljs-keyword">GRANT</span> ALL <span class="hljs-keyword">PRIVILEGES</span> <span class="hljs-keyword">ON</span> amarokdb.* <span class="hljs-keyword">TO</span> amarokuser@localhost;
<span class="hljs-keyword">FLUSH</span> <span class="hljs-keyword">PRIVILEGES</span>;
</code></pre>
<p>Надеюсь, что благодаря этой заметке у вас не возникнет больше проблем с развертыванием новых проектов из консоли. Приятного кодинга!</p>
]]></description><link>https://alexey.detr.us/posts/2012/2012-09-01-mysql-new-db-and-user</link><guid isPermaLink="false">/lang/ru/posts/2012/2012-09-01-mysql-new-db-and-user.md</guid><pubDate>Sat, 01 Sep 2012 22:21:00 GMT</pubDate></item><item><title><![CDATA[В преддверии 2012]]></title><description><![CDATA[<p>Итак, подходит к концу 2011 год. Не хотелось бы просто брать и закрывать молча эту страницу. Уже весьма скоро мы все соберемся за столами семьями, поднимем бокалы шампанского, выслушаем нашего гаранта конституции и начнется новая итерация. Хочется взглянуть на итоги того, что же принес этот год. Во-первых, лично мне он принес огромное количество опыта и хорошую работу. Благодаря опыту, ко многим вещам при разработке я стал относиться куда более серьезнее и осмысленнее. Скорость изучения новых технологий подскочила на новый уровень. В следующем году буду стремиться продолжать расширять свои познания.</p>
<p>В этом году я освоил следующие программные / серверные / девелоперские вещи: PhpStorm, Membase, MongoDB, Gearman, nginx, реализовал свой первый RESTful API, начал использовать различные веб-сервисы Amazon (чудные облачные технологии, должен признаться), попробовал разработку под Android… Что ж, перечислять можно долго, но это уже весьма малозначимые вещи.</p>
<p>Что же случилось с миром веба в 2011 году? Постараюсь бегло взглянуть на события уходящего года.</p>
<p>Вспоминается начало 2011. Не знаю, кто как, но я почему-то ждал тогда IE9. Надежда умирает последней? Да, оказалось так. Да, он стал лучше, да, он стал чертовски быстрым, но некоторые проблемы как были, так и остались. Ныне ходят слухи, да и не только слухи про IE10. Любой желающий уже может взглянуть на превью версию. Но как-то верится в него с трудом, если честно.</p>
<p>Mozilla перевела свои продукты Firefox и Thunderbird на крайне короткие итерации разработки (как я понял, чуть больше месяца), последовав примеру Google. Теперь как у Firefox, так и у Thunderbird версии обновляются синхронно. К тому же Firefox теперь обновляется довольно тихо и обыватель этого даже и не замечает. Считаю этот шаг для Mozilla крайне правильным. Так браузер будет развиваться динамичнее и различные новые фишки будут доходить до пользователя куда быстрее, пусть и маленькими порциями. Хочется также отметить, что Mozilla, как бы то ни было парадоксально, продолжает жить за счет своего главного конкурента на браузерном рынке Google. Не так давно был продлен весьма дорогостоящий контракт на то, что в Firefox поиском по умолчанию будет оставаться Google.</p>
<p>Opera Labs продолжают экспериментировать с различными новейшими технологиями, которые позволяет стандарт HTML5. Если мне не изменяет память, то недавно они выпустили первую 64-битную версию браузера. Хочется пожелать команде Opera великих свершений в их ожиданиях с экспериментами.</p>
<p>Chrome. Не могу говорить не предвзято об этом браузере лишь потому, что я его недолюбливаю. Но попробую. В 2011 этот браузер как всегда продолжает радовать своих пользователей скоростью. Ближе к концу года он даже обогнал Firefox по популярности. Теперь это самый популярный браузер в мире. Что в нем стало серьезно лучше? Хм, навскидку и не скажешь. Хотя да, в нем появились приложения (так называемые <a href="https://developer.chrome.com/apps/about_apps">hosted apps</a>). Но пожалуй это все.</p>
<p>А что Safari? Наверное, это один из самых консервативных браузеров в этом году. Честно говоря, Safari обновляю на своем десктопе регулярно, но каких-либо серьезных изменений я не заметил. Хотя наверняка Apple продолжает обновлять внутренний движок Webkit до последней версии перед каждым релизом Safari. И это не может не радовать.</p>
<p>PHP 5.4? Пожалуй вторая вещь которую я очень ждал и которую так и не дождался. Будем надеяться. В конце концов, 25 декабря уже вышла RC4, так что ждать осталось совсем недолго. Нам обещают еще больше производительности, mb_string вшитый по умолчанию, <a href="http://dron.by/post/vvedenie-v-traits.html">traits</a> (aka mixins) и много других здоровских мелочей.</p>
<p>Очень многое еще хочется написать, но уже день 31-го числа и просто физически не остается времени на это. На этом все.</p>
<p>Желаю всем счастливого Нового Года!</p>
]]></description><link>https://alexey.detr.us/posts/2011/2011-12-29-almost-2012</link><guid isPermaLink="false">/lang/ru/posts/2011/2011-12-29-almost-2012.md</guid><pubDate>Thu, 29 Dec 2011 20:54:00 GMT</pubDate></item><item><title><![CDATA[Standard PHP Library (SPL) Exceptions]]></title><description><![CDATA[<p>Как известно, в PHP помимо стандартного класса Exception имеются также и унаследованные от них (собственно SPL-исключения). Иерархию наследования можно <a href="http://php.net/manual/en/spl.exceptions.php">найти на официальном мануале</a>. Всем PHP-кодерам советую с ней ознакомиться:</p>
<pre><code class="lang-txt">Exception
    LogicException
        BadFunctionCallException
            BadMethodCallException
        DomainException
        InvalidArgumentException
        LengthException
        OutOfRangeException
    RuntimeException
        OutOfBoundsException
        OverflowException
        RangeException
        UnderflowException
        UnexpectedValueException
</code></pre>
<p>Кидайте SPL-исключения, наиболее подходящие по контексту причины потенциальной ошибки или неверных входных данных. Этим вы помогаете людям, которые будут в будущем разбираться в вашем коде или использовать его.</p>
]]></description><link>https://alexey.detr.us/posts/2011/2011-05-23-spl-exceptions</link><guid isPermaLink="false">/lang/ru/posts/2011/2011-05-23-spl-exceptions.md</guid><pubDate>Mon, 23 May 2011 08:48:00 GMT</pubDate></item><item><title><![CDATA[Проблема времени Windows / Ubuntu на одном компьютере]]></title><description><![CDATA[<p>Я думаю, если у вас на разных [разделах] жесткого диска установлены Windows и Ubuntu, вы сталкивались с проблемой, что Ubuntu интерпретирует время BIOS как UTC и учитывает ваш часовой пояс, тем самым выставляя в BIOS время как UTC и отображая его правильно (+7 часов для Новосибирска). Windows, в свою очередь интерпретирует время BIOS’а как локальное и не “мучается” со всякими UTC.</p>
<p>Проблема возникает в случае, когда вы загружаете Windows после Ubuntu. Время получается смещенным на 7 часов назад. Это очень раздражает, не так ли? Очень долго я жил с этим.</p>
<p>Но, оказалось, от этого естественно есть лекарство. Лезем в <code class="codespan-short">/etc/default/rcS</code> и меняем опцию <code class="codespan-short">UTC</code> на <code class="codespan-short">no</code>. Это решает все проблемы, описанные выше.</p>
<p>Приятного всем кодинга на выходных дома! :)</p>
]]></description><link>https://alexey.detr.us/posts/2011/2011-05-15-windows-ubuntu-time</link><guid isPermaLink="false">/lang/ru/posts/2011/2011-05-15-windows-ubuntu-time.md</guid><pubDate>Sun, 15 May 2011 16:36:00 GMT</pubDate></item><item><title><![CDATA[Форматирование JSON]]></title><description><![CDATA[<p>Сейчас работаю над проектом, в котором клиентские JavaScripts получают много JSON ответов от сервера. Я же занимаюсь разработкой back-end PHP скриптов и чтобы упростить жизнь своим коллегам по разбору JSON в браузерных консолях (будь то хоть Firebug, хоть Chrome), мне захотелось отдавать в debug режиме отформатированный читабельные JSON данные. Погуглив на эту тему, я сразу же <a href="http://www.daveperrett.com/articles/2008/03/11/format-json-with-php/">натолкнулся на необходимую функцию</a>, которой и хочу с вами сегодня поделиться:</p>
<pre><code class="lang-php"><span class="hljs-comment">/**
 * Indents a flat JSON string to make it more human-readable.
 * <span class="hljs-doctag">@param</span> string $json The original JSON string to process.
 * <span class="hljs-doctag">@return</span> string Indented version of the original JSON string.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">indent</span><span class="hljs-params">($json)</span>
</span>{

    $result = <span class="hljs-string">''</span>;
    $pos = <span class="hljs-number">0</span>;
    $strLen = strlen($json);
    $indentStr = <span class="hljs-string">'  '</span>;
    $newLine = <span class="hljs-string">"\n"</span>;
    $prevChar = <span class="hljs-string">''</span>;
    $outOfQuotes = <span class="hljs-keyword">true</span>;

    <span class="hljs-keyword">for</span> ($i = <span class="hljs-number">0</span>; $i &lt;= $strLen; $i++) {

        <span class="hljs-comment">// Grab the next character in the string.</span>
        $char = substr($json, $i, <span class="hljs-number">1</span>);

        <span class="hljs-comment">// Are we inside a quoted string?</span>
        <span class="hljs-keyword">if</span> ($char == <span class="hljs-string">'"'</span> &amp;&amp; $prevChar != <span class="hljs-string">'\\'</span>) {
            $outOfQuotes = !$outOfQuotes;

            <span class="hljs-comment">// If this character is the end of an element,</span>
            <span class="hljs-comment">// output a new line and indent the next line.</span>
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (($char == <span class="hljs-string">'}'</span> || $char == <span class="hljs-string">']'</span>) &amp;&amp; $outOfQuotes) {
            $result .= $newLine;
            $pos--;
            <span class="hljs-keyword">for</span> ($j = <span class="hljs-number">0</span>; $j &lt; $pos; $j++) {
                $result .= $indentStr;
            }
        }

        <span class="hljs-comment">// Add the character to the result string.</span>
        $result .= $char;

        <span class="hljs-comment">// If the last character was the beginning of an element,</span>
        <span class="hljs-comment">// output a new line and indent the next line.</span>
        <span class="hljs-keyword">if</span> (($char == <span class="hljs-string">','</span> || $char == <span class="hljs-string">'{'</span> || $char == <span class="hljs-string">'['</span>) &amp;&amp; $outOfQuotes) {
            $result .= $newLine;
            <span class="hljs-keyword">if</span> ($char == <span class="hljs-string">'{'</span> || $char == <span class="hljs-string">'['</span>) {
                $pos++;
            }

            <span class="hljs-keyword">for</span> ($j = <span class="hljs-number">0</span>; $j &lt; $pos; $j++) {
                $result .= $indentStr;
            }
        }

        $prevChar = $char;
    }

    <span class="hljs-keyword">return</span> $result;
}
</code></pre>
<p>Эта функция сразу же была внедрена в проект и теперь коллегам разбирать JSON куда более удобно. :)</p>
]]></description><link>https://alexey.detr.us/posts/2011/2011-03-04-json-indentation</link><guid isPermaLink="false">/lang/ru/posts/2011/2011-03-04-json-indentation.md</guid><pubDate>Fri, 04 Mar 2011 20:36:00 GMT</pubDate></item></channel></rss>