<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>IntSystem.Org - блог веб разработок</title>
	<atom:link href="https://intsystem.org/feed/" rel="self" type="application/rss+xml"/>
	<link>https://intsystem.org</link>
	<description></description>
	<lastBuildDate>Thu, 16 Jul 2026 23:35:30 +0000</lastBuildDate>
	<language>ru-RU</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.2</generator>
	<xhtml:meta content="noindex" name="robots" xmlns:xhtml="http://www.w3.org/1999/xhtml"/><item>
		<title>VirtualBox и Windows 11 и Native API</title>
		<link>https://intsystem.org/hardware/virtualbox-nad-windows-11-native-api/</link>
					<comments>https://intsystem.org/hardware/virtualbox-nad-windows-11-native-api/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Tue, 14 Jul 2026 18:27:37 +0000</pubDate>
				<category><![CDATA[Железо]]></category>
		<category><![CDATA[hyper-v]]></category>
		<category><![CDATA[win11]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[wirtualbox]]></category>
		<category><![CDATA[виртиуальная машина]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=2082</guid>

					<description><![CDATA[<p>&#8230;или как исправить зеленую черепашку в VirtualBox :) Наверное многим известна проблема запуска виртуальной машины под Win11, тем что не всегда получается использовать аппаратную функцию VT-x/AMD-V. Как правило корень этой проблемы в самой Windows 11, а именно в том что она использует эту функцию самостоятельно, соответственно заблокировав её для других. Без VT-x/AMD-V VirtualBox переключается в [&#8230;]</p>
<p>The post <a href="https://intsystem.org/hardware/virtualbox-nad-windows-11-native-api/">VirtualBox и Windows 11 и Native API</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><img fetchpriority="high" decoding="async" class="alignleft" style="float: left; object-fit: cover; object-position: 54% 52%;" src="https://intsystem.org/wp-content/uploads/2026/07/virtualbox-native-api-problem-1-e1784052676495.png" alt="" width="281" height="281" /></p>
<p>&#8230;или как исправить зеленую черепашку в VirtualBox :)</p>
<p>Наверное многим известна проблема запуска виртуальной машины под Win11, тем что не всегда получается использовать аппаратную функцию VT-x/AMD-V. Как правило корень этой проблемы в самой Windows 11, а именно в том что она использует эту функцию самостоятельно, соответственно заблокировав её для других.</p>
<p>Без VT-x/AMD-V VirtualBox переключается в режим работы через Native API (NEM), а Native API — это жуткие тормоза из-за лишней прослойки. Причины гуглятся вроде очень быстро, но, помимо очевидных причин есть и неочевидные, об этом под катом.<br />
<span id="more-2082"></span></p>
<h2>Чек-лист диагностики</h2>
<p>Быстрый чек-лист для диагностики (см. подробнее далее):</p>
<ul>
<li>BIOS — включена ли VT-x/AMD-V физически.</li>
<li><code>bcdedit /enum | findstr hypervisorlaunchtype</code> — должно быть Off.</li>
<li><code>Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All, VirtualMachinePlatform, HypervisorPlatform</code> — все должны быть Disabled.</li>
<li><code>Get-CimInstance -ClassName Win32_DeviceGuard -Namespace root\Microsoft\Windows\DeviceGuard</code> — <code>VirtualizationBasedSecurityStatus</code> должен быть 0.</li>
<li>Если статус не 0 — открыть <code>msinfo32</code>, посмотреть «Запущенные службы» в разделе VBS, и отключить именно ту конкретную службу (Credential Guard / Memory Integrity (HVCI) / System Guard) по инструкциям ниже.</li>
<li>Проверить лог самой ВМ (VBox.log) на строку <code>Using: VT-x</code> (успех) против <code>Falling back to NEM</code> (всё ещё заблокировано).</li>
</ul>
<h2>То, что делаем в первую очередь</h2>
<p>Базовые вещи, которые нужно сделать сразу и которые легко гуглятся.</p>
<h3>Включить виртуализацию в BIOS</h3>
<p>Самый первый и самый очевидный шаг — проверить BIOS. Если выключено — включить :)</p>
<h3>Отключаем компоненты виртуализации Windows</h3>
<p>Давайте начнём с того, что все рекомендуют сделать в первую очередь: жмём <code>Win+R</code>, вводим <code>optionalfeatures</code> и отключаем всю виртуализацию:</p>
<ul>
<li>Hyper-V и все подкомпоненты</li>
<li>Платформа виртуальной машины (Virtual Machine Platform)</li>
<li>Платформа гипервизора Windows (Windows Hypervisor Platform)</li>
<li>Песочница Windows (Windows Sandbox)</li>
</ul>
<h3>Отключение гипервизора</h3>
<p>В консоли с админ-правами выполняем <code>bcdedit /set hypervisorlaunchtype off</code> и отключаем запуск защищённых сред <code>bcdedit /set vsmlaunchtype off</code>.</p>
<h3>Изоляция ядра / Core Isolation</h3>
<p>Открыть Параметры (<code>Win+I</code>) → Конфиденциальность и безопасность → Безопасность Windows → Безопасность устройства. Перейти в «Сведения об изоляции ядра» и перевести ползунок «Целостность памяти» в положение «Выкл».</p>
<h2>А теперь что помогло мне</h2>
<p>Так, а теперь что делать, если перечисленное выше не помогло. (Естественно, не забываем перезагружать компьютер после каждого пункта.)</p>
<h3>Диагностика через msinfo32</h3>
<p>Открываем <code>msinfo32</code> и смотрим на строку «Безопасность на основе виртуализации». Если там значится «Выполнение» — значит, что-то из служб VBS всё ещё держит VT-x занятым. Смотрим строку «Запущенные службы» чуть ниже — она укажет, что именно нужно отключать: Credential Guard, Memory Integrity (HVCI) или System Guard Secure Launch.</p>
<h3>Credential Guard</h3>
<p>Если в запущенных службах значится Credential Guard — отключаем его через редактор реестра или через консоль:</p>
<pre>Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\LSA" -Name "LsaCfgFlags" -Value 0 -Type DWord
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard" -Name "EnableVirtualizationBasedSecurity" -Value 0 -Type DWord -ErrorAction SilentlyContinue</pre>
<p>Некоторые мануалы после этого действия рекомендуют делать две перезагрузки подряд (что-то в духе: в первый раз применяется политика, а во второй раз отключается эта защита, но я не вникал).</p>
<h3>Memory Integrity (HVCI)</h3>
<p>Если тумблер «Целостность памяти» в Параметрах не отключается или Windows включает его обратно, есть вариант через реестр:</p>
<pre>Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity" -Name "Enabled" -Value 0 -Type DWord</pre>
<h3>System Guard Secure Launch</h3>
<p>Похоже на Credential Guard — тоже VBS-служба:</p>
<pre>Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\SystemGuard" -Name "Enabled" -Value 0 -ErrorAction SilentlyContinue</pre>
<h3>Групповая политика</h3>
<p><code>gpedit.msc</code> → Конфигурация компьютера → Административные шаблоны → Система → Device Guard → отключить политику «Включить средства безопасности на основе виртуализации».</p>
<p>Актуально в первую очередь для доменных/корпоративных машин — если политика управляется централизованно, локальное отключение может быть переопределено доменной GPO, и потребуется обращение к ИТ-администратору.</p><p>The post <a href="https://intsystem.org/hardware/virtualbox-nad-windows-11-native-api/">VirtualBox и Windows 11 и Native API</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/hardware/virtualbox-nad-windows-11-native-api/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>И да, этот блог еще жив :)</title>
		<link>https://intsystem.org/etc/i-da-etot-blog-eshche-zhiv/</link>
					<comments>https://intsystem.org/etc/i-da-etot-blog-eshche-zhiv/#respond</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Mon, 13 Jul 2026 21:14:33 +0000</pubDate>
				<category><![CDATA[Прочее]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=2078</guid>

					<description><![CDATA[<p>Да-да, вы скажете, мол &#171;Дима, прошло уже 10 лет с момента последнего поста, мы думали что блог помер&#187;, и вы будете абсолютно правы, на какое то время я действительно перестал заниматься IT так плотно как раньше, и блог стал мне не интересен. Но как говорится, ты можешь уйти из IT, но IT из тебя не [&#8230;]</p>
<p>The post <a href="https://intsystem.org/etc/i-da-etot-blog-eshche-zhiv/">И да, этот блог еще жив :)</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><img decoding="async" width="430" height="320" class="wp-image-2080 alignleft" style="width: 351px; height: auto;" src="https://intsystem.org/wp-content/uploads/2026/07/0_740c4_7643ec57_L.jpg" alt="" />Да-да, вы скажете, мол &#171;Дима, прошло уже 10 лет с момента последнего поста, мы думали что блог помер&#187;, и вы будете абсолютно правы, на какое то время я действительно перестал заниматься IT так плотно как раньше, и блог стал мне не интересен.</p>
<p>Но как говорится, ты можешь уйти из IT, но IT из тебя не уйдет никогда :) поэтому все же во первых разработку я не забросил, просто занимался ей в чуть иных сферах, а во вторых есть желание вернуться и начать писать о том с чем сталкиваюсь.</p>
<p>В целом, конечно, забавно и самому прочитать ту ерунду которую ты писал будучи на 10 лет моложе, но это был тоже опыт, да и блог этот был создан чтобы в первую очередь делиться чем то, что тебе самому кажется интересным. Так что я себя ни в коем разе не осуждаю, а наоборот горжусь :)</p><p>The post <a href="https://intsystem.org/etc/i-da-etot-blog-eshche-zhiv/">И да, этот блог еще жив :)</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/etc/i-da-etot-blog-eshche-zhiv/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Цифровая подпись</title>
		<link>https://intsystem.org/security/digital-signature/</link>
					<comments>https://intsystem.org/security/digital-signature/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Sun, 15 May 2016 00:20:20 +0000</pubDate>
				<category><![CDATA[Безопасность]]></category>
		<category><![CDATA[ассиметричное шифрование]]></category>
		<category><![CDATA[криптография]]></category>
		<category><![CDATA[цифровая подпись]]></category>
		<category><![CDATA[шифрование]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=1766</guid>

					<description><![CDATA[<p>Я надеюсь вы читали одну из моих предыдущих статей Ассимитричное шифрование. Как это работает?. Если да, то давайте продолжим погружаться в тему криптографии (если нет&#8230; прочитайте). Прошлая статья подвела нас к тому, что основа ассиметричного шифрования &#8212; это публичный и приватный ключи, которыми осуществляется шифрование и дешифрование сообщений. Алгоритм в двух словах выглядит вот так: [&#8230;]</p>
<p>The post <a href="https://intsystem.org/security/digital-signature/">Цифровая подпись</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Я надеюсь вы читали одну из моих предыдущих статей <a href="https://intsystem.org/security/asymmetric-encryption-how-it-work/">Ассимитричное шифрование. Как это работает?</a>. Если да, то давайте продолжим погружаться в тему криптографии (если нет&#8230; прочитайте).</p>
<p>Прошлая статья подвела нас к тому, что основа ассиметричного шифрования &#8212; это публичный и приватный ключи, которыми осуществляется шифрование и дешифрование сообщений. Алгоритм в двух словах выглядит вот так:</p>
<ol>
<li>мы берем публичный ключ того кому хотим отправить сообщение;</li>
<li>шифруем сообщение с помощью ключа;</li>
<li>передаем сообщение по открытому каналу;</li>
<li>адресат расшифровывает сообщение своим приватным ключом.</li>
</ol>
<p>Представим что Боб получил зашифрованное сообщение от Алисы. Но от Алисы ли? Да, в конце письма может быть написано &#171;С любовью, Алиса&#187;, но откуда нам знать, что это не подлая Ева представляется чужим именем?<span id="more-1766"></span> Но впрочем с любовными письмами это может показаться не такой большой проблемой, не так ли? Тогда представьте себе более серьезную организацию &#8212; банк. Банк у которого есть множество филиалов по всему миру с головным офисом, который выполняет и отвечает за все банковские операции по переводу денег. Откуда головному офису знать, что пришедшее письмо с просьбой перевести миллиард долларов на некий счет, пришло действительно от филиала как написано в письме, а не от злоумышленника, который хочет перевести эти деньги на свой счет?</p>
<p>Да, раньше такого рода вещи подтверждались рукописной росписью в конце документа. Но в век цифровых технологий такая физическая подпись бумаг уже морально устарела. Не будет же банк пересылать почтой в головной офис отдельную бумажку с подписью для того чтобы заверить транзакцию? Это будет очень долго. Эта проблема вынудила криптографов придумывать аналог рукописной подписи в цифровом виде.</p>
<h2>Подпись документа</h2>
<p>Ассиметричное шифрование поможет нам и в этом. Алиса, чтобы подтвердить авторство своего письма&#8230; должна зашифровать его своим приватным ключом! Такое сообщение расшифруется с помощью публичного ключа Алисы. А так как публичный ключ априори известен всем, то расшифровать такое сообщение сможет любой. Но при этом, если сообщение расшифровалось, то это однозначно говорит о том что послала его именно Алиса.</p>
<p><img decoding="async" class="aligncenter size-full wp-image-2028" src="https://intsystem.org/wp-content/uploads/2016/05/sign_short.png" alt="sign_short" width="549" height="155" srcset="https://intsystem.org/wp-content/uploads/2016/05/sign_short.png 549w, https://intsystem.org/wp-content/uploads/2016/05/sign_short-300x85.png 300w" sizes="(max-width: 549px) 100vw, 549px" /></p>
<blockquote><p>Раньше мы считали, что публичный ключ используется только для зашифровывания, а приватный ключ — для расшифровывания. На самом деле эти операции можно поменять местами, так что для зашифровывания будет использоваться приватный ключ, а для расшифровывания — публичный ключ. Стойкость такого зашифровывания как правило, игнорируется, поскольку никакой безопасности оно не обеспечивает, и несет совсем другую роль.</p></blockquote>
<p>В целом такой подход называется цифровой подписью, которая выполняет две функции &#8212; аутентификация отправителя и подтверждение целостности документа. То есть если текст расшифровался публичным ключом отправителя, то это значит что автор действительно тот за кого он себя выдает, а также то что документ пришел в неизменном виде.</p>
<p>Впрочем для того чтобы удостоверить отправителя совсем не обязательно шифровать текст целиком. Можно, например, зашифровать приватным ключом хеш от исходного текста и полученную строку передавать получателю вместе с текстом. На другом конце получатель расшифровывает подпись публичным ключом, и сравнивает ее с хешем полученного документа. Вот так выглядит генерация подписи:<br />
<img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2025" src="https://intsystem.org/wp-content/uploads/2016/05/sign_hash_create.png" alt="sign_hash_create" width="300" height="140" /><br />
А вот так проверка этой подписи на стороне получателя:<br />
<img loading="lazy" decoding="async" class="aligncenter size-full wp-image-2026" src="https://intsystem.org/wp-content/uploads/2016/05/sign_hash_verify.png" alt="sign_hash_verify" width="300" height="169" /></p>
<p>В целом цифровая подпись подтверждает, что у отправителя есть приватный ключ того за кого он себя выдает. А так как принято считать, что приватный ключ держится в секрете и никому более недоступен, то такая подпись подтверждает конкретного отправителя.</p>
<h2>Передача конфиденциального письма</h2>
<p>При этом если Алиса не хочет чтобы этот текст прочитал кто-то другой, то нужно не забыть про основное шифрование описанное ранее. Вот полный алгоритм более наглядно:</p>
<table>
<tbody>
<tr>
<td style="width: 435px;"><img loading="lazy" decoding="async" class="wp-image-2010 size-full" src="https://intsystem.org/wp-content/uploads/2016/05/sign.png" alt="sign" width="435" height="464" srcset="https://intsystem.org/wp-content/uploads/2016/05/sign.png 435w, https://intsystem.org/wp-content/uploads/2016/05/sign-281x300.png 281w" sizes="auto, (max-width: 435px) 100vw, 435px" /></td>
<td style="vertical-align: top;">
<ol>
<li>Алиса шифрует сообщение своим приватным ключом;</li>
<li>шифрует еще раз публичным ключом Боба;</li>
<li>передает сообщение по открытому каналу;</li>
<li>Боб расшифровывает шифротекст своим приватным ключом;</li>
<li>расшифровывает еще раз публичным ключом Алисы.</li>
</ol>
</td>
</tr>
</tbody>
</table>
<p>Вообразите себе сообщение, окруженное хрупкой внутренней оболочкой, которая представляет собой шифрование, выполненное с помощью приватного ключа Алисы (подписи), и прочную наружную оболочку, представляющую шифрование с использованием публичного ключа Боба. Получающийся шифротекст может быть расшифрован только Бобом, потому что только он имеет доступ к приватному ключу, необходимому для того, чтобы разбить эту прочную наружную оболочку. Расшифровав наружную оболочку, Боб затем сможет легко расшифровать с помощью публичного ключа Алисы и внутреннюю оболочку; эта внутренняя оболочка служит не для того, чтобы защитить сообщение, она удостоверяет, что данное сообщение пришло от Алисы, а не от какого-нибудь мошенника.</p>
<h2>Заключение</h2>
<p>В целом это все что стоит знать о цифровой подписи. А также достаточно для того чтобы понять дальнейший материал, так как он будет связан.</p>
<p>Я решил публиковать <em>отдельные</em> статьи о определенных аспектах криптографии, вместо целой и связанной статьи. Почему отдельные? Потому что написание огромной статьи я постоянно откладывал на потом. А так глядишь &#8212; по чуть-чуть и напишу все то что хотел :)</p>
<p>В дальнейшем ждите статей на тему MiTM и сертификатов, проблеме глобального наблюдателя и о том почему конфиденциальности нет в принципе :)</p><p>The post <a href="https://intsystem.org/security/digital-signature/">Цифровая подпись</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/security/digital-signature/feed/</wfw:commentRss>
			<slash:comments>20</slash:comments>
		
		
			</item>
		<item>
		<title>Поставил сертификат Let’sEncrypt</title>
		<link>https://intsystem.org/server/letsencrypt-certificate/</link>
					<comments>https://intsystem.org/server/letsencrypt-certificate/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Thu, 07 Jan 2016 13:49:58 +0000</pubDate>
				<category><![CDATA[Сервер]]></category>
		<category><![CDATA[HTTPS]]></category>
		<category><![CDATA[ssl]]></category>
		<category><![CDATA[блог]]></category>
		<category><![CDATA[сертификат]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=1979</guid>

					<description><![CDATA[<p>Вот наконец решил попробовать SSL сертификат от Let&#8217;s Encrypt. Сподвигло меня на это то что, купленный мной ранее сертификат от COMODO, на платформе Android оказывается считается недоверенным. А я ни сном ни духом об этом( Да и вобщем хрен с ним с тем сертификатом, стоил он всего 8$, и во первых не жалко, а во вторых, [&#8230;]</p>
<p>The post <a href="https://intsystem.org/server/letsencrypt-certificate/">Поставил сертификат Let’sEncrypt</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><img loading="lazy" decoding="async" class="alignleft size-medium wp-image-1982" title="Установка сертификата от Let's Encypt" src="https://intsystem.org/wp-content/uploads/2016/01/letsencrypt-300x161.png" alt="Установка сертификата от Let's Encypt" width="300" height="161" srcset="https://intsystem.org/wp-content/uploads/2016/01/letsencrypt-300x161.png 300w, https://intsystem.org/wp-content/uploads/2016/01/letsencrypt.png 555w" sizes="auto, (max-width: 300px) 100vw, 300px" />Вот наконец решил попробовать SSL сертификат от Let&#8217;s Encrypt. Сподвигло меня на это то что, купленный мной ранее сертификат от COMODO, на платформе Android оказывается считается недоверенным. А я ни сном ни духом об этом( Да и вобщем хрен с ним с тем сертификатом, стоил он всего 8$, и во первых не жалко, а во вторых, FireFox начал ругаться на то сертификат использует небезопасное шифрование 1024bit.</p>
<p>Вообще что такое Let&#8217;s Encrypt? Это центр сертификации который выдает бесплатные HTTPS сертификаты. Ну и немножко википедии:</p>
<blockquote><p>Проект Let’s Encrypt создан для того, чтобы большая часть интернет-сайтов смогла перейти к шифрованным подключениям (HTTPS). В отличие от коммерческих центров сертификации, в данном проекте не требуется оплата, переконфигурация веб-серверов, использование электронной почты, обработка просроченных сертификатов, что делает процесс установки и настройки TLS-шифрования значительно более простым. Например, на типичном веб-сервере на базе Linux, требуется исполнить две команды, которые настроят HTTPS шифрование, получат и установят сертификат примерно за 20-30 секунд.</p></blockquote>
<p><span id="more-1979"></span></p>
<p>У сервиса есть несколько ограничений которые немного печалят, сертификат выдается сроком всего на 3 месяца, отсутствуют WildCard сертификаты. Первое ограничение объясняют тем что мол в случае компрометации, сертификат, выданный на 3 месяца, нанесет вреда меньше, чем сертификат выданный на год.</p>
<p><em>Теория заговора: хотя мне кажется все эти ограничения созданы искусственно с другой целью &#8212; оставить остальные центры сертификации на плаву. Кому то будет лень настраивать cron на перевыпуск сертификата, или же перевыпускать его вручную каждые три месяца. Кому то лень получать сертификаты на все поддомены. А так Let&#8217;s Encypt занимает свою нишу среди гиков, которым такие вещи только нравятся.</em></p>
<h2>Как установить Let&#8217;s Encypt?</h2>
<p>Для этого существует специальный одноименный клиент созданный этой же организацией:</p>
<pre class="lang:sh decode:true">git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
./letsencrypt-auto --help</pre>
<p>Вообще для nginx у них есть вроде как почти полностью автоматизированный установщик, но честно сказать я не особо рад тому что бы сторонняя утилита правила мои конфиги. Поэтому я предпочел установку сертификатов вручную.</p>
<pre class="lang:sh decode:true"> ./letsencrypt-auto certonly --webroot -w [path to webroot dir] -d [domain]</pre>
<p>В моем случае это выглядит вот так:</p>
<pre class="lang:sh decode:true"> ./letsencrypt-auto certonly --webroot -w /var/www/intsystem.org/public_html/ -d intsystem.org -d www.intsystem.org</pre>
<p>После чего будет создан каталог <code>/etc/letsencrypt/live/intsystem.org/</code> с символическими ссылками на сертификаты и ключ:</p>
<pre class="lang:sh decode:true ">lrwxrwxrwx 1 root root  cert.pem -&gt; ../../archive/intsystem.org/cert1.pem
lrwxrwxrwx 1 root root  chain.pem -&gt; ../../archive/intsystem.org/chain1.pem
lrwxrwxrwx 1 root root  fullchain.pem -&gt; ../../archive/intsystem.org/fullchain1.pem
lrwxrwxrwx 1 root root  privkey.pem -&gt; ../../archive/intsystem.org/privkey1.pem
</pre>
<p>Теперь путь к ним осталось прописать в конфиг Nginx</p>
<pre class="lang:default decode:true ">ssl_certificate "/etc/letsencrypt/live/intsystem.org/fullchain.pem";
ssl_certificate_key "/etc/letsencrypt/live/intsystem.org/privkey.pem";
ssl_trusted_certificate "/etc/letsencrypt/live/intsystem.org/chain.pem"; #Если используете OCSP</pre>
<p>и рестартуем Nginx.</p>
<h2>Как обновить сертификат?</h2>
<p>Особого умения тут тоже не надо, используем ту же самую команду, но с параметром <code>--renew-by-default</code>:</p>
<pre class="lang:sh decode:true ">./letsencrypt-auto certonly --renew-by-default --webroot -w [path to webroot dir] -d [domain]</pre>
<p>В моем случае:</p>
<pre class="lang:sh decode:true ">./letsencrypt-auto certonly --renew-by-default --webroot -w /var/www/intsystem.org/public_html/ -d intsystem.org -d www.intsystem.org</pre>
<p>После выпуска и получения новых сертификатов, должны автоматически обновится символические ссылки на них:</p>
<pre class="lang:sh decode:true ">lrwxrwxrwx 1 root root  cert.pem -&gt; ../../archive/intsystem.org/cert2.pem
lrwxrwxrwx 1 root root  chain.pem -&gt; ../../archive/intsystem.org/chain2.pem
lrwxrwxrwx 1 root root  fullchain.pem -&gt; ../../archive/intsystem.org/fullchain2.pem
lrwxrwxrwx 1 root root  privkey.pem -&gt; ../../archive/intsystem.org/privkey2.pem</pre>
<p>и рестарт Nginx (reload).</p>
<h2>Как обновлять сертификат Let&#8217;s Encypt автоматически?</h2>
<p>Для этого можно добавить следующие комманды в крон на каждые два месяца:</p>
<pre class="lang:sh decode:true ">[path]/letsencrypt-auto certonly --renew-by-default --webroot -w [path to webroot dir] -d [domain]
service nginx reload
</pre>
<p>После чего про головную боль связанную с SSL сертификатами можно забыть навсегда.</p>
<h2>Итог</h2>
<p><img loading="lazy" decoding="async" class="size-full wp-image-1988 alignnone" title="Сертификат от Let's Encypt на intsystem.org" src="https://intsystem.org/wp-content/uploads/2016/01/41.png" alt="Сертификат от Let's Encypt на intsystem.org" width="341" height="412" srcset="https://intsystem.org/wp-content/uploads/2016/01/41.png 341w, https://intsystem.org/wp-content/uploads/2016/01/41-248x300.png 248w" sizes="auto, (max-width: 341px) 100vw, 341px" /></p><p>The post <a href="https://intsystem.org/server/letsencrypt-certificate/">Поставил сертификат Let’sEncrypt</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/server/letsencrypt-certificate/feed/</wfw:commentRss>
			<slash:comments>22</slash:comments>
		
		
			</item>
		<item>
		<title>Error-Based SQL injection в MySQL</title>
		<link>https://intsystem.org/security/error-based-sql-injection-in-mysql/</link>
					<comments>https://intsystem.org/security/error-based-sql-injection-in-mysql/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Sat, 26 Dec 2015 19:52:51 +0000</pubDate>
				<category><![CDATA[SQL]]></category>
		<category><![CDATA[Безопасность]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[sql injection]]></category>
		<category><![CDATA[взлом]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=1955</guid>

					<description><![CDATA[<p>Что такое SQL инъекцию сегодня наверное знает каждый второй ребенок. Error-Based &#8212; уже менее знакомый термин, но все же так же прост для понимания. На всякий случай попробую объяснить. В некоторых ситуациях инъекция происходит в запросе который непосредственно не выводит никаких данных на страницу, либо же вообще никак не влияет на вывод. И если в [&#8230;]</p>
<p>The post <a href="https://intsystem.org/security/error-based-sql-injection-in-mysql/">Error-Based SQL injection в MySQL</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><img loading="lazy" decoding="async" src="https://intsystem.org/wp-content/uploads/2015/12/14_Native_SQLInjection_Key-c39beef7-300x185.jpg" alt="SQL injection" title="SQL injection" width="300" height="185" class="alignleft size-medium wp-image-1971" srcset="https://intsystem.org/wp-content/uploads/2015/12/14_Native_SQLInjection_Key-c39beef7-300x185.jpg 300w, https://intsystem.org/wp-content/uploads/2015/12/14_Native_SQLInjection_Key-c39beef7.jpg 550w" sizes="auto, (max-width: 300px) 100vw, 300px" />Что такое SQL инъекцию сегодня наверное знает каждый второй ребенок. Error-Based &#8212; уже менее знакомый термин, но все же так же прост для понимания. </p>
<p>На всякий случай попробую объяснить. В некоторых ситуациях инъекция происходит в запросе который непосредственно не выводит никаких данных на страницу, либо же вообще никак не влияет на вывод. И если в первом случае данные из базы можно извлечь относительно просто, используя <code>IF</code>, косвенно влияя на вывод страницы, и тем самым прибегнув к бинарному поиску. То вот во втором случае &#8212; все очень грустно, бинарный поиск, как правило, основывался на конструкции <code>SLEEP</code> и замере времени ответа, что давало огромные задержки на вывод данных через эту инъекцию.</p>
<p>Следующей вехой в развитии этой атаки стал вектор Error-Based. Он основывается на выводе информации в тексте ошибки выполнения запроса. Как вы понимаете, для этого нужен прямой вывод текста ошибки на саму страницу. Да я согласен, это бывает не часто, и вообще за вывод ошибок на продакшене нужно жестоко карать. Но если вам повезло, то это поможет существенно сократить время атаки.<br />
<span id="more-1955"></span></p>
<h2>Duplicate entry</h2>
<p>Наверное это один из самых популярных векторов атаки. Его <a href="http://forum.antichat.ru/threads/119047/#post-1494443">предложил некто Qwazar</a> еще в лохматые года. Выглядит он вот так:</p>
<pre class="nums:false lang:mysql decode:true " >SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)x GROUP BY CONCAT(MID([YOUR_QUERY], 1, 63), FLOOR(RAND(0)*2))</pre>
<p>Потом этот способ дорабатывали все кому не лень (забегу немного вперед, в т.ч. и я), в итоге самый оптимальный и короткий запрос на сегодняшний день выглядит вот так: </p>
<pre class="nums:false lang:mysql decode:true " >SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2)x GROUP BY MID([YOUR_QUERY], FLOOR(RAND(33)*2), 64)</pre>
<p>Так например, такой запрос:</p>
<pre class="nums:false lang:mysql decode:true " >SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2)x GROUP BY MID(VERSION(), FLOOR(RAND(33)*2), 64)</pre>
<p>Выведет вот такую вот ошибку:<br />
<code>Duplicate entry '<strong>5.5.25-log</strong>' for key 'group_key'</code><br />
Как видно &#8212; мы смогли передать в текст ошибки нужные нам данные, что существенно ускорит нашу атаку в дальнейшем. Ограничение &#8212; за один запрос выведется не более 64 символов.</p>
<p>[su_spoiler title=&#187;Почему это работает&#187; style=&#187;fancy&#187; anchor=&#187;whyItWork&#187;]</p>
<h3>Почему это работает?</h3>
<p>Скажу честно, ради этого я и писал эту статью)</p>
<p>Максимально упростим запрос и сделаем его нагляднее:</p>
<pre class="nums:false lang:mysql decode:true ">SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)x GROUP BY FLOOR(RAND()*2)</pre>
<p>Рассмотрим запрос внимательно. В нем 3 строки группируются по полю <code>RAND</code> (при этом <code>FLOOR(RAND()*2)</code> может вернуть либо <strong>1</strong> либо <strong>0</strong>). Для этого MySQL создает промежуточную, временную таблицу <code>temporary table</code>, и, проходя каждую строку, записывает в нее результат вычислений. После чего на основе этой таблицы строится результат самого запроса. Для нашего случая эта таблица должна бы выглядеть как то так:</p>
<pre class="nums:false lang:default decode:true " >group_key|count
---------+--------
 1       | 2
 0       | 1
</pre>
<p><strong>group_key</strong> &#8212; как уже понятно &#8212; является результатом вычисления <code>FLOOR(RAND()*2)</code> для каждой строки. При этом <strong>group_key</strong> является уникальным индексом в этой таблице, т.е. два значения <strong>group_key</strong> равным например &#171;<strong>1</strong>&#187; &#8212; недопустимая ситуация.</p>
<p>То как организуется заполнение этой временной таблицы, выглядит приблизительно так в виде псевдокода:</p>
<pre class="lang:default decode:true " >если (ключ [FLOOR(RAND()*2)] существует в temporary_table ) {
  //увеличить счетчик для данного group_key
  выполнить("UPDATE `temporary_table` SET `count` = `count` + 1 WHERE `group_key` = FLOOR(RAND()*2)")
} иначе {
  //добавить новую строку
  выполнить("INSERT INTO `temporary_table` (`group_key`, `count`) VALUES (FLOOR(RAND()*2)), 1")
}</pre>
<p><em>(на самом деле немного не так, возможно связано с какими-то дополнительными оптимизациями, надо лезть в исходники, мне лень)</em></p>
<p>Обратите внимание! В результате этих манипуляций на каждую одну строку два раза вызывается <code>RAND</code>.</p>
<p>Теперь представим на секунду что <code>FLOOR(RAND()*2))</code> выдаст следующий порядок значений на каждый вызов <code>1 1 1 1 0 1</code>, то временная таблица будет заполнятся следующим образом:</p>
<pre class="nums:false lang:mysql decode:true " >// '1' еще не существует в `temporary_table`
INSERT INTO `temporary_table` (`group_key`, `count`) VALUES (1, 1);

// '1' уже существует в `temporary_table`, увеличиваем счетчик
UPDATE `temporary_table` SET `count` = `count` + 1 WHERE `group_key` = 1;

// '0' еще не существует в `temporary_table`
INSERT INTO `temporary_table` (`group_key`, `count`) VALUES (1, 1); 
//Обратите внимание! Было проверено наличие '0', а вставляется '1'</pre>
<p>В итоге на последнем запросе мы видим ошибку &#171;<strong>Duplicate entry</strong>&#171;, так как он пытается вставить во временную таблицу второе уже существующее значение уникального индекса. Т.е. MySQL проверил что &#171;<strong>group_key</strong> = <strong>0</strong>&#187; еще нет в таблице, и решил добавить его, но при этом при добавлении вызывал еще раз <code>RAND</code>, которое вернуло другое значение &#171;<strong>1</strong>&#171;, которое уже существует в таблице.</p>
<p>Вернемся к оригинальному запросу (допустим мы хотим получить <code>VERSION()</code>):</p>
<pre class="nums:false lang:mysql decode:true " >SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3)x GROUP BY CONCAT(MID((VERSION()), 1, 63), FLOOR(RAND(0)*2))</pre>
<p>Для <code>RAND</code> здесь указан конкретный <strong>seed</strong> равный <strong>0</strong> (<code>RAND(<strong>0</strong>) <----- где "<strong>0</strong>" это seed</code>). <strong>seed</strong> &#8212; это начальное значение с помощью которого генерируются остальные случайные числа. Т.е. для всех одинаковых реализаций генератора случайных чисел с одинаковым заданным <strong>seed</strong> должен выдаваться одинаковый набор чисел. В случае MySQL гарантируется что для одинаковых версий будет сгенерирован одинаковый набор чисел.</p>
<p>При выполнении этого запроса <strong>group_key</strong> будет выглядеть как то так <strong>5.5.251</strong> и заполнятся временная таблица будет следующим образом:</p>
<pre class="nums:false lang:mysql decode:true " >// '5.5.251' еще не существует в `temporary_table`
INSERT INTO `temporary_table` (`group_key`, `count`) VALUES ('5.5.251', 1);

// '5.5.251' уже существует в `temporary_table`, увеличиваем счетчик
UPDATE `temporary_table` SET `count` = `count` + 1 WHERE `group_key` = '5.5.251';

// '5.5.250' еще не существует в `temporary_table`
INSERT INTO `temporary_table` (`group_key`, `count`) VALUES ('5.5.251', 1);</pre>
<p>На последнем запросе мы получим желанную ошибку:<br />
<code>#1062 - Duplicate entry '<strong>5.5.251</strong>' for key 'group_key'</code></p>
<p>Если подобрать другой <strong>seed</strong>, например <strong>33</strong> (еще подойдут 37,41,45,49,53,57,61,65,&#8230;), так чтобы порядок значений выдаваемых <strong>RAND</strong> сыграл нам на руку раньше, то можно немного сократить запрос:</p>
<pre class="nums:false lang:mysql decode:true " >SELECT COUNT(*) FROM (SELECT 1 UNION SELECT 2)x GROUP BY MID([YOUR_QUERY], FLOOR(RAND(33)*2), 64)</pre>
<p>На таком наборе данных мы избегаем второго шага, и тем самым нам достаточно всего двух строк в подзапросе.[/su_spoiler]</p>
<h2>Ошибка переполнения типа данных</h2>
<p>Подробнее <a href="https://rdot.org/forum/showthread.php?t=3167">здесь</a>. Буду краток, вектор выглядит вот так:</p>
<pre class="nums:false lang:mysql decode:true " >SELECT (i IS NOT NULL) - -9223372036854775808 FROM (SELECT ([YOUR_QUERY])i)a</pre>
<p>Пример ошибки:<br />
<code>BIGINT value is out of range in '(('<strong>5.5.25-log</strong>' is not null) - -(9223372036854775808))'</code><br />
Данный способ покруче предыдущего так как позволяет вывести аж <strong>475</strong> символов вместо <strong>64</strong>, но говорят что работает не на всех версиях MySQL. Проверить это у меня возможности нет, поэтому оставлю это на совести авторов. По крайней мере на версии 5.5 это работает точно.</p>
<p>Еще похожая вариация:</p>
<pre class="nums:false lang:mysql decode:true " >SELECT !(SELECT * FROM(SELECT USER())x)-~0</pre>
<p>Это выдаст вот такую ошибку:<br />
<code>'((not((select '<strong>root@localhost</strong>' from dual))) - ~(0))'</code></p>
<h2>Гео-функции</h2>
<p>Появились только в MySQL >= 5.7.5</p>
<pre class="nums:false lang:mysql decode:true " >SELECT ST_LatFromGeoHash(VERSION());
//Incorrect geohash value: '5.7.6-m16-log' for function ST_LATFROMGEOHASH

SELECT ST_LongFromGeoHash(VERSION());
//Incorrect geohash value: '5.7.6-m16-log' for function ST_LONGFROMGEOHASH

SELECT ST_PointFromGeoHash(VERSION(),0);
//Incorrect geohash value: '5.7.6-m16-log' for function ST_POINTFROMGEOHASH</pre>
<p>Удобно, но 5.7.5 &#8212; это большая редкость на сегодняшний день. Тут я это наверное привел только ради того чтобы предложить альтернативу, предыдущим техникам и разбавить статью. Вряд ли это кто то применит на практике.</p>
<h2>Заключение</h2>
<p>Основной акцент в статье был сделан на то <a href="#whyItWork">почему работает &#171;Duplicate key&#187;-метод</a> (ради того чтобы не потерять мой пост <a href="https://forum.antichat.ru/threads/360690/page-1120#post-3826895" rel="nofollow">отсюда</a>), т.к. этот метод порой вызывает огромное недоумение. Ну а в целом буду собирать здесь все известные мне методы данной атаки.</p>
<p>Благодарю за внимание.</p><p>The post <a href="https://intsystem.org/security/error-based-sql-injection-in-mysql/">Error-Based SQL injection в MySQL</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/security/error-based-sql-injection-in-mysql/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Пишем свой плагин для WordPress, добавляем в каталог плагинов</title>
		<link>https://intsystem.org/coding/change-comment-parent/</link>
					<comments>https://intsystem.org/coding/change-comment-parent/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Fri, 18 Dec 2015 04:53:31 +0000</pubDate>
				<category><![CDATA[Кодинг]]></category>
		<category><![CDATA[Прочее]]></category>
		<category><![CDATA[wordpress]]></category>
		<category><![CDATA[блог]]></category>
		<category><![CDATA[плагин]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=1914</guid>

					<description><![CDATA[<p>Вот написал простенький плагин для WordPress-а, и добавил его в официальный репозиторий плагинов. Что делает? Позволяет изменять родительский комментарий для любого пользовательского комментария. К примеру, частенько пользователи промахиваются с веткой, и отвечают не в ту, что ломает наглядность ведения комментариев. И если случайному пользователю, оставившему комментарий, дела до этого, как правило, нет, то вот последующим [&#8230;]</p>
<p>The post <a href="https://intsystem.org/coding/change-comment-parent/">Пишем свой плагин для WordPress, добавляем в каталог плагинов</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="attachment_1922" style="width: 310px" class="wp-caption alignleft"><a href="https://wordpress.org/plugins/change-comment-parent/" rel="nofollow"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-1922" class="wp-image-1922 size-medium" title="Wordpress change comment parent" src="https://intsystem.org/wp-content/uploads/2015/12/wordpress-Change-Comment-Parent-300x170.png" alt="Wordpress change comment parent" width="300" height="170" srcset="https://intsystem.org/wp-content/uploads/2015/12/wordpress-Change-Comment-Parent-300x170.png 300w, https://intsystem.org/wp-content/uploads/2015/12/wordpress-Change-Comment-Parent.png 600w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a><p id="caption-attachment-1922" class="wp-caption-text">Плагин в репозитории WordPress</p></div>
<p>Вот написал простенький плагин для WordPress-а, и добавил его в <a href="https://wordpress.org/plugins/change-comment-parent/" rel="nofollow">официальный репозиторий плагинов</a>. Что делает? Позволяет изменять родительский комментарий для любого пользовательского комментария.</p>
<p>К примеру, частенько пользователи промахиваются с веткой, и отвечают не в ту, что ломает наглядность ведения комментариев. И если случайному пользователю, оставившему комментарий, дела до этого, как правило, нет, то вот последующим читателям это может доставить неудобства.<span id="more-1914"></span></p>
<p>Не так давно задался целью привести свой блог в порядок. И одна из задач состояла в том что нужно было перебрать все свои комментарии, а точнее привести их в порядок. Так например изначально у меня были включены древовидные комментарии, потом, экспериментируя, я их отключил, а через некоторое время вернул обратно. В результате правильность ведения комментариев в виде вопрос-ответ была нарушена. А так как довольно часто в комментариях бывает полезная информация, то это, если мягко сказать, очень плохо.</p>
<p>Никаких решений для этого в интернетах я не нашел. Точнее, был один плагин, позволяющий вручную(!) указывать ID родительского комментария для каждого комментария. Выглядит это так &#8212; открываем страницу редактирования комментария и в специальное текстовое поле вводим ID родительского, т.е. помимо всего прочего нужно узнавать ID родительского коммента. Насколько это сложно проделать в совокупности хотя бы для сотни комментариев остается только догадываться.</p>
<p>В общем было решено как-нибудь упростить себе этот процесс, но желательно без лишних наворотов. В итоге сделал это прям на странице с записью при просмотре комментариев. Получилось очень удобно &#8212; т.к. именно так нагляднее всего видна древовидная структура и ошибки в ней.</p>
<h2>Как написать свой плагин для вордпресса?</h2>
<p>В принципе это не так сложно. Но для начала следует изучить <a href="https://codex.wordpress.org/Написание_плагина">официальную документацию</a> и хотя бы примерно представлять то как работает WordPress. Если в двух словах, любой дополнительный функционал плагинов добавляется в WordPress с помощью хуков, фильтров и функций-коллбеков навешанных на эти события.</p>
<h3>Подготовка</h3>
<p>Для начала создаем свой каталог для будущего плагина в <code>/wp-content/plugins/</code>, и одноименный php файл в нем, в нашем случае это <code>/wp-content/plugins/change-comment-parent/change-comment-parent.php</code>. В этот файл следует добавить специальный заголовок:</p>
<pre class="lang:php decode:true " title="Заголовок плагина">&lt;?php
/*
Plugin Name: Change Comment Parent
Description: Simple plug-in for editing the parent comments to any user comments. Use it to edit the threaded structure comments.
Version: 1.0.0
Author: Dmitriy Amirov (InSys)
Author URI: https://intsystem.org/
*/</pre>
<p>Все. Поздравляю. Свой плагин написан. Осталось включить его в админке. Правда беда в том, что он ничего не делает, т.к. пуст. Давайте добавлять функционал в него.</p>
<blockquote><p>Обращу внимание, код в статье максимально упрощен намеренно для наглядности, не надо искать в нем ошибки и высказывать свое &#171;фе&#187; по этому по поводу :)</p></blockquote>
<h3>Декомпозиция задачи</h3>
<p>Рассмотрим как работает наш плагин:<br />
<img loading="lazy" decoding="async" class="alignnone size-full wp-image-1930" title="Схема работы плагина" src="https://intsystem.org/wp-content/uploads/2015/12/comment-parent-change.png" alt="Схема работы плагина" width="414" height="186" srcset="https://intsystem.org/wp-content/uploads/2015/12/comment-parent-change.png 414w, https://intsystem.org/wp-content/uploads/2015/12/comment-parent-change-300x135.png 300w" sizes="auto, (max-width: 414px) 100vw, 414px" /><br />
Т.е. нам нужно модифицировать саму страницу которая выводит записи и комментарии под ней. Добавить в нее немного HTML, JS и CSS. Так же чтобы придерживаться гайдлайну WordPress-а, а не хакрдкодить неправильные вещи, нужно указать адрес для Ajax зароса, который будет обрабатывать этот запрос.</p>
<p>На стороне WordPress нужно добавить обработчик запроса, который будет проверять права пользователя и изменять родительский комментарий.</p>
<h3>Фронтэнд</h3>
<p>Вот так выглядит интерфейс плагина (к каждому комментарию добавляются кнопки child, parent и apply):<br />
<img loading="lazy" decoding="async" class="alignnone wp-image-1928 size-full" title="Пример комментария" src="https://intsystem.org/wp-content/uploads/2015/12/comment-example-e1450413445713.png" alt="Пример комментария" width="490" height="125" srcset="https://intsystem.org/wp-content/uploads/2015/12/comment-example-e1450413445713.png 490w, https://intsystem.org/wp-content/uploads/2015/12/comment-example-e1450413445713-300x77.png 300w" sizes="auto, (max-width: 490px) 100vw, 490px" /></p>
<p>Вот так это выглядит в виде кода:</p>
<pre class="lang:php decode:true " title="Кнопки для каждого комментария">if (get_option('thread_comments')) {
	add_filter('comment_text', array($this, 'commentTextAddHtml'), 10, 2);
}

function commentTextAddHtml($commentText, $comment)
{
	$html =
		'&lt;div class="insys-comment-parent"&gt;&lt;div&gt;' .
			'&lt;label&gt;&lt;input type="radio" name="parent" value="' . $comment-&gt;comment_ID . '"&gt;parent&lt;/label&gt;' .
			'&lt;label&gt;&lt;input type="radio" name="child" value="' . $comment-&gt;comment_ID . '"&gt;child&lt;/label&gt;' .
			'&lt;a href="#"&gt;apply&lt;/a&gt; ' .
		'&lt;/div&gt;&lt;/div&gt;';

	$commentText = $commentText . $html;

	return $commentText;
}</pre>
<p>При этом нам нужно добавить стили, скрипты, и адрес бекенда. Делается это так:</p>
<pre class="lang:default decode:true" title="Добавление стилей">wp_enqueue_style(
	'insys-comment-parent',
	plugins_url('style.css', __FILE__),
	array(),
	'1.0'
);

wp_enqueue_script(
	'insys-comment-parent',
	plugins_url('script.js', __FILE__),
	array('jquery'),
	'1.0'
);

wp_localize_script(
	'insys-comment-parent',
	'insysCommentParent',
	array( 'plugin_url'=&gt; plugins_url('', __FILE__), 'ajax_url' =&gt; admin_url( 'admin-ajax.php' ))
);</pre>
<p>Таким образом мы подключим <code>style.css</code> и <code>script.js</code> из каталога с плагином. Так же обратите внимание на <code>wp_localize_script</code> именно через нее мы передаем <code>'ajax_url'</code> в JS.</p>
<p>Так выполняется Ajax запрос:</p>
<pre class="lang:js decode:true " title="Ajax запрос из JS к WordPress">$.ajax({
	url: window.insysCommentParent.ajax_url,
	type: 'POST',
	data: {
		'action': 'insys_comment_parent',
		//...
	},
	//...
});</pre>
<h3>Бекэнд</h3>
<p>На этой стороне нам необходимо обработать Ajax запрос, проверив права пользователя:</p>
<pre class="lang:php decode:true " title="Обработка Ajax запроса">if (!current_user_can('moderate_comments' ) ) {
	return false;
}

if (is_admin()) {
	add_action('wp_ajax_insys_comment_parent', 'commentParentSet');
}

function commentParentSet(){
	$parent = $child = null;

	if (isset($_POST['parent'])) {
		$parent = intval($_POST['parent']);
	}

	if (isset($_POST['child'])) {
		$child = intval($_POST['child']);
	}

	if (empty($child)) {
		die('error');
	}

	if (!$parent || $child == $parent) {
		$parent = 0;
	}

	/* @var $wpdb wpdb */
	global $wpdb;

	$wpdb-&gt;query("UPDATE wp_comments SET comment_parent = " . intval($parent) . " WHERE comment_ID = " . intval($child) . "");

	die('ok');
}</pre>
<p>Все просто и без изысков. Получили два значения, выполнили запрос. Боюсь что добавить тут больше нечего.</p>
<h2>Как добавить плагин на wordpress.org?</h2>
<p>Ничего сложного тут нет.</p>
<h3>Readme.txt</h3>
<p>В первую очередь нужно создать специальный файл readme.txt (<a href="https://wordpress.org/plugins/about/readme.txt" rel="nofollow">пример</a>), и положить этот файл в каталог с плагином. Этот файл содержит всю информацию которая будет выводится на странице с описанием плагина на https://wordpress.org/plugins/. Так же есть специальная утилитка для <a href="https://wordpress.org/plugins/about/validator/" rel="nofollow">валидации этого файла</a>.</p>
<h3>Модерация</h3>
<p>Все плагины перед добавлением в репозиторий проходят модерацию. Посему написанный вами плагин необходимо отправить на модерацию.</p>
<p>Для этого <a href="https://wordpress.org/support/register.php" rel="nofollow">регистрируемся</a>, переходим в раздел <a href="https://wordpress.org/plugins/about/" rel="nofollow">разработчиков</a> в раздел <a href="https://wordpress.org/plugins/add/" rel="nofollow">Add Your Plugin</a>. Заполняем форму, добавляем zip архив с плагином, и отсылаем.</p>
<p>К слову сказать мне пришлось ждать не очень долго, всего три дня. Пришло вот такое письмо:<br />
<img loading="lazy" decoding="async" class="alignnone size-full wp-image-1935" title="Плагин прошел модерирование" src="https://intsystem.org/wp-content/uploads/2015/12/plugin-approved.png" alt="Плагин прошел модерирование" width="567" height="181" srcset="https://intsystem.org/wp-content/uploads/2015/12/plugin-approved.png 567w, https://intsystem.org/wp-content/uploads/2015/12/plugin-approved-300x96.png 300w" sizes="auto, (max-width: 567px) 100vw, 567px" /></p>
<h3>Репозиторий плагина</h3>
<p>Вся работа с плагином осуществляется через SVN репозиторий (честно говоря, не пользовался SVN до этого момента ни разу, и после GIT он мне показался таким топроным, что с трудом себя заставил разобраться с ним). В нем для нас уже любезно созданы специальные каталоги:<br />
<code>./assets/<br />
./branches/<br />
./trunk/<br />
./tags/<br />
</code><br />
Текущую версию закидываем в каталог <code>./trunk/</code>. А также в каталоге <code>./tags/</code> следует создать подкаталог с номером версии плагина, например <code>./tags/1.0/</code>. И сохраняем все это в репозитории.</p>
<p>При этом когда вы дорастете до новой версии плагина, то вы должны в каталоге <code>./tags/</code> по примеру выше создать подкаталог с именем новой версии плагина, куда следует залить файлы новой версии.</p>
<h3>Кастомизация страницы плагина</h3>
<p>Как уже было написано выше, информация для этой страницы берется из файла readme.txt. Но помимо этого, можно указать логотип плагина, и картинку шапки для страницы с плагином. Для этого в каталог ./assets/ можно закинуть следующие файлы:</p>
<ul>
<li><strong>icon-128&#215;128.png</strong> &#8212; лого</li>
<li><strong>icon-256&#215;256.png</strong> &#8212; лого для retina</li>
<li><strong>banner-772&#215;250.jpg</strong> &#8212; картинка в шапке</li>
<li><strong>banner-1544&#215;500-rtl.jpg</strong> &#8212; картинка в шапке для retina</li>
</ul>
<p>Туда же можно добавить скриншоты (файлы с именами screenshot-1.png, screenshot-N.jpg), которые будут добавлены в соответствующий раздел.</p>
<h2>Заключение</h2>
<p>После всех этих действий плагин можно назвать полноценным. Пользователи установившие ываего, при выходе новых версий, смогут обновляться из админки по-человечески.</p>
<p>Ну а я теперь полноценный официальный wordpress-разработчик. А мой плагин за три дня установили целых 6(!) человек, хотя мне кажется даже эти люди &#8212; случайность :D</p><p>The post <a href="https://intsystem.org/coding/change-comment-parent/">Пишем свой плагин для WordPress, добавляем в каталог плагинов</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/coding/change-comment-parent/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Теперь в РБК есть абзац про меня :)</title>
		<link>https://intsystem.org/etc/teper-v-rbk-est-abzac-pro-menya/</link>
					<comments>https://intsystem.org/etc/teper-v-rbk-est-abzac-pro-menya/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Mon, 14 Dec 2015 08:49:19 +0000</pubDate>
				<category><![CDATA[Капча]]></category>
		<category><![CDATA[Прочее]]></category>
		<category><![CDATA[captcha]]></category>
		<category><![CDATA[KeyCAPTCHA]]></category>
		<category><![CDATA[интервью]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=1909</guid>

					<description><![CDATA[<p>Ну вот, только сейчас заметил что про меня немножко написали в РБК: Бесконечная война Автор блога IntSystem.Org Дмитрий Амиров, утверждающий, что взломал Keycaptcha, называет captcha атавизмом, который заставляет пользователя совершать ненужные действия, не обеспечивая должной защиты. «Это раньше распознавание символов с картинки для компьютеров было сложной задачей, сейчас все это обходимо, с пазлами — то [&#8230;]</p>
<p>The post <a href="https://intsystem.org/etc/teper-v-rbk-est-abzac-pro-menya/">Теперь в РБК есть абзац про меня :)</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Ну вот, только сейчас заметил что про меня немножко <a href="http://www.rbc.ru/own_business/24/03/2015/550ffeca9a79473e26caaef5">написали в РБК</a>:</p>
<blockquote><p>Бесконечная война</p>
<p>Автор блога IntSystem.Org Дмитрий Амиров, утверждающий, что взломал Keycaptcha, называет captcha атавизмом, который заставляет пользователя совершать ненужные действия, не обеспечивая должной защиты. «Это раньше распознавание символов с картинки для компьютеров было сложной задачей, сейчас все это обходимо, с пазлами — то же самое, — объясняет Амиров РБК. — Взломщик, если задастся целью, сможет легко обойти любую интерактивную капчу. Очевидно, на смену ей должны прийти более продвинутые технологии». По его словам, на написание алгоритма для сбора мозаики Keycaptcha у Амирова ушел всего один вечер. Гарантировать стопроцентную защиту не может ни одна captcha, задача иная — сделать процесс взлома экономически нецелесообразным, парирует Владыкин.</p></blockquote>
<p>Для тех кто не понял, это относится к моей статье про <a href="https://intsystem.org/captcha/vzlom-keycaptcha/">взлом KeyCaptcha</a>.<br />
<span id="more-1909"></span><br />
В начале этого года, постучался ко мне корреспондент с РБК, мол пишем про эту капчу, нашли вашу статью, не могли бы вы дать комментарий. Был отчасти удивлен, но комментарий дал) В принципе, все написано с моих слов, смысл не искажен.</p>
<p>Только вот сама статья меня смущает. Не могу понять, как этот говнопроект умудрился привлечь хоть какие-то инвестиции/выйграть гранты. Но наверное это мое предвзятое мнение, связанное с нелюбовью к его авторам-хамам.</p>
<p><del>Мне нужно больше сенсаций! Пойду еще что-нибудь похекаю :)</del></p><p>The post <a href="https://intsystem.org/etc/teper-v-rbk-est-abzac-pro-menya/">Теперь в РБК есть абзац про меня :)</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/etc/teper-v-rbk-est-abzac-pro-menya/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>Проксирование, ресайз и кеширование картинок с помощью NGINX</title>
		<link>https://intsystem.org/server/proxy-resize-cache-images-with-nginx/</link>
					<comments>https://intsystem.org/server/proxy-resize-cache-images-with-nginx/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Fri, 27 Nov 2015 00:20:20 +0000</pubDate>
				<category><![CDATA[Кодинг]]></category>
		<category><![CDATA[Сервер]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[изображения]]></category>
		<category><![CDATA[конфигурация]]></category>
		<category><![CDATA[обработка]]></category>
		<category><![CDATA[проксирование]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=1763</guid>

					<description><![CDATA[<p>Есть некий удаленный сервис (далее images.service.com), по функционалу похож на каталог товаров. У него есть АПИ благодаря которому, я могу выводить этот список товаров на своем проекте (далее site.com). Но вот незадача, удаленный ресурс частенько падает. И как следствие &#8212; в каталоге пропадают абсолютно все товары, и вы сами представляете, наверняка, что это такое. Пришлось [&#8230;]</p>
<p>The post <a href="https://intsystem.org/server/proxy-resize-cache-images-with-nginx/">Проксирование, ресайз и кеширование картинок с помощью NGINX</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Есть некий удаленный сервис (далее <strong>images.service.com</strong>), по функционалу похож на каталог товаров. У него есть АПИ благодаря которому, я могу выводить этот список товаров на своем проекте (далее <strong>site.com</strong>). Но вот незадача, удаленный ресурс частенько падает. И как следствие &#8212; в каталоге пропадают абсолютно все товары, и вы сами представляете, наверняка, что это такое.</p>
<p>Пришлось прикрутить кеширование данных всех товаров через АПИ. По сути, сам проект уже работает с кешированными данными. Но вся беда в картинках каждого товара. Приблизительная схема работы проекта:<br />
<img loading="lazy" decoding="async" class="aligncenter wp-image-1829" title="Кеширование товаров" src="https://intsystem.org/wp-content/uploads/2015/11/Untitled.png" alt="Кеширование товаров" width="462" height="201" srcset="https://intsystem.org/wp-content/uploads/2015/11/Untitled.png 612w, https://intsystem.org/wp-content/uploads/2015/11/Untitled-300x130.png 300w, https://intsystem.org/wp-content/uploads/2015/11/Untitled-200x87.png 200w" sizes="auto, (max-width: 462px) 100vw, 462px" />Как только падает удаленный сервис, пропадают картинки товаров, что очень сильно сказывается на юзабилити проекта, и вообще выглядит убого.</p>
<p>Товаров в каталоге около 10 000. Картинок у каждого в среднем 30. Плюс 3 размера каждой картинки (large, medium, small), итого около 900 000 различных изображений (это около 80ГБ). Товары в каталоге динамичны &#8212; появляются, удаляются. Т.е. и за актуальностью картинок тоже надо бы следить.</p>
<p>В общем нужно как-то проксировать эти картинки с удаленного сервера, попутно сохраняя эти картинки к себе на сервер. Попутно появилась мысля &#8212; нарезать картинки в точные размеры, которые нужны нам, т.к. размеры, которые отдает сервис, больше чем требуется, а это лишний трафик для клиента.</p>
<p>Подытожим наши требования:</p>
<ul>
<li>проксировать картинки с удаленного сервиса и сохранять их локально</li>
<li>если есть уже сохраненная картинка, то отдавать ее</li>
<li>автоматически ресайзить картинки, пересохранять их в progressive JPEG</li>
<li>минимизировать обращения к удаленному серверу, а именно:
<ul>
<li>если удаленная картинка не найдена, то такой ответ сервиса кешировать</li>
<li>если сервис упал, то такие ответы тоже кешировать</li>
<li>если есть уже сохраненная картинка бОльшего размера, то использовать ее (т.е. взять ее, отресайзить, сохранить, и отдать пользователю), без обращения к удаленному сервису</li>
</ul>
</li>
<li>в случае ошибки отдавать картинку-заглушку</li>
</ul>
<p>Использовать PHP или любые другие скриптовые средства не хотелось, из-за их тормознутости/отжирания памяти. Все это удалось решить средствами NGINX.</p>
<p><span id="more-1763"></span></p>
<p>Сразу скажу, для меня это было в новинку, с nginx я работал мало, в основном только на уровне базовых правил и не более. Но за время того как делал эту задачу, можно сказать выучил документацию по nginx чуть ли не наизусть)</p>
<p>Забегу вперед и покажу схему архитектуры, которая получилась в итоге:<br />
<img loading="lazy" decoding="async" class="aligncenter wp-image-1834" title="Схема работы NGINX из двух инстансов" src="https://intsystem.org/wp-content/uploads/2015/11/Untitled2.png" alt="Схема работы NGINX из двух инстансов" width="543" height="388" srcset="https://intsystem.org/wp-content/uploads/2015/11/Untitled2.png 631w, https://intsystem.org/wp-content/uploads/2015/11/Untitled2-300x214.png 300w, https://intsystem.org/wp-content/uploads/2015/11/Untitled2-200x143.png 200w" sizes="auto, (max-width: 543px) 100vw, 543px" /></p>
<p>Как видно из схемы, пришлось создавать два инстанса nginx (две разные секции <code>server</code>), и разделять задачи между ними. В принципе можно было бы обойтись и одним, но тогда кешировать запросы с ошибками не удалось бы. А если удаленный сервис вдруг упадет, то этого кеша очень сильно не хватало бы. Поэтому решено было остановится на такой схеме.</p>
<p>Для всего этого дела был приобретен отдельный сервер (далее <strong>image.site.com</strong>), который и стал у нас в дальнейшем сервером картинок.</p>
<h2>NGINX</h2>
<p>Для описанного выше, нам нужен модуль <code>ngx_http_image_filter_module</code>, который в общем то и позволяет ресайзить картинки. Но, к сожалению, по дефолту nginx собирается без него. Поэтому пришлось пересобрать nginx, с подключением этой библиотеки. Так же заодно добавил поддержку PCRE JIT, в мануале написано, что</p>
<blockquote><p>использование PCRE JIT способно существенно ускорить обработку регулярных выражений.</p></blockquote>
<p>А в нашем случае это будет довольно полезно. Полный процесс сборки описывать не буду, он хорошо описан в официальной документации. В моем случае добавились лишь следующие аргументы при сборке:<br />
<code>./configure ... --with-http_image_filter_module --with-pcre=[путь до pcre] --with-pcre-jit</code><br />
В будущем еще планирую подключить HTTP/2. Будет полезно.</p>
<h2>Основной инстанс</h2>
<p>Собственно это тот инстанс, что открыт на 80 и 443 портах и доступен снаружи.</p>
<p>В этой секции основной упор был сделан на директиву <a href="http://nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_store" rel="nofollow">proxy_store</a>. Что она дает? Она сохраняет проксируемый файл в указанном каталоге, как есть, с сохранением структуры каталогов. Для нашей задачи подходит, как нельзя лучше. Конфиг nginx для этой секции:[su_spoiler title=&#187;Показать конфиг основного инстанса NGINX&#187; style=&#187;fancy&#187;]</p>
<pre class="height:1000 lang:default highlight:0 decode:true" title="Конфиг основного инстанса nginx">server {
	#...
	error_page 403 404 405 415 500 502 503 504 /nophoto/nophoto.jpg;

	location ~* .*(?&lt;extension&gt;jpg|jpeg|png|gif)$ {
		error_page 403 404 405 415 500 502 503 504 /nophoto/nophoto.$extension;
		access_log off;

		limit_except GET {
			deny all;
		}

		location ~* ^/images/item\d?/.+$ {
			expires max;

			try_files "/imdata${uri}" @proxy_local;
		}
	}

	location @proxy_local {
		proxy_temp_path /var/www/cust_images/data/temp;

		proxy_store	"${root_path}/imdata${uri}";
		proxy_store_access user:rw group:rw all:r;

		proxy_intercept_errors on;

		proxy_method			GET;
		proxy_pass_request_body		off;
		proxy_pass_request_headers	off;

		proxy_pass http://0.0.0.0:8085;
	}
	#...
}</pre>
<p>[/su_spoiler]Разберем по функциональным блокам.</p>
<h3>Картинка-заглушка</h3>
<pre class="lang:default highlight:0 decode:true" title="Конфиг основного инстанса nginx">error_page 403 404 405 415 500 502 503 504 /nophoto/nophoto.jpg;

location ~* .*(?&lt;extension&gt;jpg|jpeg|png|gif)$ {
	error_page 403 404 405 415 500 502 503 504 /nophoto/nophoto.$extension;
	#...
}
</pre>
<p>По дефолту, на любой ошибочный запрос отдается картинка <code>/nophoto/nophoto.jpg</code> (мало ли кто набрал вручную имя домена где у нас лежат картинки или пытается скачать какую нибудь другую страницу).</p>
<p>Но если к серверу обратились за картинкой, а ее нет, мы отдаем картинку в нужном формате (т.е. хотят PNG но ее на сервере нету, вернется <code>/nophoto/nophoto.png</code> и т.д.). Для этого в каталоге <code>/nophoto/</code> лежат картинки всех перечисленных форматов.</p>
<h3>Существующие картинки</h3>
<p>Тут все просто:</p>
<pre class="lang:default highlight:0 decode:true" title="Проверка уже сохраненных картинок">location ~* ^/images/item\d?/.+$ {
	expires max;

	try_files "/imdata${uri}" @proxy_local;
}
</pre>
<p>Если файл в каталоге <code>/imdata/</code> есть, отдаем его, если нет, то переходим в секцию <code>@proxy_local</code>.</p>
<h3>Запрос к внутреннему инстансу</h3>
<p>Здесь мы уже видим ту самую диррективу <code>proxy_store</code>:</p>
<pre class="lang:default highlight:0 decode:true" title="Запрос к внутреннему инстансу">location @proxy_local {
	proxy_temp_path /var/www/cust_images/data/temp;

	proxy_store	"${root_path}/imdata${uri}";
	proxy_store_access user:rw group:rw all:r;

	proxy_intercept_errors on;

	proxy_method				GET;
	proxy_pass_request_body		off;
	proxy_pass_request_headers	off;

	proxy_pass http://0.0.0.0:8085;
}
</pre>
<p>которая сохраняет результат запроса в каталог <code>/imdata/</code>. Здесь происходит внутренний запрос к <code>0.0.0.0:8085</code>, а точнее ко внутреннему инстансу NGINX о котором говорилось выше.</p>
<p>Причем если внутренний запрос вернет ошибку, например 404, то этот ответ сохранен не будет, а обработается в соответствии с указанной ранее директивой <code>error_page</code>, см. <a href="http://nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors" rel="nofollow">proxy_intercept_errors</a>.</p>
<p>При этом обратите внимание на то что <code>proxy_pass_request_body</code> и <code>proxy_pass_request_headers</code> установлены в off. Зачем? Затем чтобы минимизировать трафик внутри между инстансами NGINX. Предполагается что клиент запрашивает картинку (т.е. обычный статичный файл), а это значит, что никаких заголовков, типа User-Agent, Referer и прочих, для запроса не нужно. И уж тем более не нужно тело запроса (параметры POST, PUT и т.д), так как таких запросов тут быть не должно вообще, и в конфиге я целенаправленно заблокировал все кроме GET и HEAD (см. <a href="http://nginx.org/ru/docs/http/ngx_http_core_module.html#limit_except" rel="nofollow">limit_except</a>).</p>
<h2>Внутренний инстанс</h2>
<p>Этот инстанс висит на 8085 порту, но доступ к нему извне закрыт фаерволом, т.к. по сути он нужен только для внутренних запросов, и светить наружу ему попросту не за чем.<br />
[su_spoiler title=&#187;Показать конфиг внутреннего инстанса NGINX&#187; style=&#187;fancy&#187;]</p>
<pre class="height-set:false lang:default highlight:0 decode:true" title="Конфиг внутреннего инстанса nginx">proxy_cache_path
    /var/www/cust_images/data/cache
    levels=1:2
    keys_zone=remoteimages:10m
    inactive=60m
    max_size=1G;

server {
    #...
    image_filter_jpeg_quality     80;
    image_filter_buffer           10M;
    image_filter_interlace        on;

    location ~* ^(?&lt;request_1&gt;/images/item\d?/\d+/\d+/)(?&lt;request_size&gt;small|medium|large|full)(?&lt;request_2&gt;\_\d+\.(jpg|jpeg|png|gif))$ {
        proxy_temp_path               /var/www/cust_images/data/temp;

        proxy_cache                   remoteimages;
        proxy_cache_key               $proxy_host$uri;

        proxy_cache_min_uses          1;
        proxy_cache_lock              on;

        proxy_cache_valid             any                                 1m;
        proxy_cache_valid             400 404 408 415 500 502 503 504     1h;

        proxy_cache_use_stale         error timeout invalid_header updating http_500 http_502 http_503 http_504;

        proxy_connect_timeout         5s;
        proxy_read_timeout            5s;
        proxy_send_timeout            5s;

        proxy_method                  GET;
        proxy_pass_request_body       off;
        proxy_pass_request_headers    off;

        set $width  "-";
        set $height "-";

        location ~* .+/full\_\d+\.[a-z]+$ {
            proxy_pass "http://images.service.com${request_1}large${request_2}";

            set $width  "-";
            set $height "-";
        }

        location ~* .+/large\_\d+\.[a-z]+$ {
            if (-f "${root_path}/imdata${request_1}full${request_2}") {
                return 481;
            }

            proxy_pass "http://images.service.com${request_1}large${request_2}";

            set $width  625;
            set $height "-";
        }

        location ~* .+/medium\_\d+\.[a-z]+$ {
            if (-f "${root_path}/imdata${request_1}full${request_2}") {
                return 482;
            }

            if (-f "${root_path}/imdata${request_1}large${request_2}") {
                return 482;
            }

            proxy_pass "http://images.service.com${request_1}medium${request_2}";

            set $width  380;
            set $height "-";
        }

        location ~* .+/small\_\d+\.[a-z]+$ {
            if (-f "${root_path}/imdata${request_1}full${request_2}") {
                return 483;
            }

            if (-f "${root_path}/imdata${request_1}large${request_2}") {
                return 483;
            }

            if (-f "${root_path}/imdata${request_1}medium${request_2}") {
                return 483;
            }

            proxy_pass "http://images.service.com${request_1}small${request_2}";

            set $width  "-";
            set $height 100;
        }

        image_filter resize $width $height;
    }

    error_page 481 =200 @local_large;
    error_page 482 =200 @local_medium;
    error_page 483 =200 @local_small;

    location @local_large {
        try_files "/imdata${request_1}full${request_2}" =404;

        image_filter resize 625 -;
    }

    location @local_medium {
        try_files "/imdata${request_1}large${request_2}" "/imdata${request_1}full${request_2}" =404;

        image_filter resize 380 -;
    }

    location @local_small {
        try_files "/imdata${request_1}medium${request_2}" "/imdata${request_1}large${request_2}" "/imdata${request_1}full${request_2}" =404;

        image_filter resize - 100;
    }
}</pre>
<p>[/su_spoiler]<br />
С этой секцией уже посложнее. Сразу поясню по размерам картинок:<br />
[su_table]</p>
<table>
<thead>
<tr>
<td><strong>Оригинальный размер</strong></td>
<td><strong>Новый размер</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>&#8212;</td>
<td><strong>full</strong> &#8212; 940px * ?</td>
</tr>
<tr>
<td><strong>large</strong> &#8212; 940px * ?</td>
<td><strong>large</strong> &#8212; 625px * ?</td>
</tr>
<tr>
<td><strong>medium</strong> &#8212; 611px * ?</td>
<td><strong>medium</strong> &#8212; 380px * ?</td>
</tr>
<tr>
<td><strong>small</strong> &#8212; 282px * ?</td>
<td><strong>small</strong> &#8212; ? * 100px</td>
</tr>
</tbody>
</table>
<p>[/su_table]<br />
(? &#8212; пропорциональное изменение размера)</p>
<p>Как видно, практически все используемые размеры нам не подходят (единственный спорный момент &#8212; использование старых картинок типа <strong>medium</strong> вместо новых <strong>large</strong>, т.к. у них разница составляет всего 14px, и можно было бы переверстать оригинальный шаблон, но было принято решение подгонять все в точности как надо нам).</p>
<p>При этом старые полнозамерные картинки <strong>large</strong> теперь пересохраняются под новым именем <strong>full</strong>, на тот случай если нам однажды понадобится добавить новый размер.</p>
<p>Покажу то, как работает этот инстанс на примере запроса картинки <strong>medium</strong> в виде блок-схемы:<br />
<img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1855" title="Блок схема ресайзинга проксируемых картинок" src="https://intsystem.org/wp-content/uploads/2015/11/Untitled3.png" alt="Блок схема ресайзинга проксируемых картинок" width="472" height="621" srcset="https://intsystem.org/wp-content/uploads/2015/11/Untitled3.png 472w, https://intsystem.org/wp-content/uploads/2015/11/Untitled3-228x300.png 228w, https://intsystem.org/wp-content/uploads/2015/11/Untitled3-152x200.png 152w" sizes="auto, (max-width: 472px) 100vw, 472px" /><br />
(Вообще на самом деле немного не так. На деле кеш проверяется перед запросом на удаленный сервер. Но это не принципиально, а такая схема более наглядна.)</p>
<h3>Кеширование</h3>
<pre class="lang:default highlight:0 decode:true ">        proxy_cache                   remoteimages;
        proxy_cache_key               $proxy_host$uri;
 
        proxy_cache_min_uses          1;
        proxy_cache_lock              on;
 
        proxy_cache_valid             any                                 1m;
        proxy_cache_valid             400 404 408 415 500 502 503 504     1h;
 
        proxy_cache_use_stale         error timeout invalid_header updating http_500 http_502 http_503 http_504;
</pre>
<p>Как видно тут, кешируются все ответы удаленного ресурса: ошибки на 1 час, все валидные ответы на 1 минуту.</p>
<p>Зачем кешировать валидный ответ? В данном случае это делается для снижения количества обращений к удаленному сервису. Т.е. если в один момент два разных человека запросят одну и ту же картинку, то выполнится не два запроса на удаленный сервис, а один. Второй запрос будет стоять в очереди (<a href="http://nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_cache_lock" rel="nofollow">proxy_cache_lock</a>) ожидания завершения точно такого же предыдущего запроса.</p>
<h3>Работа с картинкой</h3>
<p>Проверка существования картинок осуществляется следующим образом:</p>
<pre class="lang:default highlight:0 decode:true ">        location ~* .+/medium\_\d+\.[a-z]+$ {
            if (-f "${root_path}/imdata${request_1}full${request_2}") {
                return 482;
            }

            if (-f "${root_path}/imdata${request_1}large${request_2}") {
                return 482;
            }

            #...
        }
</pre>
<p>Т.е. если найдена картинка по адресу <code>${root_path}/imdata${request_1}full${request_2}</code> или <code>${root_path}/imdata${request_1}large${request_2}</code> (где <code>${request_1}</code> и <code>${request_2}</code> &#8212; части URL запроса), то управление передается в отдельную секцию через код ошибки <code>482</code>:</p>
<pre class="lang:default highlight:0 decode:true ">	error_page 482 =200 @local_medium;
	#...
	location @local_medium {
		try_files "/imdata${request_1}large${request_2}" "/imdata${request_1}full${request_2}" =404;

		image_filter resize 380 -;
	}
</pre>
<p>(да есть и другие способы передачи обработки запроса, например через <code>rewrite</code>, <code>try_files</code> и прочее, но такой способ через <code>error_page</code> мне показался наиболее лаконичным)</p>
<p>Если же сохранной ранее картинки бОльшего размера нету, то обращаемся за ней на удаленный сервис, и ресайзим ее:</p>
<pre class="lang:default highlight:0 decode:true ">        location ~* .+/medium\_\d+\.[a-z]+$ {
            #...

            proxy_pass "http://images.service.com${request_1}medium${request_2}";

            set $width  380;
            set $height "-";
        }

        image_filter resize $width $height;
</pre>
<p>и полученный результат отдаем.</p>
<p>Честно сказать, очень печалит подход к секции if в nginx, а точнее ее неявное преобразование в еще один location. И следующее отсюда отсутствие возможности реализовывать вложенные if, отсутствие конструкции else. Хотя с другой стороны, это все таки конфигурационный файл, а не язык программирования.</p>
<h2>Обновление картинок</h2>
<p>Как помним из вступления, нам нужна была еще динамика. Т.е. добавление/удаление картинок.</p>
<p>В принципе добавление картинок на сервер происходит постепенно и прозрачно, при каждом запросе к новой картинке она сохраняется у нас, и можно было бы не заморачиваться. Рано или поздно все картинки оказались бы на нашем сервере. А точнее в нашем случае это будет скорее &#171;поздно&#187;, так как это произойдет после того как юзеры просмотрят все товары внутри каталога, и пролистают все фотографии. А так как удаленный сервис падает довольно часто, то вероятность того, что юзер попытается просмотреть еще не сохраненную у нас картинку будет очень велика.</p>
<p>Было решено приделать систему поддерживающую актуальность сохраненных картинок. Работает очень просто. На сервере проекта <strong>site.com</strong> раз в сутки по крону парсится список всех товаров, вытаскиваются все ссылки на картинки, и составляется такой список всех картинок:<br />
<code>http://images.service.com/images/item123/123/123/large_123.jpg<br />
http://images.service.com/images/item234/234/234/large_234.jpg<br />
...<br />
[и еще 300 000 линков на прочие картинки]</code><br />
Затем на сервере картинок <strong>images.site.com</strong> (тот самый про который и написан этот пост), так же раз в сутки этот файл скачивается wget-ом, после чего стартует скрипт который и обрабатывает данный список.</p>
<p>Алгоритм очень прост. Проверяем существование этого файла в каталоге <strong>/imdata/</strong>, если файла нет, то делаем HEAD запрос к локальному серверу NGINX. Можно было бы и внутри скрипта CURL-ом напрямую скачивать картинку с удаленного сервиса, но зачем? У нас же это уже реализовано, пусть и средствами веб сервера, но этого вполне достаточно.</p>
<p>В целом сам скрипт чуть сложнее, добавлена блокировка (от запуска нескольких копий скрипта), кеш списка уже скаченных картинок и буфер (в целях снижения нагрузки на жесткий диск). Если кому интересно, то можно ознакомиться с исходным кодом:<br />
[su_spoiler title=&#187;Скрипт для скачивания новых картинок&#187; style=&#187;fancy&#187;]</p>
<pre class="height-set:false lang:php decode:true " title="">&lt;?php

define('APP_DIR', rtrim(dirname(__FILE__), '\//') . DIRECTORY_SEPARATOR);

$conf = [
	'lock'		 =&gt; APP_DIR . 'lock.tmp',
	'list'		 =&gt; APP_DIR . 'images.txt',
	'ready'		 =&gt; APP_DIR . 'images_ready.txt',
	'error'		 =&gt; APP_DIR . 'error.log',
	'imagesDir'	 =&gt; '/var/www/cust_images/data/www/images.site.com/imdata/',
];

$fp = fopen($conf['lock'], 'w');

if(!flock($fp, LOCK_EX | LOCK_NB)) {
    echo 'Sync script has already started';
    exit(-1);
}

register_shutdown_function(
	function() use ($fp){
		flock($fp, LOCK_UN | LOCK_NB);
		fclose($fp);
	}
);

set_time_limit(0);
ignore_user_abort(true);
ini_set('memory_limit', '256M');

if (file_exists($conf['list'])) {
	$imagesList = file($conf['list']);
	$imagesList = array_map('trim', $imagesList);
} else {
	$imagesList = array();
}

if (file_exists($conf['ready'])) {
	$imagesReady = file($conf['ready']);
	$imagesReady = array_map('trim', $imagesReady);
} else {
	$imagesReady = array();
}

$imagesToSync = array_diff($imagesList, $imagesReady);
unset($imagesList, $imagesReady);


$buffer = [];
$funcFlushBuffer = function() use (&amp;$buffer, $conf) {
	file_put_contents($conf['ready'], implode("\n", $buffer) . "\n", FILE_APPEND);
	$buffer = [];
};

while (($item = array_pop($imagesToSync))) {
	if (count($buffer) &gt; 500) {
		$funcFlushBuffer();
		usleep(500000); //sleep 100 ms
	}

	$path = str_replace(['http://images.service.com/', 'https://images.service.com/'], '/', $item);
	$path = str_replace('large_', 'full_', $path);

	if (file_exists($conf['imagesDir'] . $path)) {
		$buffer[] = $item;
		continue;
	}

	$url = str_replace(['http://images.service.com/', 'https://images.service.com'], 'http://images.site.com/', $item);
	$url = str_replace('large_', 'full_', $url);

	$ch = curl_init();
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
	curl_setopt($ch, CURLOPT_HEADER, true);
	curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD');
	curl_setopt($ch, CURLOPT_NOBODY, true);
	curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
	curl_setopt($ch, CURLOPT_FORBID_REUSE, true);
	curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);

	$content = curl_exec($ch);
	curl_close($ch);

	$buffer[] = $item;
	$funcFlushBuffer();

	usleep(100000); //sleep 100 ms
	
	if (!file_exists($conf['imagesDir'] . $path)) {
		file_put_contents($conf['error'], date(DATE_ATOM) . ' error sync ' . $path . "\n", FILE_APPEND);
	}
}</pre>
<p>[/su_spoiler]</p>
<p>Ну а скрипт удаления ненужных картинок стартует раз в месяц, и в нем по сути нет ничего такого на чем можно было бы заострить внимание.</p>
<h2>Итог</h2>
<p>В итоге получилась такая схема взаимодействия:<br />
<img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1862" title="Кеширование товаров и картинок" src="https://intsystem.org/wp-content/uploads/2015/11/Untitled4.png" alt="Кеширование товаров и картинок" width="462" height="201" /><br />
Теперь все картинки кешируются у нас, и падений удаленного сервиса можно не боятся.</p>
<p>Как видите NGINX &#8212; очень гибкий веб сервер :)</p><p>The post <a href="https://intsystem.org/server/proxy-resize-cache-images-with-nginx/">Проксирование, ресайз и кеширование картинок с помощью NGINX</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/server/proxy-resize-cache-images-with-nginx/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
		<item>
		<title>И вот четыре года прошло…</title>
		<link>https://intsystem.org/etc/i-vot-chetyre-goda-proshlo/</link>
					<comments>https://intsystem.org/etc/i-vot-chetyre-goda-proshlo/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Fri, 20 Nov 2015 10:30:46 +0000</pubDate>
				<category><![CDATA[Прочее]]></category>
		<category><![CDATA[блог]]></category>
		<category><![CDATA[отчет]]></category>
		<category><![CDATA[традиция]]></category>
		<guid isPermaLink="false">https://intsystem.org/?p=1800</guid>

					<description><![CDATA[<p>Ну чтож вот он традиционный пост, и да, с той же самой картинкой :) Сегодня этому блогу исполняется целых четыре года. Но, наверное, это не самое главное событие, которым я хочу похвастаться в этом посте. Главнее то, что, как кто то уже заметил, я вернул этот блог из &#171;забвения&#187;, сдул двухгодовалый слой пыли, и снова [&#8230;]</p>
<p>The post <a href="https://intsystem.org/etc/i-vot-chetyre-goda-proshlo/">И вот четыре года прошло…</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<p><img loading="lazy" decoding="async" class="alignleft size-medium wp-image-10" src="https://intsystem.org/wp-content/uploads/2011/12/0_740c4_7643ec57_L-300x223.jpg" alt="nachalo_polozhenno" width="300" height="223" srcset="https://intsystem.org/wp-content/uploads/2011/12/0_740c4_7643ec57_L-300x223.jpg 300w, https://intsystem.org/wp-content/uploads/2011/12/0_740c4_7643ec57_L-200x149.jpg 200w, https://intsystem.org/wp-content/uploads/2011/12/0_740c4_7643ec57_L.jpg 430w" sizes="auto, (max-width: 300px) 100vw, 300px" />Ну чтож вот он традиционный пост, и да, с той же самой картинкой :)</p>
<p>Сегодня этому блогу исполняется целых четыре года. Но, наверное, это не самое главное событие, которым я хочу похвастаться в этом посте. Главнее то, что, как кто то уже заметил, я вернул этот блог из &#171;забвения&#187;, сдул двухгодовалый слой пыли, и снова начал им заниматься. И это несомненно здорово!</p>
<p>Мои мысли, планы на будущее и традиционный обзор интересных постов за последнее время под катом <span id="more-1800"></span></p>
<p>В первую очередь хочу принести извинения своим читателям за то, что я, можно сказать, пропал почти на два года. Уж так сложились обстоятельства, наступил творческий кризис, и пропало какое бы то ни было желание. Но есть и плюс в этом, за два года я поднабрался опыта, выучил кучу разных клевых штук, и теперь у меня есть некий план на ближайшее время о чем я буду писать тут.</p>
<p>За два года, к сожалению, растерял практически всех своих постоянных подписчиков/комментаторов, о чем очень печалюсь&#8230; Ребята, если вы вдруг это читаете, вернитесь, я хороший :) Но, как ни странно, это единственный отрицательный фактор, проявившийся за два года отсутствия активности. Позиции в поисковых системах подросли, трафик остался на прежнем уровне. И вон, не так давно, яндекс дал <a href="https://intsystem.org/seo/cy-30/">мне ТИЦ 30</a>.</p>
<p>Перейдем к главному. Не буду разбивать посты на категории, т.к. их не очень много, но вот то на что можно обратить внимание за последние два года:</p>
<ul>
<li><a href="https://intsystem.org/server/pereexal-na-novyj-server/">Переехал на новый сервер</a> &#8212; собственно сказ о том что мне дало толчок снова начать вести бог, а именно &#8212; переезд на выделенный сервер;</li>
<li><a href="https://intsystem.org/etc/uskoryaem-wordpress/">Ускоряем генерацию страниц WordPress в три раза</a> &#8212; настроиваю сервер, окружение, плагины, правлю код вордпреса &#8212; в итоге получаю существенный прирост скорости;</li>
<li><a href="https://intsystem.org/sql/neyavnaya-problema-on-duplicate-key-i-auto-incriment/">Неявная проблема ON DUPLICATE KEY и AUTO INCREMENT</a> &#8212; какую свинью вам может подложить Innodb Mysql при использовании AUTO INCREMENT;</li>
<li><a href="https://intsystem.org/server/pereexal-na-https-vklyuchil-http2/">Переехал на HTTPS, включил HTTP/2</a> &#8212; я вливаюсь во всеобщую волну истерии по поводу необходимости HTTPS, ну и плюс важное событие &#8212; включаю поддержку протокола  HTTP/2;</li>
<li><a href="https://intsystem.org/server/pochemu-fastvps-luchshe-chem-sweb-naglyadno/">Почему FastVps лучше чем sweb</a> &#8212; ну не могу не сакцентировать внимание на такой наглядной разнице между хостерами (не реклама, мое наблюдение);</li>
<li><a href="https://intsystem.org/coding/kak-rabotat-s-ipv6-v-php/">Как работать с IPv6 в PHP</a> &#8212; перевод зарубежной статьи, с учетом постепенного перехода на IPv6, я думаю, это будет полезно;</li>
<li><a href="https://intsystem.org/server/gitolite-avtodeploj-dev-ili-kak-ya-rabotayu-s-git/">Gitolite, Автодеплой dev… или как я работаю с GIT</a> &#8212; организация git репозиториев у меня на сервере.</li>
</ul>
<p>На этом, к сожалению все. В принципе, если учесть что это все по сути написано за три месяца, то, я думаю, ничего страшного, что так мало :)</p>
<p>В планах рассказать про интересный опыт работы с картинками в nginx, дописать статью про сертификаты (в продолжение статьи про <a href="https://intsystem.org/security/asymmetric-encryption-how-it-work/">асимметричное шифрование</a>) и атаку MitM, перевести еще пару интересных статей, написать про все работы, что я провел, улучшая/оптимизируя блог, и еще несколько интересных задумок.</p>
<p>И (надеюсь у меня это получится) хочу, в целях привлечения посетителей, организовать небольшой хак квест. Задумки сценария есть. В качестве приза только не знаю, что предложить. Одна из идей &#8212; год бесплатного хостинга у меня на сервере. Только не уверен что это будет кому-то интересно) но других идей нет.</p>
<p>Ну вот в принципе и все что я мог хотел рассказать в этом сообщении. Спасибо за внимание =)</p><p>The post <a href="https://intsystem.org/etc/i-vot-chetyre-goda-proshlo/">И вот четыре года прошло…</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/etc/i-vot-chetyre-goda-proshlo/feed/</wfw:commentRss>
			<slash:comments>11</slash:comments>
		
		
			</item>
		<item>
		<title>Gitolite, Автодеплой dev… или как я работаю с GIT</title>
		<link>https://intsystem.org/server/gitolite-avtodeploj-dev-ili-kak-ya-rabotayu-s-git/</link>
					<comments>https://intsystem.org/server/gitolite-avtodeploj-dev-ili-kak-ya-rabotayu-s-git/#comments</comments>
		
		<dc:creator><![CDATA[Дмитрий Амиров]]></dc:creator>
		<pubDate>Sat, 07 Nov 2015 00:32:55 +0000</pubDate>
				<category><![CDATA[Кодинг]]></category>
		<category><![CDATA[Сервер]]></category>
		<category><![CDATA[dev]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[gitolite]]></category>
		<category><![CDATA[production]]></category>
		<category><![CDATA[деплой]]></category>
		<category><![CDATA[разработка]]></category>
		<guid isPermaLink="false">http://intsystem.org/?p=1500</guid>

					<description><![CDATA[<p>В общем как то так сложилось что люблю я пользоваться git-ом. Обожаю вести в нем проекты, хранить конфиги. Ну и раз уж появился у меня целый сервер, то грех не организовать на нем удобное хранилище своих репозиториев. Gitolite позволяет управлять репозиториями, настраивать доступ, в общем практически все что нужно и даже чуточку больше. Подробнее об [&#8230;]</p>
<p>The post <a href="https://intsystem.org/server/gitolite-avtodeploj-dev-ili-kak-ya-rabotayu-s-git/">Gitolite, Автодеплой dev… или как я работаю с GIT</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="attachment_1504" style="width: 310px" class="wp-caption alignleft"><a href="https://intsystem.org/wp-content/uploads/2015/07/1.png"><img loading="lazy" decoding="async" aria-describedby="caption-attachment-1504" class="wp-image-1504 size-medium" src="https://intsystem.org/wp-content/uploads/2015/07/1-300x164.png" alt="схема деплоя dev версии" title="схема деплоя dev версии" width="300" height="164" srcset="https://intsystem.org/wp-content/uploads/2015/07/1-300x164.png 300w, https://intsystem.org/wp-content/uploads/2015/07/1-200x109.png 200w, https://intsystem.org/wp-content/uploads/2015/07/1.png 496w" sizes="auto, (max-width: 300px) 100vw, 300px" /></a><p id="caption-attachment-1504" class="wp-caption-text">Cхема деплоя dev версии</p></div>
<p>В общем как то так сложилось что люблю я пользоваться git-ом. Обожаю вести в нем проекты, хранить конфиги. Ну и раз уж появился у меня целый сервер, то грех не организовать на нем удобное хранилище своих репозиториев.</p>
<p>Gitolite позволяет управлять репозиториями, настраивать доступ, в общем практически все что нужно и даже чуточку больше. Подробнее об этом можно глянуть в википедии. Но если в кратце, то gitolite предоставляет доступ к репозиториям посредством ssh, авторизуя пользователей по ключам. С помощью коммитов в специальный репозиторий мы управляем остальными репозиториями. В общем до жути удобно, и максимально просто.<br />
<span id="more-1500"></span></p>
<h2>Установка Gitolite</h2>
<p>Это очень просто. Для начала создадим специального юзера в системе (допустим server_git), а после от его имени выполняем:<br />
<code>cd ~<br />
git clone git://github.com/sitaramc/gitolite<br />
mkdir -p bin<br />
gitolite/install -to ./bin<br />
</code><br />
После вам нужно закинуть на сервер ваш публичный ssh ключ. Закиньте его в тот же каталог (key.pub), и выполняйте:<br />
<code>./bin/gitolite setup -pk key.pub</code><br />
Это и все, gitolite установлен. По умолчанию будет создано два репозитория, gitolite-admin.git и testing.git. Первый, как вы уже понимаете &#8212; нужен для управления репозиториями. Он будет доступен по адресу:<br />
<code>ssh://server_git@server.com/gitolite-admin.git</code><br />
(естественно для авторизации нужен приватный пара-ключ для key.pub с которым вы устанавливали gitolite одним шагом назад)</p>
<h2>Администрирование Gitolite</h2>
<p>Все в принципе очень просто. Клонируем к себе <code>ssh://server_git@server.com/gitolite-admin.git</code> и с помощью коммитов в этот репозиторий управляем доступом/созданием любых репозиториев. Для того чтобы добавить пользователя добавляем в каталог <code>keydir/</code> публичный ключ этого пользователя. Создание нового репозитория еще проще. Редактируем файл <code>conf/gitolite.conf</code>. Добавляем репозиторий вот так:</p>
<pre class="lang:default highlight:0 decode:true">repo newrepo
    RW+     =   user_first
    RW+     =   user_second</pre>
<p>При этом в папке <code>keydir/</code> должны быть ключи <code>user_first.pub</code> и <code>user_second.pub</code> соответствующих пользователей. Коммитим это все и пушим в репозиторий. После этого на сервере будет создан репозиторий <code>newrepo</code>, к которому будут иметь доступ соответствующие пользователи.</p>
<h2>Автодеплой</h2>
<p>С этим чуть сложнее. Я опишу как это делал именно я. Не спорю, может быть для этого есть более легкие методы. Но я сделал как смог, и заниматься поисками других вариантов у меня просто не было ни времени ни желания. Многие тащатся от монстров типа capistrano и иже с ним. Но у меня еще не возникало каких то нерешаемых с нынешней системой развертывания dev версий проектов. В продакшн же лью руками.</p>
<p>Вкратце &#8212; система построена на хуках, и подтягивания изменений из основного репозитория в репозиторий dev версии. Вобщем глянем на схему:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1504 size-full" title="Деплой gitolite" src="https://intsystem.org/wp-content/uploads/2015/07/1.png" alt="Деплой gitolite" width="496" height="271" srcset="https://intsystem.org/wp-content/uploads/2015/07/1.png 496w, https://intsystem.org/wp-content/uploads/2015/07/1-300x164.png 300w, https://intsystem.org/wp-content/uploads/2015/07/1-200x109.png 200w" sizes="auto, (max-width: 496px) 100vw, 496px" /></p>
<p>Всей музыкой руководит приложение (назовем его <strong>AutoDeployApp</strong>), которое клонирует новый репозиторий если его еще нет (<code>git init</code> и <code>git clone</code>), или подтягивает изменения в существующий (<code>git fetch</code> и <code>git reset</code>) репозиторий Dev версии проекта.</p>
<h2>AutoDeployApp</h2>
<p>В моем случае это набор простых скриптов (скачать <a href="https://intsystem.org/wp-content/uploads/2015/11/appDeploy.zip" target="_blank">appDeploy</a>). Распаковываем в <code>~/projects/</code>. Открываем <code>~/projects/config.php</code> и правим, в моем случае конфиг выглядит вот так:</p>
<pre class="lang:php decode:true">define('CONFIG_BASEPATH', '/var/www/server_git/data/');
define('CONFIG_DEFAULT_REPOSITORIES', CONFIG_BASEPATH . 'repositories/'); //gitolite repos
define('CONFIG_DEFAULT_CONTENTS', CONFIG_BASEPATH . 'www/git.intsystem.org/'); //path to live versions

define('CONFIG_REMOTE_REPO', 'ssh://server_git@server-git-local/');</pre>
<p>Где <code>ssh://server_git@server-git-local/</code> адрес сервера с gitolite. Т.к. хук обновляет репозиторий через SSH, осуществляя подключение к самому себе же, я просто создал алиас <code>server-git-local</code> в <code>~/.ssh/config</code> в таком виде:</p>
<pre class="lang:default highlight:0 decode:true ">Host server-git-local
  HostName git.intsystem.org
  User server_git
  StrictHostKeyChecking no
  IdentityFile ~/.ssh/server_git
</pre>
<p>При этом нужно сгенерировать пару приватный/публичный ключ (<code>~/.ssh/server_git</code> и <code>~/.ssh/server_git.pub</code>) с помощью команды <code>ssh-keygen</code> (надеюсь с этим вы справитесь).</p>
<p>Теперь в репозиторий <code>ssh://server_git@server.com/gitolite-admin.git</code> нужно закинуть <code>~/.ssh/server_git.pub</code>, и для каждого репозитория добавлять <code>RW+ = server_git</code>, в качестве примера:</p>
<pre class="lang:default highlight:0 decode:true ">repo testing
    RW+     =   insys
    RW+     =   server_git
</pre>
<h2>Хук в Gitolite</h2>
<p>Добавляем хук в <code>~/.gitolite/hooks/common/post-receive</code> следующего содержания:</p>
<pre class="lang:default highlight:0 decode:true ">#!/bin/bash

CURRENT_DIR=`pwd`

/var/www/server_git/data/projects/hook_git.php "$CURRENT_DIR" "$GL_REPO_BASE" "$GL_REPO" &gt;&gt; /var/www/server_dev/data/projects/log.txt 2&gt;&amp;1

exit 0</pre>
<p>Теперь на каждый пуш новых коммитов будет вызываться данный хук. И при необходимости обновлять dev версию проекта.</p>
<h2>Конфиг вебсервера</h2>
<p>В панели ISP есть такая замечательная функция которая называется <strong>автоподдомены</strong>. Суть данной настройки заключается в том что любой каталог расположенный в корневой папке, становится доступен в виде поддомена. Вот пример конфига апача:</p>
<pre class="lang:default highlight:0 decode:true ">	...
	ServerName git.intsystem.org
	ServerAlias *.git.intsystem.org
	VirtualDocumentRoot /var/www/server_git/data/www/git.intsystem.org/%1
	...
</pre>
<h2>Создаем новый репозиторий с dev версией</h2>
<p>Для начала создаем новый репозиторий в gitolite:</p>
<pre class="lang:default highlight:0 decode:true ">repo testproject
    RW+     =   user
    RW+     =   server_git
</pre>
<p>Добавляем проект в AutoDeployApp, открываем файл <code>~/projects/projects.txt</code>, добавляем новый проект:</p>
<pre class="lang:default highlight:0 decode:true ">testproject:testproject:testproject</pre>
<p>Создаем новый поддомен для dev версии, создаем каталог <code>~/www/git.intsystem.org/testproject/</code>.</p>
<p>Теперь любой пуш в <code>ssh://server_git@server.com/testproject.git</code> будет сразу виден по адресу <code>testproject.git.intsystem.org</code></p>
<h2>Немного о production</h2>
<p>Идея автоматического применения изменения в продакшн мне не очень нравится, но и каждый раз лазить на сервер через ssh чтобы сделать <code>git pull</code> тоже надоедает. Для этого у меня есть простенький скриптик <a href="https://intsystem.org/wp-content/uploads/2015/11/helper-git.zip" target="_blank">helper-git</a>. Имеет простейший функционал:<br />
<img loading="lazy" decoding="async" class="alignnone size-full wp-image-1775" title="Деплой gitolite" src="https://intsystem.org/wp-content/uploads/2015/11/10257802.png" alt="Деплой gitolite" width="360" height="227" srcset="https://intsystem.org/wp-content/uploads/2015/11/10257802.png 360w, https://intsystem.org/wp-content/uploads/2015/11/10257802-300x189.png 300w, https://intsystem.org/wp-content/uploads/2015/11/10257802-200x126.png 200w" sizes="auto, (max-width: 360px) 100vw, 360px" /><br />
Доступ запаролен (<code>helper-git.php?pass=[pass]</code>), смотрим сам файл</p>
<pre class="lang:default decode:true ">define('ACCESS_PASSWORD', 'doe2njkle');</pre>
<p>С виду хоть и выглядит топорно и отчасти по-ламерски, но меня вполне устраивает.</p>
<h2>Итог</h2>
<p>Итого имеем 4 репозитория на каждый проект:</p>
<ul>
<li>Локальный.</li>
<li>Основной (в который пушим из локального).</li>
<li>Dev-репозиторий (в него автоматом подтягиваются изменения из основного по хукам).</li>
<li>Продакшн (в него ручками).</li>
</ul>
<p>Кое-где можно было бы автоматизировать и упростить, но желания этим заниматься у меня нет, ибо проекты я создаю очень редко, а текущая организация системы меня вполне устраивает.</p>
<p>Ну в общем вот как то так. Да может не совсем понятно объяснил, но, честно сказать, я сам иногда перестаю понимать как это все работает :)</p><p>The post <a href="https://intsystem.org/server/gitolite-avtodeploj-dev-ili-kak-ya-rabotayu-s-git/">Gitolite, Автодеплой dev… или как я работаю с GIT</a> first appeared on <a href="https://intsystem.org">IntSystem.org</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://intsystem.org/server/gitolite-avtodeploj-dev-ili-kak-ya-rabotayu-s-git/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
	</channel>
</rss>