/dev/dragon https://ast.rocks Using Letsencrypt with Docker /blog/letsencrypt-docker <p>Who needs all those paid certificate providers while we have such a brilliant project as Letsencrypt? It allows a domain owner to obtain a valid certificate for his domain in a matter of seconds without the boring stuff like generating CSR, making DNS verification records or even passing documents to the certificate issuer center. However, paid certificates most often have an expiration time of a year. And free Letsencrypt certificates live only for a couple of months. But it's definitely not a huge problem if there is a way to automate the renewal process. And there it is.</p> <p>Let's assume we are trying to generate a certificate for the `awesome-domain.com`with nginx running in `project_nginx_1` container.</p> <p>To keep nginx running while letsencrypt verifies the domain, we need to serve verification files using it. To do so, add the following lines to nginx config:</p> <pre class="language-bash"><code>location /.well-known/ { root /var/www/awesome-domain.com; } </code></pre> <p>To renew certificate use the following command:</p> <pre class="language-bash"><code>#!/bin/sh mkdir -p /var/www/awesome-domain.com docker pull certbot/certbot docker run -it --rm --name letsencrypt \ # mount letsencrypt main directories to letsencrypt container -v "/etc/letsencrypt:/etc/letsencrypt" \ -v "/var/lib/letsencrypt:/var/lib/letsencrypt" \ --volumes-from project_nginx_1 \ certbot/certbot \ certonly \ --webroot \ --webroot-path /var/www/awesome-domain.com \ --agree-tos \ --renew-by-default \ -d awesome-domain.com \ --email support@awesome-domain.com # restart nginx container to use new certs (make sure it's configured to restart!) docker kill --signal=HUP project_nginx_1 </code></pre> <p>Make sure nginx has access to required directories:</p> <pre class="language-yml"><code>nginx: restart: unless-stopped image: nginx:1.13-alpine depends_on: - backend volumes: - /var/www:/var/www - /etc/letsencrypt:/etc/letsencrypt - ./nginx.conf:/etc/nginx/conf.d/default.conf ports: - "" - "" </code></pre> <p>That's it! Now you can use certificates from `/etc/letsencrypt/live/awesome-domain.com/` directly. Just put the renewal script to cron and enjoy your secure experience.</p> 2017 /blog/year-2017 <p>The 2017 year was good for me. It's the second year in a line where I fulfilled all my new year resolutions, which is inspiring. That's why I decided to write a bit about it. The year totally earned it :)</p> <h2>The wedding</h2> <p> <img src="/files/blog/92/wedding.jpg" alt="The Wedding" title="The Wedding" class="img-responsive" /> </p> <p> I'm a married man now. I have such a beautiful wife! (and still can't fully believe that). But I don't see any changes inside me: I think I married her in my mind long before that, but it's great to have a real ceremony and to eat a real cake! :) The day before the wedding I fell from my bike and got a severe knee injury. I'll never forget dancing the waltz all in bandages like a mummy, with terrible pain and only one leg working fine, but it was worth all the efforts. </p> <h2>Travels</h2> <p> This year was great for trips too. </p> <p> <img src="/files/blog/92/prague.jpg" alt="Prague" title="Prague" class="img-responsive" /> </p> <p> I was in Prague, meeting my new colleagues and drinking an enormous amount of Czech beer with them. Night Prague view from the high point was stunningly beautiful, but meeting such great people alive was far much an incredible experience. </p> <p> <img src="/files/blog/92/usa.jpg" alt="USA" title="USA" class="img-responsive" /> </p> <p> I visited the USA the second time; we made a colossal car journey with my wife and good friends through a variety of national parks and forests. Nature was fantastic and very mind refreshing. Never thought I would see my wallpaper view with my own eyes :) Americans did a great job preserving all these great riches from destruction. Also visited my dear friends Vasily and Emily in their new home in Colorado. Keep prospering, guys! (and visit us in Moscow one day!) </p> <p> <img src="/files/blog/92/italy.jpg" alt="Italy" title="Italy" class="img-responsive" /> </p> <p> We made a winter trip to Italy, riding Segways all around the Rome, eating pizza and walking along the picturesque Tiber river. We made a one day raid to Napoli, visiting Pompei. </p> <h2>Sports</h2> <p> <img src="/files/blog/92/sports.jpg" alt="Sports" title="Sports" class="img-responsive" /> </p> <p> Despite the knee injury in August, I reached my sports goal: 10 squats with 150 kg barbell on my shoulders, precisely at the final training of the year. I need to set a new goal asap :) do you have any ideas? </p> <h2>Work and home</h2> <p> <img src="/files/blog/92/home-office.jpg" alt="Home Office" title="Home Office" class="img-responsive" /> </p> <p> In February we moved into a new shiny apartment. It's such a fantastic thing to have more than one room in a flat. I finally was able to make a home office from the smaller room, where I can work without any distractions on my projects. It's also the first year I'm working as individual entrepreneur and entirely remote. </p> Books /blog/books <p> Books were hard to get at a time when I was a kid. They were expensive and, moreover, there was a complete lack of them. Many people owned no books at all, and some other copied them manually (by hand). But there always were a lot of books in our family, and it was shocking to me back then — to see no books at all in a room. </p> <p> Not all of the books from our family collection were good. Most of them were pretty cheap detectives and other silly novels from barely known authors. The only thing they were good at is the creation of the warm relationships between me and the book species. But among this mess, there were a lot of hidden treasures. </p> <p> Once I spotted that my father left the book he was reading at that time, on the table. I, for some reasons, thought that I'm not allowed to read or even touch adult books as I was a nothing but the little child. No one was near, and I dared to take it and read it, secretly. It was «Lord Valentine's Castle» by Robert Silverberg. </p> <p> To my astonishment, it was not too different by its nature from what I've read before. It was way better than all these child books, so I fell in love with it from the first pages. I was reading this book secretly for a long time as I wasn't alone most of the times and not every day I was even aware of the location of this book. </p> <p> I think that at some point my secret love affair with this book was spotted by my father. He said nothing about it, so I'm not sure if that was the trigger. But sometime after that, he offered me another book from his collection of science fiction to read. It was the «Deathworld» by Harry Harrison. </p> <p> From that time I created a personal ritual for reading. Every evening, I take a fruit or two, go to bed, take a book and read it for an hour or two. That was the time I first met Heinlein, Simak, Asimov, Zelazny, Sheckley and other cool guys I owe so much for my very own personality, consciousness and the way I see the world now. </p> A Few Notes on Composition of Reducers /blog/composing-reducers <p> Managing the state of an application with libraries like `redux` is awesome. It provides a really easy way to write simple and testable code for state transitions. You only need to decide on a structure of the desired state and write a corresponding pure function. </p> <p> There is no doubt that a reducer could look super simple in the cases like the following one: </p> <script async src="//jsfiddle.net/astartsky/vax4kyrz/7/embed/js,html,result/"></script> <br /> <br /> <p> Our main task as developers is to keep all reducers as simple as possible. Simple reducer is easily understandable and it's not a big deal to write a couple of helpful tests for it. But the state grows bigger and becomes more and more complex as we add more cool features to our apps. We need a technique to hide this complexity somewhere. </p> <p> Every reducer is just a common pure function. No magic at all. Therefore it's possible to write such a reducer function which will take a set of other reducers on its input to produce their results in a combination of some sort. Let's look at a few examples: </p> <h2>Combine a set of reducers into a map of properties</h2> <p> Let's assume that our app needs two different counters at the same time. It would be great to code something like that (with magic `combineReducers` function): </p> <script async src="//jsfiddle.net/astartsky/0pzbsshs/embed/js,html,result/"></script> <br /> <br /> <p> And it's exactly what is possible with the standard `combineReducers` function from `redux` library. But be aware that as we use the same `counter` reducer for counter1 and counter2, they react the same way and counter's values will be the same too. </p> <h2>Combine a set of reducers into an indexed storage</h2> <p> Let's imagine that we want a couple of such state structures. We must add an `id` parameter into the action to be able to determine which one of the states we want to change. </p> <script async src="//jsfiddle.net/astartsky/d8tr1e4w/2/embed/js,html,result/"></script> <br /> <br /> <p> Let the power of pure functions be with you. </p> How To Update Cloudfront Certs With Letsencrypt /blog/how-to-update-cloudfront-certs-with-letsencrypt <p> Letsencrypt is an excellent service for obtaining totally free security encryption certificates. Fortunately, it also has a marvelous client named Certbot. Let's install it. </p> <h2>Install Certbot</h2> <p> In the most recent versions of Ubuntu (16.04 and newer), it's possible to install Certbot automatically using native operating system repositories. It's preferable: you'll get a global executable and a package update management out of a box. Just type the following command, and you're in business: </p> <pre class="language-bash"><code>#!/bin/bash $ apt-get install certbot </code></pre> <p> If it's not an option for any reason, you can always check out the Github repository manually. In that case, you should choose an installation directory and create a global symlink by yourself. </p> <pre class="language-bash"><code>#!/bin/bash $ git clone https://github.com/certbot/certbot </code></pre> <h2>Generate certificates manually</h2> <p> To generate a certificate we need to confirm that we have control over the domain. The standard practice is to launch a Letsencrypt process on ports 80 or 443. As we can't start anything on the Cloudfront distribution or s3 servers itself, we need to use a manual validation. Let's assume that our app is running on the my-awesome-cloudfront-app.com address. Run the following command: <pre class="language-bash"><code>#!/bin/bash $ certbot-auto certonly --manual -d my-awesome-cloudfront-app.com </code></pre> <p> The Certbot wizard will ask you to create the file with particular content. Create it and place at the required path. Wait a few minutes after that to ensure the CloudFront cache is updated (if necessary) or verification may fail. It looks like we've got our certificates. </p> <h2>Apply certificates to the CloudFront distribution</h2> <p> We'll use the AWS CLI tool to upload the certificates to Cloudfront. Let's begin and upload one: </p> <pre class="language-bash"><code>#!/bin/bash $ aws iam upload-server-certificate --server-certificate-name certificate_name --certificate-body file:///etc/letsencrypt/live/my-awesome-cloudfront-app.com/cert.pem --private-key file:///etc/letsencrypt/live/my-awesome-cloudfront-app.com/privkey.pem --certificate-chain file:///etc/letsencrypt/live/my-awesome-cloudfront-app.com/chain.pem --path /cloudfront/ </code></pre> <p> A few things you may need to know about the last command: </p> <ul> <li> The name must be unique — you can't have multiple certificates with the same name. And you can't just use the same name to replace the existing one. </li> <li> Don't be afraid of the strange file:/// windows-like prefix. It's for Linux. </li> <li> The /etc/letsencrypt/live/my-awesome-cloudfront-app.com/ directory will always be a symlink to a directory with your latest certificates for the domain. The previous ones will not go anywhere — you can locate them in the /etc/letsencrypt/archive subdirectory. </li> <li> The particular `path` argument is necessary to make Cloudfront aware of you certificates. You can upload them to any path, but you simply will not find them in the web console of Cloudfront. </ul> <p> If you've made a mistake or you just want to delete an obsolete certificate, use the following command: </p> <pre class="language-bash"><code>#!/bin/bash aws iam delete-server-certificate --server-certificate-name certificate_name` </code></pre> <p>Enjoy your secure web!</p> Sunrise in Moscow /blog/sunrise-in-moscow <p> <img src="/files/blog/87/sunrise.jpg" class="img-responsive" title="Sunrise in Moscow" alt="Sunrise in Moscow" /> </p> Writing Modern SPA in JavaScript: Package Management /blog/writing-modern-spa-package-management <h2>Why yet another one tutorial?</h2> <blockquote class="pull-right"> <p> A single-page application (SPA) is a web application or web site that fits on a single web page with the goal of providing a more fluid user experience similar to a desktop application. </p> <footer> <cite><a href="https://en.wikipedia.org/wiki/Single-page_application">en.wikipedia.org</a></cite> </footer> </blockquote> <p> I've been thinking of a complete tutorial on writing SPA in modern JavaScript for a long time. «There are a lot of such lessons» — you can say and be 100% right. But most of them have a very confusing part for newcomers: the horrific amount of buzzwords! The days when you were able to write the simplest «Hello World» program in just a few lines of code are long gone. Now you should know good enough answers to many important questions before you could even start coding: </p> <ul> <li> <strong>What language should I use?</strong><br /> JavaScript (es3, es5, es6)? TypeScript? CoffeeScript? Whatever-Else-Script? </li> <li> <strong>How I should manage my dependencies?</strong><br /> Can I just copy them to the project? Or should I use git submodules? Maybe I could make a use of some package manager (npm, bower)? </li> <li> <strong>How should I assemble my code for frontend?</strong><br /> Isn't the old good «just concatenate them all» the best one? Or should I use browserify? A little bit of webpack? </li> <li> <strong>How should I use styles?</strong><br /> Can I just add a style tag with an external css to my HTML file? Or should I use something more complex like BEM? Or can I just inline it? </li> <li> <strong>What framework should I use for my application base?</strong><br /> Should it be vanilla JavaScript? Or jQuery? Backbone? Angular? Maybe Angular 2 (two is bigger than one — therefore it should be better, right?), React? </li> <li> <strong>How should I manage the data in my application?</strong><br />A lot of buzzwords here. Flow? Redux? Relay? MobX? Can't I just do it the old good way (like my father and his father did)? </li> <li> <strong>Can't I just shoot myself in the head instead of diving into this mess?</strong> </li> </ul> <p> You can't write modern JavaScript and ignore those questions. So I'll try to solve this buzzword-hell-puzzle by starting a new project from scratch. We will be writing a new application with a small steps and every single step will be introducing one thing at a time. </p> <p> You should understand: sometimes you can definitely say that the tool named A is better than the tool named B. But sometimes you should also choose from tools with comparable awesomeness — just take the most beautiful one (as you see it now). </p> <h2>Let's install a Package Manager</h2> <p> The first thing you should master is a packet manager. Times when it was a good idea to install and manage dependencies manually are gone too. Now you can use a packet manager to install your application dependencies and solve conflicts among them in one simple command. The clear winner among JavaScript package managers is npm. It's a JavaScript program itself and it runs under nodejs interpreter environment. So we'll need them both to start: nodejs and npm. </p> <p> To install nodejs visit it's <a href="https://nodejs.org/en/">official website</a> for instructions for your platform. The good news: you'll receive an installed npm package after nodejs installation by default. And the bad news: it could be a little bit outdated. </p> <p> The main reason we should prefer the modern version of npm is the way how it stores your dependencies. Older versions use a huge hierarchical set of directories: it can cause problems on windows filesystems and also is not very graphical. The third version of npm uses flat directory structure where it's applicable. So let`s install it! </p> <pre class="language-bash"><code>#!/bin/bash $ npm install -g npm@3 </code></pre> <p> <strong>Hint!</strong><br /> You can use the following short alternative syntax command (if you are some kind of a Jedi): </p> <pre class="language-bash"><code>#!/bin/bash $ npm i -g npm@3 </code></pre> <p> The argument «g» means that we want this package installed globally. Otherwise it will be stored in ./node_modules subdirectory and will be usable only in a single current project. The package itself is named «npm» and «@3» is an optional postfix stating that we want the third version of npm and nothing less. </p> <p>Let's ensure we have now the desired npm version installed!</p> <pre class="language-bash"><code>#!/bin/bash $ npm -v 3.10.5 </code></pre> <h2>Start a New Project</h2> <p> We have a working package manager now. I can't wait any longer — let's start a new project! First of all, let's create a new directory to contain it. </p> <pre class="language-bash"><code>#!/bin/bash $ mkdir todo $ cd todo </code></pre> <p> The next command will launch a simple text wizard. Answer all the questions as you wish (you can leave default values — it doesn't matter now) and you'll see a new file named package.json in your project directory. It will contain basic meta-information about your application. </p> <pre class="language-bash"><code>#!/bin/bash $ npm init </code></pre> <p> It will have a structure just like this: </p> <pre class="language-javascript"><code>{ "name": "todo", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } </code></pre> <p> Let's clean it a little bit and remove all unnecessary lines to make things look simpler. Note that you have no need to run «npm init» if you don't want — you can write package.json manually, it's not rocket science. </p> <pre class="language-javascript"><code>{ "name": "todo", "version": "1.0.0" } </code></pre> <p> Nice! We can finish coding config and start coding the actual code right now. It will not be a complete todo application for now, but hey, Rome wasn't built in a day. Let's create an HTML file as entry point and a simple one line script: </p> <pre class="language-html"><code>&lt;!-- index.html --&gt; &lt;html&gt; &lt;body&gt; &lt;h1&gt;I'll be a great app one day&lt;/h1&gt; &lt;div id=&quot;app&quot;&gt;Not alive yet...&lt;/div&gt; &lt;script src=&quot;./src/index.js&quot;&gt;&lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <pre class="language-javascript"><code>// src/index.js document.getElementById('app').innerHTML = 'Alive!'; </code></pre> <p> Not very impressive yet, so let's go further and add a function (old plain es4-like one) to make things look more like an application: </p> <pre class="language-javascript"><code>// src/index.js function getSalutation(name) { return 'Hi, ' + name + '!'; } document.getElementById('app').innerHTML = getSalutation('user'); </code></pre> <p> What we got: </p> <p> <img class="img-responsive" src="/files/blog/86/1.png" alt="Result" /> </p> <h2>Install First Dependency</h2> <p> But why do we depend on getSalutation function parameter validity? We always want to greet user with respect and should capitalize his name in any case. Let's use a separate module named string.js to do this. First of all we need to install it: </p> <pre class="language-bash"><code>#!/bin/bash $ npm install --save string </code></pre> <p> And a Jedi version: </p> <pre class="language-bash"><code>#!/bin/bash $ npm i -S string </code></pre> <p> The «save» argument denotes that we want not only to install this package to project directory but also to store it like a project dependency in package.json. If we want to be able to deploy our app somewhere else we need to know what dependencies it has — just to be able to reproduce certain conditions in which the app is meant to run or be built. Let's look at what had happened to project after this command: </p> <p> A new package named «string» is installed locally under node_modules directory: </p> <pre class="language-bash"><code>#!/bin/bash $ ls node_modules string </code></pre> <p> The «string» package also had been added to package.json config: </p> <pre class="language-javascript"><code>{ "name": "lesson", "version": "1.0.0", "dependencies": { "string": "^3.3.1" } } </code></pre> <p> As we didn't specify any concrete version of this package, npm fetched the last one and saved this version as minimal satisfiable. The «^3.3.1» syntax means that npm could give us version greater or equal to denoted. It is called «semver» and you really should read a few great articles about it: </p> <ul> <li> <a href="https://nodesource.com/blog/semver-a-primer/">Semver: A Primer</a> </li> <li> <a href="https://nodesource.com/blog/semver-tilde-and-caret/">Semver: Tilde and Caret</a> </li> </ul> <p> Now let's use it in out application: </p> <pre class="language-javascript"><code>// src/index.js function getSalutation(name) { return 'Hi, ' + S(name).capitalize() + '!'; } document.getElementById('app').innerHTML = getSalutation('user'); </code></pre> <pre class="language-html"><code>&lt;!-- index.html --&gt; &lt;html&gt; &lt;body&gt; &lt;h1&gt;I'll be a great app one day&lt;/h1&gt; &lt;div id=&quot;app&quot;&gt;Not alive yet...&lt;/div&gt; &lt;script src=&quot;./node_modules/string/dist/string.js&quot;&gt;&lt;/script&gt; &lt;script src=&quot;./src/index.js&quot;&gt;&lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p> And the result is: </p> <p> <img class="img-responsive" src="/files/blog/86/2.png" alt="Result" /> </p> <p> Please keep in mind that the path to third party module and its API vary depending on package developer decisions. With the same probability the string.js variable could be named «StringJS», «s», or any other way. We'll talk about how to deal with it later. </p> <h2>Freeze dependency versions</h2> <p> I bet you already have a thought about compatibility. What will happen to our app if the string.js developer will make some changes in their API? We had tested our project only with this concrete version of string.js and don't want any other. </p> <p> The simplest solution could be to change the required version from «^3.3.1» to exactly «3.3.1». But that will solve only the problem with string.js package itself. What if string.js has it's own dependencies? We need to fix their versions too, but we can't write it in package.json — we have no such dependencies there. Shrinkwrap to the rescue! </p> <pre class="language-bash"><code>#!/bin/bash $ npm shrinkwrap </code></pre> <p> This command simply scans through our ./node_modules subdirectory and writes all found package versions to a separate file named npm-shrinkwrap.json. The npm install command respects versions specified in this file and will use them instead of the last ones. </p> <h2>Conclusion</h2> <p> Our journey is just beginning. The most interesting parts are in the future. You already know how to use the package manager to satisfy your dependencies in automatic mode. And the next article will cover the usage of modern JavaScript dialects and will guide you through the setup of assemble process. Stay online! </p> Начните использовать ES6 и SASS в браузерах сегодня /blog/start-using-es6-and-sass-in-browsers-now <img src="/files/blog/84/1.jpg" alt="code" title="code" class="img-responsive" width="500" height="333" /> <br/> <p> Браузеры всегда были очень инертной средой для исполнения наших программ. Даже когда язык JavaScript ещё не развивался так быстро как сейчас, браузеры уже не успевали за ним. Годами на троне сидел какой-нибудь хтонический монстр вроде IE6 и заставлял с ним считаться каждого. Нам пришлось на долгое время забыть об использовании чистых языковых конструкций и научиться доверять очередной библиотеке, знающей разницу между браузерами и их версиями. Появились армии разработчиков, неплохо знающих jQuery, но как огня боящихся самого языка JavaScript. В то же время, дела со стилями в чем-то были еще хуже. Несмотря на появление множества новых свойств, сам формат не получил вообще никаких значимых улучшений и, в сыром виде, даже сейчас едва ли может быть удобен кому-либо. </p> <p> Конечно, со временем, ситуация начала исправляться: поддержка JavaScript стала лучше, появились полифиллы для устаревших браузеров, компиляторы CSS и, наконец, транспайлеры в ES6. Но, к сожалению, это всё не доступно из коробки каждому, это удовольствие нужно настраивать разработчику и не всем из нас ясно как же это сделать. На первый взгляд всё кажется намного сложнее, чем дела обстоят на самом деле. Кажется, что если у вас нет специального сервера для сборки, CI, CD, какого-нибудь хитрого сборщика, то не стоит и пытаться что-то сделать. Но, на самом деле, это и не требуется. </p> <blockquote cite="https://learn.javascript.ru/dom-polyfill"> Полифилл — это библиотека, которая добавляет в старые браузеры поддержку возможностей, которые в современных браузерах являются встроенными </blockquote> <blockquote> Транспайлер — это процесс компиляции программы с одного языка программирования в коды другого языка программирования, при условии одинакового уровня абстракции этих языков </blockquote> <h2>Установка Node.Js и npm</h2> <blockquote> Node.Js — это среда исполнения JavaScript, основанная на JavaScript движке V8, и предлагающая асинхронное API для работы с сетью и диском. </blockquote> <blockquote> npm — это менеджер пакетов для Node.Js </blockquote> <p> Они понадобится для установки инструментов разработчика и запуска транспайлера, так что нужны будет только там, где будет происходить непосредственно сама сборка проекта. В простейшем случае, это может быть машина разработчика: тогда результаты работы транспайлера можно добавить в систему версий как обычные файлы. Итак, установим их: </p> <pre class="language-bash"><code> curl -sL https://deb.nodesource.com/setup_5.x | sudo -E bash - sudo apt-get install nodejs build-essential $ node -v v5.8.0 $ npm -v 3.7.3 </code></pre> <p>А теперь создадим новый пакет:</p> <pre class="language-bash"><code> $ npm init </code></pre> <p> Ответьте на все вопросы мастера. По результатам будет сгенерирован файл package.json подобного вида: </p> <pre class="language-javascript"><code> { "name": "awesome-website", "version": "1.0.0", "description": "", "main": "index.js" } </code></pre> <h2>JavaScript: сборка модулей в один файл</h2> <p> Для сборки всех файлов и модулей в один файл, готовый к «отгрузке» клиентскому браузеру, мы используем browserify. Установим его: </p> <pre class="language-bash"><code> $ npm install --save-dev browserify </code></pre> <p> Данная команда найдет подходящую версию для browserify, установит её и сохранить информацию об этой зависимости в секции «devDependencies» файла «package.json». </p> <p> Теперь создадим скрипт для npm, который будет запускать сборку JavaScript. Для этого измените секцию «scripts» в файле «package.json» следующим образом: </p> <pre class="language-javascript"><code> "scripts": { "build-js": "browserify ./src/bundle.js > ./dist/bundle.js" } </code></pre> <p> Сейчас и в дальнейшем, можно запускать сборку следующей командой: </p> <pre class="language-bash"><code> $ npm run build-js > awesome-website@1.0.0 build-js /var/www/project/www > browserify ./src/bundle.js > ./dist/bundle.js </code></pre> <p>Не беда, что browserify недоступен как исполняемый файл глобально. npm самостоятельно разберется с путями до ./node_modules/.bin, если запускать команду через npm run.</p> <h2>JavaScript: транспайлинг из ES6 в ES5</h2> <p> Нет сомнений, что сборка модулей в один файл это уже очень неплохо и большой шаг вперед, пока мы всё еще ждем HTTP2. Но пока мы не можем использовать никаких современных возможностей ES6, так как всё еще не настроили транспайлинг. Самое время сделать это. Для начала, установим сам транспайлер, плагины к нему, а также адаптер для интеграции с browserify: </p> <pre class="language-bash"><code> $ npm install --save-dev babel babel-preset-es2015 babel-preset-react babelify </code></pre> <p> После этого, модифицируйте скрипт «build-js» в файле «package.json» следующим образом: </p> <pre class="language-javascript"><code> "scripts": { "build-js": "browserify ./src/bundle.js -t babelify -d > ./dist/bundle.js" } </code></pre> <p> Также потребуется указать, как именно мы хотим проводить транспайлинг. В нашем случае, мы установили babel-preset-es2015, который позволит использовать возможности ES6. При желании, аналогичным образом можно также поставить и другие вариант транспиляции, например для преобразования jsx или ES7 в ES5. Добавьте новую секцию «browserify» следующего содержания в «package.json» и укажите, какие именно преобразования необходимо произвести над исходным кодом: </p> <pre class="language-javascript"><code> "browserify": { "transform": [["babelify", { "presets": ["es2015", "react"}]] } </code></pre> <h2>JavaScript: добавление полифилла для ES5</h2> <p> На данном этапе, код, который мы получаем от транспайлера, может работать в любом браузере, поддерживающем ES5. К сожалению, есть небольшой процент браузеров, которые не в полной мере поддерживают ES5. Мы можем использовать так называемый shim, добавляющий часть возможностей ES5. К сожалению, не все методы могут быть реализованы таким образом. Но хоть что-то — уже не так плохо. </p> <pre class="language-bash"><code> $ npm install --save-dev es5-shim </code></pre> <pre class="language-javascript"><code> "scripts": { "build-js": "browserify -r \"es5-shim\" ./src/bundle.js -t babelify -d > ./dist/bundle.js" } </code></pre> <h2>JavaScript: обфускация и минификация</h2> <p> Хотя на прошлом шаге мы уже добились исполнения JavaScript ES6 в браузере, он всё еще не оптимизирован по размеру. Для обфускации и уменьшения размера файла можно использовать, например, uglifyjs: </p> <pre class="language-bash"><code> $ npm install --save-dev uglifyjs </code></pre> <pre class="language-javascript"><code> "scripts": { "build-js": "browserify -r \"es5-shim\" ./src/bundle.js -t babelify -d | uglifyjs -mc warnings=false > ./dist/bundle.js" } </code></pre> <h2>CSS: компиляция из SASS</h2> <p> Аналогичным образом, мы можем обработать и SASS, преобразовав его в CSS, понимаемый всеми браузерами, обфусцировав его. Для начала, установим компилятор из SASS в CSS: </p> <pre class="language-bash"><code> $ npm install --save-dev node-sass </code></pre> <p> Добавим новый скрипт для сборки стилей: </p> <pre class="language-javascript"><code> "scripts": { "build-css": "node-sass ./src/bundle.scss ./dist/bundle.css" } </code></pre> <pre class="language-bash"><code> $ npm run build-css > astgo@1.0.0 build-css /var/www/astgo/www > node-sass ./src/bundle.scss ./dist/bundle.css </code></pre> <h2>CSS: расстановка префиксов</h2> <p>Никому не нравится писать десять различных префиксов для разных браузеров, чтобы использовать какую-нибудь более-менее новую возможность CSS. К счастью, за нас это может делать и машина:</p> <pre class="language-bash"><code> $ npm install --save-dev postcss-cli autoprefixer </code></pre> <pre class="language-javascript"><code> "scripts": { "build-css": "node-sass ./src/bundle.scss | postcss --use autoprefixer -b 'last 2 versions' > ./dist/bundle.css" } </code></pre> <h2>CSS: обфускация и минификация</h2> <pre class="language-bash"><code> $ npm install --save-dev cssmin </code></pre> <pre class="language-javascript"><code> "scripts": { "build-css": "node-sass ./src/bundle.scss | postcss --use autoprefixer -b 'last 2 versions' | cssmin > ./dist/bundle.css" } </code></pre> <h2>Пример</h2> <p>Результат можно посмотреть в репозитарии: <br/> <a href="https://bitbucket.org/astartsky/modern-es6-sass-boilerplate">https://bitbucket.org/astartsky/modern-es6-sass-boilerplate</a> </p> <pre class="language-bash"><code> $ git clone https://bitbucket.org/astartsky/modern-es6-sass-boilerplate . $ npm install $ npm run build-css $ npm run build-js $ node dist/bundle.js my awesome es6 is working fine! </code></pre> <h2>Итоги</h2> <p> Итак, теперь ничто не мешает нам писать код на современном диалекте JavaScript, а стили на SASS. Что самое приятное в данном подходе — он применим практически в любом случае. Вам не требуется ни сервер сборки, ни современный код, ни определенная архитектура, ни разработка проекта с нуля. Вы можете применять этот подход в любом самом запущенном legacy-проекте, развернув небольшой параллельный стек технологий рядом с основным. Даже ваш бекенд не обязан об этом ничего знать и может быть любым. </p> Just saying «Hello» and chilling around /blog/just-saying-hello-and-chilling-around <p> I have never been really good at writing. I won some rewards in my childhood. I wrote some shitty verses in my youth. None of those achievements really were significant for the universe around me. It was all like a silly child play with the symbols I barely know. Move one back and another forward, make a stanza, make another one, make them a little bit more shiny. I experienced that well-known fear of a blank page. And I experienced that recursive «oh, I can do this paragraph better» annoying thing too. But I love writing, I really do. I have dreamt about writing a book all my life long just like my grandfather had dreamt before me. That’s why I have decided to write my thoughts in English: to master my slack skills with practice that makes me happy. </p> <p> We are what we do and what we think about. The oceans of information we swim in are unstable in our epoch. Their coast and bottom are changing their shapes every second with new memes rising and fighting each other to the bloody death. The key point is that those ideas are battling mostly in English. Native speakers are lucky in some way: they don’t need to do anything to be in the middle of that storm and to see every glimpse of new fancy ideas worth knowing about. </p> <p> Not that I say that other languages are not good. They are wonderful, all of them. But they act in this battle as smidge waves in the far away bays. And what I am interested in as a contemporary engineer and transhumanist is this battle itself. I want to be in the vanguard of this mess crushing metaphoric skulls with my own great axe of knowledge and will. </p> <p> And then again I asked myself a question: «Do I really want to battle ooze and slime in memetic swamps of my native language? Do I really want to waste my life for that dolorous purpose?» </p> <p> So here I am now. </p> Как я перестал бояться и полюбил конфиг /blog/how-i-learned-to-stop-worrying-and-love-the-config <blockquote class="pull-right" style="width: 100%;"> Как правило, наибольшего успеха добивается тот, кто располагает лучшей информацией <footer>Бенджамин Дизраэли</footer> </blockquote> <p> Лишь совсем простые приложения могут обойтись без файла конфигурации. У остальных всегда найдется, что хранить в конфиге: настройки окружения, настройки логики, часто меняющиеся фрагменты текста, etc. Словом, вопроса «что хранить в конфигурации» обычно не возникает: кандидатов достаточно. А вот вопрос «как хранить конфигурацию» возникает часто и еще чаще решается не самыми оптимальными способами. В этой статье я хотел бы рассмотреть варианты хранения конфигурации приложения, их плюсы и минусы. </p> <h1>Форматы конфигов</h1> <h2>Ваш собственный формат</h2> <p> Время от времени, у очередного разработчика загорается над головой лампочка и он понимает, что придумал новый, прекрасный и изящный формат для конфигурационного файла. Лишенный любых недостатков и интуитивно понятный всем, а особенно ему. Так вот, это совсем не так — гасите лампочку немедленно. Любые преимущества нового формата имени вас будут перечеркнуты тем немаловажным фактом, что формат новый, а значит, для него не существует: проработанного стандарта, исключающего детские болезни формата; протестированных парсеров под разные языки; и никто не умеет им пользоваться. </p> <h3>Плюсы</h3> <ul> <li>Разрабатывать новые форматы — это весело!</li> </ul> <h3>Минусы</h3> <ul> <li>Потребуется самостоятельное написание парсера и тестов к нему для каждой используемой платформы</li> <li>Потребуется обучение коллег работе с новым форматом</li> <li>Вы непременно узнаете новые интересные факты о своем детище ближе к финалу проекта</li> <li>Нет встроенной валидации данных (ведь правда?)</li> </ul> <h2>Нативный формат платформы</h2> <p> Самое простое решение — использовать возможности языка для создания конфига. Например, в PHP это могло бы выглядеть примерно так: </p> <pre><code class="language-php"> // config.php $config = [ "foo": "bar", "baz": [ "a": "b", "c": "d" ] ]; if ($config["foo"]) { $confif["set"] = true; } $config["calculated"] = calculation(); return $config; </code></pre> <p> Невероятно гибкий подход. К сожалению, гибкость часто приводит к дурным последствиям: с одной стороны, я использовал многие возможности языка для того, чтобы облегчить себе жизнь; с другой стороны, это чистое зло в дальнейшей перспективе проекта. Условные конструкции в конфиге вообще не нужны — логика допустима в загрузчике, размазывать её и по конфигу нет никакого смысла. В приведенном выше примере, «set» является, фактически, лишь функцией от «foo», которая уже известна. </p> <pre><code class="language-php"> function isFooSet($config) { return $config["foo"] ? true : null; } $config["set"] = isFooSet($config); </code></pre> <p> В такой формулировке хорошо заметно, что нет никакого смысла совершать эту операцию именно в конфиге. Код приложения справится с этой задачей ничуть не хуже. При этом конфиг останется местом для хранения базовой атомарной конфигурации. В идеале всегда следует стремиться к тому, чтобы конфиг не повторял сам себя. Всё, что можно вывести из значений, уже хранящихся в конфиге, не должно лежать там же. Считайте это такой нормальной формой для конфига, которую можно нарушать только в том случае, если вы осознаете зачем вы это делаете. </p> <p> А как быть с вызовом функции? Очевидно, она возвращает некое значение. Не будем заглядывать в функцию сразу, а попробуем прикинуть по описанию, что там может быть? По вызову видно, что функция не принимает никаких параметров. Значит, либо она всегда возвращает одно и то же значение, либо имеет какие-то неявные зависимости. В первом случае, функция в конфиге совершенно точно не нужна, так как может быть заменена сразу на это значение. Во втором случае, конфиг получается зависим от каких-то таинственных сил. В идеале следует стремиться к тому, чтобы конфиг не зависел ни от чего — это краеугольный камень приложения. </p> <pre><code class="language-php"> function calculation() { return new Datetime(); } </code></pre> <p> В функции могло быть написано что-то такое. В данном случае, получается, что конфигурация неявным образом зависит от времени. Каждый раз, когда приложение запускается, конфиг меняется. Это все равно что строить высотный дом на плавуне: не очень весело и итог немного предсказуем. </p> <h3>Плюсы</h3> <ul> <li>Синтаксис понятен всем, кому известен используемый язык программирования</li> <li>Возможно хранение структур данных любой сложности</li> <li>Это действительно быстро, Флеш гордился бы вами</li> </ul> <h3>Минусы</h3> <ul> <li>Формат позволяет внедрять в конфиг неявные зависимости</li> <li>Прочитать конфиг с другой платформы скорее всего не получится</li> <li>Нет встроенной валидации данных</li> </ul> <h2>INI</h2> <p> Представляет собой обычный текстовый файл, нехитрым образом разделенный на секции, ключи и значения. Широко используется для хранения простых конфигурационных данных в программах (например: php.ini, boot.ini). Относительно легко читаем в простых случаях, но при этом не позволяет почти ничего в более сложных. Следует десять раз подумать, прежде чем останавливать свой выбор на нём. </p> <pre><code class="language-ini"> [config] foo=bar baz[a]=b baz[c]=d </code></pre> <h3>Плюсы</h3> <ul> <li>Достаточно широко распространен</li> <li>Легко читается человеком в случае простых конфигов</li> </ul> <h3>Минусы</h3> <ul> <li>Подходит только для хранения самых простых структур</li> <li>Нет единой спецификации, реализации для разных платформ могут отличаться</li> <li>Нет встроенной валидации данных</li> </ul> <h2>XML</h2> <p> Дедушка всех конфигов, его величество XML — первый формат, который приходит в голову при размышлениях об универсальных конфигах. В самом деле, сложно представить какой-нибудь язык программирования, в котором еще не реализовано чтение XML. С другой стороны, чтение XML может быть достаточно медитативным занятием — формат совсем не прост. А количество избыточных символов в XML способно вызвать дислексию у слабых духом. </p> <pre><code class="language-javascript"> &lt;MyAwesomeConfig&gt; &lt;foo&gt;&lt;![CDATA[bar]]&gt;&lt;/foo&gt; &lt;baz a=&quot;b&quot; c=&quot;d&quot;&gt; &lt;/MyAwesomeConfig&gt; </code></pre> <h3>Плюсы</h3> <ul> <li>Широко распространен и всем известен</li> <li>Очень подробная спецификация</li> <li>Встроенная валидация с помощью XSD</li> </ul> <h3>Минусы</h3> <ul> <li>Формат чрезвычайно многословен — забудьте слово «лаконичность»</li> <li>Программно чтение может быть реализовано достаточно сложным образом</li> </ul> <h2>JSON</h2> <p> Другой широко известный формат, первоначально предназначен для передачи данных в JavaScript. Немногим уступает по распространенности XML. Любим многими за простой синтаксис и расширяемость. </p> <pre><code class="language-javascript"> { "foo": "bar", "baz": { "a": "b", "c": "d" } } </code></pre> <h3>Плюсы</h3> <ul> <li>Широко распространен и всем известен</li> <li>Легко читается как человеком, так и машиной</li> <li>Довольно краток</li> </ul> <h3>Минусы</h3> <ul> <li>Нет встроенной валидации данных</li> </ul> <h2>YAML</h2> <p> Представляет из себя еще более упрощенный JSON. К сожалению, распространен значительно меньше последнего. Отличается еще большей простотой чтения для человека при сохранении всех возможностей. В отличии от JSON, в основном применяется как раз в конфигах. </p> <pre><code class="language-javascript"> foo: "bar" baz: a: "b" c: "d" </code></pre> <h3>Плюсы</h3> <ul> <li>Легко читается человеком</li> <li>Очень краток</li> </ul> <h3>Минусы</h3> <ul> <li>Пока еще ограниченная поддержка на разных платформах</li> <li>Нет встроенной валидации данных</li> </ul> <h2>Environment</h2> <p> Не обязательно конфиг должен храниться в отдельном файле. Всё, что нам требуется от конфига — возможность его прочитать. Как конкретно будет реализовано хранилище данных — вопрос десятый, пока оно надежно и не требует дополнительной инициализации (как база данных, например, к которой нужно уметь подключаться, заранее зная её адрес). Одним из подобных вариантов является хранение конфигурации приложения в переменных окружения. </p> <pre><code class="language-php"> // read_config.php $foo = get_env("FOO"); $baz_a = get_env("BAZ_A"); $baz_c = get_env("BAZ_C"); </code></pre> <p> На первый взгляд, идея кажется не привносящей ничего хорошего в хранение конфигурации. Хранить в ней структуру невозможно, только строковые значения. Как сохранять эти значения тоже неясно: через шелл или отдельный скрипт? Да это же еще хуже, чем INI, бывает ли такое? Пустая затея — если бы не одно но: эта схема отлично ложится на идею контейнерной виртуализации и микросервисов. Например, при запуске контейнера Docker можно указать переменные окружения для процесса: </p> <pre><code class="language-javascript"> # docker-app/docker-compose.yml servicedef: build: ./build/my-awesome-service environment: FOO: "bar" BAZ_A: "b" BAZ_C: "d" </code></pre> <p> Контейнер, запущенный с этой конфигурацией docker-compose получил переменные окружения FOO, BAZ_A и BAZ_C. Мы можем создать любое количество контейнеров с разными значениями конфигурации, переданными приложению и для этого нам вовсе не нужны никакие манипуляции с файлами. </p> <h3>Плюсы</h3> <ul> <li>Хранение конфигурационных значений во внешних по отношению к приложению конфигах</li> <li>Работает с любыми языками, способными читать переменные окружения</li> <li>Легко читается человеком</li> </ul> <h3>Минусы</h3> <ul> <li>Можно хранить только строковые значения</li> <li>Нет встроенной валидации данных</li> <li>Не самая простая идея</li> </ul> <p> В общем, есть из чего выбрать формат. В дальнейших примерах я буду использовать YAML как наиболее современный вариант. </p> <h1>Организации конфигурации</h1> <h2>Один на всех</h2> <p> Самый простой подход — один конфиг для всех экземпляров приложения. Так как, по факту, переменные конфига в разных окружениях просто обязаны быть разными, существует некий золотой «эталон» конфига, хранящийся в системе контроля версий с говорящим именем вроде «config.php.example», а также существует правило исключения реального файла конфига «config.php» из хранения в системе версий. Предлагается при установке приложения скопировать из примера конфиг и затем вручную поддерживать его актуальность по мере изменения или обновления приложения. </p> <pre><code class="language-bash"> $ ls config config.yml config.yml.example </code></pre> <pre><code class="language-javascript"> # config/config.yml.example foo: "value for foo goes here" baz: "value for baz goes here" </code></pre> <pre><code class="language-javascript"> # config/config.yml foo: "bar" baz: "abcd" </code></pre> <pre><code class="language-bash"> # .gitignore config/config.yml </code></pre> <h3>Плюсы</h3> <ul> <li>Это самый простой способ из всех</li> <li>В системе хранения версий не хранятся важные данные, только примеры</li> <li>Относительно просто установить новый экземпляр приложения</li> </ul> <h3>Минусы</h3> <ul> <li>Операции по обновлению конфига придется производить вручную, т.к. реальный конфиг не версионифицирован</li> <li>Необходимо обрабатывать возможность отсутствия файла конфига</li> </ul> <h2>По конфигу на окружение</h2> <p> Вместо хранения примера конфига, можно хранить все возможные конфиги под разными именами. Также потребуется реализация механизма определения того конфига, который следует использовать в данном экземпляре: либо через переменные окружения, либо через отдельный конфиг, работающий по первой схеме. </p> <pre><code class="language-bash"> $ ls config development.yml production.yml playground-for-that-weird-new-guy.yml $ cat config_pointer.yml config: development.yml </code></pre> <h3>Плюсы</h3> <ul> <li>Не требует ручного обновления конфигурационных файлов при изменении или обновлении</li> <li>Способ относительно прост</li> </ul> <h3>Минусы</h3> <ul> <li>В хранилище версий могут попасть важные данные! НЕ ДЕЛАЙТЕ ТАК И БУДЬТЕ ВНИМАТЕЛЬНЫ</li> <li>Большая избыточность данных в конфигах, значения хранятся во всех конфигах, даже если они одинаковы</li> <li>Необходимо дополнительно к чтению основного конфига реализовать чтение конфига с указателем на текущее окружение</li> </ul> <h3>Наследуемые конфиги</h3> <p> Один из минусов прошлого подхода (избыточность) можно убрать, если реализовать механизм наследования для конфигов. Например, шаблоны писем для всех экземпляров приложения лежат в одной и той же директории, но она все равно вынесена в конфиг для удобства будущих изменений. Нет никакого смысла хранить свой указатель на эту директорию для каждого окружения, достаточно одного общего. А вот база данных каждому экземпляру нужна своя собственная. </p> <pre><code class="language-bash"> $ ls config development.yml production.yml playground-for-that-weird-new-guy.yml base.yml $ cat config_pointer.yml config: development.yml </code></pre> <pre><code class="language-javascript"> # development.yml parent: base.yml foo: bar </code></pre> <pre><code class="language-javascript"> # base.yml foo: not_bar baz: abcd </code></pre> <p> Тогда для окружения «development» будут приняты следующие значения: </p> <pre><code class="language-javascript"> foo: bar baz: abcd </code></pre> <h3>Плюсы</h3> <ul> <li>Нет избыточности данных</li> <li>Не требует ручного обновления конфигурационных файлов при обновлении или изменении</li> </ul> <h3>Минусы</h3> <ul> <li>В хранилище версий могут попасть важные данные! НЕ ДЕЛАЙТЕ ТАК И БУДЬТЕ ВНИМАТЕЛЬНЫ</li> <li>Необходимо дополнительно к чтению основного конфига реализовать чтение конфига с указателем на текущее окружение</li> <li>Необходимо дополнительно реализовать механизм наследования (возможно, рекурсивный)</li> <li>Чтение нескольких конфигов медленнее чтения одного конфига (можно решить кешированием)</li> </ul> <h2>Конфиг с заглушками</h2> <p> На самом деле, не требуется создавать миллион разных файлов с конфигурацией для миллиона установленных экземляров приложения. Как правило, все конфиги сводятся к небольшому набору типовых, отличающихся между собой только несколькими значениями. Нет ведь никакой разницы, какой именно адрес базы данных указан в конфиге приложения — с точки зрения конфига и то, и другое — просто строки. Важен набор настроек, которые вместе меняют логику программы. Например, показывать ли расширенные сообщения об ошибках или использовать ли агрессивное кеширование. Именно вместе, в совокупности, этот набор имеет ценность. </p> <pre><code class="language-bash"> $ ls config development.yml production.yml base.yml </code></pre> <pre><code class="language-javascript"> # development.yml parent: base.yml foo: %FOO% </code></pre> <p> Обратите внимание: больше нет необходимости ни в дополнительном конфиге с указателем текущего окружения, ни в дополнительном конфиге для того-странного-нового-парня. Вместо них можно использовать переменные окружения. Например, переменная окружения MY_AWESOME_APP_ENVIRONMENT будет равна «development.yml», а «FOO» будет равна «baz». </p> <h3>Плюсы</h3> <ul> <li>Минимальная избыточность данных</li> <li>Не требует ручного обновления конфигурационных файлов при обновлении или изменении</li> <li>Важные данные можно хранить в переменных состояния, а в системе хранения версий заглушки и менее важные данные</li> </ul> <h3>Минусы</h3> <ul> <li>Необходимо дополнительно реализовать замену заглушек на соответствуюшие значения из переменных окружения</li> <li>Необходимо дополнительно реализовать механизм наследования (возможно, рекурсивный)</li> <li>Чтение нескольких конфигов медленнее чтения одного конфига (можно решить кешированием)</li> </ul> <h1>Вместо заключения</h1> <p> Способы организации хранения и работы с конфигурационными файлами не ограничиваются описанными выше. Любой из них можно улучшить. В определенных ситуациях, у того или иного способа могут появиться неожиданные плюсы и минусы — выше разобраны только общие случаи. Не бойтесь экспериментировать и размышлять. </p> Бесплатный сертификат от Letsencrypt /blog/letsencrypt <p><img src="/files/blog/82/letsencrypt.png" class="img-responsive" alt="Lets encrypt" title="Lets encrypt" /></p> <h1>Зачем это?</h1> <p>У защищенного соединения есть множество преимуществ перед старым добрым http: защита от перехвата траффика, защита от подмены содержимого, лучшее ранжирование в поисковых системах и т.д. Мне вот, например, нравится зеленый значок в адресной строке. Раньше за всё это удовольствие нужно было платить, поэтому сертификаты можно было увидеть только на крупных проектах, либо платежных системах (где риск компрометации данных велик и без сертификата вас просто не поймут). Проект letsencrypt.org призван изменить это положение вещей раз и навсегда.</p> <p>Получение бесплатного, но полностью функционального сертификата теперь доступно каждому. Проект предоставляет утилиту командной строки, с помощью которой можно сгенерировать ключ, сертификат, и даже, в типовых случаях, установить этот сертификат в ваш конфиг веб-сервера. Единственное серьезное отличие от привычных платных сертификатов — время жизни в три месяца. Но это не минус, а намеренный подход: предполагается, что сервер будет настроен на автоматическое получение нового сертификата взамен полученного ранее (это должно помочь в борьбе с компрометацией ключей).</p> <h2>Настройка</h2> <p>Заявлено, что предоставляемая утилита командной строки умеет самостоятельно анализировать и обновлять конфиги Apache и nginx, генерируя новый сертификат и указывая все необходимые для него настройки. Понятно, что это позволит обновить малой кровью типовые конфиги, но, я считаю, этот подход далеко не всегда оправдан. Во-первых, ваш конфиг может быть значительно сложнее устроен, чем типовой и нет никакой гарантии, что автоматический режим с ним справится. Во-вторых, хорошо иметь полное представление, что и почему происходит на вашем сервере, чтобы иметь возможность исправить недочеты или повторить действия для получения результата. В-третьих, в моем случае, nginx был завернут внутрь контейнера docker -- к такому утилиту точно не готовили. Так что мы будем действовать по старинке!</p> <p>Для начала скачаем утилиту:</p> <pre><code class="language-bash"> cd /opt mkdir letsencrypt && cd letsencrypt git clone https://github.com/letsencrypt/letsencrypt . </code></pre> <p>Теперь напишем скрипт, генерирующий сертификат. Здесь /var/www/astgo -- директория, в которой находятся исходники приложения. Важно: перед созданием сертификата, нужно освободить порты (если они заняты), чтобы утилита могла использовать их для подтверждения владения вами указанного домена. В указанном ниже примере для этого останавливаются контейнеры docker, после обновления сертификата их нужно поднять обратно.</p> <pre><code class="language-bash"> # /opt/update-cert.sh (cd /var/www/astgo && docker-compose stop && docker-compose rm --force) && (cd /opt/ && ./letsencrypt/letsencrypt-auto certonly -d ast.rocks --email astartsky@gmail.com --standalone --renew-by-default --agree-tos --standalone-supported-challenges tls-sni-01) && cp /etc/letsencrypt/live/ast.rocks/cert.pem /var/www/astgo/build/nginx && cp /etc/letsencrypt/live/ast.rocks/chain.pem /var/www/astgo/build/nginx && cp /etc/letsencrypt/live/ast.rocks/fullchain.pem /var/www/astgo/build/nginx && cp /etc/letsencrypt/live/ast.rocks/privkey.pem /var/www/astgo/build/nginx && (cd /var/www/astgo/ && docker-compose build --no-cache && docker-compose up -d) </code></pre> <p>Разберем подробнее вызов утилиты:</p> <p> <ul> <li><strong>certonly</strong> -- только генерация сертификата, никаких действий по автоматической установке не требуется;</li> <li><strong>-d</strong> -- домен, на который будет сгенерирован сертификат;</li> <li><strong>--email</strong> -- электронная почта, по которой с вами сможет связаться letsencrypt;</li> <li><strong>--standalone</strong> -- для подтверждения владения доменом, будет поднят отдельный веб-сервер;</li> <li><strong>--renew-by-default</strong> -- при повторном вызове не будет создан новый сертификат;</li> <li><strong>--agree-tos</strong> -- разумеется, при первом запуске нужно согласиться с условиями представления услуги;</li> <li><strong>--standalone-supported-challenges tls-sni-01</strong> -- разрешить использовать 443 порт для проверки домена;</li> </ul> </p> <p>После запуска скрипта заветные сертификаты окажутся в указанных выше папках. Дело осталось за малым: указать их в конфигах nginx. Например, так:</p> <pre><code class="language-nginx"> ssl_stapling_verify on; ssl_stapling on; ssl_trusted_certificate /opt/chain.pem; ssl_certificate_key /opt/privkey.pem; ssl_certificate /opt/fullchain.pem; listen 443 ssl; </code></pre> <p>Также имеет смысл поместить вызов этого скрипта в cron каждые два месяца, чтобы обновление сертификата было полностью автоматическим.</p> Latest docker for Ubuntu LTS /blog/latest-docker-for-ubuntu-lts For Ubuntu 14.04 LTS <h2>Latest docker version (automatic update)</h2> <pre><code class="language-bash"> $ apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D $ echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" >> /etc/apt/sources.list.d/docker.list $ apt-get update $ apt-get purge lxc-docker* $ apt-get install docker-engine </code></pre> And result will be like this: <pre><code class="language-bash"> $ docker -v Docker version 1.9.0, build 76d6bc9 </code></pre> <h2>Latest docker-compose version</h2> <pre><code class="language-bash"> $ curl -L https://github.com/docker/compose/releases/download/1.5.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose $ chmod +x /usr/local/bin/docker-compose </code></pre> Валидация форм на React JS /blog/reactjs-form-validation <h2>Нам нужны формы. Больше форм.</h2> <p> Любой разработчик знает это. Каким бы хитрым фреймоворком вы ни пользовались, какие бы неожиданные идеи ни привносила в процесс разработки ваша очередная революционная концепция, как бы ни был устроен поток данных приложения, вам никуда не уйти от форм. Как только ваше приложение требует ввода более-менее сложных данных, старое доброе решение остается самым простым и эффективным, самым понятным пользователю и простым в реализации. И в React Js та же самая, старая как мир история — нам снова нужны формы. </p> <p> Первой идеей было взять понятное и простое <a href="http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html">готовое решение</a>, но, на второй взгляд, оно оказалось плохим: там используются неправильные с точки зрения архитектуры React решения: назначение props уже после создания элемента, жесткая связь между элементами. Как это обычно и бывает, неправильная архитектура ведет к неправильному поведению приложения в базовых сценариях. Представьте себе, например, форму, которая меняется динамически в зависимости от вашего ввода — это же как раз то, зачем React и сделан, именно та задача, с которой он отлично справляется. </p> <p> Но не в этом случае. Если назначить props уже после создания элемента (чего делать не стоит — это, к слову, warning), они не сохранятся после пересоздания компонента, когда React решит еще раз запустить render(). Так что жесткая связь лишает главного преимущества React — реактивных view. Правильным решением было бы использовать события — они позволяют общаться элементам, не связывая их напрямую друг с другом. Всё, что для этого нужно — общая шина. Предлагаю своё решение, вы можете сразу посмотреть <a href="#demo">демо</a>.</p> <h2>API</h2> <p>Для уменьшения объема кода, в производных компонентах данные методы должны вызываться в контексте самого компонента (ниже будет пример реализации формы и текстового поля ввода). К сожалению, это делает их зависимыми от внутренней структуры компонента, вызывающего их. С другой стороны, опций немного, так что все их несложно отследить.</p> <ul> <li><strong>FormBus.Form.attachForm()</strong> — подписка формы на события, необходимо вызвать в componentWillMount компонента формы.</li> <li><strong>FormBus.Form.detachForm()</strong> — отписка формы от событий, необходимо вызвать в componentWillUnmount компонента формы.</li> <li><strong>FormBus.Form.validateField()</strong> — валидация одного поля формы, возвращает promise, состояние которого изменится на «выполнено» сразу после генерации события `field_validation`.</li> <li><strong>FormBus.Form.validate()</strong> — валидация всех полей формы, возвращает promise, состояние которого изменится на «выполнено» сразу после завершения валидации последнего поля.</li> <li><strong>FormBus.Input.attachField()</strong> — подписка поля ввода на события, необходимо вызвать в componentWillMount компонента ввода.</li> <li><strong>FormBus.Input.detachField()</strong> — отписка поля ввода от события, необходимо вызвать в componentWillUnmount компонента ввода.</li> <li><strong>FormBus.Input.updateField()</strong> — обновить значение поля в модели, необходимо вызвать после изменения введенного значения в поле ввода</li> </ul> <h2>События</h2> <p>Весь компонент форм построен по принципу единой шины обмена событиями, где сама форма и поля ввода используют один и тот же канал для обмена данными о состоянии модели и валидации.</p> <li><strong>field_validation</strong>: {command: "field_validation", name: "foo", message: "foo is too bar"} — результат валидации поля</li> <li><strong>mount</strong>: {command: "mount", name: "foo", value: "bar"} — появление на форме нового поля</li> <li><strong>unmount</strong>: {command: "unmount", name: "foo"} — исчезновение поля с формы</li> <li><strong>update</strong>: {command: "update", name: "foo", value: "bar"} — изменение значения в поле</li> <li><strong>model_update</strong>: {command: "model_update", model: {foo: "bar"}} — изменение модели формы</li> <h2>Компонент формы</h2> <p>Создадим компонент формы. Он будет выполнять роль аггрегатора данных, поступающих из элементов ввода.</p> <pre><code class="language-javascript"> var React = require(&#39;react&#39;); var FormBus = require(&#39;components/form-bus&#39;); var Form = React.createClass({ getDefaultProps: function () { return { // уникальное название формы, для нескольких форм на одной странице // следует задать элементам каждой формы уникальный formns formns: &#39;form_event&#39;, onSubmit: function (model) { // переопределите этот метод при создании родительского класса } }; }, getInitialState: function () { return { // состояние модели формы model: {}, // ошибки формы errors: {}, // состояние проверки формы valid: true }; }, componentWillMount: function () { // схема валидации пустая при загрузке, чтобы форма // не валидировала пустые значения при отрисовке this.schema = {}; // подписываемся на события FormBus.Form.attachForm.bind(this)(); }, componentWillUnmount: function () { // отписываемся от событий FormBus.Form.detachForm.bind(this)(); }, componentDidMount: function () { // после загрузки формы, загружаем настоящую схему this.schema = this.props.schema(); }, submit: function (e) { var component = this; e.preventDefault(); // принудительный запуск валидации после отправки формы FormBus.Form.validate.bind(this)() // после завершения валидации, будет выполнен promise .then(function () { if (component.state.valid) { // если форма валидна, запускаем назначенный обработчик component.props.onSubmit(component.state.model); } }); }, render: function () { return ( &lt;form noValidate=&quot;novalidate&quot; onSubmit={this.submit}&gt; {this.props.children} &lt;div className=&quot;form-group&quot;&gt; &lt;div className=&quot;col-sm-offset-2&quot;&gt; &lt;button className=&quot;btn btn-primary&quot; type=&quot;submit&quot;&gt;Сохранить&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/form&gt; ); } }); module.exports = Form; </code></pre> <h2>Компонент текстового поля</h2> <p>Создадим также простейший компонент с логикой текстового поля. Основная роль любого компонента ввода: при создании заявить миру о своем существовании, при кончине сообщить о ней, а также держать всех в курсе изменения значения. Всё это достигается вызовом соответствующих методов FormBus.Input в контексте текущего класса. Важно, чтобы у компонента был задан `formns` в свойствах, а также `value` в состоянии.</p> <pre><code class="language-javascript"> var React = require(&#39;react&#39;); var FormBus = require(&#39;components/form-bus&#39;); var TextInput = React.createClass({ getDefaultProps: function () { return { name: &#39;Field&#39;, formns: &#39;form_event&#39; }; }, getInitialState: function () { return { // значение задается в родительском классе, либо берется пустое value: this.props.value || &#39;&#39;, error: null }; }, componentWillMount: function () { // подписываемся на события FormBus.Input.attachField.bind(this)(); }, componentWillUnmount: function () { // отписываемся от событий FormBus.Input.detachField.bind(this)(); }, setValue: function (e) { var component = this; // сохраняем в состоянии текущее значение пользовательского ввода this.setState({ value: e.currentTarget.value }, function () { // отправляем событие об изменении значения FormBus.Input.updateField.bind(component)(); }); }, render: function () { var error = this.state.error ? ( &lt;span id=&quot;name-error&quot; className=&quot;error&quot;&gt;{this.state.error}&lt;/span&gt; ) : null; return ( &lt;div className=&quot;form-group&quot;&gt; &lt;label className=&quot;control-label&quot;&gt; {this.props.name} {error} &lt;/label&gt; &lt;input name={this.props.name} type=&quot;text&quot; className=&quot;form-control&quot; onChange={this.setValue} value={this.state.value} /&gt; &lt;/div&gt; ); } }); module.exports = TextInput; </code></pre> <h2>Собираем детали вместе</h2> <pre><code class="language-javascript"> var React = require(&#39;react&#39;); var Layout = require(&#39;ui-components/layouts&#39;); var Form = require(&#39;ui-components/form&#39;); var TextInput = require(&#39;ui-components/text-input&#39;); var validator = require(&#39;validator&#39;); var dispatcher = require(&#39;validator&#39;); var FormScreen = React.createClass({ // уникальное название для формы formns: &quot;form1&quot;, componentWillMount: function () { // подписаться на события формы form1 dispatcher.on(this.formns, this.listener); }, componentWillUnmount: function () { // отписаться от событий формы form1 dispatcher.off(this.formns, this.listener); }, // схема валидации, где каждому ключу с именем поля соответствует массив // простых литералов вида {validate: function (value) {...}, message: &quot;Err&quot;} getValidationSchema: function () { return { name: [ { validate: function (value) { return validator.isLength(value, 1, 255); }, message: &quot;Пожалуйста, укажите имя&quot; } ], surname: [ { validate: function (value) { return validator.isLength(value, 1, 256); }, message: &quot;Пожалуйста, укажите фамилию&quot; } ], email: [ { validate: function (value) { return validator.isLength(value, 1, 256); }, message: &quot;Пожалуйста, укажите email&quot; }, { validate: validator.isEmail, message: &quot;Пожалуйста, укажите корректный email&quot; } ] } }, submit: function(model) { // форма успешно валидирована }, listener: function (event) { if (event.command == &#39;model_update&#39;) { // обновить состояние модели this.setState({model: event.model}); } }, render: function() { // поле `фамилия` появится только после заполнения поля `email` var surname = (this.state.model &amp;&amp; this.state.mode.email) ? ( &lt;TextInput formns={this.formns} name=&quot;surname&quot; title=&quot;Фамилия&quot; /&gt; ) : null; return ( &lt;Layout&gt; &lt;Form formns={this.formns} onSubmit={this.submit} schema={this.getValidationSchema}&gt; &lt;TextInput formns={this.formns} name=&quot;name&quot; title=&quot;Имя&quot; /&gt; {surname} &lt;TextInput formns={this.formns} name=&quot;email&quot; title=&quot;Email&quot; /&gt; &lt;/Form&gt; &lt;/Layout&gt; ); } }); module.exports = FormScreen; </code></pre> <h2>Рабочий пример</h2> <a name="demo"></a> <div id="example-form-application"> <h3>Подождите, идет загрузка примера...</h3> </div> <script type="text/javascript" src="/files/blog/80/form-sample.js"></script> <h2>Библиотека</h2> <p>Использование jquery для промисов и event-emitter для событий не принципиально. Это могут быть любые промисы (например, q) и любая шина событий.</p> <pre><code class="language-javascript"> var dispatcher = require(&#39;dispatcher&#39;); var jquery = require(&#39;jquery&#39;); var FormBus = { /** * Attach form to form bus */ attachForm: function () { dispatcher.on(this.props.formns, FormBus.listener.bind(this)); }, /** * Detach form from form bus */ detachForm: function () { dispatcher.off(this.props.formns, FormBus.listener.bind(this)); }, /** * Validate form */ validate: function () { var promises = []; for (var name in this.state.model) { promises.push(FormBus.validateField.bind(this)(name)); } var promise = jquery.Deferred(); jquery.when(...promises).then(function() { promise.resolve(); }); return promise; }, /** * Validate field with current model value * @param name */ validateField: function (name) { var promise = jquery.Deferred(); if (this.schema[name]) { var isValid = true; for (var rule_key in this.schema[name]) { if (this.schema[name].hasOwnProperty(rule_key)) { var rule = this.schema[name][rule_key]; isValid = rule.validate(this.state.model[name]); if (!isValid) { break; } } } var errors = this.state.errors; if (isValid) { delete errors[name]; } else { errors[name] = rule.message; } var errorCount = 0; for (var key in this.state.errors) { errorCount++; } this.setState({errors: errors, valid: errorCount == 0}, function () { dispatcher.emit(this.props.formns, {command: &#39;field_validation&#39;, name: name, message: isValid ? null : rule.message}); promise.resolve(); }); return promise; } }, /** * Form events listener * @param event */ listener: function (event) { var component = this; var model = this.state.model; var errors = this.state.errors; switch (event.command) { /** * Field mounted */ case &#39;mount&#39;: model[event.name] = event.value; FormBus.setModel.bind(this)(model, errors); break; /** * Field updated */ case &#39;update&#39;: model[event.name] = event.value; FormBus.setModel.bind(this)(model, errors, function () { FormBus.validateField.bind(component)(event.name); }); break; /** * Field unmounted */ case &#39;unmount&#39;: delete model[event.name]; delete errors[event.name]; FormBus.setModel.bind(this)(model, errors, function () { FormBus.validateField.bind(component)(event.name); }); break; } }, setModel: function (model, errors, callback) { this.setState({model: model, errors: errors}, function() { if (callback) { callback(); } dispatcher.emit(this.props.formns, {command: &#39;model_update&#39;, model: model}); }); } }; var InputBus = { /** * Attach field to form bus */ attachField: function () { // subscribe to events dispatcher.on(this.props.formns, InputBus.listener.bind(this)); // emit field creation event dispatcher.emit(this.props.formns, {command: &#39;mount&#39;, name: this.props.name, value: this.state.value}); }, /** * Detach field from form bus */ detachField: function () { // unsubscribe from events dispatcher.off(this.props.formns, InputBus.listener.bind(this)); // emit field removal event dispatcher.emit(this.props.formns, {command: &#39;unmount&#39;, name: this.props.name, value: this.state.value}); }, /** * Update field value */ updateField: function () { // emit field update event dispatcher.emit(this.props.formns, {command: &#39;update&#39;, name: this.props.name, value: this.state.value}); }, /** * Field events listener * @param event */ listener: function (event) { switch (event.command) { /** * Field validation result */ case &#39;field_validation&#39;: if (this.props.name == event.name) { this.setState({error: event.message}); } break; } } }; module.exports = { Form: FormBus, Input: InputBus }; </code></pre> <p>Буду рад услышать ваши мысли и критику.</p> Как показать себя и свои наработки /blog/how-to-ngrok <img src="/files/blog/79/grok.png" alt="Grok" title="Grok" class="img-responsive" width="434" height="188" /> <h2>За что мы боремся?</h2> <p>В жизни разработчика регулярно возникает необходимость показать результаты работы кому-либо еще — коллеге, заказчику, тестировщику. Всё довольно просто, если вы сидите в одной комнате или, хотя бы, здании: можно пригласить его к своему столу (скорее всего придется пообещать кофе или печеньки) и показать лично на собственном мониторе. Всё сложнее, если вы находитесь в разных районах, городах или даже странах. А иногда вам может пригодиться доступный из интернета API, развернутый локально — например, если вы разрабатываете или тестируете мобильные приложения.</p> <p>Путей решения проблемы много. Одним из них является покупка белого внешнего ip-адреса, проброска портов, покупка доменного имени и его настройка. Решение полноценное, но не всегда доступное — не у всех провайдеров можно получить белый адрес, не все готовы разрешить вам использовать необходимые порты. Я уж молчу о том, что вы оказываетесь привязаны к выбранному провайдеру и не можете использовать это решение, сидя в кафе с рабочим ноутбуком по широкополосному соединению. Еще можно арендовать сервер у какого-нибудь хостера и всё, что требуется показать, сперва деплоить туда. Тут, понятно, возникают другие проблемы: ограничение производительности этого сервера, лишний элемент инфраструктуры, требующий поддержки. Да и постоянный деплой изменений, которые вы просто хотите показать, но, возможно, не готовы даже на коммит?</p> <h2>На помощь спешит ngrok</h2> <p>Ngrok — это сервис, позволяющий прокинуть порт локального компьютера на сервера ngrok. Любой желающий сможет получить доступ к вашему локальному приложению по глобально доступному субдомену сервиса .ngrok.io.</p> <h2>Установка</h2> <pre><code class="language-bash"> wget https://dl.ngrok.com/ngrok_2.0.19_linux_amd64.zip unzip ngrok_2.0.19_linux_amd64.zip mv ngrok /usr/local/bin sudo chmod +x /usr/local/bin/ngrok </code></pre> <h2>Использование</h2> <p>В простейшем варианте достаточно:</p> <pre><code class="language-bash"> ngrok http 80 </code></pre> <p>Вы увидите монитор вашего туннеля:</p> <pre><code class="language-bash"> Tunnel Status online Version 2.0.19/2.0.19 Web Interface Forwarding http://8d323578.ngrok.io -> localhost:80 Forwarding https://8d323578.ngrok.io -> localhost:80 </code></pre> <p>Откроем наш уникальный адрес в браузере. В мониторе появится информация о нашем запросе:</p> <pre><code class="language-bash"> HTTP Requests ------------- GET /favicon.ico 200 OK GET /css/all.css 200 OK GET /js/index.js 200 OK GET /images/loader.gif 200 OK GET / 200 OK </code></pre> <p>Приятной фишкой является возможность поднять basic auth, указав один лишний ключ:</p> <pre><code class="language-bash"> ngrok http -auth="login:password" 80 </code></pre> <p>Узнать о других возможностях сервиса можно так:</p> <pre><code class="language-bash"> ngrok help </code></pre> Модальное окно на React JS /blog/reactjs-universal-modal <img src="/files/blog/78/bycycle.jpg" alt="Велосипед" title="Велосипед" class="img-responsive" width="432" height="306" /> <br/> <p> При создании любого приложения, рано или поздно возникает необходимость запросить подтверждение от пользователя (особенно полезно, если вы пишете веб-интерфейс управления ядерным реактором). </p> <p> И хотя в арсенале любого фронтенд-разработчика есть простая как топор функция confirm(), будем честны: она страшна как смертный грех, по-разному выглядит в разных браузерах и запросто может разрушить весь эффект от вашего крутого дизайна. Выход один — использовать собственное модальное окно. Всё крайне просто, если вы используете какой-нибудь jQuery и императивно командуете DOM-элементом, в котором оно описано, тем более что и готовых решений существует множество. А вот если вы используете React JS с его парадигмой реактивного программирования, решение может быть достаточно хитрым из-за особенностей связи между родительскими и дочерними компонентами. </p> <p> Для начала создадим компонент React для модального окна: </p> <pre><code class="language-javascript"> # app/ui-components/modal.jsx var React = require('react'); var $ = require('jquery'); var Modal = React.createClass({ getInitialState: function () { return { visible: false, cancel_title: this.props.cancel_title ? this.props.cancel_title : 'Отмена', action_title: this.props.action_title ? this.props.action_title : 'ОК', title: '', text: '' }; }, // Обработчик закрытия модального окна, вызовет обработчик отказа close: function () { this.setState({ visible: false }, function () { return this.promise.reject(); }); }, // Обработчик действия модального окна, вызовет обработчик действия action: function () { this.setState({ visible: false }, function () { return this.promise.resolve(); }); }, // Обработчик открытия модального окна. Возвращает promise // ( при желании, можно передавать также названия кнопок ) open: function (text, title = '') { this.setState({ visible: true, title: title, text: text }); // promise необходимо обновлять при каждом новом запуске окна this.promise = new $.Deferred(); return this.promise; }, render: function () { var modalClass = this.state.visible ? "modal fade in" : "modal fade"; var modalStyles = this.state.visible ? {display: "block"} : {}; var backdrop = this.state.visible ? ( &lt;div className="modal-backdrop fade in" onClick={this.close} /&gt; ) : null; var title = this.state.title ? ( &lt;div className="modal-header"&gt; &lt;h4 className="modal-title"&gt;{this.state.title}&lt;/h4&gt; &lt;/div&gt; ) : null; return ( &lt;div className={modalClass} style={modalStyles}&gt; {backdrop} &lt;div className="modal-dialog"&gt; &lt;div className="modal-content"&gt; {title} &lt;div className="modal-body"&gt; &lt;p&gt;{this.state.text}&lt;/p&gt; &lt;/div&gt; &lt;div className="modal-footer"&gt; &lt;button type="button" className="btn btn-default" onClick={this.close}&gt;{this.state.cancel_title}&lt;/button&gt; &lt;button type="button" className="btn btn-primary" onClick={this.action}&gt;{this.state.action_title}&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; &lt;/div&gt; ); } }); module.exports = Modal; </code></pre> <p>Встраиваем наш новый компонент на родительский:</p> <pre><code class="language-javascript"> # app/screens/sample.js var React = require('react'); var Layout = require('ui-components/layouts/layout'); var Modal = require('ui-components/modal'); var SampleScreen = React.createClass({ modal: function () { return this.refs.modal; }, popup: function () { this.refs.modal().open("Вы уверены?") .then(function() { // Действие }) .fail(function() { // Отмена }); }, render: function() { return ( &lt;Layout&gt; &lt;div className="row"&gt; &lt;a onClick={this.popup} className="btn btn-primary"&gt;Popup&lt;/a&gt; &lt;/div&gt; &lt;Modal ref="modal"/&gt; &lt;/Layout&gt; ); } }); module.exports = SampleScreen; </code></pre> <p>В принципе, можно использовать вместо refs передачу метода SampleScreen в Modal посредством props, но отладчик React выдает на такие действия warning (хотя всё работает).</p> Docker ambassador pattern /blog/docker-ambassador-pattern <img src="/files/blog/75/ambassador.jpg" alt="Ambassador" title="Ambassador" class="img-responsive" width="600" height="453" /> <br/> <p>При проектировании информационной системы очень важна ясность. Нет ничего лучше, чем элегантно построенное приложение, в котором нет лишних деталей, в том числе — лишних связей между компонентами.</p> <p>К счастью, Docker позволяет изолировать процессы друг от друга и организовывать связь между ними посредством заранее указанных портов и общих директорий. В большинстве случаев, для успеха мероприятия необходима только однонаправленная связь контейнеров, например: веб-сервер использует php-fpm, либо php-fpm использует базу данных. В этом простом случае, никаких проблем не возникает, установить такую связь между несколькими контейнерами, используя Docker Compose, очень просто:</p> <pre><code class="language-bash"> # ~/docker-apps/testapp/test.yml fpm: build: ./build/fpm nginx: build: ./build/nginx links: - fpm </code></pre> <p>Запускаем. Как видно, всё работает:</p> <pre><code class="language-bash"> $ docker-compose -f test.yml up -d Creating testapp_fpm_1... Creating testapp_nginx_1... $ docker-compose ps Name Command State Ports ------------------------------------------------------------------------- testapp_fpm_1 /usr/sbin/php5-fpm -F Up 9000/tcp testapp_nginx_1 /usr/sbin/nginx Up 80/tcp </code></pre> <p>Из контейнера testapp_nginx_1 можно адресовать testapp_fpm_1 посредством прописанных самим Docker хостов. Посмотрим на /etc/hosts:</p> <pre><code class="language-bash"> $ docker exec testapp_nginx_1 cat /etc/hosts ... testapp_fpm_1 2bae5bf71e09 fpm 2bae5bf71e09 testapp_fpm_1 fpm_1 2bae5bf71e09 testapp_fpm_1 </code></pre> <p>В принципе, возможна ситуация, когда оба связанных процесса должны знать друг о друге. Например, если поднят Selenium: необходимо из него иметь доступ до веб-сервера, чтобы исполнять тестовые задания; но также от веб-сервера нужен доступ до самого Selenium, чтобы запускать эти задания посредством BDD-фреймворка. Для ясности примера относительно предыдущего, допустим, что нам нужен доступ из nginx в php-fpm и наоборот.</p> <pre><code class="language-bash"> # ~/docker-apps/testapp/test2.yml fpm: build: ./build/fpm links: - nginx nginx: build: ./build/nginx links: - fpm </code></pre> <p>Запускаем?</p> <pre><code class="language-bash"> $ docker-compose -f test2.yml up -d Circular import between fpm and nginx </code></pre> <p>К счастью, решение есть. Невозможно добавить перекрестные ссылки только на этапе старта, добавить их после старта — никто запретить не может. На помощь придет ambassador — образ процесса-прокси, единственная задача которого — создание перекрестных ссылок между контейнерами.</p> <pre><code class="language-bash"> # ~/docker-apps/testapp/test3.yml fpm: build: ./build/fpm links: - ambassador:nginx nginx: build: ./build/nginx links: - ambassador:fpm ambassador: image: cpuguy83/docker-grand-ambassador volumes: - "/var/run/docker.sock:/var/run/docker.sock" command: "-name testapp_fpm_1 -name testapp_nginx_1" </code></pre> <p>Запускаем:</p> <pre><code class="language-bash"> $ docker-compose -f test3.yml up -d Creating testapp_ambassador_1... Creating testapp_nginx_1... Creating testapp_fpm_1... </code></pre> <p>Что произошло?</p> <pre><code class="language-bash"> $ docker exec astgo_nginx_1 cat /etc/hosts ... ambassador_1 f3437e869aff testapp_ambassador_1 testapp_ambassador_1 f3437e869aff fpm f3437e869aff testapp_ambassador_1 </code></pre> <p>Как видно, Docker добавил стандартные записи для ambassador, а уже ambassador добавил запись, указывающую на контейнер fpm. Стоит заметить, не напрямую — а через тот же самый ambassador.</p> Миграция медиабиблиотеки Wordpress в S3 /blog/move-wordpress-media-to-s3 <p> В жизни каждого разработчика есть такие задачи, которыми невозможно гордиться, а упоминать их немного стыдно — потому что всё, абсолютно всё, связанное с такими задачами плохо. Поговорим, например, о Wordpress: он хранит данные в сериализованных массивах в базе данных, не умеет в миграции данных, нарушает все мыслимые и немыслимые, гласные и негласные правила написания хорошего кода... Но зато используется в продакшене, приносит бизнесу деньги, а значит — должен поддерживаться, развиваться... а, ну еще вам может понадобиться перенести всю медиа-библиотеку на S3 (на самом деле, это может быть любой CDN). </p> <h2> Примерный план действий такой: </h2> <p> <ol> <li>Установить плагин для работы с S3</li> <li>Найти все изображения</li> <li>Залить их на S3</li> <li>Изменить пути до изображений в постах</li> <li>По возможности сохранить рассудок</li> </ol> </p> <p> Чтобы было веселее — все эти действия должны произойти в автоматическом режиме, без нажимания кнопочек в админке, изменения конфигов, т.к. установка предполагается в AWS Elastic Beanstalk. И лучше не спрашивайте меня почему. </p> <h2>Решение</h2> <p>Для начала, необходимо установить в Wordpress штатным образом плагин «tantan_wordpress_s3», чтобы не изобретать в очередной раз велосипед. С файлами плагина всё просто — они станут частью приложения, а вот конфигурацию придется немного подправить вручную.</p> <pre><code class="language-php"> $config = array( "key" => AWS_KEY, "secret" => AWS_SECRET, "bucket" => AWS_BUCKET, "wp-uploads" => 1, "expires" => 315360000, "permissions" => "public", "cloudfront" => "" ); $db->query(" INSERT INTO `wp_options` (`option_name`, `option_value`, `autoload`) VALUES ('tantan_wordpress_s3', :config, 'yes'); ", array( 'config' => serialize($config) )); </code></pre> <p>Плагин также потребуется активировать:</p> <pre><code class="language-php"> $pluginsSerialized = $db->fetchOne("SELECT `option_value` FROM `wp_options` WHERE `option_name` = 'active_plugins'"); $plugins = unserialize($pluginsSerialized); $plugins[] = "tantan-s3-cloudfront/wordpress-s3.php"; $db->query("UPDATE `wp_options` SET `option_value` = :option_value WHERE `option_name` = 'active_plugins';", array( 'option_value' => serialize($plugins) )); </code></pre> <p>На этом этапе все новые пополнения медиа-библиотеки попадут на S3. Осталось дело за миграцией старых данных. Для начала, выберем их все:</p> <pre><code class="language-php"> $stmt = $db->query(" SELECT * FROM wp_postmeta WHERE meta_key = '_wp_attachment_metadata' "); </code></pre> <p>Осталось изменить ссылки в старых данных с локальных путей на пути до файлов на CDN. В качестве библиотеки для доступа к локальным файлам использована knplabs/gaufrette.</p> <pre><code class="language-php"> foreach ($stmt->fetchAll() as $row) { $data = unserialize($row['meta_value']); $file = substr($data['file'], strpos($data['file'], '/wp-content')); $data = array( 'bucket' => AWS_S3_BUCKET, 'key' => '/wp-content/uploads/' . $file ); $serialized = serialize($data); $stmt2 = $db->query(" INSERT INTO wp_postmeta (post_id, meta_key, meta_value) VALUES (:post_id, :meta_key, :meta_value) ", array( 'meta_value' => $serialized, 'post_id' => $row['post_id'], 'meta_key' => 'amazonS3_info' )); } foreach ($localFilesystem->listKeys() as $key) { $db->query(" UPDATE `wp_posts` SET `post_content` = REPLACE(`post_content`, '/old_relative_url/wp-content/{$key}', 'http://AWS_BUCKET.s3.amazonaws.com/wp-content/{$key}') "); } </code></pre> <p>Самое главное — не забудьте скопировать сами файлы, благо это самая простая часть задачи. </p> Как прочесть переменные окружения в fpm /blog/environment-in-fpm <p>Если вы используете Docker, скорее всего, вы активно используете и переменные окружения (environment variables). В php-cli или mod_php прекрасно работает стандартный метод их получения:</p> <pre><code class="language-php"> # ~/envapp/read.php &lt;?php $env = getenv("foo"); echo $env; </code></pre> <pre><code class="language-bash"> $ php read.php bar </code></pre> <p>Однако, в php-fpm так сделать не получится: дело в настройке «clear_env» (по-умолчанию «true»). Если она активна, прочитать можно будет только те переменные окружения, что указаны в специальном белом списке. Решения, соответственно, два: либо отключить опцию «clear_env», либо добавить необходимые переменные в белый список. В разных обстоятельствах могут понадобиться оба решения:</p> <h2>Отключение clear_env</h2> <pre><code class="language-bash"> echo "clear_env = no" >> /etc/php5/fpm/pool.d/www.conf </code></pre> <h2>Белый список</h2> <pre><code class="language-bash"> echo "env[foo] = bar" >> /etc/php5/fpm/pool.d/www.conf </code></pre> Zsh: больше чем bash /blog/oh-my-zsh <p>Нет ничего удобнее хорошей командной оболочки и zsh, наверное, одна из лучших. На большинстве серверов и рабочих машин по-умолчанию стоит bash и многие даже не задумываются, что командная оболочка может или должна делать что-то еще, кроме запуска команд. Например, она должна помогать пользователю выбрать: какую команду запустить.</p> <h2>Установка</h2> <p>Установить «базовый комплект» проще простого. Например, в Ubuntu: </p> <pre><code class="language-bash"> apt-get install zsh usermod USER -s /usr/bin/zsh </code></pre> <p>Установить дополнительные плюшки тоже не сложно:</p> <pre><code class="language-bash"> curl -L https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh | sh </code></pre> <h2>Результат</h2> <p>Например, вот так выглядит zsh у меня:</p> <p><img src="/files/blog/56/zsh.png" width="943" height="358" alt="zsh" title="zsh" class="img-responsive" /></p> Удаление паролей из истории Git /blog/remove-passwords-from-git <img src="/files/blog/56/great_scott.jpg" class="img-responsive" width="600" height="338" /> <h2>Почему это важно?</h2> <p>Любая система хранения версий кода создана с целью сохранения всех изменений проекта за всю историю его существования и (следует признать) любая система хранения версий справляется с этим отлично. Поэтому будьте уверены, если когда-то в вашем коде промелькнул пароль, токен или ключ — он не пропадет в тот момент, когда вы уберете его из актуального кода и злоумышленник сможет получить к нему доступ тогда, когда все уже забудут про сам факт существования важной информации в каком-то старом коммите.</p> <p>Плохие новости состоят в том, что не существует волшебной пилюли, которая просто всё исправит и не потребует от вас никаких лишний действий или головной боли после. Хорошие новости в том, что решение всё таки существует, хотя оно и изменит каждый коммит, начиная с момента «изъятия» важной информации из хранилища — а значит, вы получите совсем другую ветку кода, с совсем другими хешами коммитов.</p> <pre><code class="language-bash"> git filter-branch --tree-filter 'git ls-files -z "*" |xargs -0 perl -p -i -e "s#(PASSWORD1|PASSWORD2|PASSWORD3)#NOT_A_PASSWORD_ACTUALLY#g"' -- --all git reset --hard git gc --aggressive --prune </code></pre> <p>Вся магия — в первой команде, которая пройдет по всем коммитам текущей веткеи и заменит PASSWORD1, PASSWORD2, PASSWORD3 на безопасный плейсхолдер — NOT_A_PASSWORD_ACTUALLY.</p> Миграция с Mercurial на Git /blog/migrating-mercurial-to-git <p><img src="/files/blog/55/migra1.jpg" alt="Миграция" title="Миграция" class="img-responsive" width="600" height="400" /></p> <p>Иногда возникает необходимость продолжать работу над старыми проектами, в которых могут использоваться устаревшие системы версий — например, mercurial или, боже упаси, svn. К счастью, для перехода с mecurial на git не требуется начинать проект с чистого листа и терять историю изменений, есть утилита, которая всё сделает для нас сама.</p> <pre><code class="language-bash"> cd git clone git://repo.or.cz/fast-export.git git init git_repo cd ~/git_repo ~/fast-export/hg-fast-export.sh -r ~/mercurial-repo git checkout HEAD </code></pre> <p>В итоге, в текущей папке мы получим самый настоящий git.</p> Собственный VPN за пять минут /blog/own-vpn-in-five-minutes <p> <img src="/files/blog/53/spy.png" class="img-responsive" alt="Шпион" title="Шпион" width="216" height="234" /> </p> <blockquote class="pull-right"> Существует несколько способов сжечь книгу. И мир полон людей, бегущих с зажженными спичками. <footer>«Звук бегущих ног», Рей Бредбери</footer> </blockquote> <h2>Лирическое вступление</h2> <p>Наиболее устойчивые системы — децентрализованные и, к счастью, интернет из их числа. Своим появлением эта технология изменила всё. Никогда ранее не было возможно так просто обмениваться знаниями и новостями, иметь свободный доступ к практически любой точке земного шара. Это позволяет любому мнению быть услышанным — и это прекрасно и очень важно. </p> <p>К сожалению, кроме людей созидающих знания, есть люди запрещающие знания. Запрещать знания — попросту глупо: знание есть знание, у него нет никакой моральной подоплеки, но оно, определенно, может быть кому-то не выгодно. И потому жгли людей, потом книги, теперь же они открыли для себя интернет. Но с интернетом такой фокус уже не пройдет. Потому что нам на помощь спешит VPN — виртуальные тоннели, с помощью которых мы будем выходить в интернет, например, из Амстердама, на самом же деле потягивая чаек из натурального русского самовара. </p> <h2>Установка сервера PPTP</h2> <p>Для начала потребуется сервер, находящийся в той точке мира, через которую вы хотите выходить в сеть, с установленным на него Docker и утилитой Docker Compose. Для этого можно использовать виртуальные сервера от Amazon или DigitalOcean — путь от регистрации до работающего сервера займет несколько минут.</p> <p>Создайте директорию /docker-apps/pptpd и разместите в ней следующие файлы.</p> <pre><code class="language-bash"> # /docker-apps/pptpd/pptpd.conf option /etc/ppp/pptpd-options pidfile /var/run/pptpd.pid localip remoteip </code></pre> <pre><code class="language-bash"> # /docker-apps/pptpd/pptpd-options name pptpd refuse-pap refuse-chap refuse-mschap require-mschap-v2 require-mppe-128 proxyarp nodefaultroute lock nobsdcomp novj novjccomp nologfd ms-dns ms-dns </code></pre> <p>В этом файле описаны данные, которые потребуются для подключения к нашему серверу:</p> <pre><code class="language-bash"> # /docker-apps/pptpd/chap-secrets username1 * password1 * username2 * password2 * </code></pre> <p>И, наконец, конфигурация для запуска демона в контейнере Docker:</p> <pre><code class="language-js"> # /docker-apps/pptpd/docker-compose.yml pptpd: image: astartsky/pptpd ports: - 1723:1723 volumes: - ./pptpd.conf:/etc/pptpd.conf - ./pptpd-options:/etc/ppp/pptpd-options - ./chap-secrets:/etc/ppp/chap-secrets privileged: true net: host restart: always </code></pre> <p>На настройках запуска этого контейнера стоит остановиться поподробнее.</p> <p>К сожалению, Docker на данный момент умеет форвардить с сетевого интерфейса хоста на виртуальный сетевой интерфейс контейнера только протоколы TCP и UDP. Так как протокол PPTPD также использует GRE для передачи данных, требуется запускать этот контейнер с сетью в режиме «host». Это означает, что контейнер будет использовать родной сетевой интерфейс своего хоста, а не виртуальный собственный — следовательно, форвардинг не потребуется вовсе. Не самое красивое решение, поэтому будем надеяться, что Docker скоро научится форвардить GRE тоже. </p> <h2>Торжественный запуск</h2> <pre><code class="language-bash"> cd /docker-apps/pptpd/ docker-compose up -d </code></pre> <p>Ура!</p> Набор гика-бегуна /blog/geek-runner-set <img src="/files/blog/54/set.jpg" class="img-responsive" alt="Набор гика-бегуна" title="Набор гика-бегуна" width="640" height="640" /> Docker: оркестрация /blog/docker-orchestration <p> <img src="/files/blog/52/1.jpg" width="500" height="216" class="img-responsive" alt="Docker" /> </p> <h2>Что такое оркестрация?</h2> <p> Оркестрация — это координация взаимодействия нескольких контейнеров. В принципе, ничто не мешает создать контейнер, в котором запущены сразу все необходимые процессы, но этот подход лишен гибкости при масштабировании, изменении архитектуры, а также создает проблемы с безопасностью, т.к. в этом случае процессы никак не изолированы и могут без ограничений влиять друг на друга. Оркестрация же позволяет строить информационные системы из небольших кирпичиков-контейнеров, каждый из которых ответственен только за одну задачу, а общение осуществляется через сетевые порты и общие директории. При необходимости контейнеры в таком «оркестре» можно заменять на другие: например, чтобы проверить работу приложения на другой версии базы данных. </p> <h2>Установка Docker Compose</h2> <p> Почитать об установке Docker Compose на разные ОС можно на официальном сайте проекта: <a href="https://docs.docker.com/compose/">https://docs.docker.com/compose/</a> </p> <h2>Настройка оркестрации</h2> <p> Для управления параметрами оркестризации используется конфигурационный файл, подробно описывающий как параметры запуска отдельных контейнеров, так и все связи между ними. По-умолчанию, конфиг называется docker-compose.yml и имеет подобный вид: </p> <p> <pre><code class="language-javascript"> fpm: build: ./fpm/ links: - db - mc volumes: - ./www:/var/www db: build: ./db/ volumes: - ./db:/var/lib/mysql ports: - 3306:3306 mc: build: ./mc/ ports: - 8080:1080 nginx: build: ./nginx/ links: - fpm ports: - 80:80 - 443:443 volumes: - ./www:/var/www </code></pre> </p> <p> В примере представлено окружение для простого приложение на php/mysql, предназначенного для тестирования и разработки. </p> <p> <ul> <li><strong>build</strong> — Относительный путь до папки, содержащей Dockerfile. Позволяет заново собрать контейнер. Альтернативой ему является <strong>image</strong>, указывающий название образа. Для одного узла можно указать либо сборку, либо готовый образ — но не вместе. </li> <li><strong>links</strong> — Прямые связи между контейнерами в связке. Например, контейнер fpm сможет обращаться к контейнеру db напрямую, используя хост «db». Использовать этот адрес можно где угодно, так как он представляет из себя запись в /etc/hosts, оставленную Docker при запуске контейнеров и указывающую на текущий контейнер db.</li> <li><strong>volumes</strong> — Монтирование директорий хоста в файловую систему контейнера. Несколько контейнеров могут делить доступ к директории. Так, например, в примере и Nginx, и Fpm имеют доступ к корневой директории проекта.</li> <li><strong>ports</strong> — Перенаправление портов хоста в порты контейнера. На самом деле порты не обязаны совпадать. Никто не мешает направлять порт контейнера 80 в порт хоста 8080 (или наоборот). Также можно указать сетевой интерфейс, на котором порт хоста будет «слушать». Например,</li> </ul> </p> <p> И т.д. Параметры во многом аналогичны тем, что используются при запуске контейнера через «docker run». Подробно можно прочитать на официальном сайте. </p> <h2>Сборка контейнеров</h2> <p> <pre><code class="language-bash"> # остановка контейнеров (если они уже были собраны ранее) $ sudo docker-compose stop # удаление контейнеров (если они уже были собраны ранее и остановлены) $ sudo docker-compose rm # сборка $ sudo docker-compose build </code></pre> </p> <h2>Запуск контейнеров</h2> <p>При указании ключа «d» утилита будет запущена в фоновом режиме.</p> <p> <pre><code class="language-bash"> $ sudo docker-compose up </code></pre> </p> Docker: создание и запуск контейнера /blog/docker-create-container <p> <img src="/files/blog/51/1.png" class="img-responsive" alt="Docker" width="708" height="183" /> </p> <blockquote class="pull-right" cite="http://www.dockerbook.com"> Containerization is the new virtualization <footer>The Docker Book</footer> </blockquote> <div style="clear:both;"></div> <h2>Что такое Docker?</h2> <p> В отличии от классических систем виртуализации, которые эмулируют работу компьютерного железа и ядра операционной системы поверх него, Docker использует виртуализацию на уровне ядра: все виртуализируемые им процессы делят ядро операционной системы хоста, что позволяет значительно сократить как требуемые ресурсы, так и время, необходимое на запуск/обслуживание таких систем. В идеале, каждый процесс изолируется в собственном контейнере (linux container, lxc), который содержит набор необходимых ему библиотек, что позволяет забыть о dependecy hell, а также легко переносить стек проекта между серверами. В статье ниже я проиллюстрирую, как можно использовать Docker для настройки среды разработчика, повторяющей среду продакшена: предположим, что там используется Ubuntu LTS, PHP 5.4, Nginx. </p> <h2>Установка Docker</h2> <p> Почитать об установке Docker на разные ОС можно на официальном сайте проекта: <a href="https://docs.docker.com/installation/#installation">https://docs.docker.com/installation/#installation</a> </p> <h2>Настройка контейнера</h2> <p> Прежде всего, мы должны написать инструкцию, по которой Docker должен сформировать образ (image), на основе которого будет работать наше приложение — Dockerfile. </p> <p> <small>src: build/backend/Dockerfile</small> <pre><code class="language-bash"> # Используем за основу контейнера Ubuntu 14.04 LTS FROM ubuntu:14.04 # Переключаем Ubuntu в неинтерактивный режим — чтобы избежать лишних запросов ENV DEBIAN_FRONTEND noninteractive # Устанавливаем локаль RUN locale-gen ru_RU.UTF-8 && dpkg-reconfigure locales # Добавляем необходимые репозитарии и устанавливаем пакеты RUN apt-get install -y software-properties-common RUN add-apt-repository -y ppa:ondrej/php5-5.6 RUN add-apt-repository -y ppa:nginx/stable RUN sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4F4EA0AAE5267A6C RUN apt-get update RUN apt-get upgrade -y RUN apt-get install -y wget curl php5-fpm php5-mysql php5-gd php5-curl php-pear php-apc php5-mcrypt php5-imagick php5-memcache supervisor nginx # Добавляем описание виртуального хоста ADD astgo.ru /etc/nginx/sites-enabled/astgo.ru # Отключаем режим демона для Nginx (т.к. запускать будем сами) RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf # Отключаем режим демона для php-fpm RUN sed -i -e "s/;daemonize\s*=\s*yes/daemonize = no/g" /etc/php5/fpm/php-fpm.conf # Добавляем конфиг supervisor (описание процессов, которые мы хотим видеть запущенными на этом контейнере) ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf # Объявляем, какие директории мы будем подключать VOLUME ["/var/www"] # Объявляем, какой порт этот контейнер будет транслировать EXPOSE 80 # Запускаем supervisor CMD ["/usr/bin/supervisord"] </code></pre> </p> <p> <small>src: build/backend/supervisord.conf</small> <pre><code class="language-ini"> [supervisord] nodaemon=true loglevel=debug [program:nginx] command=/usr/sbin/nginx autorestart=true [program:php5-fpm] command=/usr/sbin/php5-fpm autorestart=true </code></pre> </p> <p> <ul> <li><strong>FROM</strong> — указывает название образа (image), который будет взят за основу. </li> <li><strong>ENV</strong> — устанавливает переменную среды</li> <li><strong>RUN</strong> — запустить команду в контейнере (все команды исполняются с полными правами в пределах контейнера)</li> <li><strong>ADD</strong> — добавить файл в контейнер</li> <li><strong>VOLUME</strong> — указать монтируемые директории (их можно монтировать на хост машину или на другие контейнеры)</li> <li><strong>EXPOSE</strong> — указать транслируемые порты (их можно транслировать на хост машину или на другие контейнеры)</li> <li><strong>CMD</strong> — запустить процесс (это и будет процесс, вокруг которого построен контейнер)</li> </ul> </p> <p> С полным списком инструкций, допустимых в Dockerfile, можно обратиться к <a href="https://docs.docker.com/articles/dockerfile_best-practices/#the-dockerfile-instructions">руководству</a>. </p> <h2>Сборка образа</h2> <p>По написанной нами инструкции необходимо собрать образ. Назовем его astgo.ru/dev</p> <p> <pre><code class="language-bash"> $ sudo docker build -t astgo.ru/dev ~/PATH_TO_DOCKERFILE_DIR </code></pre> </p> <p>Мы увидели пошаговую сборку нашего образа. Если в процессе сборки произошли ошибки, на этом этапе их можно исправить и снова попробовать собрать образ. Проверим, что всё прошло удачно и образ появился в системе:</p> <p> <pre><code class="language-bash"> $ sudo docker images | grep 'astgo.ru/dev' astgo.ru/dev latest d2444af3ee61 3 minutes ago 387.2 MB </code></pre> </p> <h2>Запуск контейнера</h2> <p>Теперь создадим контейнер, запустив образ. Мы будем запускать образ astgo.ru/dev, свяжем 80 порт хоста с 80 портом контейнера, а также директорию /var/www хоста с /var/www контейнера. Последнее позволяет нам хранить код проекта на самой хост машине, не теряя его каждый раз, когда мы, по каким-то причинам, пересоздаем контейнер.</p> <p> <pre><code class="language-bash"> $ sudo docker run -v /var/www:/var/www -p 80:80 -t astgo.ru/dev </code></pre> </p> <p> Все, контейнер запущен. Мы можем убедиться в том, что он работает, посмотрев в список запущенных контейнеров: </p> <p> <pre><code class="language-bash"> $ sudo docker ps | grep 'astgo.ru/dev' d8429cc192c0 astgo.ru/dev:latest "/usr/bin/supervisor 20 seconds ago Up 19 seconds>80/tcp reverent_fermi </code></pre> </p> <p>Так как мы не указали имя для нового контейнера, то он получил автоматически сгенерированное имя reverent_fermi, по которому мы теперь можем к нему обращаться. </p> <h2>Подключение к запущенному контейнеру</h2> <p>Попробуем зайти в контейнер и посмотреть, как у него дела. Это может потребоваться, например, для запуска каких-то консольных команд в среде приложения.</p> <p> <pre><code class="language-bash"> $ sudo docker exec -i -t reverent_fermi bash root@d8429cc192c0:/# </code></pre> </p> <p>В следующих статьях я расскажу как создать контейнер для базы данных, а также как можно упростить процесс запуска стека контейнеров.</p> «No space left on device» и inodes /blog/no-space-left-on-device-and-inodes <div> <blockquote class="pull-right" cite="http://lkml.indiana.edu/hypermail/linux/kernel/0207.2/1182.html"> Честно говоря, я тоже не знаю. Это был всего лишь термин, который мы начали использовать. Из-за немного необычной структуры файловой системы, при которой информация о доступе к файлам хранится в виде плоского (двумерного) массива на диске, отдельно от всей информации об иерархии каталогов, лучшее, что я могу предположить (для «и») - это «индекс». Таким образом, и-номер являлся индексом в этом массиве, и-нод - выбираемым элементом массива. (Приставка «и-» использовалась в первой версии руководства; со временем дефис перестали употреблять). <footer>Деннис Ритчи</footer> </blockquote> </div> <p> Я был очень удивлен, когда получил ошибку следующего содержания при попытке открытия сессии: </p> <p> <pre><code class="language-bash"> PHP Warning: Unknown: open(/tmp/sess_e34ad6u6f51gum3htmqkd7ldn6, O_RDWR) failed: No space left on device (28) </code></pre> </p> <p> Ведь при этом df -h показывал такую картину, что свободного пространства много: </p> <p> <pre><code class="language-bash"> df -h </code></pre> </p> <p> <pre><code class="language-bash"> Filesystem 1K-blocks Used Available Use% Mounted on /dev/sda2 19091584 3784332 14314332 21% / udev 8192628 8 8192620 1% /dev tmpfs 3280992 284 3280708 1% /run none 5120 4 5116 1% /run/lock none 8202472 92 8202380 1% /run/shm /dev/mapper/vg0-var 47926152 24100456 21368096 54% /var </code></pre> </p> <p>Оказалось, кончились файловые дескрипторы (inode), но в сообщении об ошибке этого не указывается. Проверить количество файловых дескрипторов в системе можно такой командой:</p> <p> <pre><code class="language-bash"> df -i </code></pre> </p> <p> Имеет смысл отслеживать эту метрику, как и занятое на диске пространство. </p> «Strangler Application» и SSI /blog/strangler-application-and-ssi <p> <blockquote class="pull-right" cite="http://martinfowler.com/bliki/StranglerApplication.html"> When Cindy and I went to Australia, we spent some time in the rain forests on the Queensland coast. One of the natural wonders of this area are the huge strangler vines. They seed in the upper branches of a fig tree and gradually work their way down the tree until they root in the soil. Over many years they grow into fantastic and beautiful shapes, meanwhile strangling and killing the tree that was their host. <footer>Martin Fowler, «Strangler application»</footer> </blockquote> </p> <p> <img src="/files/blog/48/2.jpg" class="img-responsive" alt="strangler vines" /> </p> <p> Однажды мне достался большой и запущенный веб-проект, обладающий, наверное, всеми ярко выраженными признаками и проблемами, что вообще свойственны наследуемым системам. Все компоненты системы были написаны таким образом, что дальнейшее их расширение, изменение и поддержка если и были вообще возможны, превращались в настоящий кошмар для разработчиков и тестировщиков. Даже основные модули, такие как ядро и маршрутизация, были тесно завязаны на серверное окружение, миллион магических цифр, динамически назначаемых констант, огромных ветвистых функций и неочевидных хаков. Цикломатическая сложность с тремя нулями не была чем-то удивительным. </p> <p> Переписать всю эту логику, сохраняя работоспособность системы во всех граничных случаях не представлялось возможным, как и развитие имеющейся системы. Было принято решение использовать принцип «Strangler Application», постепенно реализуя отдельные компоненты с нуля и заменяя ими старые. Но, к сожалению, цена входа оказалась слишком велика. Не было проблемой переписать серверную логику, формирующую отдельный блок, но чтобы интегрировать получившийся код на страницу, также требовалось бы переписать серверную логику и всех остальных блоков на странице, т.к. при инициализации приложение серьёзно затрудняло возможность аккуратного подключения каких-то сторонних компонентов. Но вместо того чтобы кушать слона целиком, слона следует кушать понемногу :) </p> <p> В такой ситуации может оказаться полезной такая технология, как SSI. Она позволяет осуществлять пост-процессинг тела ответа, например, применять условные функции или делать дополнительные запросы. При реализации «Strangler Application» это может пригодиться следующим образом: старое приложение загружается как обычно, но вместо какого-то отдельного блока на странице происходит обращение к новому приложению. Веб-сервер, получив такие инструкции, делает дополнительный запрос по указанному адресу и вместо инструкции SSI размещает полученный ответ. Можно сравнить это с загрузкой блока посредством AJAX-запроса, но только значительно быстрее и совершенно прозрачно для пользователя, т.к. вся магия происходит еще на сервере, до отправки ответа клиенту. </p> <p> Такая строка в коде сайта заставит сервер сделать запрос по адресу «/my-new-application-url» и «вклеить» его результат в код сайта: </p> <p> <pre><code class="language-php"> &lt;!--# include virtual=&quot;/my-new-application-url&quot; --&gt; </code></pre> </p> <p> В конфиге nginx следует разрешить такие запросы: </p> <p> <pre><code class="language-nginx"> location / { ... ssi on; ... } </code></pre> </p> <p>Вот так всё просто. Минусом может стать производительность такого подхода, но в данном случае старое ядро грузилось 5-8 секунд, а новое около 150 мс, что не делает большой погоды.</p> <p>Следует также учесть, что обработка инструкций SSI происходит только в том случае, если nginx получает ответ от апстрима в чистом виде, без сжатия. Если ответ будет сжат, инструкции выполнены не будут.</p> Удаление слешей из урла в nginx /blog/nginx-remove-url-slashes <p>Иногда требуется удалить множественные слеши из адреса, например:</p> <p><strong>http://mysite.com//page//1.htm/</strong> --> <strong>http://mysite.com/page/1.htm</strong></p> <p>В этом случае, можно использовать следующую конструкцию для удаления слешей из середины:</p> <p> <pre><code class="language-nginx"> set $test_uri $scheme://$host$request_uri; if ($test_uri != $scheme://$host$uri$is_args$args) { rewrite ^ $scheme://$host$uri$is_args$args? permanent; } </code></pre> </p> <p>И с конца:</p> <p> <pre><code class="language-javascript"> rewrite ^/(.*)/$ /$1 permanent; </code></pre> </p> Событийная архитектура веб-приложения /blog/event-based-javascript-architechture <p> <img src="/files/blog/46/1.jpg" class="img-responsive" alt="2+2" title="2+2" /> </p> <p> Одной из самых плохо расширяемых частей любого веб-приложения является его клиентский код, как правило, написанный на javascript. Во многих проектах он представляет собой джунгли из функций, принимающих коллбеки — и это в лучшем случае. Многие склонны винить в таком положении дел непосредственно сам язык, припоминая его «низкое» происхождение, странное поведение и отсутствие синтаксического сахара. Несомненно, в этом есть своя правда. Но я полагаю, что основная причина такой запутанности заключается в том, что построить для взаимодействия с интерфейсом стройную и расширяемую архитектуру, руководствуясь только принципами императивного программирования — невозможно. И хотя модель реализации событий в браузере сама подводит к идее организации кода декларативно, почему-то немногие на это отваживаются. </p> <p> Приведу пример простейшего API корзины интернет-магазина, а для событий используем уже имеющуюся «шину» — элемент body. </p> <p> <pre><code class="language-javascript"> function add(item_id, quantity) { $.ajax({ type: "POST", url: "/api/cart/add", dataType: 'json', data: {item_id: item_id, quantity: quantity} }).done(function (data) { $('body').trigger('QuantityChanged', [data.item_id, data.quantity]); $('body').trigger('PriceChanged', [data.item_id, data.sum]); }); }; </code></pre> </p> <p> Теперь после добавления товара отправляется два события: изменилось количество, изменилась цена. Можно было бы реализовать это и одним событием с большим количеством параметров, но так лучше не делать: если слушателю события интересно только изменение цены, нет нужды передавать ему и все остальные тридцать три параметра. Это повышает шанс ошибки и ведет к неразберихе в коде. События должны быть предельно атомарными, чтобы и слушателей можно было делать небольшими, выполняющими лишь одну роль — но хорошо. Можно также расширить количество событий, добавив, например, своё событие для ошибки, начала запроса и т.д. </p> <p> <pre><code class="language-javascript"> /** * Обновить количество товара в корзине */ $('body').on('QuantityChanged', function (e, item_id, quantity) { $('[data-item-quantity="' + item_id + '"]').html(quantity); }); /** * Обновить цену товара в корзине */ $('body').on('PriceChanged', function (e, item_id, sum) { $('[data-item-sum="' + item_id + '"]').html(sum); }); </code></pre> </p> <p> Никто теперь не мешает нам расширить систему, добавив на какой-то отдельной странице дополнительное действие при добавлении товара в корзину. И это всё без изменения API, без изменения старых событий и без условных операторов. </p> Как научить Toggl находить трекеры по нестандартным адресам /blog/how-to-teach-toggl <p>Есть такой замечательный сервис Toggl, позволяющий отслеживать затраты времени между разными трекерами задач, или вовсе без оных. К сожалению, расширение для Google Chrome, добавляющее автоматические кнопки к задачам в трекере, работает не везде, где хотелось бы. Например, оно будет работать, если вы покупаете Jira в облаке (*.atlassian.net), но и усом не поведет, если у вас Jira на собственном сервере (jira.*.com). Ничего, выход есть — и довольно простой.</p> <p> <pre><code class="language-git"> git clone https://github.com/toggl/toggl-button cd toggl-button nano src/manifest.json </code></pre> </p> <p> Необходимо добавить строчки в permissions и content_scripts: </p> <pre><code class="language-javascript"> ... "permissions": [ "tabs", ... "*://myawesomeredmine.com/*", "*://jira.myawesomesite.com/*" ], ... "content_scripts": [ { "matches": [ ... "*://myawesomeredmine.com/*", "*://jira.myawesomesite.com/*" ], ... { "matches": ["*://myawesomeredmine.com/*"], "js": ["scripts/content/redmine.js"] }, { "matches": ["*://jira.myawesomesite.com/*"], "js": ["scripts/content/jira.js"] } ] </code></pre> <p>Этого достаточно. Полученную версию можно установить прямо в распакованном виде (в режиме разработчика), либо запаковать и установить обычным способом.</p> Пакет для балансировки запросов к статическим файлам /blog/url-balancer <h1>Url Balancer</h1> <p>Пакет предназначен для балансировки запросов статических файлов (если, вдруг, они у вас не на CDN). Такая техника позволяет параллельно загружать статику, обходя ограничения на количество одновременных потоков до одного домена. </p> <p> <ul> <li>Поддержка Composer (через packagist)</li> <li>Поддержка Silex</li> <li>Расширяемость стратегий балансировки</li> </ul> </p> <h2>Установка</h2> <p>Рекомендуется установка через Composer:</p> <pre><code class="language-javascript"> { "require": { "astartsky/url-balancer": "1.1.2" } } </code></pre> <h2>Пример использования</h2> <pre><code class="language-php"> $urlBalancer = new \Astartsky\UrlBalancer\UrlBalancer(); $urlBalancer->setStrategy(new \Astartsky\UrlBalancer\Strategy\HashStrategy()) $urlBalancer->addBucket(new \Astartsky\UrlBalancer\Domain("s1.myawesomesite.com")); $urlBalancer->addBucket(new \Astartsky\UrlBalancer\Domain("s2.myawesomesite.com")); $urlBalancer->addBucket(new \Astartsky\UrlBalancer\Domain("s3.myawesomesite.com")); $url = $urlBalancer->getUrl("/images/my_impressive_content.png"); </code></pre> <h2>Ссылки</h2> <p>На Packagist.org: <a href="https://packagist.org/packages/astartsky/url-balancer">пакет</a></p> <p>На Bitbucket.org: <a href="https://bitbucket.org/astartsky/urlbalancer">репозитарий</a></p> Как команда технарей свою студию создавала /blog/kak-komanda-tehnarej-svoju-studiju-sozdavala <p>Интересная история о том, как мы внезапно создали с нуля студию в изложении andry:</p> <p><img src="http://habr.habrastorage.org/post_images/3ea/58d/5e6/3ea58d5e6d43613e8952550e52612a8f.jpg" class="img-responsive" alt="Команда"/></p> <p>Уверен, многих технарей посещала идея создания своего бизнеса. Вот и у нас в определенный момент все звёзды сложились так, что казалось — это беспроигрышный вариант: сильная техническая команда, откуда ни возьмись появились менеджеры, готовые продавать наши услуги, есть даже пара проектов на старт. Грех не попробовать. И мы рискнули. Фактически всё надо ставить с нуля. И в первый же месяц всё перевернулось с ног на голову.</p> <p><a href="http://habrahabr.ru/post/211813/">Читать дальше...</a></p> Minecraft /blog/minecraft <p><img src="/files/blog/60/minecraft.jpg" alt="Minecraft" title="Minecraft" class="img-responsive" /></p> <p>Виртуальные миры бывают очень разные. Куда-то лучше не заходить без цепкого глаза, куда-то без быстрой реакции, а куда-то и без мощной видеокарты. Где-то требуют яростно кликать, где-то быстро думать, а где-то можно и дремать в процессе. В них есть знойные пустыни, длинные реки, высокие горы, дикие леса. Всего там в избытке, кроме свободы. Даже в самых свободных мирах выбор сводится к короткому списку, выгравированному на инварианте сюжетной линии и игрового процесса. К чему я все это пишу? Хочу рассказать вам о мире, в котором вы можете заложить под этот самый инвариант добрый кусок динамита, построить настоящий компьютер, прокатиться по нему верхом на свинье, а потом все это взорвать к чертям. Или не взрывать, свобода же.</p> А вот и я, всем привет /blog/here-i-am <p><img src="/files/blog/61/dragon.jpg" alt="Dragon" title="Dragon" class="img-responsive" /></p> <p> Сразу же отвечу на вопрос о том, куда же я пропал на целый год: да никуда и не пропадал! Живу себе тихо своей жизнью, хоть она и немного поменялась за последнее время, а всякие мысли лытдыбрового характера все мельчают, и мельчают, пока не попадают прямиком в твиттер. Ну а для технических штук у меня отдельная площадка есть, чтобы вас не утомлять мало кому интересными темами. Хотя это вполне работает, я по вам все равно скучаю. Ведь сколько копий сломано в комментариях в моей ленте или, может, ваших, друзья. Напряженные холивары, разговоры за жизнь, просто болтовня -- все это очень круто было, и мне этого не хватает. Но вспоминаю я о таком все реже. </p> <p> Что ж у меня нового? </p> <p> Я отпустил бороду. Борода это круто. Вы можете, конечно, со мной спорить, если хотите, но это факт. Она иногда немного мешает целоваться, зато в остальное время тепло и уютно, можно дергать её в задумчивости, поглаживать, даже расччесывать, словом, очень полезная штука. Гриву отпустил, но её приходится смирять резинкой в форме хвоста: мои волосы категорически отказываются слушаться меня по-хорошему, приходится так, по плохому. Но иногда я отпускаю их погулять. </p> <p> Я совсем перестал участвовать в холиварах в интернете. Да, это весело, да это забавно, но это требует такую уйму времени, которой у меня, к сожалению, уже нет. Помню как мы спорили с кем-то на очередную мировоззренческую тему года так три назад, я тогда только устроился работать на фултайм, приходил домой и... половину вечера убивал на составление внятного ответа. Меня хватило очень ненадолго. Есть много более интересных вещей, которыми можно заняться для развлечения: троллинг, например. </p> <p> Еще я переехал в Москву, в Крылатское: и очень тому рад. С трех сторон тут зелень, до метро рукой подать, панорамный вид на лесопарк. Можно забыть о пробках на Можайке, о переполненных душных электричках и всяких вокзалах, о соседях-алкашах и всем таком родном, но надоевшем. Свой дом, пусть и условно свой -- это, все таки, уже крепость. На работу с апреля по конец ноября катался на своих двоих колесах, на велосипеде. В один конец около 15-20 километров, намотал тысячи километров за год. Ветер в лицо, хэви металл в уши: до этого никогда не радовался дороге так, как в этом году. </p> <p> В общем, я всегда где-то здесь. Может, еще что-то расскажу скоро. </p> Ветка git в командной строке /blog/git-branch-at-command-promtp <p> Если активная работа над проектом идет над несколькими ветками сразу, всегда полезно знать, на какой ветке в текущий момент находится указатель HEAD репозитария. Чтобы не полагаться на память, можно добавить отображение названия ветки в приглашение командной строки. Для этого откроем на редактирование ~/.bashrc</p> <pre><code class="language-bash"> export PS1=&#39;\u@\h: \w\[\033[01;33m\]$(__git_ps1) \$\[\033[00m\] &#39; </code></pre> <p> Любуемся результатом:</p> <p> <img alt="" src="/files/blog/40/1.jpg" class="img-responsive" /> </p> <p>Но, по хорошему, лучше просто <a href="/blog/oh-my-zsh">поставьте zsh</a> — оно того стоит.</p> Добавление файла подкачки в Ubuntu /blog/ubuntu-add-swap-file <p> Иногда случаются ситуации, когда для корректного совершения всех задуманных операций у компьютера просто не хватает реальной оперативной памяти. В этом случае, можно занять её у диского пространства, поступившись скоростью обращения: прожорливую задачу мы выполним, хоть и не так быстро, как хотелось бы :) Во многих системах файл подкачки создается еще при установке системы. Однако виртуальные сервера, как правило, поставляются без виртуальной памяти вообще.&nbsp;</p> <h2>Создание файла</h2> <pre><code class="language-bash"> sudo dd if=/dev/zero of=/swap bs=1G count=4 </code></pre> <p>Выглядит как достаточно непонятная команда. Рассмотрим параметры подробно:</p> <ul> <li> <strong>if, Input File:</strong>&nbsp;Источник данных для файла: а нашем случае он будет полностью заполнен нулевыми байтами (ASCII NUL, 0x00). Не то чтобы нам это было важно, просто это просто и быстро;&nbsp;</li> <li> <strong>of, Output File:</strong>&nbsp;Вывод данных. Здесь это простой файл;</li> <li> <strong>bs, Bytes:</strong> Количество байт для одной операции записи;</li> <li> <strong>count</strong>: Количество блоков заданного размера;</li> </ul> <p> <p>Есть альтернативный вариант, если вы торопитесь:</p> <pre><code class="language-bash"> sudo fallocate -l 4G /swap </code></pre> <p>Итого, мы получим 4 гигабайта чистого счастья (1024 раза по 524288 килобайт).</p> <h2>Разметка файла подкачки</h2> <p> Время подготовить заготовку файла для высокой миссии.</p> <pre><code class="language-bash"> mkswap /swap </code></pre> <h2> Установка прав доступа</h2> <p> Пользоваться файлом должна иметь возможность только система. Иначе мы создадим опасную уязвимость, когда непривилегированное приложение может получить доступ к чужой оперативной памяти. &nbsp;</p> <pre><code class="language-bash"> chown root:root /swap chmod 0600 /swap </code></pre> <h2> Активация файла подкачки</h2> <p> Пришло время потянуть за главный рычаг и включить файл подкачки.</p> <pre><code class="language-bash"> swapon /swap </code></pre> <p> Почти всё. Осталось только сохранить достигнутые успехи на века: каждый раз после перезагрузки вводить какие-то команды совершенно непрактично.</p> <pre><code class="language-bash"> nano /etc/fstab </code></pre> <pre><code class="language-bash"> /swap swap swap defaults 0 0 </code></pre> <p> Теперь при загрузке системы виртуальная память включится сама.</p> <pre><code class="language-bash"> free -m </code></pre> <p> Если все прошло хорошо, мы увидим эффект подключения файла подкачки.</p> Как вежливо попросить git напомнить о миграциях /blog/git-show-migrations <p> Иногда важно помнить о том, что вместе с апдейтом кода появилась и новая миграция, которую нужно бы не забыть запустить. Первый вариант &mdash; git merge-base и еще несколько полезных команд. Задачу это решает, но требует множества действий &mdash; есть простор для фантазии и автоматизации. Пускай git сам напоминает нам о новых миграциях!</p> <pre><code class="language-bash"> #!/bin/bash HAS_NEW_MIGRATIONS=0 git diff HEAD@{1} HEAD@{0} --name-only --diff-filter=A | grep &#39;migration&#39; | while read FILENAME; do if [ &quot;$HAS_NEW_MIGRATIONS&quot; == 0 ] ; then echo -en &quot;\033[32mМиграции добавлены: \033[0m \n&quot; HAS_NEW_MIGRATIONS=1 fi echo -en &quot;\033[32m &mdash; &quot; $FILENAME &quot;\033[0m \n&quot; done HAS_MODIFIED_MIGRATIONS=0 git diff HEAD@{1} HEAD@{0} --name-only --diff-filter=M | grep &#39;migration&#39; | while read FILENAME; do if [ &quot;$HAS_MODIFIED_MIGRATIONS&quot; == 0 ] ; then echo -en &quot;\033[31mМиграции изменены: \033[0m \n&quot; HAS_MODIFIED_MIGRATIONS=1 fi echo -en &quot;\033[31m &mdash; &quot; $FILENAME &quot;\033[0m \n&quot; done exit 0 </code></pre> <pre><code class="language-bash"> ~/project/.git/hooks/post-checkout ~/project/.git/hooks/post-merge ~/project/.git/hooks/post-rewrite </code></pre> <p>Миссия выполнена. Теперь при обновлении мы будем получать зеленый список новых миграций и красный список измененных миграций (но лучше такого вовсе не допускать). Не забудьте поставить файлу права на выполнение.</p> <p>На самом деле, лучше используйте какое-нибудь готовое решение для учета миграций, которое, в том числе, умеет их откатывать. А еще лучше — делайте это автоматически, используя Continious Delivery.</p> Снятие кадров с вебкамеры на Windows /blog/windows-webcam-screenshot <p> Для начала нам понадобится <a href="http://www.videolan.org/vlc/">VCL</a>. После его установки, мы можем отдавать ему команды из консоли, в том числе, прозрачно для пользователя. Например, вот такой файл позволит снять один кадр с вебкамеры и положить его в указанную директорию.</p> <pre><code class="language-bash"> &quot;C:\Program Files (x86)\VideoLAN\VLC\vlc.exe&quot; --dshow-vdev=&quot;iLook 310&quot; --dshow-size=640x480 --video-filter=scene --no-audio --scene-path=C:\Users\Ast\Pictures\Snaps\ --scene-format=jpeg --scene-prefix=snap --run-time=1 --intf=dummy -V dummy --scene-replace --dummy-quiet &quot;dshow://&quot; vlc://quit set datetimef=%date:~-4%_%date:~3,2%_%date:~0,2%__%time:~0,2%_%time:~3,2%_%time:~6,2% copy &quot;C:\Users\Ast\Pictures\Snaps\snap.jpeg&quot; &quot;C:\Users\Ast\Pictures\Snaps\%datetimef%.jpeg&quot; </code></pre> <p>В данном примере &laquo;iLook 310&raquo; &mdash; название источника, его можно посмотреть через GUI VLC.</p> Первичная настройка git /blog/git-first-config <h2> Алиасы</h2> <p> Получение статуса</p> <pre><code class="language-git"> git config --global alias.st status git st </code></pre> <p> Наглядный лог</p> <pre><code class="language-git"> git config --global alias.lg &quot;log --color --graph --pretty=format:&#39;%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)&lt;%an&gt;%Creset&#39; --abbrev-commit&quot; git lg git lg -p </code></pre> <h2> Настройка пользователя</h2> <pre><code class="language-git"> git config --global user.name &quot;Username&quot; git config --global user.email &quot;foo@bar.com&quot; </code></pre> <h2> Добавим цвета</h2> <pre><code class="language-git"> git config --global color.branch auto git config --global color.diff auto git config --global color.interactive auto git config --global color.status auto </code></pre> Алиас для подключения по SSH /blog/ssh-alias <p>Иногда бывает удобно прописать короткий адрес для подключения по ssh. Например, когда для подключения необходимы одни и те же параметры, либо когда предполагается возможность достаточно частой смены параметров, либо просто &mdash; порядка ради. В любом случае, если вам больше нравится возможность заменить первое на второе, добро пожаловать.</p> <pre><code class="language-bash"> ssh -p 22 -i ~/.ssh/git_id_rsa git@git.test.com </code></pre> <pre><code class="language-bash"> ssh repository </code></pre> <p>Все, что нам нужно, это описать параметры подключения в конфиге. Например, так.</p> <pre><code class="language-bash"> # ~/.ssh/config Host repository Hostname git.test.com Port 22 User git IdentityFile ~/.ssh/git_id_rsa </code></pre> Как привести в порядок репозитарий /blog/cleaning-repository <p> Всегда приятно начинать проект с нуля, самому создавать под него репозитарий, определять принятый в нём стиль кодирования, да и многое другое. Но иногда нам достаются &laquo;трудные дети&raquo; с тяжелым прошлым, включающим мешанину табуляции и пробелов, разных способов обрыва строки, кодировок. Понятное дело, что все это приводит систему контроля версий к истерике. Но не беда. Linux to the rescue!</p> <h2> Форматирование</h2> <p> Нам пригодится утилита <a href="http://linux.die.net/man/1/expand">expand</a>.</p> <pre><code class="language-bash"> find ./ -type f -name &quot;*.php&quot; -exec sh -c &#39;expand -t 4 {} &gt; _tmp_ &amp;&amp; mv _tmp_ {}&#39; \; </code></pre> <p> Данная команда заменит в каждом *.php файле табуляцию на 4 пробела. В обратную сторону работает команда <a href="http://linux.die.net/man/1/unexpand">unexpand</a>.</p> <h2> Завершение строки</h2> <p> Исторически так сложилось, что способов завершить строку в текстовом файле довольно много. Самые распространенные: версия Windows (CR+LF) и Unix (LF). В репозитарии же следует выбрать что-то одно. Нам придет на выручку другая замечательная утилита <a href="http://linux.die.net/man/1/dos2unix">dos2unix </a>(еще более замечательная тем, что нет необходимости париться с временными файлами!)</p> <pre><code class="language-bash"> find ./ -type f -name &quot;*.php&quot; -exec sh -c &#39;dos2unix {}&#39; \; </code></pre> <h2> Еще одно решение: hook для git`а</h2> <p> В этом случае, мы обрабатываем только те файлы, которые были изменены в этом коммите, а не все файлы в репозитарии вообще. Кроме того, мы проверяем только определенные типы файлов, менять табы на пробелы в бинарных файлах, по распространенному мнению, не стоит. </p> <pre><code class="language-bash"> nano repository_path/.git/hooks/pre-commit </code></pre> <pre><code class="language-bash"> #!/bin/bash ALLOWED_EXTENSIONS=('php' 'phtml' 'js' 'css' 'html' 'htm' ) git diff --cached --name-only --diff-filter=ACM | while read FILENAME; do FILENAME_EXTENSION=${FILENAME#*.} for ALLOWED_EXTENSION in "${ALLOWED_EXTENSIONS[@]}" do if [ "$ALLOWED_EXTENSION" == "$FILENAME_EXTENSION" ] ; then # fixing line endings dos2unix --quiet $FILENAME # converting tabs into spaces expand -t 4 $FILENAME > _tmp_ && mv _tmp_ $FILENAME git add $FILENAME fi done done exit 0 </code></pre> Смена пароля пользователя MySQL /blog/mysql-password-recovery <h2>1. Остановите сервер</h2> <p>Вы должны остановить процесс сервера mysql (одним из указанных способов)</p> <pre><code class="language-bash"> /etc/init.d/mysql stop </code></pre> <pre><code class="language-bash"> service mysql stop </code></pre> <h2>2. Запустите сервер без проверки привилегий</h2> <p>В этом режиме сервер будет предоставлять полные права, даже если пароль не будет указан.</p> <pre><code class="language-bash"> mysqld_safe --skip-grant-tables </code></pre> <h2>3. Меняем пароль!</h2> <p>Теперь ничего не мешает нам подключиться к серверу и вручную сменить пароль требуемому пользователю</p> <pre><code class="language-bash"> mysql -u root </code></pre> <p>Дальнейшие команды вводятся в командном интерфейсе MySQL</p> <pre><code class="language-sql"> USE mysql; UPDATE user SET password = PASSWORD("SECRET-PASSWORD") WHERE user = "root"; FLUSH PRIVILEGES; QUIT; </code></pre> <h2>4. Готово? Почти</h2> <p>Пароль уже изменен. Но не забываем, что сервер все еще работает без проверки полномочий. Нам нужно его снова остановить и запустить в обычном режиме.</p> <pre><code class="language-bash"> /etc/init.d/mysql stop /etc/init.d/mysql start </code></pre> <pre><code class="language-bash"> service mysql stop service mysql start </code></pre> <p>Вот теперь все. Обратите внимание, что скрипты инициализации могут находиться в другой директории для вашей системы.</p> Изменение src у изображения в Opera /blog/javascript-imgsrc <p>Казалось бы, ну что может быть проще чем сменить изображение на странице при помощи JavaScript? Вроде на дворе самый настоящий 2010 год, уже давно в ходу разные кроссбраузерные библиотеки вроде jQuery, предусматривающие малейший каприз любого движка. Ну какие проблемы вообще могут возникнуть? Оказалось, всякие. Очевидная попытка просто сменить src у img не дала ровным счетом ничего.</p> <pre><code class="language-javascript"> $('#kcaptcha').attr('src', '../includes/captcha/run.php'); </code></pre> <p>По видимому, браузер считал что менять надо что-то одно на что-то другое, а одно и то же записывать дважды никакого смысла не имеет. В этом, конечно, есть своя логика, но, наверное, не стоит программам спорить с программистами. Ладно, попробовал еще добавить в путь какую-нибудь изменяющуюся часть, которая не повлияла бы никак на вывод изображения из скрипта, но при этом заставляла браузер обновлять его.</p> <pre><code class="language-javascript"> $('#kcaptcha').attr('src', '../includes/captcha/run.php#' Math.random()); </code></pre> <p>На такой трюк купились все кроме Opera: честно загружали заново это несчастное изображение, как будто это совсем разные пути, а не добавленный якорь. Но норвежская красавица упорствовала в своем нежелании идти на компромиссы. И чтобы подружиться с ней, пришлось внести определенные коррективы, убрав из якоря случайное число и поместив его в GET:</p> <pre><code class="language-javascript"> $('#kcaptcha').attr('src', '../includes/captcha/run.php?junk=' Math.random()); </code></pre> <p>Вот тут Opera подобрела и стала менять послушно менять изображение.</p> Путевые записки из страны пингвинов /blog/linux-hints <h2>Удобные сокращения</h2> <p>Есть такая полезная тема в никсах как сокращения команд «alias». Наверное, каждый хоть раз задумывался об этом, набивая очередной длинный путь до какой-нибудь команды. А все, на самом деле, проще некуда!</p> <p>Итак, чтобы создать алиас, нужно ввести такую команду:</p> <pre><code class="language-bash"> alias zf = 'sudo sh /home/ast/vhosts/ZendFramework/bin/zf.sh' </code></pre> <p>Лучший вариант: ввести необходимые алиасы в настройки:</p> <pre><code class="language-bash"> sudo gedit ~/.bashrc </code></pre> <p>Готово! Теперь можно сравнить оригинальный вариант команды и новый, использующий алиасы:</p> <pre><code class="language-bash"> sudo sh /home/ast/vhosts/ZendFramework/bin/zf.sh create project Sample<br/> zf create project Sample </code></pre> <p>Польза очевидна.</p> <h2>Консольное редактирование</h2> <p>Используя ssh очень удобно удаленно управлять серверами: это требует минимум пропускной способности канала и обладает всей мощью консоли никсов. Однако, если команды запомнить несложно, иной раз возникает необходимость и какой-нибудь конфиг отредактировать. Ради этого подключать графический интерфейс — охотиться на муху с ракетной установкой. К счастью, есть vim.</p> <p>Использовать его очень просто, как обычный редактор:</p> <pre><code class="language-bash"> sudo vim /etc/php5/apache2/php.ini </code></pre> <p>Я расскажу о самом базовом функционале этой мощной штуковины, который позволит нам осуществить «программу-минимум» — редактирование конфигов. </p> <p>«/» — Позволяет ввести текст для простейшего поиска. Чтобы найти следующее вхождение искомой строки, нужно вызвать команду без параметра;</p> <p>«i» — Переключает программу в режим редактирования. Вернуться в режим просмотра можно в любой момент, нажав «ESC»;</p> <p>«:w!» — Сохранение файла со всеми изменениями;</p> <p>«:quit» — Выход из программы без сохранения изменений.</p> Решение проблемы с циклом перезагрузки Gigabyte /blog/gigabyte-reboot-loop-solution <h2>Хьюстон, у нас проблема!</h2> <p>Уже давно все компьютеры у меня настроены на уход в спящий режим вместо обычного выключения. Это очень удобно, так как значительно сокращает потерю времени впустую на загрузку. И когда утром начались традиционные проблемы с электроснабжением, техника была «выключена», а я был спокоен. Как оказалось, немного зря. Вечером, попробовав включить компьютер, обнаружил то, что в интернетах именуется «reboot loop», цикл перезагрузок и является известнейшей проблемой материнских плат от Gigabyte, а также некоторых других на платформе Sandy Bridge.</p> <p>И в чем она заключается, эта проблема? После включения компьютер работает несколько секунд, разгоняет кулера, мигает лампочками, но, не доходя до POST, выключается. Проходит еще несколько секунд тишины и он снова стартует, чтобы также бесславно закончить и эту попытку. И это весь цикл, продолжаться может бесконечно, без малейшего прогресса.</p> <h2>Конфигурация системы</h2> <p> <ul> <li>Процессор Intel Core i7-2600K (3.40ГГц, 4x256КБ 8МБ, EM64T, GPU) Socket1155</li> <li>Материнская плата Gigabyte GA-P67A-UD3 (iP67, 4xDDR3, 2xPCI-E, SB, USB3.0, ATX) rev 1.0</li> <li>Оперативная память 2x DDR3 SDRAM Kingston «HyperX» KHX1600C9D3K2/4GX (PC12800, 1600МГц, CL9)</li> </ul> </p> <h2>Проблемы нужно решать</h2> <p>Поясню сразу: эта проблема может возникать по довольно разным причинам, от включенного PLL Overvoltage (используется для разгона, есть только в экспериментальных прошивках для этой платы), до неправильно выставленных параметров памяти. И совсем не факт, что решение, которое помогло мне, может помочь еще кому-то. Но вдруг? :)</p> <p>Собственно, у меня был как раз второй случай. Насколько я понял, после нарушенного спящего режима, система не может синхронизировать работу с памятью, а виноват в этом, скорее всего, микрокод Intel. Но не будем забегать вперед, нужно решать проблему поэтапно. Для начала, нужно сбросить CMOS, чтобы вообще добраться до настроек BIOS. Достаем батарейку и некоторое время ничего не трогаем. Идеальным вариантом будет также вытащить всю лишнюю память, оставить одну планку. По прошествии 10-20 минут, ставим обратно батарейку (но не память!) и запускаем машину. Reboot loop на этом этапе уже должен бы исчезнуть, но проблема еще не решена, так как установлена только одна планка оперативки. А стоит поставить обратно все четыре, вернется и reboot loop. Что делать!?</p> <p>Для начала, нужно прошить BIOS на более позднюю версию. Я отправился на <a href="http://www.gigabyte.ru/products/page/mb/ga-p67a-ud3rev_10/download/bios" target="_blank">сайт производителя</a> и обнаружил, что моей версии прошивки F3 там вообще нет, а самая младшая F4 сопровождается комментарием о том, что там исправлен как раз таки наш злодей — микрокод. Аккуратно перепрошиваем, следуя <a href="http://www.gigabyte.ru/forum/viewtopic.php?f=1">руководству</a>. Актуальная версия Git на Ubuntu /blog/latest-git-on-ubuntu <p>Добавляем репозиторий</p> <pre><code class="language-bash"> $ add-apt-repository ppa:git-core/ppa $ apt-get update $ apt-get install git-core </code></pre> <p>Если в системе нет <strong>add-apt-repository</strong>, необходимо перед этим поставить еще один пакет</p> <pre><code class="language-bash"> sudo apt-get install python-software-properties </code></pre> <p>Ура, у нас современная версия git!</p> Friends will be friends /blog/friends-will-be-friends <p><img src="/files/blog/62/friends.jpg" class="img-responsive" alt="Friends" title="Friends" /></p> Дети /blog/just-children <p><img src="/files/blog/63/children.jpg" alt="Children" title="Children" class="img-responsive" /></p> Глен Кук, «Черный отряд» /blog/cook-black-company <p> <img src="/files/blog/18/1.jpg" class="img-responsive" alt="Черный отряд" /> </p> <p>С творчеством Глена Кука я познакомился достаточно давно, но, как оказалось, через «заднюю калитку»: проглотил целиком его серию про детектива Гаррета, чем и был очень доволен. Она произвела на меня изрядное впечатление, но кончилась неприлично быстро. И недавно во мне созрело желание познакомиться с остальным творчеством автора, выбор пал на Черный Отряд. Поначалу, книга мне не понравилась. Для стиля Кука характерны скупые мазки, минимум лишних описаний, но сразу очень много действия и персонажей. А с моей стороны — отчаянные попытки понять кто есть кто, за каким прозвищем кто стоит и кем приходится главному герою. Но уже через пару глав мне было сложно оторваться от чтения: вырисовывалась густая, мрачная атмосфера, из отрывочных деталей, скупо раскиданных по строкам, начали появляться персонажи, один другого краше и харизматичнее. Вселенная Черного Отряда непохожа на то, что я видел в фентази до этого. Нет тут излишнего, вырвиглазного благородства и пафоса, но то хорошее, что встречается в этих людях — много ценнее. И то, что они сражаются на стороне злой колдуньи, ничуть не говорит против Черного Отряда, ведь другая сторона ничуть не краше.</p> Любовь? Коварство! /blog/kovarstvo <p> <img src="/files/blog/4/1.jpg" class="img-responsive" alt="Любовь? Коварство!" /> </p>