<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Mind Drops</title><link href="https://dmitry.khlebnikov.net/" rel="alternate"/><link href="https://dmitry.khlebnikov.net/feeds/all.atom.xml" rel="self"/><id>https://dmitry.khlebnikov.net/</id><updated>2026-07-19T06:11:02+10:00</updated><subtitle>assorted bits of wit</subtitle><entry><title>NordicTrack Commercial 1750 Treadmill</title><link href="https://dmitry.khlebnikov.net/2026/07/17/nordictrack-commercial-1750-treadmill/" rel="alternate"/><published>2026-07-17T14:00:00+10:00</published><updated>2026-07-17T14:13:56+10:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2026-07-17:/2026/07/17/nordictrack-commercial-1750-treadmill/</id><summary type="html">&lt;p&gt;The initial overview of the device and its customisation capabilities.&lt;/p&gt;</summary><content type="html">&lt;p&gt;My wife has convinced me to purchase on a treadmill.  Since I am quite picky
when it comes to purchasing anything that contains some kind of technology, so
I went into a rabbit hole of researching on the state of modern treadmills.
After reading a lot of articles, watching reviews on YouTube, and looking into
the issues people are encountering with their treadmills, I chose to go ahead
and order NordicTrack Commercial 1750.&lt;/p&gt;
&lt;p&gt;What I found in my research is that no matter how much a treadmill costs, it
basically consists of the following components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A motor (we are only discussing the electric treadmills here).  There is a
    variety of motors used in treadmills, but basically the industry standard
    seems to be a brushless motor with two to four horse power with more
    powerful being used on more premium models;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A deck that supports the user and on top of which the belt is running.
    Surprisingly, it turned out to be the cheapest and weakest part of even the
    most premium treadmill models.  For example, the $4,000 treadmill such as
    NordicTrack Commercial 1750 uses an Medium Density Fibreboard (MDF) deck,
    which is basically compressed paper.  For this reason, NordicTrack refuses
    to provide warranty for outdoor use: MDF absorbs water as a sponge;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A belt that is the primary working surface of the treadmill.  This is where
    the user is running on.  It seems that all existing treadmills settled on a
    layered vinyl-based belts with some fabric underneath that absorbs
    lubrication agent.  The idea is that the belt is slides on top of the deck,
    so the lubrication in the fabric layer reduces the friction when there is
    user&amp;rsquo;s step that presses the belt against the deck;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A controller which is the &amp;ldquo;brain&amp;rdquo; of the machine.  Usually, located next to
    the motor, it is quite a simple piece of electronics and usually controls
    the speed of the motor (or motors, if the model supports changing the
    treadmill inclination);&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;And finally, the control interface which allows the user to operate the
    machine.  In low-end models, the interface is usually quite simplistic and
    consists of segmented LED displays and buttons for changing the speed,
    inclination, and program selectors.  In more premium models, it is usually
    implemented as a touch screen with the addition of some physical buttons
    for controlling speed and inclination.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is a myriad of quality of life options that can be present on a particular
model.  For example, the rails may have integrated heartbeat sensors or there
could be cooling fans installed.&lt;/p&gt;
&lt;p&gt;Anyhow, when I was choosing the treadmill, my key criteria were to have more
premium materials for the construction of the frame and to have a convenient
interface for the user.  I also wanted to ensure that I am not purchasing some
quite expensive equipment just to be then locked into the manufacturer&amp;rsquo;s
subscription: it seems both NordicTrack and Pelaton are trying their best to
hook you up on their respective memberships which are quite costly (around
AUD$50 per month).&lt;/p&gt;
&lt;p&gt;So, the NordicTrack Commercial 1750 has a touch screen that runs a custom
Android build.  My initial analysis shows that one can get access to the
underlying system by following a procedure known publicly: on a welcome screen
tap ten times on a non-interactive background, wait approximately 7 seconds,
and tap ten times again.  The interface will challenge you with a code to enter
the privileged mode.  The code can be acquired by visiting
https://getresponsecode.com (you may want to download the page locally, in case
the person who is hosting that page decides to shut it down &amp;ndash; the logic of
acquiring the code is implemented as the client Javascript, so you can have a
local copy and it will work).&lt;/p&gt;
&lt;p&gt;I was able to access the underlying system, so at least I should be able to do
stuff other users have successfully achieved already, e.g. there is a &lt;a href="https://www.reddit.com/r/nordictrackandroid/wiki/index/"&gt;nice
Wiki on Reddit&lt;/a&gt; for
maintaining the privileged mode on the device.&lt;/p&gt;
&lt;p&gt;It seems that Nordic Unchained was a one off attempt by somebody to &amp;ldquo;own&amp;rdquo; their
device and the archive does not have a definitive home.  I downloaded it from
the Reddit Wiki and looked at the installation script.  Basically, all it does
is: disables the system iFit apps using adb and then installs a bunch of
Android Package Kits (APKs) which the author of the script wanted to have on
their device (like Chrome, Hulu, etc.).  I think, the most important part the
script is doing can be summarised as follows:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu4c345109ff4fedfe3d8e1147648cec52fb6ea21a" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu4c345109ff4fedfe3d8e1147648cec52fb6ea21a" class="line-numbers icon list-numbered"&gt;&lt;code class="language-bash"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;adb&lt;span class="w"&gt; &lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;pm&lt;span class="w"&gt; &lt;/span&gt;disable-user&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;com.ifit.eru
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;adb&lt;span class="w"&gt; &lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;pm&lt;span class="w"&gt; &lt;/span&gt;disable-user&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;com.ifit.launcher
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;adb&lt;span class="w"&gt; &lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;pm&lt;span class="w"&gt; &lt;/span&gt;disable-user&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;com.ifit.standalone
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;...
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;adb&lt;span class="w"&gt; &lt;/span&gt;shell&lt;span class="w"&gt; &lt;/span&gt;cmd&lt;span class="w"&gt; &lt;/span&gt;package&lt;span class="w"&gt; &lt;/span&gt;set-home-activity&lt;span class="w"&gt; &lt;/span&gt;com.teslacoilsw.launcher/com.teslacoilsw.launcher.NovaLauncher&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, the first three lines disable the system provided iFit apps, then a bunch
of APKs (including iFit) are installed as a user.  One of the APKs is for the
alternative launcher (Nova Launcher in this case) so the last line above is
registering that launcher as the default.&lt;/p&gt;
&lt;p&gt;So, what&amp;rsquo;s next?  I will most likely start the way Nordic Unchained did it,
since my wife is using the treadmill every day and I can&amp;rsquo;t just disassemble and
experiment with the stuff until I am happy with the results.&lt;/p&gt;
&lt;p&gt;The ultimate goal is to have my own build of the firmware and to have the full
control of the device. I also want to reverse-engineer how the metadata is
incorporated in iFit&amp;rsquo;s training courses: I suspect they just add an additional
track (like subtitles track) to their video containers and that track encodes
speed and inclination, so when you are following the course the machine adjusts
accordingly.  If I can figure that out, I would be able to create my own
&amp;ldquo;exercises&amp;rdquo;, e.g. using OpenStreetMap, etc.&lt;/p&gt;
&lt;p&gt;There is a camera in the touch screen device, but at least in my machine it was
attached upside down.  I am still deciding whether I want to physically rotate
it or do it virtually.  In any case, it would be nice to have it for video
calls while you are exercising :).&lt;/p&gt;
&lt;p&gt;I will try to update my blog with the progress on this topic.  Stay tuned!&lt;/p&gt;</content><category term="nordictrack"/><category term="hacks"/><category term="iot"/><category term="android"/><category term="embedded"/></entry><entry><title>Samsung CSC Change Using Modem Commands</title><link href="https://dmitry.khlebnikov.net/2025/10/09/samsung-csc-change-using-modem-commands/" rel="alternate"/><published>2025-10-09T15:30:00+11:00</published><updated>2026-03-05T18:25:34+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2025-10-09:/2025/10/09/samsung-csc-change-using-modem-commands/</id><summary type="html">&lt;p&gt;A technical guide for manually changing the Country Service Code (CSC)
on modern Samsung phones.&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is a brief brain dump, so I can recall it when needed.&lt;/p&gt;
&lt;p&gt;Each time I get a new Samsung phone I have to figure out a way to change the
Country Service Code (CSC) to a country that allows automated call recordings,
for example, Indonesia (XID).  Previously, approximately 5 years ago, it was
quite straight forward: you needed to download the HOME_CSC image for the
region you are interested in, ensure that it was for the same bootloader
version, and flash it to the CSC partition.&lt;/p&gt;
&lt;p&gt;However, something changed in recent years (and I was not paying attention to
the developments in Samsung world).  I just recently purchased Samsung ZFold 7
and the trick with flashing did not work, so I started to investigate.
Firstly, I discovered that the modern Samsung devices come with a multi-CSC
configuration, so everything is already pre-installed on the device &amp;ndash; one just
needs to change the setting to set the desired CSC for the device.  Secondly,
the articles on the net on the change of CSC were all pointing to the SamFW
tool, but I do have an issue with using that tool: for starters, it is a
Windows x86_64 tool and I don&amp;rsquo;t have any Intel based devices to run Windows
x86_64.  So, I started to research on how SamFW was doing the change of the
CSC.  According to what I gathered, the tool was using the Serial-over-USB to
perform the change.&lt;/p&gt;
&lt;p&gt;Below are the steps I took to successfuly change the CSC on my phone from &lt;code&gt;XSA&lt;/code&gt;
(Australia) to &lt;code&gt;XID&lt;/code&gt; (Indonesia).&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You need to &lt;a href="https://www.samsung.com/uk/support/mobile-devices/how-do-i-turn-on-the-developer-options-menu-on-my-samsung-galaxy-device/"&gt;enable the &amp;ldquo;Developer options&amp;rdquo; menu&lt;/a&gt; on your Samsung device: go to Settings -&amp;gt; About phone -&amp;gt; Software information and tap on the &amp;ldquo;Build number&amp;rdquo; line 7 times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the &amp;ldquo;Developer options&amp;rdquo; menu make sure that you enable the following options:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;a) &amp;ldquo;Stay awake&amp;rdquo; &amp;ndash; this is optional, but you need to ensure that the phone is awake and not locked while you are going to communicate with it via USB, so it is easier to turn this option on and once the changes are done to turn it back off;&lt;/p&gt;
&lt;p&gt;b) &amp;ldquo;USB debugging&amp;rdquo; &amp;ndash; I am not really sure whether this is strictly needed, but in my session it was enabled;&lt;/p&gt;
&lt;p&gt;c) &amp;ldquo;3GPP AT commands&amp;rdquo; &amp;ndash; This will enable the interface we are going to use to issue modem commands;&lt;/p&gt;
&lt;p&gt;d) &amp;ldquo;Default USB configuration&amp;rdquo;, select &amp;ldquo;Transferring files&amp;rdquo; &amp;ndash; this is important since we are going to land on the debug screen that would not allow us to access notifications, so there is no way to change the USB configuration on the fly.  You may want to change to your preferred setting after the CSC change is applied, though.&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;p&gt;Launch the dialler application and enter &lt;code&gt;*#0*#&lt;/code&gt;, the screen will change to a white background with lots of square buttons for diagnostic purposes.  I believe this step is essential to enable some of the proprietary commands in the modem interface.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Connect your phone to your computer.  Mine is Apple Macbook M4, so it runs macOS.  Yours may run Linux or Windows.  It does not really matter, except with Windows you will need to install serial drivers for Samsung (e.g. if you install any official Samsung tools like KIES, you already should have the necessary drivers installed).  For macOS and Linux, it is much easier &amp;ndash; you don&amp;rsquo;t need to install anything, the Serial-over-USB should be supported out of the box.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Locate the modem endpoint on your operating system.  On macOS, use &lt;code&gt;ls -l /dev/cu.usbmodem*&lt;/code&gt; to locate it.  On Linux, I would suggest to do &lt;code&gt;dmesg | tail -30&lt;/code&gt; after connecting the device, you should be able to see which &lt;code&gt;/dev/tty*&lt;/code&gt; has been assigned for the Serial-over-USB.  For Windows, check your Device Manager to see which &lt;code&gt;COM*&lt;/code&gt; port was assigned.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use your favourite communication program to connect to the endpoint.  On macOS, I prefer to use &lt;code&gt;cu&lt;/code&gt;, which is included with the operating system and my examples below will use it.  On Linux, you have myriad of options, but I would use either &lt;code&gt;screen&lt;/code&gt; or &lt;code&gt;minicom&lt;/code&gt;.  On Windows, I think, HyperTerminal is still included with the operating system by default, but last time I have seen Windows up close was years ago.  Anyhow, you need to connect your communication program to the located endpoint, set the baudrate to 115200, and you should be all set.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now is the magic :).  Below is my terminal session on macOS with my comments after the snippet:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu9ad25de3e40b2237a346bf138d57e8d6e38dc347" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu9ad25de3e40b2237a346bf138d57e8d6e38dc347" class="line-numbers icon list-numbered"&gt;&lt;code class="language-bash"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;galaxy@galaxy&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="w"&gt; &lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt;            &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Password:
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;root@galaxy&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# ls -la /dev/cu.*                    &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;crw-rw-rw-&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt;  &lt;/span&gt;wheel&lt;span class="w"&gt;  &lt;/span&gt;0x9000003&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;:39&lt;span class="w"&gt; &lt;/span&gt;/dev/cu.Bluetooth-Incoming-Port
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;crw-rw-rw-&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt;  &lt;/span&gt;wheel&lt;span class="w"&gt;  &lt;/span&gt;0x9000001&lt;span class="w"&gt; &lt;/span&gt;Aug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;26&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;:38&lt;span class="w"&gt; &lt;/span&gt;/dev/cu.debug-console
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;crw-rw-rw-&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt;  &lt;/span&gt;wheel&lt;span class="w"&gt;  &lt;/span&gt;0x9000005&lt;span class="w"&gt; &lt;/span&gt;Oct&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;14&lt;/span&gt;:03&lt;span class="w"&gt; &lt;/span&gt;/dev/cu.usbmodemXXXXXXXXXXXX
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Eris:~&lt;span class="w"&gt; &lt;/span&gt;root#&lt;span class="w"&gt; &lt;/span&gt;cu&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;/dev/cu.usbmodemXXXXXXXXXXXX&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;115200&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Connected.
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;AT
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;ATI
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Manufacturer:&lt;span class="w"&gt; &lt;/span&gt;SAMSUNG&lt;span class="w"&gt; &lt;/span&gt;ELECTRONICS&lt;span class="w"&gt; &lt;/span&gt;CORPORATION
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Model:&lt;span class="w"&gt; &lt;/span&gt;SM-F966B
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Revision:&lt;span class="w"&gt; &lt;/span&gt;F966BXXU4AYI1
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;SVN:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;01&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;IMEI:&lt;span class="w"&gt; &lt;/span&gt;IIIIIIIIIIIIIII
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;+GCAP:&lt;span class="w"&gt; &lt;/span&gt;+CGSM
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;AT+DUMPCTRL&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,0
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;+CME&lt;span class="w"&gt; &lt;/span&gt;Error:PACM&lt;span class="o"&gt;(&lt;/span&gt;AP&lt;span class="o"&gt;)&lt;/span&gt;,PROTECTED_NO_TOK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;AT+SWATD&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt;DR&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;CHANGE&lt;span class="w"&gt; &lt;/span&gt;TO&lt;span class="w"&gt; &lt;/span&gt;DDEXE&lt;span class="o"&gt;[&lt;/span&gt;ATD&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;CHANGE&lt;span class="w"&gt; &lt;/span&gt;TO&lt;span class="w"&gt; &lt;/span&gt;DDEXE
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;AT+ACTIVATE&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;,0,0
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;+CME&lt;span class="w"&gt; &lt;/span&gt;Error:PACM&lt;span class="o"&gt;(&lt;/span&gt;AP&lt;span class="o"&gt;)&lt;/span&gt;,UNREGISTED
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;AT+SWATD&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt;DR&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;CHANGE&lt;span class="w"&gt; &lt;/span&gt;TO&lt;span class="w"&gt; &lt;/span&gt;ATD&lt;span class="o"&gt;[&lt;/span&gt;ATD&lt;span class="o"&gt;]&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;CHANGE&lt;span class="w"&gt; &lt;/span&gt;TO&lt;span class="w"&gt; &lt;/span&gt;ATD
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;AT+PRECONFG&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;,XID
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;+PRECONFG:2,OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;AT+CFUN&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,1
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;+CFUN&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;,1:NA
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;OK
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;~.
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Disconnected.
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;root@galaxy&lt;span class="w"&gt; &lt;/span&gt;~&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I used &lt;code&gt;AT&lt;/code&gt; at the beginning of the session to confirm that the modem interface
is active (I got an &lt;code&gt;OK&lt;/code&gt;, so it was active). Then I used &lt;code&gt;ATI&lt;/code&gt; to ensure that I
am actually talking to the desired device (it was indeed &lt;code&gt;SM-F966B&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;From this point on, all commands I am using are &lt;code&gt;AT+&lt;/code&gt; and, moreover, they
all come from the Application Processor (AP) module.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AT+DUMPCTRL=1,0&lt;/code&gt; is needed to enable the following &lt;code&gt;AT+SWATD=&lt;/code&gt; commands to work.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AT+ACTIVATE=0,0,0&lt;/code&gt; is a weird one.  There is not too much information on the
net regarding this command, but it seems that every single occurrence is related
to enabling something in the firmware to allow the privileged settings to be
changed, like we do with the very next command.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AT+PRECONF=2,nnn&lt;/code&gt; is the primary command we want to execute.  The command sets
the CSC of the device, where &lt;code&gt;nnn&lt;/code&gt; is a three letter CSC used by Samsung.  In my
case it was &lt;code&gt;XID&lt;/code&gt; for Indonesia.  You can get a list of all available/known CSC
from &lt;a href="https://samfw.com/"&gt;SamFW&lt;/a&gt; (the site is really heavy on ads, though, so beware).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AT+CFUN=1,1&lt;/code&gt; is supposed to reboot the device.  In my case, it did not, so I
rebooted it manually.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once you rebooted your device, go to Settings -&amp;gt; About phone -&amp;gt; Software
information and look at the &amp;ldquo;Service provider software version&amp;rdquo; section.  Mine
looks like the following (after the change):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuc64a97b1bb913fff857131a02eb6925efec44a31" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuc64a97b1bb913fff857131a02eb6925efec44a31" class="line-numbers icon list-numbered"&gt;&lt;code class="language-text"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;XID/XID,XID/XSA,OPS&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Somewhere on the net you could find the format of this string, but basically it
means that I have a dual-SIM enabled device with XID (Indonesia) CSC enabled
for both SIMs, that the custom CSC is XID, but factory one is XSA (Australia).
In your case, you should see your selected CSC in that line, if everything
worked out correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is basically it. Enjoy! :)&lt;/p&gt;</content><category term="hacks"/><category term="hacks"/><category term="howto"/><category term="console"/></entry><entry><title>macOS builtin VPN client tricks</title><link href="https://dmitry.khlebnikov.net/2022/11/25/macos-builtin-vpn-client-tricks/" rel="alternate"/><published>2022-11-25T13:33:00+11:00</published><updated>2025-10-15T23:06:20+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2022-11-25:/2022/11/25/macos-builtin-vpn-client-tricks/</id><summary type="html">&lt;p&gt;A post that explains how to overcome the limitations of the macOS
built-in VPN client, which the author finds lacks flexibility in its GUI and
exposes only a fraction of its supported features. Despite these issues often
leading users to third-party apps, the author finds the native client is
usually sufficient. The work-in-progress guide uses Windscribe VPN as an
example to demonstrate how to configure the client for most IKEv2 VPN services.
This is achieved by using an Apple Configuration Profile with custom XML
settings, which can produce a working connection where the standard GUI
configuration fails.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I think everyone would agree that when it comes to the VPN configuration macOS
builtin VPN client lacks any flexibility and its GUI exposes a fraction of
supported features.  This is one of the reasons why there are so many third
party VPN clients for macOS in the world, I believe.  However, I found that the
majority of the time I can get away by using the standard macOS VPN client to
connect to the desired VPN endpoint.&lt;/p&gt;
&lt;p&gt;In this article I am going to describe my journey of configuring macOS VPN
client to support Windscribe VPN, but the described approach applies to vast
majority of the VPN service providers who support IKEv2.&lt;/p&gt;
&lt;p&gt;XXX: This article is still work in progress, but I thought an example published
is better than nothing at all.&lt;/p&gt;
&lt;p&gt;I happen to be a customer of Windscribe VPN service.&lt;/p&gt;
&lt;p&gt;The macOS client is terrible
The native builtin VPN client is good enough for me.
GUI configuration produces unworkable result.&lt;/p&gt;
&lt;p&gt;Apple Configuration Profile Reference &lt;a href="https://developer.apple.com/business/documentation/Configuration-Profile-Reference.pdf#43"&gt;VPN Payload&lt;/a&gt;&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu5d8c5e39df20b844c3474818bc8109bf3c9d6997" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu5d8c5e39df20b844c3474818bc8109bf3c9d6997" class="line-numbers icon list-numbered"&gt;&lt;code class="language-xml"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE plist PUBLIC &amp;quot;-//Apple//DTD PLIST 1.0//EN&amp;quot; &amp;quot;http://www.apple.com/DTDs/PropertyList-1.0.dtd&amp;quot;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;plist&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;version=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;1.0&amp;quot;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadContent&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;array&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;IKEv2&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;AuthName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@LOGIN@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;AuthPassword&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@PASSWORD@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;AuthenticationMethod&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;None&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;DeadPeerDetectionRate&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;High&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;ExtendedAuthEnabled&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;IKESecurityAssociationParameters&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;dict&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;DiffieHellmanGroup&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;20&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;EncryptionAlgorithm&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;AES-256-GCM&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;LocalIdentifier&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;RemoteAddress&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@REMOTE_SERVER@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;RemoteIdentifier&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@REMOTE_SERVER@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadDisplayName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@DISPLAY_NAME@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadIdentifier&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.apple.vpn.managed.@@UUID1@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadType&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;com.apple.vpn.managed&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadUUID&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@UUID1@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadVersion&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;UserDefinedName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@DISPLAY_NAME@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;VPNType&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;IKEv2&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/array&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadDisplayName&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@DISPLAY_NAME@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadIdentifier&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;VPN.@@UUID2@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadType&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;Configuration&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadUUID&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;@@UUID2@@&lt;span class="nt"&gt;&amp;lt;/string&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;key&amp;gt;&lt;/span&gt;PayloadVersion&lt;span class="nt"&gt;&amp;lt;/key&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;integer&amp;gt;&lt;/span&gt;1&lt;span class="nt"&gt;&amp;lt;/integer&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;/dict&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;&amp;lt;/plist&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;</content><category term="macos"/><category term="macos"/><category term="howto"/><category term="vpn"/></entry><entry><title>Fixing "Content wider than screen"</title><link href="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/" rel="alternate"/><published>2021-09-03T13:00:00+10:00</published><updated>2025-10-15T23:06:20+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2021-09-03:/2021/09/03/fixing-content-wider-than-screen/</id><summary type="html">&lt;p&gt;The post provides a step-by-step guide for debugging the &amp;ldquo;Content
wider than screen&amp;rdquo; mobile usability error reported by Google Search Console,
even when the issue isn&amp;rsquo;t visually apparent. The author demonstrates how to use
Chrome DevTools, specifically the &amp;ldquo;Layers&amp;rdquo; view, to identify the root cause. By
inspecting the layer hierarchy, one can spot elements with unusually large
dimensions that extend far beyond the document&amp;rsquo;s viewport, causing the error.
The process involves finding these oversized layers, locating the corresponding
element in the DOM tree, and analyzing its CSS to understand why it&amp;rsquo;s being
rendered off-screen. In the author&amp;rsquo;s case, the problem was caused by a CSS
technique intended to hide a label for screen readers by pushing it far to the
left, which was fixed by applying overflow: hidden and opacity: 0 to contain
the element without affecting its accessibility.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently, I started to get notifications from my &lt;a class="external" href="https://search.google.com/search-console" rel="nofollow"&gt;Google Search Console&lt;/a&gt; that
some pages have their &lt;a class="external" href="https://support.google.com/webmasters/answer/9063469#content_not_sized_to_viewport" rel="nofollow"&gt;content wider than screen&lt;/a&gt; and that impacts Mobile
Usability.  However, when I went to the affected pages and performed a test
through &amp;ldquo;&lt;a class="external" href="https://search.google.com/test/mobile-friendly?url=https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/" rel="nofollow"&gt;TEST LIVE PAGE&lt;/a&gt;&amp;rdquo; no
Mobile Usability issues were found.  Yet, the re-validation requests through
Google Search Console were returning the error again and again.&lt;/p&gt;
&lt;p&gt;I searched for possible root cause and suggested fixes on forums, but did not
find any satisfactory approaches for investigating and fixing the issue.
Below, is my process of hunting the root cause down, which resulted in
resolving the issue of &amp;ldquo;Content wider than screen&amp;rdquo; in my Google Search Console.&lt;/p&gt;
&lt;p&gt;Let me start by saying that the website the error was reported against does not
have any visual issues on mobile devices: it renders fine, achieves &lt;a class="external" href="https://developers.google.com/speed/pagespeed/insights/?url=https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/&amp;amp;tab=mobile" rel="nofollow"&gt;90+ in
PageSpeed Insights&lt;/a&gt;, and, overall, is very standards aligned.&lt;/p&gt;
&lt;p&gt;So, how one can find what Google Search does not like about their seemingly
perfect website?  In this particular case, I found &lt;a class="external" href="https://developer.chrome.com/docs/devtools/" rel="nofollow"&gt;Chrome DevTools&lt;/a&gt; to be an
invaluable tool to investigate the issue, specifically its insight into how
layers are composed.  To open the DevTools one need to simply right-click
anywhere on the page they are about to analyse and choose the &amp;ldquo;Inspect&amp;rdquo; item at
the bottom of the context menu.  Once we are inside the DevTools window, we
need to the mobile view (by clicking on the &amp;ldquo;Toggle device toolbar&amp;rdquo; icon in the
upper left corner of the DevTools window &amp;ndash; all in all, we are investigating
the Mobile Usability issue.&lt;/p&gt;
&lt;p&gt;&lt;a class="figure icon link-ext" href="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-layers.png" target="_blank"&gt;&lt;img alt="A screenshot of Chrome DevTools with the More Tools menu selected" src="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-layers.png"&gt;&lt;/a&gt;
If you never used the Layers view of DevTools before, you can find that view by
clicking on three vertical dots in the upper right corner of the DevTools
window, then expand the &amp;ldquo;More tools&amp;rdquo; sub-menu, and select &amp;ldquo;Layers&amp;rdquo;.  This will
switch your DevTools to a view representing how different layers are rendered
by the web-browser engine.  The view is usually split in three distinctive
panes: the layer hierarchy, the visual representation of layers, and the
details pane for the currently selected layer.  Depending on whether you have
the WebGL support or not, the representation of layers may be unavailable.  If
it is unavailable, it would be a bit inconvenient, but not a big issue, since
our primary working horse is the layer hierarchy pane.&lt;/p&gt;
&lt;p&gt;&lt;a class="figure icon link-ext" href="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-layers-view.png" target="_blank"&gt;&lt;img alt="A screenshot of Chrome DevTools with the Layers view" src="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-layers-view.png"&gt;&lt;/a&gt;
We should start with expanding the top level layer container and check the
dimensions next to the first child, which is &lt;code&gt;#document&lt;/code&gt;.  In my example, the
&lt;code&gt;#document&lt;/code&gt; layer has the size of &lt;code&gt;720&lt;/code&gt; by &lt;code&gt;8811&lt;/code&gt; pixels.  These dimensions
represent the size of the viewport (assuming that your website visually fits
into the mobile view with no horizontal scroll bars).  Looking at my example,
we can immediately see an issue with the rendering: instead of a rectangle
representing our page we only see a single line across the pane.  The pane is
using auto scaling to fit all rendered layers inside the pane.  This means that
some element (or elements) in out page are so wide or rendered so far away from
the primary content that to represent all the layers Chrome needed to scale it
down significantly.&lt;/p&gt;
&lt;p&gt;&lt;a class="figure icon link-ext" href="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-elements-header-expand.png" target="_blank"&gt;&lt;img alt="A screenshot of Chrome DevTools showcasing how to expand nested levels recursively" src="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-elements-header-expand.png"&gt;&lt;/a&gt;
Now that we know what is wrong with the page, how do we locate the offending
element(s)?  First, we need to look at other layers in the layer hierarchy
pane: any layer that is bigger horizontally (or vertically) than our
&lt;code&gt;#document&lt;/code&gt; layer is the likely suspect for triggering the Mobile Usability
error of content being wider than screen.  In my case, the &lt;code&gt;header&lt;/code&gt; layer is
extremely wide: &lt;code&gt;3200060&lt;/code&gt; pixels wide, to be exact &amp;ndash; so, something inside the
header container is responsible for this.  It is time to switch to the Elements
view of DevTools and interrogate the DOM tree.  To make the search easier, once
I located the header container, I recursively expanded all children of that
container.&lt;/p&gt;
&lt;p&gt;&lt;a class="figure icon link-ext" href="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-elements-header-view.png" target="_blank"&gt;&lt;img alt="A screenshot of Chrome DevTools with the Elements view focused on the header element" src="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-elements-header-view.png"&gt;&lt;/a&gt;
This allows me to select the topmost container (in my case, it is &amp;ldquo;header&amp;rdquo;),
select &amp;ldquo;Computed&amp;rdquo; tab to see the position and dimensions of the selected
element, and then by pressing the down arrow key on my keyboard walk the tree
until I find the offending element.  We start with the &amp;ldquo;header&amp;rdquo; element and, as
you can see from the screenshot, its dimensions are reasonable and are smaller
than the dimensions of the &amp;ldquo;body&amp;rdquo; element which represents our document.&lt;/p&gt;
&lt;p&gt;&lt;a class="figure icon link-ext" href="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-elements-offender.png" target="_blank"&gt;&lt;img alt="A screenshot of Chrome DevTools with the Element view focused on the offending element" src="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-elements-offender.png"&gt;&lt;/a&gt;
Basically, any child element of the &amp;ldquo;body&amp;rdquo; container should be equal or smaller
than the dimensions of the &amp;ldquo;body&amp;rdquo; container.  In some extreme cases, it would
require one to do some tedious maths each time you move down the tree, but in
most cases skimming through will reveal a standout element like the label element
I stumbled upon.&lt;/p&gt;
&lt;p&gt;&lt;a class="figure icon link-ext" href="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-elements-offender-styles.png" target="_blank"&gt;&lt;img alt="A screenshot of Chrome DevTools with the Styles view focused on the offending element" src="https://dmitry.khlebnikov.net/2021/09/03/fixing-content-wider-than-screen/images/devtools-elements-offender-styles.png"&gt;&lt;/a&gt;
Despite that the size of this label element is just &lt;code&gt;40&lt;/code&gt; by &lt;code&gt;40&lt;/code&gt; pixels, its
position is way to the left (by &lt;code&gt;1599980&lt;/code&gt; pixels, to be precise).  To understand
why this element behaves like this we need to switch to the &amp;ldquo;Styles&amp;rdquo; tab to
look at the associated CSS rules for the offending element.  Sure enough, after
a bit of reading through the styles, I located the corresponding rule that was
pushing the element to the far left side of the universe.&lt;/p&gt;
&lt;p&gt;Once you find the offending element, you need to understand why it was
implemented like that and what goal was pursued by the author.  When this is
clear, you should be able to come up with an appropriate fix addressing the
Mobile Usability issue while preserving the intended behaviour of the element.
In my case, the author was me and this particular element was part of the UI.
Since my website is responsive and tries to cater for different audiences
(including people who are using screen readers), I needed to hide some text
from the visual audiences (they are fine with icons) and present textual label
to people who are using assistive technology like screen readers.
Unfortunately, I followed one of the articles I found on the web, which
suggested to hide elements by pushing them off the screen.  Therefore, my fix
in this instance was to cut the overflowing label off using &lt;code&gt;overflow: hidden&lt;/code&gt;
and &lt;code&gt;white-space: nowrap&lt;/code&gt;. I also used &lt;code&gt;opacity: 0&lt;/code&gt; for the &amp;ldquo;contextual&amp;rdquo; class,
which was used by some static, non-interactive elements on the page.&lt;/p&gt;
&lt;p&gt;I hope this small tutorial helped you to hunt down the source of your Google
Search Console errors related to the content being outside the viewport.&lt;/p&gt;</content><category term="web design"/><category term="html"/><category term="css"/><category term="design"/></entry><entry><title>Truly Responsive Pagination</title><link href="https://dmitry.khlebnikov.net/2020/06/14/truly-responsive-pagination/" rel="alternate"/><published>2020-06-14T20:07:00+10:00</published><updated>2025-10-15T23:06:20+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2020-06-14:/2020/06/14/truly-responsive-pagination/</id><summary type="html">&lt;p&gt;This post details author&amp;rsquo;s process for creating a truly responsive
pagination widget using only HTML and CSS. The goal was to build a widget that
maintains its appearance across different viewport sizes by omitting middle
page numbers when space is limited, without compromising accessibility for
screen readers. After exploring flexbox, the author developed a solution that
divides the pagination list into three parts (&amp;ldquo;head,&amp;rdquo; &amp;ldquo;middle,&amp;rdquo; and &amp;ldquo;tail&amp;rdquo;) and
uses row-reverse on the &amp;ldquo;tail&amp;rdquo; section to ensure the first and last page
numbers remain visible during resizing. A pseudo-element with a dotted border
is used to visually indicate omitted pages. Despite creating a workable
solution, the author notes remaining challenges, including accessibility issues
caused by reversing the list order in the HTML and an edge case where omission
indicators appear incorrectly.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Searching the web for an example of a fully responsive pagination widget I
found that either people consider &amp;ldquo;responsive&amp;rdquo; to be something else or that it
is a non-trivial problem to solve.  In any case, I decided to have a crack at
it myself.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A sketch of how pagination widget should work" class="figure" src="https://dmitry.khlebnikov.net/2020/06/14/truly-responsive-pagination/images/paginator-sketch.png"&gt;&lt;/p&gt;
&lt;p&gt;My definition of a responsive pagination widget is pretty simple: such a widget
should maintain its look and feel when the viewport (or a window) changes the
size, yet the excessive information such as page numbers in the middle should
be omitted if there is no space for them.&lt;/p&gt;
&lt;p&gt;In this post I am going to describe my journey on the topic and the solution I
ended up with.&lt;/p&gt;
&lt;p&gt;My starting point was pretty simple &amp;ndash; I had the following HTML markup
structure that was representing the page numbers in a pagination widget:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menua4f75b5f0cd5b78e44bc8cffef25cea5b925fe77" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menua4f75b5f0cd5b78e44bc8cffef25cea5b925fe77" class="line-numbers icon list-numbered"&gt;&lt;code class="language-html"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;paginator&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;2&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;3&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;4&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;5&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;6&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;7&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;8&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;9&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;10&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The goal was to create a fully responsive widget using pure CSS and HTML and to
try to avoid messing up the HTML structure unnecessarily, since I needed to
preserve the &amp;ldquo;cleanness&amp;rdquo; of navigation for screen readers (i.e. accessibility
is important!).&lt;/p&gt;
&lt;p&gt;After a bit of brainstorming I ended up with two candidates for the
implementation of my idea:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the &amp;ldquo;old-school&amp;rdquo; approach of presenting a list as a line of &lt;code&gt;inline-block&lt;/code&gt; elements &lt;code&gt;float&lt;/code&gt;ing next to each other;&lt;/li&gt;
&lt;li&gt;the recently new trend in web design to use the flexbox model.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since I did not use &lt;code&gt;display: flex&lt;/code&gt; before, I decided to try the flexbox layout
first.  The idea was that I could use two flex rows which wrap when the width
was shrinking.  The challenge was that I wanted to implement two visual
effects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the currently selected page should be in the middle of the pagination
     widget and the wrapping of the rows should be adjacent to that selected
     page number;&lt;/li&gt;
&lt;li&gt;it should be visually unambiguous that the ranges of off-screen pages were
     omitted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After thinking a bit I realised that the above description demands to have
three logically separate components: a part before the selected page number (I
call it &amp;ldquo;head&amp;rdquo;), a part containing the selected page number (I call it
&amp;ldquo;middle&amp;rdquo;), and a part following the selected page number (I call it &amp;ldquo;tail&amp;rdquo;).
Hence, my HTML markup became:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menudc19a6c091e9fc9a6e8591c3b6e6caa8595d98e7" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menudc19a6c091e9fc9a6e8591c3b6e6caa8595d98e7" class="line-numbers icon list-numbered"&gt;&lt;code class="language-html"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;paginator&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;head&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;lt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;1&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;2&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;3&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;4&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;middle&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;5&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tail&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;6&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;7&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;8&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;9&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;10&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initially, I just laid out the lists in a line just to see how it looks and
whether I like the mechanics of it, so I started with the following style
definitions:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuaa85bbc2842c46fe26b532304c1d79af8fe46aa7" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuaa85bbc2842c46fe26b532304c1d79af8fe46aa7" class="line-numbers icon list-numbered"&gt;&lt;code class="language-css"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;paginator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* width: 50%; */&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* margin: 0 auto; */&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;white-space&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nowrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* 5 items 3em each */&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;flex-flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;list-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;text-decoration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;middle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above produced the following result (the example is wrapped in a resizable
&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, so if you are using a browser that supports it, like Chrome, you
should be able to play with it right away):&lt;/p&gt;
&lt;div class="demo1 resizable"&gt;
&lt;style&gt;
    div[class^='demo'].resizable {
        position: relative;
        overflow: auto;
        resize: horizontal;
        border: solid 0.25em rgba(0,0,0,0.5);
        max-width: 100%;
        min-width: 18.5em;
        padding: 0.6em 1.2em;
        border-radius: 0.5em;
        margin-bottom: 1.5em;
    }

    .demo1 .paginator {
        /* width: 50%; */
        /* margin: 0 auto; */
        display: flex;
        white-space: nowrap;
        overflow: hidden;
        min-width: 15em;    /* 5 items 3em each */
    }

    .demo1 ul {
        display: flex;
        position: relative;
        flex-flow: row wrap;
        list-style: none;
        margin: 0;
        padding: 0;
        line-height: 1.2;
        height: 3.2em;
        overflow: hidden;
        min-width: 6em;
    }

    .demo1 ul li {
        display: block;
        width: 3em;
        height: 3em;
        line-height: 3;
        margin: 0;
    }

    .demo1 ul li a {
        display: block;
        text-align: center;
        text-decoration: none;
    }

    .demo1 ul.middle {
        min-width: 3em;
    }
&lt;/style&gt;
    &lt;span class="paginator"&gt;
        &lt;ul class="head"&gt;
            &lt;li&gt;&lt;a href="#"&gt;&amp;lt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;1&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;2&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;3&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;4&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;ul class="middle"&gt;
            &lt;li&gt;&lt;a href="#"&gt;5&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;ul class="tail"&gt;
            &lt;li&gt;&lt;a href="#"&gt;6&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;7&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;8&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;9&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;10&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;&amp;gt;&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;There are two issues which are immediately manifesting themselves when you
resize the widget:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &amp;ldquo;tail&amp;rdquo; section wraps at the end truncating the list of the available
     pages and making it impossible to jump to the last page number in the
     list;&lt;/li&gt;
&lt;li&gt;There is no visual guidance whatsoever that the page number were omitted.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first issue is a difficult one since in the flexbox model we cannot easily
control which side of the row is used for wrapping.  However, if we adjust our
HTML markup and leverage not so often used &lt;code&gt;row-reverse&lt;/code&gt; value of the flexbox,
then achieve the desired effect.&lt;/p&gt;
&lt;p&gt;So, in the HTML markup I reversed the &amp;ldquo;tail&amp;rdquo; list:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu67052a9f2b2d2fe58aaf4d83c68881e2646fbf16" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu67052a9f2b2d2fe58aaf4d83c68881e2646fbf16" class="line-numbers icon list-numbered"&gt;&lt;code class="language-html"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tail&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="ni"&gt;&amp;amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;10&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;9&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;8&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;7&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;#&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;6&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and I adjusted the style of the &amp;ldquo;tail&amp;rdquo; list to apply &lt;code&gt;row-reverse&lt;/code&gt; (which basically
visually presents the list in the reverse order):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuaa8d28b6151db770232745b54055862fcbfdb0b8" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuaa8d28b6151db770232745b54055862fcbfdb0b8" class="line-numbers icon list-numbered"&gt;&lt;code class="language-css"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;tail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;flex-flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;row-reverse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the above changes applied the result looks much more closer to what we
want:&lt;/p&gt;
&lt;div class="demo2 resizable"&gt;
&lt;style&gt;
    .demo2 .paginator {
        /* width: 50%; */
        /* margin: 0 auto; */
        display: flex;
        white-space: nowrap;
        overflow: hidden;
        min-width: 15em;    /* 5 items 3em each */
    }

    .demo2 ul {
        display: flex;
        position: relative;
        flex-flow: row wrap;
        list-style: none;
        margin: 0;
        padding: 0;
        line-height: 1.2;
        height: 3.2em;
        overflow: hidden;
        min-width: 6em;
    }

    .demo2 ul li {
        display: block;
        width: 3em;
        height: 3em;
        line-height: 3;
        margin: 0;
    }

    .demo2 ul li a {
        display: block;
        text-align: center;
        text-decoration: none;
    }

    .demo2 ul.middle {
        min-width: 3em;
    }

    .demo2 ul.tail {
        flex-flow: row-reverse wrap;
    }
&lt;/style&gt;
    &lt;span class="paginator"&gt;
        &lt;ul class="head"&gt;
            &lt;li&gt;&lt;a href="#"&gt;&amp;lt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;1&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;2&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;3&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;4&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;ul class="middle"&gt;
            &lt;li&gt;&lt;a href="#"&gt;5&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;ul class="tail"&gt;
            &lt;li&gt;&lt;a href="#"&gt;&amp;gt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;10&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;9&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;8&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;7&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;6&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;Now, to tackle with the visual guidance for the omitted page numbers we can do
something like the following: we can display dots in space when the page number
is being wrapped.  To do so we could use pseudo-elements for the &amp;ldquo;middle&amp;rdquo; list:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu5f9ecbd8b1a96caa2930152f31e1a9d5d2d417ee" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu5f9ecbd8b1a96caa2930152f31e1a9d5d2d417ee" class="line-numbers icon list-numbered"&gt;&lt;code class="language-css"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;middle&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;border-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;dotted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things to note: we are creating a pseudo-element which is three times
wider than its parent, we position it absolutely, shift it 1/3 to the left and
half way down.  With the only border defined to be the top border we are
getting a dotted line that goes through the middle of our &amp;ldquo;middle&amp;rdquo; list.&lt;/p&gt;
&lt;p&gt;However, to make this work we also need to ensure that the border we created
can extend beyond the box defined by the &amp;ldquo;middle&amp;rdquo; list, hence we need to set
&lt;code&gt;overflow&lt;/code&gt; to &lt;code&gt;visible&lt;/code&gt; and let the border overflow the boundaries:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuc2f2a28c6dbfba696b736d3a8cffb15313494de9" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuc2f2a28c6dbfba696b736d3a8cffb15313494de9" class="line-numbers icon list-numbered"&gt;&lt;code class="language-css"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;middle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;visible&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c"&gt;/* show the background dot line */&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, some additional cosmetic touches are required:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the list items have transparent background and the border line is visible
    behind the page numbers, so to address this we need to explicitly set the
    &lt;code&gt;background&lt;/code&gt; property for list items;&lt;/li&gt;
&lt;li&gt;to overlay our explicitly defined background on top of the border line we
    need to bump our list items up in the rendering order by setting &lt;code&gt;z-index&lt;/code&gt;
    to be higher;&lt;/li&gt;
&lt;li&gt;we need to handle a situation when the widget is compressed to its minimal
    size so there are just three page numbers: the first, the current, and the
    last page number &amp;ndash; in this situation we would not have any space between
    list items, yet we need to indicate that the page ranges were omitted &amp;ndash; we
    can do this by playing with &lt;code&gt;min-width&lt;/code&gt; for the &amp;ldquo;head&amp;rdquo; and &amp;ldquo;tail&amp;rdquo; lists.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The above translates to the following styling rules:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu13d1f45cd36be0b5f0e2dbdb86342ed04e36883d" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu13d1f45cd36be0b5f0e2dbdb86342ed04e36883d" class="line-numbers icon list-numbered"&gt;&lt;code class="language-css"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;whitesmoke&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;head&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;tail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;6.5&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK, it is time to see the result of all these changes:&lt;/p&gt;
&lt;div class="demo3 resizable"&gt;
&lt;style&gt;
    .demo3 .paginator {
        /* width: 50%; */
        /* margin: 0 auto; */
        display: flex;
        white-space: nowrap;
        overflow: hidden;
        min-width: 15em;    /* 5 items 3em each */
    }

    .demo3 ul {
        display: flex;
        position: relative;
        flex-flow: row wrap;
        list-style: none;
        margin: 0;
        padding: 0;
        line-height: 1.2;
        height: 3.2em;
        overflow: hidden;
        min-width: 6em;
    }

    .demo3 ul li {
        display: block;
        width: 3em;
        height: 3em;
        line-height: 3;
        margin: 0;
        z-index: 1;
        background: whitesmoke;
    }

    .demo3 ul li a {
        display: block;
        text-align: center;
        text-decoration: none;
    }

    .demo3 ul.middle::before {
        content: '';
        width: 300%;
        height: 1em;
        position: absolute;
        border-top: dotted;
        left: -100%;
        top: 1.5em;
    }

    .demo3 ul.middle {
        min-width: 3em;
        overflow: visible;
    }

    .demo3 ul.tail {
        flex-flow: row-reverse wrap;
    }

    .demo3 ul.head,
    .demo3 ul.tail {
        min-width: 6.5em;
    }
&lt;/style&gt;
    &lt;span class="paginator"&gt;
        &lt;ul class="head"&gt;
            &lt;li&gt;&lt;a href="#"&gt;&amp;lt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;1&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;2&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;3&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;4&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;ul class="middle"&gt;
            &lt;li&gt;&lt;a href="#"&gt;5&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
        &lt;ul class="tail"&gt;
            &lt;li&gt;&lt;a href="#"&gt;&amp;gt;&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;10&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;9&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;8&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;7&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href="#"&gt;6&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;Although the result looks promising and is a workable solution, there is still
a couple of issues left unsolved:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The biggest concern I have with this solution is that it requires
     reordering of the &amp;ldquo;tail&amp;rdquo; list and it is messing up the screen readers,
     therefore making it much harder for people with assistive technologies to
     use it;&lt;/li&gt;
&lt;li&gt;I also discovered an edge case if the current page was adjacent to &amp;ldquo;head&amp;rdquo;
     (e.g. the second page) or to &amp;ldquo;tail&amp;rdquo; (e.g. the second to last page) the
     range dots would be still present in that case since they are regulated by
     that &lt;code&gt;.5em&lt;/code&gt; space I left for the &amp;ldquo;head&amp;rdquo; and &amp;ldquo;tail&amp;rdquo; lists in the
     &lt;code&gt;min-width&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first issue may be solved by introducing a shadow copy of the pagination
widget that is designed specifically for the screen readers while making this
visually pleasing widget hidden from the screen readers.&lt;/p&gt;
&lt;p&gt;The second issue is much more puzzling and so far I could not find a pure HTML
+ CSS solution for it.  It can be addressed by a bit of JavaScript, but I did
not want to go that route for my blog.  Most likely, since my blog is
statically generated I will address it using the logic of the generator itself.&lt;/p&gt;
&lt;p&gt;Still, I think the techniques I demonstrated here may be quite useful to some
people who are designing pure HTML and CSS adaptive widgets.&lt;/p&gt;</content><category term="web design"/><category term="html"/><category term="css"/><category term="design"/></entry><entry><title>Automating Static Website Deployment</title><link href="https://dmitry.khlebnikov.net/2020/05/17/automating-static-website-deployment/" rel="alternate"/><published>2020-05-17T21:32:00+10:00</published><updated>2025-10-18T02:54:24+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2020-05-17:/2020/05/17/automating-static-website-deployment/</id><summary type="html">&lt;p&gt;A comprehensive guide to setting up a fully automated deployment
pipeline for a static website using a private source repository and a
public GitHub Pages repository. The author details a process that
leverages GitHub Actions to build a Pelican-based site from a private
repository — keeping API tokens and experimental code secure—and then deploys the
generated static files to a public repository for hosting. Key steps include
configuring separate public and private repositories, using a deploy key for
secure authentication instead of a personal access token, and creating a
detailed GitHub Action workflow. This workflow automates checking out both
repositories, restoring file modification times for accurate content
generation, installing dependencies, building the site with Pelican, and
committing the changes to the public GitHub Pages repository only if new
content has been generated.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;&lt;span class="toctitle"&gt;Table of Contents&lt;/span&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#setting-up-github-pages"&gt;Setting up GitHub Pages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#setting-up-the-private-code-repository"&gt;Setting up the private, code repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#configuring-the-github-action-for-publishing"&gt;Configuring the GitHub Action for publishing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;In this post I am going to document the steps I took to implement a fully
automated deployment of my blog using GitHub Actions and GitHub Pages.&lt;/p&gt;
&lt;p&gt;As always, I started my journey with the definition of what I really wanted to
get at the end:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The website is published on GitHub pages&lt;/p&gt;
&lt;p&gt;Since the website is static and all of its content can be easily downloaded
using a web crawler (like &lt;code&gt;wget --mirror https://website.tld&lt;/code&gt;) I was OK
with exposing the structure in the public repository, which is what GitHub
offers on a free plan.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The code to generate the website should be private&lt;/p&gt;
&lt;p&gt;I do a lot of work on the &lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt; (which is Pelican in my case) itself: extend
it with plug-ins that may contain &lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt; tokens to reach out to some third
party &lt;span&gt;&lt;abbr title="Application Programming Interface"&gt;API&lt;/abbr&gt;&lt;/span&gt;s, hack the core code when I want to quickly test stuff, etc. &amp;ndash;
so, I really did not have any desire to publish publicly all the commotions
I did in the background (sometimes I do more than a hundred commits per day
just to experiment with different ideas I have).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There should be a valid history of changes in both repositories&lt;/p&gt;
&lt;p&gt;Well, I would get the history on my private repository for free, since it
is the core value of maintaining a repository in the &lt;abbr title="Version Control System"&gt;VCS&lt;/abbr&gt;, but I also wanted
to have clean history of changes to the content I publish publicly.&lt;/p&gt;
&lt;p&gt;It would be a pleasant bonus if the changes in the public repository could
refer back to the corresponding commit in the private repository.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One may say that to do what I set out to do I would need to subscribe for a
paid account with GitHub since according to their &lt;a href="https://help.github.com/en/github/working-with-github-pages/getting-started-with-github-pages"&gt;help page&lt;/a&gt; GitHub Pages
for private repositories are only available on the paid plans.&lt;/p&gt;
&lt;p&gt;However, as I pointed out above, it does not make sense to hide the content of
the actual static website, hence all I needed to do is to find a way how to
&amp;ldquo;publish&amp;rdquo; the resulting artefact to the GitHub Pages repository, and,
preferably, that &amp;ldquo;publishing&amp;rdquo; should happen on GitHub&amp;rsquo;s side.&lt;/p&gt;
&lt;p&gt;Luckily for me, GitHub started to support GitHub Actions on the free plan some
time ago and as long as it is not abused according to their terms and
conditions, it is a perfect vehicle for what I am trying to do, in my opinion.&lt;/p&gt;
&lt;h2 id="setting-up-github-pages"&gt;Setting up GitHub Pages&lt;/h2&gt;
&lt;p&gt;There are multiple howtos and tutorials on the Internet regarding how to set GitHub
Pages up, including &lt;a href="https://help.github.com/en/github/working-with-github-pages/getting-started-with-github-pages"&gt;the official help section on this topic&lt;/a&gt;, so I will
only elaborate on details where I did something specific for the purposes of
achieving my goals.&lt;/p&gt;
&lt;p&gt;There are different types of GitHub Pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;user or organisation&lt;/li&gt;
&lt;li&gt;per-project&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The difference between two is subtle (the former requires a dedicated
repository for your website, while the latter allows you to keep it in a branch
of the existing repository), but for the purposes of this article I am assuming
that we are working with the user level GitHub pages which are residing in the
repository named &amp;ldquo;&lt;strong&gt;&amp;lt;username&amp;gt;&lt;/strong&gt;.github.io&amp;rdquo; (where &lt;strong&gt;&amp;lt;username&amp;gt;&lt;/strong&gt;
is your GitHub user name) as per the official documentation.&lt;/p&gt;
&lt;p&gt;A few caveats I found and spent some time solving after following the official
documentation are listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GitHub&amp;rsquo;s documentation assumes use of Jekyll for the site generation.&lt;/p&gt;
&lt;p&gt;It is not obvious how to use a different &lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt; (like Pelican).  As far as I
understand, there are multiple triggers for GitHub to consider that the
web site is in a &amp;ldquo;published&amp;rdquo; state, so just ignore any references to Jekyll
in the documentation: you will trip one of the triggers sooner or later,
for example by pushing HTML files into your repository.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure your &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; &lt;em&gt;before&lt;/em&gt; setting the custom domain name in GitHub Pages.&lt;/p&gt;
&lt;p&gt;Pushing the &lt;code&gt;CNAME&lt;/code&gt; file with the name of your custom domain within will
trigger a &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; check from GitHub to see that your custom domain name is
pointing back to GitHub Pages.&lt;/p&gt;
&lt;p&gt;&lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; heavily relies on caching and is depending on the &lt;abbr title="Time To Live"&gt;TTL&lt;/abbr&gt; settings in your
zone: if a negative check is performed (that is, when GitHub fails to
retrieve the corresponding record) you will likely need to wait for quite a
while for GitHub to retry.&lt;/p&gt;
&lt;p&gt;Setting up the &lt;code&gt;CNAME&lt;/code&gt; record in advance and then verifying it with a query
&lt;em&gt;before&lt;/em&gt; you commit the &lt;code&gt;CNAME&lt;/code&gt; file to your repository ensures that you
will get the quickest validation response from GitHub, e.g. I set up my
&lt;code&gt;CNAME&lt;/code&gt; records and then verified it from the command line (before)
submitting the request to GitHub:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menub3624fa5c401a8f42a9ad178012f88e626ac6471" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menub3624fa5c401a8f42a9ad178012f88e626ac6471" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;host&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;cname&lt;span class="w"&gt; &lt;/span&gt;dmitry.khlebnikov.net&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;.8.8.8
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Using domain server:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Name: 8.8.8.8&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Address: 8.8.8.8#53&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Aliases:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;dmitry.khlebnikov.net is an alias for galaxy4public.github.io.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are some shenanigans with the &amp;ldquo;Enforce HTTPS&amp;rdquo; option.&lt;/p&gt;
&lt;p&gt;It is not obvious from the documentation, but the enforcement of HTTPS for
custom domains on GitHub&amp;rsquo;s side is dependent on the several things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;before the checkbox is enabled your custom domain name should be
    confirmed by GitHub (your &lt;code&gt;CNAME&lt;/code&gt; file is in place and the repository
    settings show that the name was recognised);&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;CNAME&lt;/code&gt; record should point to your &amp;ldquo;&lt;strong&gt;&amp;lt;username&amp;gt;&lt;/strong&gt;.github.io.&amp;rdquo;
    &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; record (or, you can point it directly to GitHub Pages IP addresses
    if you want to conceal the repository name in the &lt;abbr title="Domain Name System"&gt;DNS&lt;/abbr&gt; output);&lt;/li&gt;
&lt;li&gt;if GitHub did not like something and you adjusted anything in the above
    dot points the &lt;strong&gt;only&lt;/strong&gt; way to trigger the enforcement of HTTPS is to
    re-submit the &lt;code&gt;CNAME&lt;/code&gt; file to the repository (yes, you read it right:
    you need to delete the file and push it to the repository again);&lt;/li&gt;
&lt;li&gt;Removing the &lt;code&gt;CNAME&lt;/code&gt; file from the repository is a disruptive action &amp;ndash;
    the site will not be accessible for the duration of the file being
    missing.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OK, you have your public repository configured the way you want, so let&amp;rsquo;s look
at the settings we need to be able to publish our code to this public
repository.&lt;/p&gt;
&lt;p&gt;When I try to automate something, I usually start with writing down manual
steps I would do to achieve the results.  This helps me to see patterns and to
understand what I can easily automate and what will require some brain-storming
to resolve.&lt;/p&gt;
&lt;p&gt;In the case of updating the repository it is quite trivial: if I were to push
updates manually, all I need is a private &lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt; key with the corresponding public
&lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt; key configured with write privileges for the repository and I could push
with &lt;code&gt;git push&lt;/code&gt; from my local copy of the repository.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a name="quote-private-keys" href="#quote-private-keys" aria-hidden="true"&gt;&lt;/a&gt; &amp;hellip; private keys are called &amp;ldquo;private&amp;rdquo; for a reason &amp;ndash; they are not supposed
to leave the device under any circumstances. [&amp;hellip;] please pay attention when
you read of hear somebody advising you to upload your private keys somewhere,
it is usually bad advice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My private keys are called &amp;ldquo;private&amp;rdquo; for a reason &amp;ndash; they are not supposed to
leave my device(s) under any circumstances (except for backup purposes such as
storing them in a safe).  So, please pay attention when you read or hear
somebody advising you to upload your private keys somewhere, it is usually
bad advice.&lt;/p&gt;
&lt;p&gt;For the integration purposes, GitHub provides so-called &amp;ldquo;Deploy keys&amp;rdquo; and
&amp;ldquo;Personal access tokens&amp;rdquo;.  The former is just an &lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt; key pair associated with a
particular repository (you can configure it in repository&amp;rsquo;s setting) while the
latter is an OAuth access token associated with &lt;em&gt;your&lt;/em&gt; account.&lt;/p&gt;
&lt;p&gt;While you can successfully use both, I would recommend to use the &amp;ldquo;Deploy keys&amp;rdquo;
only: despite that you can try to scope access down for a personal token,
it would not be good enough and the actions performed using that token will
look like &lt;em&gt;you&lt;/em&gt; are executing them.&lt;/p&gt;
&lt;p&gt;To configure a &amp;ldquo;Deploy key&amp;rdquo; we need two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Generate an &lt;abbr title="Secure Shell"&gt;SSH&lt;/abbr&gt; key pair, e.g.:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuaa92d79e6e51ac7ad98647c1edb2562d3fd2f3f6" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuaa92d79e6e51ac7ad98647c1edb2562d3fd2f3f6" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;ed25519&lt;span class="w"&gt; &lt;/span&gt;-N&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Updating the blog from GH Action&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;~/gh-action
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Generating public/private ed25519 key pair.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Your identification has been saved in /home/user/gh-action&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Your public key has been saved in /home/user/gh-action.pub&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;The key fingerprint is:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;SHA256:7W9pUV5IlrRVE0RAVkjLgKJz4RdtVbH7GKQu8AfYITw Updating the blog from GH Action&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;The key&amp;#39;s randomart image is:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;+--[ED25519 256]--+&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|          o.+BOX*|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|       + o o+.=oo|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|      o E +  =oo |&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|     o o B . oo o|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|      o S + .o.o |&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|         + o. .o.|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|          + oo. .|&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|           ++    |&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;|           o.    |&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;+----[SHA256]-----+&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, I chose the &lt;code&gt;ed25519&lt;/code&gt; key type since it is the shortest from the
GitHub supported key types at the moment, yet it is strong enough.&lt;/p&gt;
&lt;p&gt;I also made the key pair passphrase-less (&lt;code&gt;-N ''&lt;/code&gt;) since the purpose of the
key pair is to automate things in the unattended fashion and there will be
nobody to type in the passphrase.&lt;/p&gt;
&lt;p&gt;The key pair comment just makes it easier to maintain your keys, but is optional.&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;-f ~/gh-action&lt;/code&gt; option specifies where the generated private
key is going to be stored.  The public counterpart will use the same path with
the &lt;code&gt;.pub&lt;/code&gt; suffix appended to it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set the newly generated &lt;em&gt;public&lt;/em&gt; key up as the &amp;ldquo;Deploy key&amp;rdquo;:&lt;/p&gt;
&lt;p&gt;All you need to do is to go to the repository settings page for the public
 repository you created for GitHub Pages, click on &amp;ldquo;Deploy keys&amp;rdquo; in the
 left side menu, then click on the &amp;ldquo;Add deploy key&amp;rdquo; button in the upper
 right corner.&lt;/p&gt;
&lt;p&gt;On the next page, provide a sensible description for the deploy key (I
 used the same text as I put into the keys comment, i.e. &amp;ldquo;Updating the blog
 from GH Action&amp;rdquo;) and copy and paste the recently generated &lt;em&gt;public&lt;/em&gt; key.
 GitHub does not allow you to upload files over there, so you need to copy
 the content of the &lt;strong&gt;public&lt;/strong&gt; key file and paste it into the form, e.g.:&lt;/p&gt;
&lt;pre id="code3" class="highlight"&gt;&lt;input type="radio" name="code_menub22e806c4d34ece0b29796b1b2792941793e2f85" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menub22e806c4d34ece0b29796b1b2792941793e2f85" class="line-numbers icon list-numbered"&gt;&lt;code class="language-text" data-file="~/gh-action.pub"&gt;&lt;div id="code3.1" class="line"&gt;&lt;a href="#code3.1" aria-hidden="true"&gt;&lt;/a&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGHCy+stVCBjsrVO2ld1DwKCwcKL9+i1sjxcZu4u4lFQ Updating the blog from GH Action&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You need to ensure that you tick the &amp;ldquo;Allow write access&amp;rdquo; checkbox,
 otherwise it would not be possible to push to the repository with the
 corresponding private key.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This, actually, concludes the configuration of the GitHub Pages repository for
now &amp;ndash; in later articles I will document how one could leverage the repository
Issues for managing comments on the web site and maintain the counters for
likes on the pages, but it would be a completely separate post :).&lt;/p&gt;
&lt;h2 id="setting-up-the-private-code-repository"&gt;Setting up the private, code repository&lt;/h2&gt;
&lt;p&gt;A typical Pelican repository layout is quite simple and comprises one
mandatory directory, one semi-mandatory file, and everything else is optional,
but could be used to enhance your experience.&lt;/p&gt;
&lt;p&gt;The mandatory directory is the so-called
&amp;ldquo;&lt;a href="https://docs.getpelican.com/en/stable/install.html#kickstart-your-site"&gt;content&lt;/a&gt;&amp;rdquo;
directory (in Pelican&amp;rsquo;s terms).  The name of the directory can be anything you
want, but it is better be reflected in &lt;a href="https://docs.getpelican.com/en/stable/settings.html#PATH"&gt;the &lt;code&gt;PATH =&lt;/code&gt;
directive&lt;/a&gt; of the
setting file.&lt;/p&gt;
&lt;p&gt;I am saying &amp;ldquo;better be&amp;rdquo; since Pelican can operate without any configuration
files, but the result will be limited, hence I call the &lt;code&gt;pelicanconf.py&lt;/code&gt; file
(which is the default name for the configuration file) to be &amp;ldquo;semi-mandatory&amp;rdquo;.
The name of the configuration file can be also anything you like, however, I
suggest to stick with the default for now.&lt;/p&gt;
&lt;p&gt;Basically, you can quickly start by following the Pelican documentation and
doing something as follows:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu660612c8263b4e0abe109b6ff3b7e08776303164" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu660612c8263b4e0abe109b6ff3b7e08776303164" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;virtualenv&lt;span class="w"&gt; &lt;/span&gt;~/venv/pelican
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;created virtual environment CPython3.8.2.final.0-64 in 477ms&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  creator CPython3Posix(dest=/home/user/venv/pelican, clear=False, global=False)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  seeder FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=/home/user/.local/share/virtualenv/seed-app-data/v1.0.1)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;~/venv/pelican/bin/activate
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;~/blog
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/blog
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;init
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Initialized empty Git repository in /home/user/blog/.git/&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--local&lt;span class="w"&gt; &lt;/span&gt;user.email&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;your@github-email.here&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--local&lt;span class="w"&gt; &lt;/span&gt;user.name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Joe Happy&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;pelican-quickstart&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Welcome to pelican-quickstart v4.2.0.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;This script will help you create a new Pelican-based website.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Please answer the following questions so this script can generate the files&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;needed by Pelican.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Where do you want to create your new web site? [.] &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; What will be the title of this web site? My Awesome Blog&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Who will be the author of this web site? Joe Happy&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; What will be the default language of this web site? [en] &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Do you want to specify a URL prefix? e.g., https://example.com   (Y/n) n&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Do you want to enable article pagination? (Y/n) &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; How many articles per page do you want? [10] &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; What is your time zone? [Europe/Paris] Australia/Melbourne&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;gt; Do you want to generate a tasks.py/Makefile to automate generation and publishing? (Y/n) n&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Done. Your new project is available at /home/user/blog&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;total 16&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x 2   user   user 4096 May 10 00:31 content&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x 2   user   user 4096 May 10 00:31 output&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r-- 1   user   user  869 May 10 00:31 pelicanconf.py&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r-- 1   user   user  589 May 10 00:31 publishconf.py&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;output&lt;span class="w"&gt; &lt;/span&gt;publishconf.py
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;content&lt;span class="w"&gt; &lt;/span&gt;pelicanconf.py&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp gp-VirtualEnv"&gt;(pelican)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[master (root-commit) f077002] Initial commit&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; 1 file changed, 35 insertions(+)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; create mode 100644 pelicanconf.py&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A short break down of the above session snippet is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On line 1 we create a virtual Python environment, so we could install Pelican locally;&lt;/li&gt;
&lt;li&gt;We enter the newly created virtual environment on line 2, which makes Pelican available to us;&lt;/li&gt;
&lt;li&gt;We create an empty repository (&lt;code&gt;~/blog&lt;/code&gt;) and initialise it using Pelican&amp;rsquo;s quickstart;&lt;/li&gt;
&lt;li&gt;Since we are not using the default publishing capabilities and we are not interested in storing the generated pages in our code repository, we clean things up a bit;&lt;/li&gt;
&lt;li&gt;Finally, we commit the generated skeleton to Git.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A good test at this stage would be to ensure that Pelican is working and likes our structure:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu9ed554635baab30f3cc4e84f924e332b17bf3d0e" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu9ed554635baab30f3cc4e84f924e332b17bf3d0e" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;pelican
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;WARNING: No valid files found in content for the active readers:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  | BaseReader (static)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  | HTMLReader (htm, html)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  | MarkdownReader (md, markdown, mkd, mdown)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  | RstReader (rst)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Done: Processed 0 articles, 0 drafts, 0 pages, 0 hidden pages and 0 draft pages in 0.07 seconds.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far so good, but it is not a real test, since there are no source files to generate something from, so let&amp;rsquo;s give Pelican something to work on:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuf690118aac4b259cb5f132a1d2aefc4155ddbd06" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuf690118aac4b259cb5f132a1d2aefc4155ddbd06" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Title: First Post\nDate: 2020-05-10\n\n#First post\nPelican is awesome!&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;content/first.md
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;pelican
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Done: Processed 1 article, 0 drafts, 0 pages, 0 hidden pages and 0 draft pages in 0.12 seconds.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;elinks&lt;span class="w"&gt; &lt;/span&gt;-dump&lt;span class="w"&gt; &lt;/span&gt;output/index.html&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;                               [1]My Awesome Blog&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;     • [2]misc&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;                                 [3]First Post&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;   Published: Sun 10 May 2020&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;    By [4]Joe Happy&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;   In [5]misc.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;                                   First post&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;   Pelican is awesome!&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output from &lt;code&gt;elinks&lt;/code&gt; was truncated on purpose since I just wanted to
showcase that Pelican has indeed generated the structure for a static website
from just one article file we created.&lt;/p&gt;
&lt;p&gt;Before we push our local repository to GitHub we may want to do some house
keeping first, e.g. create the &lt;code&gt;.gitignore&lt;/code&gt; file and list the temporary
things we do not want Git to track.  A good enough version of the &lt;code&gt;.gitignore&lt;/code&gt;
file I am using for my code repository is the following:&lt;/p&gt;
&lt;pre id="code7" class="highlight"&gt;&lt;input type="radio" name="code_menu7f4cf7b3cc8f2931067f3492b670d7935d5d410b" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu7f4cf7b3cc8f2931067f3492b670d7935d5d410b" class="line-numbers icon list-numbered"&gt;&lt;code class="language-txt" data-file=".gitignore"&gt;&lt;div id="code7.1" class="line"&gt;&lt;a href="#code7.1" aria-hidden="true"&gt;&lt;/a&gt;*~
&lt;/div&gt;&lt;div id="code7.2" class="line"&gt;&lt;a href="#code7.2" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="gs"&gt;*.pyc&lt;/span&gt;
&lt;/div&gt;&lt;div id="code7.3" class="line"&gt;&lt;a href="#code7.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="gs"&gt;.*&lt;/span&gt;.swp
&lt;/div&gt;&lt;div id="code7.4" class="line"&gt;&lt;a href="#code7.4" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code7.5" class="line"&gt;&lt;a href="#code7.5" aria-hidden="true"&gt;&lt;/a&gt;**/__pycache__
&lt;/div&gt;&lt;div id="code7.6" class="line"&gt;&lt;a href="#code7.6" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code7.7" class="line"&gt;&lt;a href="#code7.7" aria-hidden="true"&gt;&lt;/a&gt;/output&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not forget to actually commit that &lt;code&gt;.gitignore&lt;/code&gt; file to your local
repository using the &lt;code&gt;git add .gitignore &amp;amp;&amp;amp; git commit -m 'Added .gitignore'&lt;/code&gt;,
by the way.&lt;/p&gt;
&lt;p&gt;Now, we need to create a private repository on GitHub, so jump into your
browser, go to your GitHub account, press the &amp;ldquo;+&amp;rdquo; icon in the upper right
corner (right next to your profile icon), and select &amp;ldquo;New repository&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;On the &amp;ldquo;Create repository&amp;rdquo; page put whatever you desire as the name and the
description of the repository you are about to create.  Ensure that the
&amp;ldquo;Private&amp;rdquo; radio button is selected and uncheck the &amp;ldquo;Initialize this repository
with a README&amp;rdquo; if it was checked.&lt;/p&gt;
&lt;p&gt;Once the repository is created, you will be presented with a page that
enumerates your options for the next step, but I will just go ahead and show a
session dump of what you will need to do.  In the following session snippet
&lt;code&gt;blog&lt;/code&gt; is the repository name I chose for my private code repository and you
will need to replace it with your private repository name (the working
directory is our newly created local repository):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu767b12e1270d9628ac06fe6c1271acf21d6806c8" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu767b12e1270d9628ac06fe6c1271acf21d6806c8" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;git@github.com:galaxy4public/blog.git
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;master
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Enter passphrase for key &amp;#39;/home/user/.ssh/keys/github&amp;#39;: &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Enumerating objects: 6, done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Counting objects: 100% (6/6), done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Delta compression using up to 4 threads&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Compressing objects: 100% (4/4), done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Writing objects: 100% (6/6), 1008 bytes | 1008.00 KiB/s, done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Total 6 (delta 0), reused 0 (delta 0), pack-reused 0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;To github.com:galaxy4public/blog.git&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; * [new branch]      master -&amp;gt; master&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Branch &amp;#39;master&amp;#39; set up to track remote branch &amp;#39;master&amp;#39; from &amp;#39;origin&amp;#39;.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do you remember how we generated a deploy key pair earlier and installed the
public key part into the public blog repository, so GitHub would allow the
bearer of the private key to authenticate and deploy changes to the public blog
repository?  Well, since the purpose of this article is to introduce the full
automation, the bearer of the key would be the GitHub Action associated with
the private repository, hence we need to provide the action with the private
key somehow.&lt;/p&gt;
&lt;p&gt;GitHub has a feature called &amp;ldquo;repository secrets&amp;rdquo; and it is a perfect candidate
to pass the private key to the GitHub Action.  We need to follow the &lt;a href="https://help.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository"&gt;official
documentation for the feature&lt;/a&gt; and create a secret called &amp;ldquo;DEPLOY_KEY&amp;rdquo; with the
content of the private part of the deploy key.  This will be used in the last step
of the GitHub Action we are about to define.&lt;/p&gt;
&lt;h2 id="configuring-the-github-action-for-publishing"&gt;Configuring the GitHub Action for publishing&lt;/h2&gt;
&lt;p&gt;Everything is well and good, but &amp;ldquo;where is the automation?&amp;rdquo; you may ask.  After
all, I suspect this was the primary reason you are reading this post.  Well, we
are about to start to look into the automation part and it is rather short in
comparison to all the steps we did to set repositories up.&lt;/p&gt;
&lt;p&gt;Our automation relies on the GitHub Action feature of GitHub.  In plain terms,
GitHub Action is a free compute resource provided by GitHub (there are some
limits, but for the purposes of a personal blog it is unlikely that you will
ever hit these limits).&lt;/p&gt;
&lt;p&gt;Each GitHub Action is associated with a specific repository and is defined
using quite a simple &lt;abbr title="YAML Ain't Markup Language"&gt;YAML&lt;/abbr&gt; configuration file which instructs GitHub on how to
provision a required compute environment and what to run inside that
environment.  The &lt;abbr title="YAML Ain't Markup Language"&gt;YAML&lt;/abbr&gt; file can be arbitrarily named and resides in the
&lt;code&gt;.github/workflows/&lt;/code&gt; subdirectory (starting from the root of the corresponding
repository).&lt;/p&gt;
&lt;p&gt;The GitHub Action I am using for my blog web site is stored in
&lt;code&gt;.github/workflows/pelican.yml&lt;/code&gt; and contains the following (we will dissect it
further down the post):&lt;/p&gt;
&lt;pre id="code9" class="highlight"&gt;&lt;input type="radio" name="code_menu606d183290ef7658eed278296fc46835e3446c0f" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu606d183290ef7658eed278296fc46835e3446c0f" class="line-numbers icon list-numbered"&gt;&lt;code class="language-yaml" data-file=".github/workflows/pelican.yml"&gt;&lt;div id="code9.1" class="line"&gt;&lt;a href="#code9.1" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Static Website Generator&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.2" class="line"&gt;&lt;a href="#code9.2" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.3" class="line"&gt;&lt;a href="#code9.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.4" class="line"&gt;&lt;a href="#code9.4" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.5" class="line"&gt;&lt;a href="#code9.5" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;master&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.6" class="line"&gt;&lt;a href="#code9.6" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.7" class="line"&gt;&lt;a href="#code9.7" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.8" class="line"&gt;&lt;a href="#code9.8" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;LANG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;en_AU.UTF-8&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.9" class="line"&gt;&lt;a href="#code9.9" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.10" class="line"&gt;&lt;a href="#code9.10" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.11" class="line"&gt;&lt;a href="#code9.11" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.12" class="line"&gt;&lt;a href="#code9.12" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.13" class="line"&gt;&lt;a href="#code9.13" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ubuntu-latest&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.14" class="line"&gt;&lt;a href="#code9.14" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="code9.15" class="line"&gt;&lt;a href="#code9.15" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.16" class="line"&gt;&lt;a href="#code9.16" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Initialise locale&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.17" class="line"&gt;&lt;a href="#code9.17" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.18" class="line"&gt;&lt;a href="#code9.18" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;if [ &amp;quot;$LANG&amp;quot; != &amp;#39;C&amp;#39; -a &amp;quot;{$LANG:0:2}&amp;quot; != &amp;#39;C.&amp;#39; ]; then&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.19" class="line"&gt;&lt;a href="#code9.19" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;CP=&amp;quot;${LANG#*.}&amp;quot; &amp;amp;&amp;amp; [ -z &amp;quot;$CP&amp;quot; ] &amp;amp;&amp;amp; CP=UTF-8 ||:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.20" class="line"&gt;&lt;a href="#code9.20" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;sudo sed -i -E &amp;quot;/^\s*$LANG(\s|\$)/{:a;n;ba;q};\$a$LANG $CP&amp;quot; /etc/locale.gen&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.21" class="line"&gt;&lt;a href="#code9.21" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;sudo locale-gen&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.22" class="line"&gt;&lt;a href="#code9.22" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;sudo localectl set-locale LANG=&amp;quot;${LANG:-C.UTF-8}&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.23" class="line"&gt;&lt;a href="#code9.23" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.24" class="line"&gt;&lt;a href="#code9.24" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;locale -a&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.25" class="line"&gt;&lt;a href="#code9.25" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Checkout the primary repo&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.26" class="line"&gt;&lt;a href="#code9.26" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v2&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.27" class="line"&gt;&lt;a href="#code9.27" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.28" class="line"&gt;&lt;a href="#code9.28" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;fetch-depth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;0&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.29" class="line"&gt;&lt;a href="#code9.29" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;submodules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;recursive&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.30" class="line"&gt;&lt;a href="#code9.30" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Restore modification times for content&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.31" class="line"&gt;&lt;a href="#code9.31" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.32" class="line"&gt;&lt;a href="#code9.32" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;git log --pretty=tformat:&amp;quot;%at&amp;quot; --name-status --no-merges -- \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.33" class="line"&gt;&lt;a href="#code9.33" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;content \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.34" class="line"&gt;&lt;a href="#code9.34" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;themes/mind-drops/content \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.35" class="line"&gt;&lt;a href="#code9.35" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;| sed -nE &amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.36" class="line"&gt;&lt;a href="#code9.36" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;/^\s*$/d;/^[[:digit:]]+$/{h;d};&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.37" class="line"&gt;&lt;a href="#code9.37" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;/^[UXB]/d;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.38" class="line"&gt;&lt;a href="#code9.38" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;/^[AMT]/{s,^\S\s+,,;G;s,^(.+)\n(.+),\2 \1,;p};&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.39" class="line"&gt;&lt;a href="#code9.39" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;/^[DR]/{&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.40" class="line"&gt;&lt;a href="#code9.40" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="no"&gt;s,^(D|[CR][[:digit:]]+)\s+,\1 ,;G;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.41" class="line"&gt;&lt;a href="#code9.41" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="no"&gt;s,^(\S+) ([^[=\t=]]+[[=\t=]])?(.*)\n(.+),\4\1 \3,;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.42" class="line"&gt;&lt;a href="#code9.42" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="no"&gt;p&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.43" class="line"&gt;&lt;a href="#code9.43" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;}&amp;#39; \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.44" class="line"&gt;&lt;a href="#code9.44" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;| LC_ALL=C sort -k2 -k1rn | uniq -f1 \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.45" class="line"&gt;&lt;a href="#code9.45" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;| sed -E &amp;#39;/^[[:digit:]]+D /d&amp;#39; \&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.46" class="line"&gt;&lt;a href="#code9.46" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;| while read TSTAMP FILE; do&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.47" class="line"&gt;&lt;a href="#code9.47" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;if [ -f &amp;quot;$FILE&amp;quot; ]; then&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.48" class="line"&gt;&lt;a href="#code9.48" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="no"&gt;echo &amp;quot;$FILE =&amp;gt; $(date -d @$TSTAMP)&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.49" class="line"&gt;&lt;a href="#code9.49" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="no"&gt;touch -m -d &amp;quot;@$TSTAMP&amp;quot; -- &amp;quot;$FILE&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.50" class="line"&gt;&lt;a href="#code9.50" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="no"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.51" class="line"&gt;&lt;a href="#code9.51" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;done&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.52" class="line"&gt;&lt;a href="#code9.52" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Checkout Pages repo&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.53" class="line"&gt;&lt;a href="#code9.53" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/checkout@v2&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.54" class="line"&gt;&lt;a href="#code9.54" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.55" class="line"&gt;&lt;a href="#code9.55" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;galaxy4public/galaxy4public.github.io&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.56" class="line"&gt;&lt;a href="#code9.56" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;output&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.57" class="line"&gt;&lt;a href="#code9.57" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Set up Python&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.58" class="line"&gt;&lt;a href="#code9.58" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;actions/setup-python@v2&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.59" class="line"&gt;&lt;a href="#code9.59" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.60" class="line"&gt;&lt;a href="#code9.60" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;3.x&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.61" class="line"&gt;&lt;a href="#code9.61" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Install dependencies&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.62" class="line"&gt;&lt;a href="#code9.62" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.63" class="line"&gt;&lt;a href="#code9.63" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;python -m pip install --upgrade pip&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.64" class="line"&gt;&lt;a href="#code9.64" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;pip install -r requirements.txt&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.65" class="line"&gt;&lt;a href="#code9.65" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Generate the website&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.66" class="line"&gt;&lt;a href="#code9.66" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.67" class="line"&gt;&lt;a href="#code9.67" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;ls -laR content/&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.68" class="line"&gt;&lt;a href="#code9.68" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;rm -rf output/*&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.69" class="line"&gt;&lt;a href="#code9.69" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;ls -la output/&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.70" class="line"&gt;&lt;a href="#code9.70" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;TIMEZONE=$(sed -nE &amp;#39;s|^\s*TIMEZONE\s*=\s*[&amp;#39;&amp;quot;&amp;#39;&amp;quot;&amp;#39;&amp;quot;]([^&amp;#39;&amp;quot;&amp;#39;&amp;quot;&amp;#39;&amp;quot;]+)[&amp;#39;&amp;quot;&amp;#39;&amp;quot;&amp;#39;&amp;quot;].*|\1|;T;p&amp;#39; pelicanconf.py)&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.71" class="line"&gt;&lt;a href="#code9.71" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;TZ=&amp;quot;${TIMEZONE:-UTC}&amp;quot; pelican&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.72" class="line"&gt;&lt;a href="#code9.72" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Publish to GitHub Pages&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.73" class="line"&gt;&lt;a href="#code9.73" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.74" class="line"&gt;&lt;a href="#code9.74" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;DEPLOY_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;${{ secrets.DEPLOY_KEY }}&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.75" class="line"&gt;&lt;a href="#code9.75" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;|&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.76" class="line"&gt;&lt;a href="#code9.76" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;cd output&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.77" class="line"&gt;&lt;a href="#code9.77" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;git config --local user.email &amp;quot;action@github.com&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.78" class="line"&gt;&lt;a href="#code9.78" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;git config --local user.name &amp;quot;GitHub Action&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.79" class="line"&gt;&lt;a href="#code9.79" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;if output=$(git status --porcelain) &amp;amp;&amp;amp; [ -z &amp;quot;$output&amp;quot; ]; then&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.80" class="line"&gt;&lt;a href="#code9.80" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;echo &amp;quot;No new content was generated, exiting gracefully&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.81" class="line"&gt;&lt;a href="#code9.81" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;else &lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.82" class="line"&gt;&lt;a href="#code9.82" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;git add -A&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.83" class="line"&gt;&lt;a href="#code9.83" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;git commit -m &amp;quot;Updated content on $(date)&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.84" class="line"&gt;&lt;a href="#code9.84" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;eval $(ssh-agent)&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.85" class="line"&gt;&lt;a href="#code9.85" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;echo &amp;quot;$DEPLOY_KEY&amp;quot; | ssh-add -t 5m /dev/stdin&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.86" class="line"&gt;&lt;a href="#code9.86" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;ssh-add -l&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.87" class="line"&gt;&lt;a href="#code9.87" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;git push git@github.com:galaxy4public/galaxy4public.github.io.git&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.88" class="line"&gt;&lt;a href="#code9.88" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;ssh-add -D&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.89" class="line"&gt;&lt;a href="#code9.89" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="no"&gt;ssh-agent -k&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.90" class="line"&gt;&lt;a href="#code9.90" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.91" class="line"&gt;&lt;a href="#code9.91" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;cd ..&lt;/span&gt;
&lt;/div&gt;&lt;div id="code9.92" class="line"&gt;&lt;a href="#code9.92" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="no"&gt;echo Completed&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a copy of my live GitHub Action for deploying my blog that you are most
likely reading right now and I decided not to edit anything, so if you just
want to re-use it you will need to replace a few things, namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;en_AU.UTF-8&lt;/code&gt; =&amp;gt; to a locale you are using (you can run &lt;code&gt;locale -a&lt;/code&gt; if you
    are running Linux to see the list of locales available on your system);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;content&lt;/code&gt; =&amp;gt; you may need to change that to the name of your content
    directory (if you did not use the default name);&lt;/li&gt;
&lt;li&gt;&lt;code&gt;themes/mind-drops/content&lt;/code&gt; =&amp;gt; you will need to drop this line since it is
    my theme&amp;rsquo;s content directory and you would not have it;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;galaxy4public/galaxy4public.github.io&lt;/code&gt; =&amp;gt; to &lt;strong&gt;&amp;lt;your_username/your_blog_repo_name&amp;gt;&lt;/strong&gt;, obviously :)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s look a bit more closely to understand how this GitHub Action is
structured and what each step is doing.&lt;/p&gt;
&lt;p&gt;It all starts with the definition of the action itself, the conditions of
how it is triggered, and how it runs: you can get a formal description of
the &lt;abbr title="YAML Ain't Markup Language"&gt;YAML&lt;/abbr&gt; structure of this configuration file in the official GitHub
documentation on &lt;a href="https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions"&gt;Workflows&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here, we are only going to focus on steps defined under the &amp;ldquo;jobs:&amp;rdquo; section of
the file since these steps are defining the logic we are after.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Initialise locale&amp;rdquo; step is quite important for Pelican since with a
misconfigured locale Pelican tends to produce incorrect output (which is kind
of expected).  So in this step we are trying to determine whether the user (us
:) ) has supplied the &lt;code&gt;LANG&lt;/code&gt; variable and if they did we update
&lt;code&gt;/etc/locale.gen&lt;/code&gt; file, run the &lt;code&gt;locale-gen&lt;/code&gt; command to update the
corresponding files, and set the locale of the container to the
requested locale.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Checkout the primary repo&amp;rdquo; step is leveraging the official &amp;ldquo;Checkout V2&amp;rdquo;
Action and checks out a full copy of the source code repository of our blog
and all the linked submodules.  Initially, I was using a shallow copy using
&lt;code&gt;fetch-depth: 1&lt;/code&gt;, but the next step was requiring the full repository history
to do its job reliably and I changed it to be a full history clone.&lt;/p&gt;
&lt;p&gt;Since &lt;code&gt;git&lt;/code&gt; &lt;a href="https://git.wiki.kernel.org/index.php/GitFaq#Why_isn.27t_Git_preserving_modification_time_on_files.3F"&gt;does not store timestamps&lt;/a&gt; for the files and directories under its
control, yet Pelican relies on timestamps to populate the modification time of
the artefacts &amp;ndash; we need to find a way to reconstruct at least file timestamps
after the tree was checked out.  One of the possible approaches would be to
create a plugin that could determine whether we are inside a &lt;code&gt;git&lt;/code&gt; working tree
or not and depending on that apply different timestamp extraction policies, but
I thought that a much easier way would be to prepare the checked out tree,
hence making it compatible with the way Pelican expects things to be.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Restore modification times for content&amp;rdquo; step is my variant of how one
could reconstruct the timestamps for files close enough to make it possible to
use with Pelican.  The approach relies on the fact that &lt;code&gt;git&lt;/code&gt; records the
timestamp of each commit including adding, updating, and deleting files.  We
create a list of all these file events using &lt;code&gt;git log&lt;/code&gt; for file trees under
&amp;ldquo;content&amp;rdquo; (where our blog content lives) and &amp;ldquo;themes/mind-drops/content&amp;rdquo; (where
my custom theme injects some content such as the Web service worker script),
then we use &lt;code&gt;sed&lt;/code&gt; to filter and to re-arrange the output a bit, followed by
reverse sorting to help to remove the entries that were introduced and later
deleted.  In the end, we have a list of file names with timestamps, so we go
through the list in a loop and set the timestamps to files using &lt;code&gt;touch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Checkout Pages repo&amp;rdquo; step is cloning the public blog repository into the
&amp;ldquo;output&amp;rdquo; directory where Pelican will put the generated files.  This is needed
to ensure that we can track the changes to the public repository, since Pelican
is careful enough (if not instructed otherwise) only to update the files it
generates and leave everything else in place as is.  We use this later to
determine whether any new content has been generated or not.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Set up Python&amp;rdquo; and the &amp;ldquo;Install dependencies&amp;rdquo; steps are pretty generic:
the former is using the official GitHub Action to install and configure the
latest available version of Python 3.x and the latter is leveraging &lt;code&gt;pip&lt;/code&gt; to
install all blog&amp;rsquo;s dependencies (including Pelican itself).&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;Generate the website&amp;rdquo; step is running &lt;code&gt;pelican&lt;/code&gt; to process our articles
and pages and to generate the result in the &amp;ldquo;output&amp;rdquo; directory.  There are a
couple of tricks with this step, though.&lt;/p&gt;
&lt;p&gt;The first trick, which is not that obvious, is that we are removing the content
of the &amp;ldquo;output&amp;rdquo; directory.  It seems a bit weird since we just checked it out
several steps before, does not it?  Well, we are removing everything &lt;strong&gt;except&lt;/strong&gt;
hidden files and directories which happen to contain the &amp;ldquo;.git&amp;rdquo; subdirectory
with all the actual data about the repository.  Why do we do it?  It is simple,
this helps us to determine a situation if some file or directory was removed,
so we could propagate that knowledge to the public blog repository.  If we did
not clean up the content of the &amp;ldquo;output&amp;rdquo; directory we would only append new
changes and would never remove anything &amp;ndash; this is how it was before I stumbled
upon the problem, by the way. :)&lt;/p&gt;
&lt;p&gt;The second trick of the &amp;ldquo;Generate the website&amp;rdquo; step is the extraction of the
time zone information from the configuration files and is setting the &lt;code&gt;TZ&lt;/code&gt;
variable correctly just before we call &lt;code&gt;pelican&lt;/code&gt;.  Without this either Pelican
may fail or if it does not it will produce &lt;abbr title="Coordinated Universal Time"&gt;UTC&lt;/abbr&gt; based date and times, which
would be undesirable (at least for me, since my time zone is in Australia).&lt;/p&gt;
&lt;p&gt;The final step is to push the updated content to the public blog repository,
which will make it visible via GitHub Pages.  Several things to notice there
are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In the &lt;code&gt;env:&lt;/code&gt; section we are setting up the &lt;code&gt;DEPLOY_KEY&lt;/code&gt; variable &amp;ndash; this
     syntax is used to retrieve a named secret value from the associated secret
     key for a repository.  We store the private part of the deploy key we
     specifically generated for this purpose at the beginning of this article
     in the private repository secret.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git status&lt;/code&gt; is used to determine whether there are any changes between
     what we have in the working tree and the repository index.  If no changes
     were detected we just exit gracefully.&lt;/li&gt;
&lt;li&gt;If any change to the generated content was detected, we temporarily load
     the private part of the deploy key into the &lt;code&gt;ssh-agent&lt;/code&gt; (for 5 minutes),
     push changes to the public blog repository, then clean up the key from
     the agent and kill the agent itself.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From this point on, any push to the private codebase repository will trigger
the GitHub Action and if the change has resulted in any updated content such
content will be published to GitHub Pages!&lt;/p&gt;
&lt;p&gt;There are quite a few things we could improve: such as introducing a broken
links check, doing some sanity checks, etc. &amp;ndash; but this would be for another
article, I guess. :)&lt;/p&gt;</content><category term="blog"/><category term="pelican"/><category term="blog"/><category term="github"/></entry><entry><title>Wrap indicator in &lt;pre&gt; blocks</title><link href="https://dmitry.khlebnikov.net/2020/05/10/wrap-indicator-in-pre-blocks/" rel="alternate"/><published>2020-05-10T21:00:00+10:00</published><updated>2025-10-21T17:14:44+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2020-05-10:/2020/05/10/wrap-indicator-in-pre-blocks/</id><summary type="html">&lt;p&gt;This post presents a pure CSS-based solution for adding a visual wrap
indicator to lines within &lt;pre&gt; code blocks to improve responsiveness and
readability. The author was annoyed that long lines wrap without any clear
indication, making code blocks difficult to read. The method involves wrapping
each physical line of text within the &lt;pre&gt; tag in its own &lt;div&gt;. Using the
::after pseudo-element on these divs, a repeating background image is
positioned just outside the text block. A clever CSS clip trick is then used
to hide the indicator on the last segment of a wrapped line, creating a clean
and intuitive visual cue. The author also details a &amp;ldquo;bonus round&amp;rdquo; method for
placing the indicator on the left side and notes the challenges of
integrating this semantic HTML structure with syntax highlighters like
PrismJS, for which they developed a custom plugin.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am not a front-end developer, not a UI designer, nor a UX guru, but I am an
engineer, so when I face a puzzle worth solving my brain switches on and I
cannot let it go until I find a satisfactory solution to the puzzle.&lt;/p&gt;
&lt;p&gt;My blog heavily relies on me sharing session dumps and file excerpts using the
code blocks.  I am using &lt;a href="https://prismjs.com"&gt;PrismJS&lt;/a&gt; to highlight syntax in these blocks.  However,
after a while I found that there is one thing that really irritates me: these
code blocks are not designed to be truly responsive, for instance &amp;ndash; when a
line wraps there is no easy to spot indication that such a wrap happened.&lt;/p&gt;
&lt;p&gt;I quick search on the topic highlighted that this is kind of an unresolved
issue and I found just &lt;a href="https://blog.iany.me/2012/02/css-line-wrap-indicator/"&gt;&lt;em&gt;one&lt;/em&gt; blog post&lt;/a&gt; from 2012 by Ian Yang
after I already implemented my solution.&lt;/p&gt;
&lt;p&gt;The approaches are quite close, but I like mine better since I think it is more
semantically correct (I am using &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; instead of &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; and I did a bit
deeper research into how to make it universal).&lt;/p&gt;
&lt;p&gt;So, below I present you with my version of the solution and I would appreciate
any feedback you may have, especially if it could help to find a nicer solution
in the end.&lt;/p&gt;
&lt;p&gt;Before we dive into details on how I come up with the solution, feel free to
play with the basic demo &amp;ndash; try to resize the box by dragging the bottom right
corner and see how the text inside responds (if you are using anything than
the Chrome browser you may be out of luck with the resizable &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s, sorry):&lt;/p&gt;
&lt;div class="demo resizable"&gt;
    &lt;style&gt;
        div[class^='demo'].resizable {
            position: relative;
            overflow: auto;
            resize: both;
            border: solid 0.25em rgba(0,0,0,0.5);
            max-width: 100%;
            min-width: 4.1em;
            padding: 0.6em 1.2em;
            border-radius: 0.5em;
            margin-bottom: 1.5em;
        }

        div[class^='demo'] pre {
            word-break: break-word;
            word-wrap: break-word;
            overflow-wrap: break-word;
            white-space: pre-wrap;
            font-size: 1em;
            line-height: 1.2;
        }

        div.demo pre div.line {
            position: relative;
            min-height: 1em;
        }

        div.demo pre div.line::after {
            content: '';
            position: absolute;
            display: block;
            font-family: inherit;
            font-size: inherit;
            width: 1.2em;
            right: -1.2em;
            background-size: 1.2em 1.2em;
            margin-bottom: 1.2em;
            clip: rect(1.2em 1.2em 100vh 0);
            height: 100%;
            bottom: 0;
            opacity: .5;
            background:
                url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==)
                no-repeat repeat;
        }
    &lt;/style&gt;
    &lt;pre&gt;&lt;div class="line"&gt;This is quite a long line.  It is long enough to ensure that it will wrap no matter how big your screen is.  Well, it is possible that in 20 years from the moment I type this humanity will invent a medium that could display the whole line with no breaks, but until then it should be good enough for the demonstration purposes.&lt;/div&gt;&lt;div class="line"&gt;A very short line.&lt;/div&gt;&lt;div class="line"&gt;The short line above is used to demonstrate that it stays clear of additional info when its neighbour lines are wrapped.&lt;/div&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;I hope that demo gods were not angry at me and the demo above worked for you as
expected, so if you want to understand how it works let&amp;rsquo;s dive under the hood
of this solution :)&lt;/p&gt;
&lt;h2 id="preparation"&gt;Preparation&lt;/h2&gt;
&lt;p&gt;First of all, we need to define what we have and what we want to achieve &amp;ndash; it
helps to stay on course, to understand when we reached a solution, and to
assess how good the solution is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we have one or more code blocks expressed through the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; HTML element;&lt;/li&gt;
&lt;li&gt;these code blocks may have continuous white space that we want to preserve;&lt;/li&gt;
&lt;li&gt;our page layout is flexible and there are no guarantees that the width of a
    particular viewport (a window through which a browser renders the visible
    part of the page) is enough to display the whole line of code;&lt;/li&gt;
&lt;li&gt;we want to ensure that layout does not break when a very long code line is
    encountered, so we expect that line to be wrapped to the next line upon
    hitting width of the containing box;&lt;/li&gt;
&lt;li&gt;we need to present the reader (a user who is consuming information) a
    visual indicator that the line was wrapped;&lt;/li&gt;
&lt;li&gt;we also want the syntax highlighter (such as &lt;a href="https://prismjs.com"&gt;PrismJS&lt;/a&gt; in my case) not to be
    affected by whatever we come up with.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think the above quite a solid definition of the goals and requirements for
our little project.  It is time to assess the artefacts we have and what we can
do with them.&lt;/p&gt;
&lt;h2 id="analysis"&gt;Analysis&lt;/h2&gt;
&lt;p&gt;Our primary artefact is the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element.  It contains the information we
want to share with the world and we want to do this in the most easy to digest
way.&lt;/p&gt;
&lt;p&gt;By default, the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element is quite a simplistic container.  Its semantic
meaning dictates that the content preserves the original formatting (such as
amount and type of white space on the lines).  The element also provides some
basic functionality: you can define the size of the container and browsers are
supposed to present you with controls to scroll the content if it does not fit
the size of the container.&lt;/p&gt;
&lt;p&gt;The following is just such bare almost not styled &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; block wrapped into a
resizable &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; container, so you could play with the dimensions of the
container (in order to preserve the layout I am keeping the wrapping styles, so
the box would fit into the layout):&lt;/p&gt;
&lt;div class="demo1 resizable"&gt;
    &lt;pre&gt;This is an example of a basic &amp;lt;pre&amp;gt; container.  You should
be able to resize it by dragging the bottom right corner (but if you are
using anything other than Chrome you may be out of luck with the resizing).

This line was made specifically long to trigger the line wrapping, so even those who are using a browser that cannot resize should be able to play with it :)&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;However, if you played with the above container you may have noticed that at
some point all lines are so mixed up together and it is really hard to tell
that the original text comprised of five separate lines.&lt;/p&gt;
&lt;p&gt;Unfortunately, the CSS standards do not provide any selectors or
pseudo-elements to anchor the soft breaks.  All we can do with the CSS
selectors on the contained text is to choose the first letter (with
&lt;code&gt;:first-letter&lt;/code&gt;) and the first line of the text inside the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element
(with &lt;code&gt;:first-line&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This is not good enough if you ask me.  I would love to see some CSS selectors
for soft breaks in the future iterations of the standard, but we are not there
yet, so we have to work with what we have got.&lt;/p&gt;
&lt;p&gt;And all we have is the standard &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model" title="Introduction to the CSS box model"&gt;CSS basic box model&lt;/a&gt; presentation focused
selectors such as &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, if we look how text is rendered inside the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; block (browser&amp;rsquo;s
DevTools is a really good tool to do the observations, by the way) we will
notice that the browser considers the entire text as a single entity &amp;ndash; there
are no lines or anything, just a blob of text.&lt;/p&gt;
&lt;p&gt;Therefore, the first piece of the puzzle would be to introduce something that
could make the distinction between different lines of text possible.&lt;/p&gt;
&lt;p&gt;Assuming that we can solve the above issue with introducing lines, the next
step would be to display a marker on the right side of the line.  That marker
should follow the height of the line box as the line wraps.&lt;/p&gt;
&lt;p&gt;Finally, we will need to figure out how not to display the marker on the last
line of the wrapped multiline text to denote the end of wrapped line.&lt;/p&gt;
&lt;h2 id="implementation"&gt;Implementation&lt;/h2&gt;
&lt;p&gt;Since the &lt;a href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element"&gt;definition of the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; element&lt;/a&gt; only stipulates that the content of
the element is pre-formatted with white space we can use other tags inside,
e.g. we can wrap each physical line (a line that is terminated by a newline
character) in a block element such as &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how it looks on our sample &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; block when we wrap each physical
line in &lt;code&gt;&amp;lt;div class="line"&amp;gt;&lt;/code&gt;&amp;hellip;&lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;.  Keep in mind that &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is a block
element, so in order not to introduce unintended line breaks we need to ensure
that every newline character in the original text except the very first one is
replaced with the combination of the closing and opening tags such as
&lt;code&gt;&amp;lt;/div&amp;gt;&amp;lt;div class="line"&amp;gt;&lt;/code&gt;, below is the result of such a change on our sample:&lt;/p&gt;
&lt;div class="demo2 resizable"&gt;
&lt;pre&gt;&lt;div class="line"&gt;This is an example of a basic &amp;lt;pre&amp;gt; container.  You should&lt;/div&gt;&lt;div class="line"&gt;be able to resize it by dragging the bottom right corner (but if you are&lt;/div&gt;&lt;div class="line"&gt;using anything than Chrome you may be out of luck with the resizing).&lt;/div&gt;&lt;div class="line"&gt;&lt;/div&gt;&lt;div class="line"&gt;This line was made specifically long to trigger the line wrapping, so even those who are using a browser that cannot resize should be able to play with it :)&lt;/div&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Visually, it is almost exactly the same as the original sample we presented in
the &amp;ldquo;Analysis&amp;rdquo; section, however there is a couple of differences.&lt;/p&gt;
&lt;p&gt;The first one is visual and is obvious: the empty line (the fourth line of the
sample) has collapsed into nothing.  This is an undesired side effect and the
reason for that is that the corresponding &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; block has no content, hence
it has zero height.  We can fix it by defining the &lt;code&gt;min-height&lt;/code&gt; attribute for
the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s defining line boundaries.&lt;/p&gt;
&lt;p&gt;The second difference is not so obvious, but if you inspect any line of this
&lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; block using your browser&amp;rsquo;s DevTools you will see that now we can
differentiate between lines (when you walk through the DOM in your DevTools
your browser will highlight the corresponding line inside the &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; block and
this is what we wanted to achieve at this step!&lt;/p&gt;
&lt;p&gt;The next step is to display a marker on the right side of the line and that
marker should occupy the exactly the same height as the line block we are
attaching it to.  Well, this can be easily done with the &lt;code&gt;::after&lt;/code&gt;
pseudo-element:&lt;/p&gt;
&lt;pre id="demo3" class="highlight line-numbers"&gt;&lt;input type="radio" name="code_menu853a0d6221957bb0502dfdcffe08903513b05b2d" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu853a0d6221957bb0502dfdcffe08903513b05b2d" class="line-numbers icon list-numbered" checked&gt;&lt;code class="language-css"&gt;&lt;div id="demo3.1" class="line"&gt;&lt;a href="#demo3.1" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.2" class="line"&gt;&lt;a href="#demo3.2" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;/* so we could position the child */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.3" class="line"&gt;&lt;a href="#demo3.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* to avoid collapsing empty lines */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.4" class="line"&gt;&lt;a href="#demo3.4" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.5" class="line"&gt;&lt;a href="#demo3.5" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="demo3.6" class="line"&gt;&lt;a href="#demo3.6" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.7" class="line"&gt;&lt;a href="#demo3.7" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;/* we need content for pseudo elements */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.8" class="line"&gt;&lt;a href="#demo3.8" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.9" class="line"&gt;&lt;a href="#demo3.9" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.10" class="line"&gt;&lt;a href="#demo3.10" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c"&gt;/* without width it will be invisible */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.11" class="line"&gt;&lt;a href="#demo3.11" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c"&gt;/* to match the height of the parent */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.12" class="line"&gt;&lt;a href="#demo3.12" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.13" class="line"&gt;&lt;a href="#demo3.13" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.14" class="line"&gt;&lt;a href="#demo3.14" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;/* temporary, to make it visible */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo3.15" class="line"&gt;&lt;a href="#demo3.15" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that we also introduced &lt;code&gt;position: relative&lt;/code&gt; to our &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&amp;rsquo;s.  It is
needed since if we want to position our pseudo-element relative to its parent,
the parent needs to have the position attribute set to anything but &lt;code&gt;static&lt;/code&gt;.
In our context, &lt;code&gt;relative&lt;/code&gt; is the desired positioning for the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element.
The corresponding rendered sample with the above stylesheet applied follows:&lt;/p&gt;
&lt;div class="demo3 resizable"&gt;
    &lt;style&gt;
        div.demo3 pre div.line {
            min-height: 1em;
            position: relative;
        }

        div.demo3 pre div.line::after {
            content: '';
            position: absolute;
            display: block;
            width: 1em;
            height: 100%;
            top: 0;
            right: -1em;
            background-color: blue;
        }
    &lt;/style&gt;
&lt;pre&gt;&lt;div class="line"&gt;This is an example of a basic &amp;lt;pre&amp;gt; container.  You should&lt;/div&gt;&lt;div class="line"&gt;be able to resize it by dragging the bottom right corner (but if you are&lt;/div&gt;&lt;div class="line"&gt;using anything than Chrome you may be out of luck with the resizing).&lt;/div&gt;&lt;div class="line"&gt;&lt;/div&gt;&lt;div class="line"&gt;This line was made specifically long to trigger the line wrapping, so even those who are using a browser that cannot resize should be able to play with it :)&lt;/div&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;So far so good!  We have a blue coloured strip hanging on the right side just
next to our text.  Let&amp;rsquo;s style it a bit so it looks more appealing.&lt;/p&gt;
&lt;p&gt;We have a couple of options here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;we can just use a transparent bitmap image with the desired marker;&lt;/li&gt;
&lt;li&gt;we can leverage SVG as the source of the image.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The issue I have with transparent bitmap images is that they are not very
responsive: to accommodate to all the possible resolutions I would end up with
creating many bitmaps of different sizes to ensure that the marker looks good
no matter what device is rendering the page.&lt;/p&gt;
&lt;p&gt;On the other hand, the SVG graphics was not well supported on some browsers
like Internet Explorer 6, etc. &amp;ndash; The support is much better now, hence I would
choose SVG every time.&lt;/p&gt;
&lt;p&gt;Ideally, I did not want to use an image, but I could not figure out how I could
put a repeating text block next to my lines, hence I found the following
compromise (this is the content of the SVG file I created):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu864304a0e9d0de8d3eb04abc039eec5aa9a02481" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu864304a0e9d0de8d3eb04abc039eec5aa9a02481" class="line-numbers icon list-numbered"&gt;&lt;code class="language-svg"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;1.1&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0 0 24 24&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;19&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;x23ce&lt;/span&gt;&lt;span class="o"&gt;;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;text&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first two lines are mandatory and describe to a browser that it works with
an SVG image file.&lt;/p&gt;
&lt;p&gt;The third line (&lt;code&gt;viewBox=&lt;/code&gt;) is really important since it allows the SVG image
to be scaleable, basically it says that the dimensions of the image are 25 by
25 units (the units are abstract, but you may think of them as pixels if it
makes it easier).&lt;/p&gt;
&lt;p&gt;Finally, the &lt;code&gt;&amp;lt;text&amp;gt;&lt;/code&gt; element puts a text box with just a single Unicode
character for the return symbol (&amp;#x23ce;), since I think it is quite an
adequate symbol to represent a line wrap point.&lt;/p&gt;
&lt;p&gt;There are different ways how you can incorporate an SVG image into CSS rule,
but since the content of the SVG file is so small and I did not want to host it
externally (it would be an additional fetch request and more maintenance), I
just did the following:&lt;/p&gt;
&lt;pre class="highlight" data-user="user"&gt;&lt;input type="radio" name="code_menu76952dc7db91671cf2ccc9391757758f9b72d03f" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu76952dc7db91671cf2ccc9391757758f9b72d03f" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;nl.svg&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;lt;svg xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;        version=&amp;quot;1.1&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;        viewBox=&amp;quot;0 0 24 24&amp;quot;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;    &amp;lt;text x=&amp;quot;0&amp;quot; y=&amp;quot;19&amp;quot;&amp;gt;&amp;amp;#x23ce;&amp;lt;/text&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;lt;/svg&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1{h;d};$!H;${H;x;s,\s\+, ,g;s,&amp;gt;\s*&amp;lt;,&amp;gt;&amp;lt;,g;p}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nl.svg&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;base64&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In other words, I removed all the white space, then deleted all line breaks, so
I got a single line SVG body, and I converted it to Base64.  The resulting line
is usable as the inline definition of an image.  Let&amp;rsquo;s update our stylesheet
with the new values (we just replaced the last line to change the &lt;code&gt;background&lt;/code&gt;
to &lt;code&gt;background-image&lt;/code&gt;) and added dimensions for the background itself:&lt;/p&gt;
&lt;pre id="demo4" class="highlight line-numbers"&gt;&lt;input type="radio" name="code_menuc4ca362f710da0cb9e9de275a8c552589ef0610c" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuc4ca362f710da0cb9e9de275a8c552589ef0610c" class="line-numbers icon list-numbered" checked&gt;&lt;code class="language-css"&gt;&lt;div id="demo4.1" class="line"&gt;&lt;a href="#demo4.1" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.2" class="line"&gt;&lt;a href="#demo4.2" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;/* so we could position the child */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.3" class="line"&gt;&lt;a href="#demo4.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* to avoid collapsing empty lines */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.4" class="line"&gt;&lt;a href="#demo4.4" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.5" class="line"&gt;&lt;a href="#demo4.5" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="demo4.6" class="line"&gt;&lt;a href="#demo4.6" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.7" class="line"&gt;&lt;a href="#demo4.7" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;/* we need content for pseudo elements */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.8" class="line"&gt;&lt;a href="#demo4.8" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.9" class="line"&gt;&lt;a href="#demo4.9" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.10" class="line"&gt;&lt;a href="#demo4.10" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="c"&gt;/* without width it will be invisible */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.11" class="line"&gt;&lt;a href="#demo4.11" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c"&gt;/* to match the height of the parent */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.12" class="line"&gt;&lt;a href="#demo4.12" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.13" class="line"&gt;&lt;a href="#demo4.13" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.14" class="line"&gt;&lt;a href="#demo4.14" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.15" class="line"&gt;&lt;a href="#demo4.15" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo4.16" class="line"&gt;&lt;a href="#demo4.16" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class="demo4 resizable"&gt;
    &lt;style&gt;
        div.demo4 pre div.line {
            position: relative;
            min-height: 1em;
        }

        div.demo4 pre div.line::after {
            content: '';
            position: absolute;
            display: block;
            width: 1em;
            height: 100%;
            top: 0;
            right: -1em;
            background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==);
            background-size: 1em 1em;
        }
    &lt;/style&gt;
&lt;pre&gt;&lt;div class="line"&gt;This is an example of a basic &amp;lt;pre&amp;gt; container.  You should&lt;/div&gt;&lt;div class="line"&gt;be able to resize it by dragging the bottom right corner (but if you are&lt;/div&gt;&lt;div class="line"&gt;using anything than Chrome you may be out of luck with the resizing).&lt;/div&gt;&lt;div class="line"&gt;&lt;/div&gt;&lt;div class="line"&gt;This line was made specifically long to trigger the line wrapping, so even those who are using a browser that cannot resize should be able to play with it :)&lt;/div&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;Look at that, it is almost shaping into what we want :).  However, something is
not right &amp;ndash; the alignment of the markers to the corresponding lines is nowhere
to be seen.  It is, like, they are of different sizes despite that the
&lt;code&gt;font-size&lt;/code&gt; property for both of them is the same.  Hmm.&lt;/p&gt;
&lt;p&gt;If we recall that &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model" title="Introduction to the CSS box model"&gt;CSS basic model document&lt;/a&gt; we will discover in the very last
paragraph that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a name="CSS_basic_model_line_height" href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Box_Model/Introduction_to_the_CSS_box_model"&gt;&lt;/a&gt;
note that for non-replaced inline elements, the amount of space taken up (the
contribution to the height of the line) is determined by the line-height
property&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Is this our hint?  Let&amp;rsquo;s try to explicitly set the &lt;code&gt;line-height&lt;/code&gt; property for
our lines (reading documentation you may find that the default value is
&lt;code&gt;normal&lt;/code&gt; and you can also find that &lt;code&gt;normal&lt;/code&gt; equals &lt;code&gt;1.2&lt;/code&gt; for the majority of
browsers, but relying on that, I think is a bad idea).  We also need to adjust
our markers with the new information we have got:&lt;/p&gt;
&lt;pre id="demo5" class="highlight"&gt;&lt;input type="radio" name="code_menu335ebac2912303db5d7164eb96f4844f011daf44" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu335ebac2912303db5d7164eb96f4844f011daf44" class="line-numbers icon list-numbered"&gt;&lt;code class="language-css"&gt;&lt;div id="demo5.1" class="line"&gt;&lt;a href="#demo5.1" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.2" class="line"&gt;&lt;a href="#demo5.2" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;/* so we could position the child */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.3" class="line"&gt;&lt;a href="#demo5.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* to avoid collapsing empty lines */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.4" class="line"&gt;&lt;a href="#demo5.4" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;/* set the height explicitly */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.5" class="line"&gt;&lt;a href="#demo5.5" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.6" class="line"&gt;&lt;a href="#demo5.6" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="demo5.7" class="line"&gt;&lt;a href="#demo5.7" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.8" class="line"&gt;&lt;a href="#demo5.8" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;/* we need content for pseudo elements */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.9" class="line"&gt;&lt;a href="#demo5.9" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.10" class="line"&gt;&lt;a href="#demo5.10" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.11" class="line"&gt;&lt;a href="#demo5.11" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c"&gt;/* without width it will be invisible */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.12" class="line"&gt;&lt;a href="#demo5.12" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c"&gt;/* to match the height of the parent */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.13" class="line"&gt;&lt;a href="#demo5.13" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.14" class="line"&gt;&lt;a href="#demo5.14" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.15" class="line"&gt;&lt;a href="#demo5.15" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.16" class="line"&gt;&lt;a href="#demo5.16" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.17" class="line"&gt;&lt;a href="#demo5.17" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;repeat-y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* optional, but nice to have */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.18" class="line"&gt;&lt;a href="#demo5.18" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;/* optional, but better to be explicit */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo5.19" class="line"&gt;&lt;a href="#demo5.19" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class="demo5 resizable"&gt;
    &lt;style&gt;
        div.demo5 pre div.line {
            position: relative;
            min-height: 1em;
            line-height: 1.2;
        }

        div.demo5 pre div.line::after {
            content: '';
            position: absolute;
            display: block;
            width: 1.2em;
            height: 100%;
            top: 0;
            right: -1.2em;
            background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==);
            background-size: 1.2em 1.2em;
            background-repeat: repeat-y;
            background-position: top;
        }
    &lt;/style&gt;
&lt;pre&gt;&lt;div class="line"&gt;This is an example of a basic &amp;lt;pre&amp;gt; container.  You should&lt;/div&gt;&lt;div class="line"&gt;be able to resize it by dragging the bottom right corner (but if you are&lt;/div&gt;&lt;div class="line"&gt;using anything than Chrome you may be out of luck with the resizing).&lt;/div&gt;&lt;div class="line"&gt;&lt;/div&gt;&lt;div class="line"&gt;This line was made specifically long to trigger the line wrapping, so even those who are using a browser that cannot resize should be able to play with it :)&lt;/div&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This is an improvement, is not it?  So we just need some final touches to call
it done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As it stands, we are showing the marker for the last part of the wrapped line, but we should not;&lt;/li&gt;
&lt;li&gt;The colour of the marker is too bright and matches the text colour of the line wrapping of which we are highlighting.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To address the latter a simple play with the &lt;code&gt;opacity&lt;/code&gt; attribute will suffice,
e.g. I think 50% transparency should make the marker less distracting.&lt;/p&gt;
&lt;p&gt;The former, however, requires some thinking.  Given that the post is already too long, I will just state that out of multiple options I had I chose the following: raise the background image by one &lt;code&gt;line-height&lt;/code&gt; from the bottom, and clip a square 1x1 &lt;code&gt;line-height&lt;/code&gt; at the top to compensate, as follows:&lt;/p&gt;
&lt;pre id="demo6" class="highlight"&gt;&lt;input type="radio" name="code_menuc438befcff22d7ed369579e05b3c3138174225ad" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuc438befcff22d7ed369579e05b3c3138174225ad" class="line-numbers icon list-numbered"&gt;&lt;code class="language-css"&gt;&lt;div id="demo6.1" class="line"&gt;&lt;a href="#demo6.1" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.2" class="line"&gt;&lt;a href="#demo6.2" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;/* so we could position the child */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.3" class="line"&gt;&lt;a href="#demo6.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* to avoid collapsing empty lines */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.4" class="line"&gt;&lt;a href="#demo6.4" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;/* set the height explicitly */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.5" class="line"&gt;&lt;a href="#demo6.5" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.6" class="line"&gt;&lt;a href="#demo6.6" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="demo6.7" class="line"&gt;&lt;a href="#demo6.7" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.8" class="line"&gt;&lt;a href="#demo6.8" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;/* we need content for pseudo elements */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.9" class="line"&gt;&lt;a href="#demo6.9" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.10" class="line"&gt;&lt;a href="#demo6.10" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.11" class="line"&gt;&lt;a href="#demo6.11" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c"&gt;/* without width it will be invisible */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.12" class="line"&gt;&lt;a href="#demo6.12" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c"&gt;/* to match the height of the parent */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.13" class="line"&gt;&lt;a href="#demo6.13" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.14" class="line"&gt;&lt;a href="#demo6.14" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.15" class="line"&gt;&lt;a href="#demo6.15" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.16" class="line"&gt;&lt;a href="#demo6.16" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.17" class="line"&gt;&lt;a href="#demo6.17" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;repeat-y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* optional, but nice to have */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.18" class="line"&gt;&lt;a href="#demo6.18" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;/* optional, but better to be explicit */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.19" class="line"&gt;&lt;a href="#demo6.19" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.20" class="line"&gt;&lt;a href="#demo6.20" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;vh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.21" class="line"&gt;&lt;a href="#demo6.21" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo6.22" class="line"&gt;&lt;a href="#demo6.22" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The only tricky part in the added lines is the &lt;code&gt;clip: rect(1.2em 1.2em 100vh
0)&lt;/code&gt; line.  The &lt;code&gt;clip&lt;/code&gt; property defines the visible rectangle the browser should
preserve, but there is a conundrum: the height of the wrapped line is variable,
so we cannot deterministically set this.  The trick here is that it is highly
unlikely (on the border of completely impossible :) ) that the visible height
of the wrapped line would be bigger that the height of the viewport the user is
looking through.&lt;/p&gt;
&lt;p&gt;Therefore, we are setting the height of the clipped area to the height of the
viewport (&lt;code&gt;100vh&lt;/code&gt;), but this will be a rare edge case to actually utilise this
&amp;ndash; in the majority of cases the height of the clipping rectangle will be
limited by the height of the block containing the wrapped line.  Basically,
this is exactly what we want :).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see the final result in action:&lt;/p&gt;
&lt;div class="demo6 resizable"&gt;
    &lt;style&gt;
        div.demo6 pre div.line {
            position: relative;
            min-height: 1em;
            line-height: 1.2;
        }

        div.demo6 pre div.line::after {
            content: '';
            position: absolute;
            display: block;
            width: 1.2em;
            height: 100%;
            bottom: 0;
            right: -1.2em;
            background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==);
            background-size: 1.2em 1.2em;
            background-repeat: repeat-y;
            background-position: top;
            margin-bottom: 1.2em;
            clip: rect(1.2em 1.2em 100vh 0);
            opacity: .5;
        }
    &lt;/style&gt;
&lt;pre&gt;&lt;div class="line"&gt;This is an example of a basic &amp;lt;pre&amp;gt; container.  You should&lt;/div&gt;&lt;div class="line"&gt;be able to resize it by dragging the bottom right corner (but if you are&lt;/div&gt;&lt;div class="line"&gt;using anything than Chrome you may be out of luck with the resizing).&lt;/div&gt;&lt;div class="line"&gt;&lt;/div&gt;&lt;div class="line"&gt;This line was made specifically long to trigger the line wrapping, so even those who are using a browser that cannot resize should be able to play with it :)&lt;/div&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;h2 id="bonus-round"&gt;Bonus Round&lt;/h2&gt;
&lt;p&gt;It should be mentioned, it is also possible to apply the same approach to the
left side of the lines with a bit of a twist.&lt;/p&gt;
&lt;pre id="demo7" class="highlight"&gt;&lt;input type="radio" name="code_menu26d76c78f39ad0bcd250cd18f961b9ce7f7c9d39" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu26d76c78f39ad0bcd250cd18f961b9ce7f7c9d39" class="line-numbers icon list-numbered"&gt;&lt;code class="language-css"&gt;&lt;div id="demo7.1" class="line"&gt;&lt;a href="#demo7.1" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.2" class="line"&gt;&lt;a href="#demo7.2" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c"&gt;/* so we could position the child */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.3" class="line"&gt;&lt;a href="#demo7.3" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c"&gt;/* to avoid collapsing empty lines */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.4" class="line"&gt;&lt;a href="#demo7.4" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c"&gt;/* set the height explicitly */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.5" class="line"&gt;&lt;a href="#demo7.5" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;text-indent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;-1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* first line indent */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.6" class="line"&gt;&lt;a href="#demo7.6" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;padding-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* padding for the line */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.7" class="line"&gt;&lt;a href="#demo7.7" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.8" class="line"&gt;&lt;a href="#demo7.8" aria-hidden="true"&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div id="demo7.9" class="line"&gt;&lt;a href="#demo7.9" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;line&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;before&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.10" class="line"&gt;&lt;a href="#demo7.10" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c"&gt;/* we need content for pseudo elements */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.11" class="line"&gt;&lt;a href="#demo7.11" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.12" class="line"&gt;&lt;a href="#demo7.12" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.13" class="line"&gt;&lt;a href="#demo7.13" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c"&gt;/* without width it will be invisible */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.14" class="line"&gt;&lt;a href="#demo7.14" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="c"&gt;/* to match the height of the parent */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.15" class="line"&gt;&lt;a href="#demo7.15" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.16" class="line"&gt;&lt;a href="#demo7.16" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.17" class="line"&gt;&lt;a href="#demo7.17" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sx"&gt;data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.18" class="line"&gt;&lt;a href="#demo7.18" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.19" class="line"&gt;&lt;a href="#demo7.19" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;repeat-y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c"&gt;/* optional, but nice to have */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.20" class="line"&gt;&lt;a href="#demo7.20" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;background-position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;top&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="c"&gt;/* optional, but better to be explicit */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.21" class="line"&gt;&lt;a href="#demo7.21" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;clip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;vh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.22" class="line"&gt;&lt;a href="#demo7.22" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.23" class="line"&gt;&lt;a href="#demo7.23" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;rotateY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="kt"&gt;deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;/* mirror the arrow */&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.24" class="line"&gt;&lt;a href="#demo7.24" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;transform-origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div id="demo7.25" class="line"&gt;&lt;a href="#demo7.25" aria-hidden="true"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The trick here is the play between &lt;code&gt;padding-left&lt;/code&gt; and &lt;code&gt;text-indent&lt;/code&gt; for the
&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element since &lt;code&gt;text-indent&lt;/code&gt; will be applied to the first line only
(effectively negating the padding set for the whole element), thus the rest of
wrapped line will be padded to make space for the marker, which we place as
usual with the clipping region to suppress it for the very first part of the
wrapped line.&lt;/p&gt;
&lt;p&gt;The result looks as follows (and I am actually torn which side I like better,
so, maybe, for my blog I may just go with the left-side solution):&lt;/p&gt;
&lt;div class="demo7 resizable"&gt;
    &lt;style&gt;
        div.demo7 pre div.line {
            position: relative;
            min-height: 1em;
            line-height: 1.2;
            text-indent: -1.2em;
            padding-left: 1.2em;
        }

        div.demo7 pre div.line::after {
            content: '';
            position: absolute;
            display: block;
            width: 1.2em;
            height: 100%;
            top: 0;
            left: 1em;
            background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDI0IDI0Ij48dGV4dCB4PSIwIiB5PSIxOSI+JiN4MjNjZTs8L3RleHQ+PC9zdmc+Cg==);
            background-size: 1.2em 1.2em;
            background-repeat: repeat-y;
            background-position: top;
            clip: rect(1.2em 1.2em 100vh 0);
            opacity: .5;
            transform: rotateY(180deg);
            transform-origin: left;
        }
    &lt;/style&gt;
&lt;pre&gt;&lt;div class="line"&gt;This is an example of a basic &amp;lt;pre&amp;gt; container.  You should&lt;/div&gt;&lt;div class="line"&gt;be able to resize it by dragging the bottom right corner (but if you are&lt;/div&gt;&lt;div class="line"&gt;using anything than Chrome you may be out of luck with the resizing).&lt;/div&gt;&lt;div class="line"&gt;&lt;/div&gt;&lt;div class="line"&gt;This line was made specifically long to trigger the line wrapping, so even those who are using a browser that cannot resize should be able to play with it :)&lt;/div&gt;&lt;/pre&gt;
&lt;/div&gt;

&lt;p&gt;This idea for the &lt;code&gt;padding-left&lt;/code&gt; and &lt;code&gt;text-indent&lt;/code&gt; trick came from &lt;a href="http://shaunwagner.com/"&gt;C. Shaun
&amp;ldquo;Kainaw&amp;rdquo; Wagner&lt;/a&gt; when &lt;a href="https://stackoverflow.com/a/48936538/3316011"&gt;he answered a question about
the wrap indicators&lt;/a&gt; on Stack
Overflow (I am sure that it was known before, but this is where I learnt it).&lt;/p&gt;
&lt;h2 id="assessment-of-the-result"&gt;Assessment of the result&lt;/h2&gt;
&lt;p&gt;We started with the following requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;these code blocks may have continuous white space that we want to preserve;&lt;/p&gt;
&lt;p&gt;Achieved: we did some changes to ensure that even empty lines are preserved.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;our page layout is flexible and there are no guarantees that the width of a
    particular viewport (a window through which a browser renders the visible
    part of the page) is enough to display the whole line of code&lt;/p&gt;
&lt;p&gt;Achieved: our blocks are flexible and if a line is too long it is wrapped
in an easy to understand way.  We proved it by ensuring that any block
can be dynamically resized.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we want to ensure that layout does not break when a very long code line is
    encountered, so we expect that line to be wrapped to the next line upon
    hitting width of the containing box;&lt;/p&gt;
&lt;p&gt;Achieved: ditto as the previous item.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we need to present the reader (a user who is consuming information) a
    visual indicator that the line was wrapped;&lt;/p&gt;
&lt;p&gt;Achieved: we got our dynamic markers showing when the line was wrapped.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;we also want the syntax highlighter (such as PrismJS in my case) not to be
    affected by whatever we come up with.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;Unknown: I think it would be the material for another post, but I do not
expect a lot of issues since we only replaced the newline characters and
preserved the rest of lines intact.i&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;It turned out, that it was quite a hassle since none of plugins PrismJS
have for working with lines expect semantic markup of the lines inside the
block.  Therefore, I spent some time and came up with a plugin that does it
&amp;ldquo;right&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;It is highly likely, that my plugin will get merged into the PrismJS tree
(see the corresponding &lt;a href="https://github.com/PrismJS/prism/pull/2389"&gt;Pull Request&lt;/a&gt;).&lt;/del&gt;  After a discussion with upstream, it
seems they want to rework a large chunk of the project to use the proposed
functionality and make it a library of some sort.  I do not have time to
actively see it through, so one of PrismJS developers will continue the
integration of my idea into PrismJS and something comparable will be
implemented in PrismJS in near future.  However, if for some reason it would
not be or you need the functionality &amp;ldquo;here &amp;amp; now&amp;rdquo;, you can always get it
from my &lt;a href="https://github.com/galaxy4public/prism/tree/plugin-lines"&gt;fork of PrismJS&lt;/a&gt; (I will keep the &lt;code&gt;plugin-lines&lt;/code&gt; branch until &lt;del&gt;it
is merged upstream&lt;/del&gt; upstream decides how to incorporate my idea).&lt;/p&gt;
&lt;p&gt;By the way, my Lines plugin is the primary working horse for all syntax
highlighted blocks on this site (so you have already seen it in action).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I hope this helped you and you learnt something :).  If you have any
constructive feedback, I would appreciate it, specifically if you have ideas on
how to improve the presented solution.&lt;/p&gt;</content><category term="web design"/><category term="html"/><category term="css"/><category term="design"/></entry><entry><title>Migrating blog to Pelican</title><link href="https://dmitry.khlebnikov.net/2020/05/01/migrating-blog-to-pelican/" rel="alternate"/><published>2020-05-01T20:00:00+10:00</published><updated>2025-10-15T23:06:20+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2020-05-01:/2020/05/01/migrating-blog-to-pelican/</id><summary type="html">&lt;p&gt;Dissatisfied with the Blogger platform&amp;rsquo;s changing features and lack of
control, the author decided to migrate their decade-old blog to a self-hosted,
low-maintenance solution. After researching static site generators (SSGs) that
aligned with Jamstack principles and met their requirements for stability,
extensibility, and language (preferring Python), they chose Pelican over
contenders like Jekyll and Gatsby. The author then undertook a significant
theme development project, initially attempting to port an existing HTML theme
called &amp;ldquo;Striped&amp;rdquo; but ultimately deciding to recreate a similar theme from
scratch due to inconsistencies in the original code. The resulting website is
the foundation for a planned series of posts about the development process and
infrastructure.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I had my blog site for more than a decade now, but until now I was not putting
any effort or thoughts into maintaining my audience or promoting the site.  It
was dormant for nearly a decade so I decided to rejuvenate it and start using
it as a platform I could leverage to share some ideas I think are worth
sharing.&lt;/p&gt;
&lt;p&gt;Before I embarked on the journey of renovating the blog site I needed to set
some goals and requirements up, so I would be able to assess my progress and
estimate how much effort is required.  The primary goal is already known at
this point: I need a tool that would allow me to easily share my ideas, grow
and nurture the audience, and accumulate knowledge in one place over time.&lt;/p&gt;
&lt;p&gt;I started to be increasingly unhappy with the &lt;a href="https://blogger.com"&gt;Blogger&lt;/a&gt; platform: they were
changing things and as the result parts of my blog became defunct, e.g.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;in 2017, I lost the comment capability and did not have enough time to
    investigate and fix it,&lt;/li&gt;
&lt;li&gt;in 2018, my theme became incompatible with the new trends of the platform,&lt;/li&gt;
&lt;li&gt;and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Therefore, the first requirement became to self-host my blog site, so I would
have full control over the software that is supporting the site.  This would
give the flexibility and determinism of how the site is configured and operates.&lt;/p&gt;
&lt;p&gt;The downside for hosting the site myself is that it would require ongoing
maintenance cost (primarily calculated in the time I spend on the maintenance)
and with my quite busy schedule I was not sure that I could afford it on the
ongoing basis.  So the second requirement became the low maintenance cost of
the solution.  Preferably, I wanted to make changes only when I needed to
change site&amp;rsquo;s functionality.&lt;/p&gt;
&lt;p&gt;I did some research and figured that a static website generator could be a
solution that addressed both aforementioned requirements assuming that I could
find a place where all underlying maintenance of the infrastructure and
application layer handled by somebody else (and, preferably, free of charge).&lt;/p&gt;
&lt;p&gt;There are plenty of static site generators (&lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt;).  According to some articles,
there are more than 400 different &lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt; at the moment, so the process of
selecting one that is right for you could be quite challenging.  I decided to
define what I would like to see in the generator I could use and be happy about
it before I start shopping around:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;should be written in one of the scripting languages that are widely adopted&lt;/p&gt;
&lt;p&gt;This requirement comes from my desire of running that software on a
platform that is fully managed by somebody else (my initial thought was to
host the site in an &lt;abbr title="Amazon Web Services"&gt;AWS&lt;/abbr&gt; S3 bucket and generate the site using an &lt;abbr title="Amazon Web Services"&gt;AWS&lt;/abbr&gt; Lambda
function)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;should be lean with minimum dependencies for the generator itself&lt;/p&gt;
&lt;p&gt;The more dependencies software has the more likely it is to get an
unexpected behaviour once one of the upstream dependencies make an
undesirable breaking change.  I really wanted stability and to touch the
configuration in rare occasions only when I needed to change the behaviour
of my site and not in an adhoc response to an upstream breaking change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;should be extensible and should support a plug-in mechanism&lt;/p&gt;
&lt;p&gt;I have a really high bar on what the final result should be, e.g. valid and
standard compliant HTML and CSS documents, semantic structuring, etc.  In
order not to be limited by the generator I needed a way on how I could
influence the generation process without patching or tweaking the core of
the generator itself, since doing it this way would mean maintaining a fork
of the generator and as I mentioned above, it does not fit the second
requirement.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the template language should be flexible and should allow conditional logic&lt;/p&gt;
&lt;p&gt;Over the years I worked with multiple template engines and I found that the
most comfortable engines are those that potentially allow you to break the
concept of separating design from code.  Not that I recommend doing so, but
having such power gives you yet another flexible interface to express
yourself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the architecture of the generator should feel clean, stable, and thought out&lt;/p&gt;
&lt;p&gt;I do not know how to easily describe this, but my understanding of the well
engineered software is when its layout is simple to understand, structured,
yet easily extendable without introducing any invasive changes to the
structure, e.g. callbacks/hooks in the key transition points, so you could
hook the external code up and influence the logic, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A bit more research brought me to the realisation that there is a trend in the
community and it has a name: &lt;a href="https://jamstack.org/"&gt;Javascript, API, and Markup Stack(Jamstack)&lt;/a&gt;.
The ideas behind Jamstack resonate with my vision on how I wanted to run my
blog, so if you want to understand the reasoning behind many choices I made
their website would be a good place to start reading about the approach.&lt;/p&gt;
&lt;p&gt;After a lot of consideration the following three &lt;span&gt;&lt;abbr title="Static Site Generator"&gt;SSG&lt;/abbr&gt;&lt;/span&gt;s were the primary
contenders to become the engine of my blog site:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://jekyllrb.com/"&gt;Jekill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gatsbyjs.org/"&gt;Gatsby&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://getpelican.com/"&gt;Pelican&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of these three were aligned to the requirements, however I have an
indescribable allergy to NodeJS ecosystem and I prefer Python over Ruby, hence
Pelican was the first generator to spike with and to see whether we are a match
or not.&lt;/p&gt;
&lt;p&gt;The more I worked with Pelican the more I was falling in love with the
software: with less than 600K of source code spread across just 15 files (with
a couple being redundant in my configuration) it is packed with features and is
very extensible through a plug-in system.&lt;/p&gt;
&lt;p&gt;The next step was to create the templates (the collection of templates used to
generate the output is called a theme in Pelican).  While I was spiking I found
a theme I somewhat liked (&lt;a href="https://brutalistpelican.com/"&gt;Brutalist&lt;/a&gt; by Matt
McManus), then I found a free theme that I liked a lot
(&lt;a href="https://html5up.net/striped"&gt;Striped&lt;/a&gt; by AJ), but that theme was just an
HTML/CSS/JavaScript concept which was unrelated to Pelican in any way.&lt;/p&gt;
&lt;p&gt;Initially, I started to port the Striped theme to Pelican using the Brutalist
theme as the wireframe, but in the middle of that work I realised that despite
the Striped theme was posed as &amp;ldquo;&lt;q cite="https://html5up.net/striped"&gt;a free, fully responsive HTML5 site
template&lt;/q&gt;&amp;rdquo; it was generated in a haste and was not that responsive as the
claim would make you think.  There were lots of inconsistencies and no
overarching conventions on how things were structured within.&lt;/p&gt;
&lt;p&gt;After a while, struggling to fix everything I was not happy with in Striped
theme&amp;rsquo;s code, I decided to take a different approach: I decided to re-create
Striped-like Pelican theme from scratch using Pelican&amp;rsquo;s default theme called
&amp;ldquo;simple&amp;rdquo; as the foundation.&lt;/p&gt;
&lt;p&gt;The result (which is still an ongoing process) as you can see has some
resemblance to the theme I was taking inspiration from, but on the other hand
it is quite different and standalone in its own right.  Up until some point I
was even retaining &amp;ldquo;Design by HTML5 UP&amp;rdquo; at the bottom of the left hand side
menu, but when I realised that almost nothing left of the original design and
layout, I removed it, the theme evolved much further that the inspirational
theme I started with.&lt;/p&gt;
&lt;p&gt;This post is already getting too long, so I will conclude with the following:
I am going to publish a series of posts covering the development of this
website, the tips and tricks I learnt over the course of creating it, and the
infrastructure supporting the site and its deployment process in detail.&lt;/p&gt;
&lt;p&gt;All blog posts related to these topics will be tagged with the &amp;ldquo;&lt;a href="/tag/blog/"&gt;blog&lt;/a&gt;&amp;rdquo; tag, so
you should be able to find them easily (or even subscribe to the &lt;a href="/feeds/blog.atom.xml"&gt;blog RSS feed&lt;/a&gt;
(you may need to copy the link into your RSS Reader) to get the latest updates
as soon as I publish them).&lt;/p&gt;</content><category term="blog"/><category term="pelican"/><category term="blog"/></entry><entry><title>nginx + a backend with a dynamic IP (e.g. AWS ELB)</title><link href="https://dmitry.khlebnikov.net/2017/01/13/nginx-a-backend-with-a-dynamic-ip-eg-aws-elb/" rel="alternate"/><published>2017-01-13T10:00:00+11:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2017-01-13:/2017/01/13/nginx-a-backend-with-a-dynamic-ip-eg-aws-elb/</id><summary type="html">&lt;p&gt;This post presents a non-intrusive solution for NGINX to connect to
backend servers with dynamic IP addresses, such as an AWS ELB, replacing a
previous method that involved a now-unstable patch. The new approach uses
systemd-socket-proxyd, a utility that forwards traffic from a local socket to a
remote target. The configuration involves setting up NGINX to pass FastCGI
requests to a local Unix socket, which is then monitored by a systemd socket
unit. When a request arrives, systemd activates the systemd-socket-proxyd
service, which resolves the backend&amp;rsquo;s dynamic domain name and proxies the
connection. The author also provides template unit files for reusability and a
custom SELinux policy to ensure the proxy service runs securely with the least
privilege principle.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Recently, I wrote about &lt;a href="https://dmitry.khlebnikov.net/2016/09/20/dynamic-resolution-of-upstream-servers-in-nginx/"&gt;the dynamic resolution of upstream servers&lt;/a&gt; in
nginx which was achieved by quite an intrusive patch to the core nginx module.
The patch was invented a while ago and was working very well up until recent
nginx versions were released.&lt;/p&gt;
&lt;p&gt;With the release of nginx 1.10 it was noticed that the patch crashes some
workers under heavy load and this was unacceptable for the production load,
hence a new approach was implemented.&lt;/p&gt;
&lt;p&gt;The beauty of the new solution is that it is non-intrusive and works with any
services that communicate via sockets.&lt;/p&gt;
&lt;p&gt;In a nutshell, I just looked at the problem from a little bit different angle
after I defined the requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nginx needs to delegate the requests to a FastCGI server over a socket&lt;/li&gt;
&lt;li&gt;we want to work with the standard packages provided by the distribution&lt;/li&gt;
&lt;li&gt;the FastCGI server could be on a dynamic IP address&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since we are not allowed to patch the application the only place we can meddle
with the communication between nginx and the FastCGI server is the socket nginx
connects to. Therefore, we need some kind of a proxy that would take requests
from nginx, determine the FastCGI endpoint, and forward the requests to that
endpoint.&lt;/p&gt;
&lt;p&gt;Initially, I thought that I would use something like netcat or a similar tool
for this, but then I found that systemd provides &lt;code&gt;systemd-socket-proxyd&lt;/code&gt; binary
which fits the purpose perfectly and could be configured to be socket
activated.&lt;/p&gt;
&lt;p&gt;Before we dive into the implementation details of this solution, let&amp;rsquo;s describe
what we are going to do and how:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;nginx will be configured to talk to the locally bound socket - there are
    two options, actually: either a local TCP socket or a Unix socket&lt;/li&gt;
&lt;li&gt;a proxy service will be socket activated by a request coming from nginx to
    that local socket&lt;/li&gt;
&lt;li&gt;a proxy service should resolve the target and forward the request there&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The nginx part is easy &amp;ndash; we just need to replace the FastCGI server endpoint
address (in our example below it was &lt;code&gt;remote.php.backend.domain.tld.:9000&lt;/code&gt;)
with our local socket (we are using a unix socket at
&lt;code&gt;/run/systemd-socket-proxyd/fastcgi.sock&lt;/code&gt; in this example):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menue279035ef62deb93a7229f0b88d8954c5a83d302" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menue279035ef62deb93a7229f0b88d8954c5a83d302" class="line-numbers icon list-numbered"&gt;&lt;code class="language-nginx"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;upstream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;php&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;#server remote.php.backend.domain.tld.:9000;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;server&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;unix:/run/systemd-socket-proxyd/fastcgi.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sr"&gt;\.php$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;try_files&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;fastcgi_index&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kn"&gt;include&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="s"&gt;fastcgi.conf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next step is to define the socket activated proxy service. This generally
requires creating two files in &lt;code&gt;/etc/system/systemd&lt;/code&gt; directory: one for the
socket and the other for the proxy itself. However, in this article we will go
an extra mile and will define template units so the same configuration could be
reused to launch multiple proxies using the same base templates.&lt;/p&gt;
&lt;p&gt;The first template file is for the systemd service which will provide the proxy
capability:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu1175a0c0ab66cfab0aa9e2b9ec9c0e881777311a" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu1175a0c0ab66cfab0aa9e2b9ec9c0e881777311a" class="line-numbers icon list-numbered"&gt;&lt;code class="language-systemd" data-file="/etc/systemd/system/systemd-socket-proxyd@.service"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Generic Socket Proxy (%I)&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;Documentation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://www.freedesktop.org/software/systemd/man/systemd-socket-proxyd.html&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;network.service&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-/etc/sysconfig/systemd-socket-proxyd.%i&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;User&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;nobody&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;Group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;nobody&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;OOMScoreAdjust&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;-1000&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;UMask&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;077&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/lib/systemd/systemd-socket-proxyd $TARGET&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;on-failure&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;PrivateTmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;PrivateDevices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;#PrivateUsers=true&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;#ProtectSystem=strict&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;ProtectSystem&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;full&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;#ProtectKernelTunables=true&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;#ProtectControlGroups=true&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;#NoNewPrivileges=true&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;#ProtectKernelModules=true&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;#MemoryDenyWriteExecute=true&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the version of your &lt;code&gt;systemd&lt;/code&gt; manager you may be able to uncomment
more lines than was shown in this example, which was tested on CentOS 7.3.1611.&lt;/p&gt;
&lt;p&gt;Also, in the example template given above we are using &lt;code&gt;OOMScoreAdjust=-1000&lt;/code&gt; to
protect this proxy service from being killed in the event the system is
starving for memory &amp;ndash; this may be something you do not need.&lt;/p&gt;
&lt;p&gt;The second template file is for the socket unit that would trigger the
activation of the proxy service when a request arrives on the socket:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu4d81a796a4cae0ba3b1778d4e1226127746cc6c2" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu4d81a796a4cae0ba3b1778d4e1226127746cc6c2" class="line-numbers icon list-numbered"&gt;&lt;code class="language-systemd" data-file="/etc/systemd/system/systemd-socket-proxyd@.socket"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Socket for Generic Socket Proxy (%I)&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;Documentation&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;https://www.freedesktop.org/software/systemd/man/systemd-socket-proxyd.html&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;[Socket]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;ListenStream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/run/systemd-socket-proxyd/%i.sock&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;SocketUser&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;SocketGroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;SocketMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0660&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;DirectoryMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;0711&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sockets.target&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You may notice that the defaults are pretty strict:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the socket is owned by root and only root is allowed to work with the
    socket;&lt;/li&gt;
&lt;li&gt;the directory permissions are set in such a way that the
    &lt;code&gt;/run/systemd-socket-proxyd&lt;/code&gt; directory file list is not readable by anyone
    except root.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are safe and sane defaults and can be tweaked per instance that was
instantiated using the template as shown later in this article.&lt;/p&gt;
&lt;p&gt;Since our goal is to connect nginx to the backend FastCGI service we need to
ensure that the proxy socket is read/write accessible to nginx&amp;rsquo;es workers, so
we need to tweak the settings of the socket unit:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu27998f57d01bb24aa37caf31ea0999dcfa40b8f9" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu27998f57d01bb24aa37caf31ea0999dcfa40b8f9" class="line-numbers icon list-numbered"&gt;&lt;code class="language-systemd" data-file="/etc/systemd/system/systemd-socket-proxyd@fastcgi.socket.d/fastcgi.conf"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;[Socket]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;SocketGroup&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;nginx&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note how we extended the template for a specific named instance of the socket
unit: we defined the &lt;code&gt;systemd-socket-proxyd@fastcgi.socket.d&lt;/code&gt; sub-directory and
put a drop-in configuration snippet there.&lt;/p&gt;
&lt;p&gt;Now, to achieve our goal we need to specify the target endpoint for our proxy
service (the named instance we spawn using the template):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu12308524cbf428b281daf1f11567ffd99737e27b" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu12308524cbf428b281daf1f11567ffd99737e27b" class="line-numbers icon list-numbered"&gt;&lt;code class="language-systemd" data-file="/etc/systemd/system/systemd-socket-proxyd@fastcgi.service.d/fastcgi.conf"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Reset the ExecStart, so we could override it&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="na"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/usr/lib/systemd/systemd-socket-proxyd remote.php.backend.domain.tld.:9000&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mechanics behind extending the configuration is the same as for the socket
unit, but here we overrode the &lt;code&gt;ExecStart&lt;/code&gt; command to specify the target endpoint
for the proxy.&lt;/p&gt;
&lt;p&gt;OK, we are done with the configuration of &lt;code&gt;systemd&lt;/code&gt;, so it would be a good time
to reload the &lt;code&gt;systemd&lt;/code&gt; daemon:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu1890d918bbd740675f143763585069f1b474c11d" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu1890d918bbd740675f143763585069f1b474c11d" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;daemon-reload&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you run a system where SELinux is disabled (why?!) you don&amp;rsquo;t need to do
anything additional and should be good, but if you are security conscious and
want to ensure that you follow the least privilege principle, then read on :)&lt;/p&gt;
&lt;p&gt;Unfortunately, &lt;code&gt;systemd-socket-proxyd&lt;/code&gt; seems not to be used a lot by the
community (most likely people are just unaware of it) so the tool has no
dedicated policy attached to it in the targeted SELinux policy. I plan to push
the change into the SELinux reference policy, but before I do we are going to
use a custom loadable policy module.&lt;/p&gt;
&lt;p&gt;To build a module you need the SELinux reference policy development framework
installed on the instance you are building your policies (it can be the same
instance you are running your proxy on, but I would advise to use a temporary
VM for building/compiling policies since the only time you need that
development stuff is when you are compiling the module from sources).&lt;/p&gt;
&lt;p&gt;On CentOS, you can install all the necessary bits to build a loadable SELinux
module by installing the &lt;code&gt;selinux-policy-devel&lt;/code&gt; package using &lt;code&gt;yum&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu2b83652d42034e71d05d77d2b6901604f384fbe4" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu2b83652d42034e71d05d77d2b6901604f384fbe4" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;selinux-policy-devel&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note, I skipped the output of the command since it does not provide any useful
information for the purposes of this article.&lt;/p&gt;
&lt;p&gt;Once the SELinux development framework is installed we can start designing our
loadable policy for the &lt;code&gt;systemd-socket-proxyd&lt;/code&gt; service.&lt;/p&gt;
&lt;p&gt;Our policy will consist of two files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;systemd-socket-proxyd.te&lt;/code&gt; &amp;ndash; the type enforcement ruleset; and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;systemd-socket-proxyd.fc&lt;/code&gt; &amp;ndash; the file context ruleset.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eventually, we will need to introduce the corresponding module interface
support file too, but for the purposes of this article we should be fine with
the automatically generated one.&lt;/p&gt;
&lt;p&gt;The content of the systemd-socket-proxy.te file is listed below:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu303ae941afb9ed879e7e4742c6cd6bfb379781de" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu303ae941afb9ed879e7e4742c6cd6bfb379781de" class="line-numbers icon list-numbered"&gt;&lt;code class="language-selinux" data-file="systemd-socket-proxy.te"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;policy_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;proxyd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;proxyd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;labelled&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_port_t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;gen_tunable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_bind_any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;proxyd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;instead&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;labelled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;##&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;gen_tunable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_connect_any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;systemd_domain_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_unit_file_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;systemd_unit_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_unit_file_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;sysnet_dns_name_resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolver&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;unix_dgram_socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;getopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setopt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sendto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_port_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;corenet_port&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_port_t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;tcp_socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;tunable_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_bind_any&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_port_t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;tcp_socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name_bind&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;tunable_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_bind_any&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corenet_tcp_bind_all_ports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;tunable_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_connect_any&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;tcp_socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name_connect&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;tunable_policy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_connect_any&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corenet_tcp_connect_all_ports&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;consumer&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;daemon&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;systemd_socket_proxyd_t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;unix_stream_socket&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;connectto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is still work in progress, but so far it works at least for my projects.
Note that there are two SELinux booleans defined which affect the behaviour of
the policy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;systemd_socket_proxyd_bind_any&lt;/p&gt;
&lt;p&gt;allows to bind proxy to any TCP socket if set to true, otherwise the proxy
would be able to connect to TCP ports labelled with
&lt;code&gt;systemd_socket_proxyd_port_t&lt;/code&gt;;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;systemd_socket_proxyd_connect_any&lt;/p&gt;
&lt;p&gt;allows proxy to connect to any target TCP ports if set to true, otherwise
the target is limited by the ports labelled with
&lt;code&gt;systemd_socket_proxyd_port_t&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, to allow the proper transition into the &lt;code&gt;systemd_socket_proxyd_t&lt;/code&gt; domain we
need to label the &lt;code&gt;systemd-socket-proxyd&lt;/code&gt; binary with the
&lt;code&gt;systemd_socket_proxyd_exec_t&lt;/code&gt; label.&lt;/p&gt;
&lt;p&gt;The content of the systemd-socket-proxy.fc file that implements this behavior
is as follows:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu0fa1047433e9cf6bdeabc4c94fe57b8da97f3ae9" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu0fa1047433e9cf6bdeabc4c94fe57b8da97f3ae9" class="line-numbers icon list-numbered"&gt;&lt;code class="language-selinux" data-file="systemd-socket-proxy.fc"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;/(usr/lib|etc)/systemd/system/systemd-socket-proxyd\.service  gen_context(system_u:object_r:systemd_socket_proxyd_unit_file_t,s0)
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;/usr/lib/systemd/systemd-socket-proxyd -- gen_context(system_u:object_r:systemd_socket_proxyd_exec_t,s0)&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, we have everything we need to compile a policy module, so let&amp;rsquo;s
just do it now:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu6b3a080fc62691c3c861fb8e0931c05e1bae25b8" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu6b3a080fc62691c3c861fb8e0931c05e1bae25b8" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;total 8&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r--. 1 root root  235 Jan  5 05:21 systemd-socket-proxyd.fc&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r--. 1 root root 1467 Jan  6 00:10 systemd-socket-proxyd.te&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/usr/share/selinux/devel/Makefile
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Compiling targeted systemd-socket-proxyd module&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/bin/checkmodule:  loading policy configuration from tmp/systemd-socket-proxyd.tmp&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/bin/checkmodule:  policy configuration loaded&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/bin/checkmodule:  writing binary representation (version 17) to tmp/systemd-socket-proxyd.mod&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Creating targeted systemd-socket-proxyd.pp policy package&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;rm tmp/systemd-socket-proxyd.mod tmp/systemd-socket-proxyd.mod.fc&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;semodule&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;systemd-socket-proxyd.pp
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;restorecon&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/systemd/systemd-socket-proxyd
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;restorecon reset /usr/lib/systemd/systemd-socket-proxyd context system_u:object_r:init_exec_t:s0-&amp;gt;system_u:object_r:systemd_socket_proxyd_exec_t:s0&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;semodule -i systemd-socket-proxyd.pp&lt;/code&gt; command has actually installed the
module into the system, but if you were building the module on a different
instance, then instead of installing the module you just need to grab the
resulting &lt;code&gt;systemd-socket-proxyd.pp&lt;/code&gt; file and transfer it to the target instance
where proxy is going to be running and only apply the last two commands
(&lt;code&gt;semodule -i ...&lt;/code&gt; and &lt;code&gt;restorecon&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We approached the time when we need to perform the pre-flight checks before
launching our new service :). First, we need to check whether the file context
was applied to the &lt;code&gt;systemd-socket-proxyd binary&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menub1bbacbfacf313ca72e5a1c097ed766eec8fecda" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menub1bbacbfacf313ca72e5a1c097ed766eec8fecda" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-ldZ&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/systemd/systemd-socket-proxyd&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rwxr-xr-x. root root system_u:object_r:systemd_socket_proxyd_exec_t:s0 /usr/lib/systemd/systemd-socket-proxyd&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks good! Let&amp;rsquo;s start the socket unit and check the permissions set on the
socket file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu87c6c5d57435f9c9f2372ffb3f57ec33245e7d8c" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu87c6c5d57435f9c9f2372ffb3f57ec33245e7d8c" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;systemd-socket-proxyd@fastcgi.socket
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-ldZ&lt;span class="w"&gt; &lt;/span&gt;/run/systemd-socket-proxyd&lt;span class="o"&gt;{&lt;/span&gt;,/fastcgi.sock&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwx--x--x. root root  system_u:object_r:var_run_t:s0   /run/systemd-socket-proxyd&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;srw-rw----. root nginx system_u:object_r:var_run_t:s0   /run/systemd-socket-proxyd/fastcgi.sock&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This also looks as expected. The next test would be to check that our proxy
service is running with the desired set of permissions and in the correct
SELinux domain:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu07c03a413ad1b729cc8096ea09fbd8bd611bc269" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu07c03a413ad1b729cc8096ea09fbd8bd611bc269" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;socat&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;unix-client:/run/systemd-socket-proxyd/fastcgi.sock&lt;span class="w"&gt; &lt;/span&gt;stdin
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;lt; 2017/01/13 00:59:22.987083  length=1 from=0 to=0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;ps&lt;span class="w"&gt; &lt;/span&gt;uZ&lt;span class="w"&gt; &lt;/span&gt;-C&lt;span class="w"&gt; &lt;/span&gt;systemd-socket-proxyd
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;LABEL                           USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;system_u:system_r:systemd_socket_proxyd_t:s0 nobody 28643 0.0  0.0 86628 756 ? Ssl  00:59   0:00 /usr/lib/systemd/systemd-socket-proxyd 127.0.0.1:9000&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/proc/28643/status&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Name: systemd-socket-&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;State: S (sleeping)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Tgid: 28643&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Ngid: 0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Pid: 28643&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;PPid: 1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;TracerPid: 0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Uid: 99 99 99 99&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Gid: 99 99 99 99&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;FDSize: 64&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Groups: 99 &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[truncated]&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first &lt;code&gt;socat&lt;/code&gt; command was needed to trigger the socket activation that
resulted in the &lt;code&gt;systemd-socket-proxyd.service&lt;/code&gt; being launched.&lt;/p&gt;
&lt;p&gt;The second command confirmed that the service is running under the nobody user
and within the &lt;code&gt;systemd_socket_proxyd_t&lt;/code&gt; domain.&lt;/p&gt;
&lt;p&gt;Finally, the third command confirmed that the privileges were properly dropped
and there is no way the service could regain the escalated privileges back.&lt;/p&gt;
&lt;p&gt;Looks like we are all, so let&amp;rsquo;s make our changes persistent. To achieve this we
just need to enable the &lt;code&gt;systemd-socket-proxyd@fastcgi.socket&lt;/code&gt; unit:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu4bc6e9f8b4fbc7cbba74f2b6e15f15439bc13918" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu4bc6e9f8b4fbc7cbba74f2b6e15f15439bc13918" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;systemd-socket-proxyd@fastcgi.socket
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Created symlink from /etc/systemd/system/sockets.target.wants/systemd-socket-proxyd@fastcgi.socket to /etc/systemd/system/systemd-socket-proxyd@.socket.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;systemd-socket-proxyd@fastcgi.socket&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;Loaded:
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;   Loaded: loaded (/etc/systemd/system/systemd-socket-proxyd@.socket; enabled; vendor preset: disabled)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just to show that the configuration is working I set a simple lab up in a VM
and followed this article with the only exception of the PHP/FPM location which
I am running on the same VM:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu41dd77b091a96cca9e70ff322531ec95f38d687d" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu41dd77b091a96cca9e70ff322531ec95f38d687d" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/usr/share/nginx/html/test.php
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;&amp;lt;?php echo &amp;quot;This is the output from PHP\n&amp;quot; ?&amp;gt;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/etc/nginx/conf.d/php-upstream.conf&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;upstream php {&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;    #&lt;/span&gt;server&lt;span class="w"&gt;        &lt;/span&gt;remote.php.backend.domain.tld.:9000
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;    server         unix:/run/systemd-socket-proxyd/fastcgi.sock;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/etc/nginx/default.d/php.conf
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;location ~ \.php$ {&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;    try_files      $uri = 404;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;    fastcgi_pass   php;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;    fastcgi_index  index.php;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;    include        fastcgi.conf;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/etc/systemd/system/systemd-socket-proxyd@fastcgi.service.d/fastcgi.conf
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[Service]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;# &lt;/span&gt;Reset&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;ExecStart,&lt;span class="w"&gt; &lt;/span&gt;so&lt;span class="w"&gt; &lt;/span&gt;we&lt;span class="w"&gt; &lt;/span&gt;could&lt;span class="w"&gt; &lt;/span&gt;override&lt;span class="w"&gt; &lt;/span&gt;it
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;ExecStart=&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;ExecStart=/usr/lib/systemd/systemd-socket-proxyd 127.0.0.1:9000&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;telnet&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Trying 0.0.0.0...&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Connected to 0.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Escape character is &amp;#39;^]&amp;#39;.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;GET /test.php HTTP/1.0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;HTTP/1.1 200 OK&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Server: nginx/1.10.2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Date: Fri, 13 Jan 2017 03:01:16 GMT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Content-Type: text/html&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Connection: close&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;X-Powered-By: PHP/5.4.16&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;This is the output from PHP&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Connection closed by foreign host.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Works as expected :).&lt;/p&gt;
&lt;p&gt;There is one thing one needs to be aware of: when I started to work on this I
discovered that &lt;code&gt;systemd-socket-proxyd&lt;/code&gt; had a hard-coded limit for the number
of connections set to 256 (I &lt;a href="https://github.com/systemd/systemd/commit/dc3b8afb93f188f212218487ab62fd3f12a2b58f"&gt;introduced the &lt;code&gt;-c&lt;/code&gt; parameter&lt;/a&gt; to
&lt;code&gt;systemd-socket-proxyd&lt;/code&gt;, so one could dynamically set the limit, however it
would take some time until this change is propagated to all major distros).&lt;/p&gt;
&lt;p&gt;Also, it is worth it to mention that the provided configuration is not
efficient if you are using a domain name for the target endpoint for the proxy,
so if this is the case I would advise to run a local DNS caching service (e.g.
&lt;code&gt;dnsmasq&lt;/code&gt;) so you would not spend time on the DNS queries.&lt;/p&gt;
&lt;p&gt;As always, I would appreciate any feedback you may have.&lt;/p&gt;</content><category term="hacking"/><category term="nginx"/><category term="patch"/></entry><entry><title>Dynamic resolution of upstream servers in nginx</title><link href="https://dmitry.khlebnikov.net/2016/09/20/dynamic-resolution-of-upstream-servers-in-nginx/" rel="alternate"/><published>2016-09-20T10:00:00+10:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2016-09-20:/2016/09/20/dynamic-resolution-of-upstream-servers-in-nginx/</id><summary type="html">&lt;p&gt;A &amp;ldquo;howto&amp;rdquo; post which details a custom patch created for the community
version of NGINX to enable the dynamic DNS resolution of upstream servers. This
functionality addresses the challenge of working with cloud load balancers,
whose IP addresses can change automatically, a feature not supported in the
free version. The patch implements a resolve keyword compatible with the
commercial NGINX version, ensuring seamless configuration if a user decides to
upgrade. While used successfully in production, the author notes this approach
is now outdated and has been superseded by a method using
systemd-socket-proxyd.&lt;/p&gt;</summary><content type="html">&lt;p&gt;UPDATE: This approach was superseded by the &lt;a href="https://dmitry.khlebnikov.net/2017/01/13/nginx-a-backend-with-a-dynamic-ip-eg-aws-elb/"&gt;proxying through
systemd-socket-proxyd&lt;/a&gt; approach.&lt;/p&gt;
&lt;p&gt;Many of my clients are running application stacks consisting of nginx plus some
kind of scripting engine behind it (be it PHP, Ruby, or something else).&lt;/p&gt;
&lt;p&gt;The architecture I designed for this kind of workload involves at least two
load balancers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an external, frontend load balancer that serves the web requests from
    visitors; and&lt;/li&gt;
&lt;li&gt;an internal, backend load balancer that distributes load between the
    backends.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything looks great when you implement this using &amp;ldquo;in-house&amp;rdquo; infrastructure
where you control most of the networking aspects.&lt;/p&gt;
&lt;p&gt;However, the tendency is that most enterprises are moving to the cloud
providers and with that we lose some control.&lt;/p&gt;
&lt;p&gt;Specifically, often the cloud providers define their load-balancers as
auto-scaling entities that change their IP addresses depending on the
scale-in/out activity.&lt;/p&gt;
&lt;p&gt;Unfortunately, the community version of nginx does not know how to dynamically
resolve the specified upstream servers (such a functionality is available from
the nginx commercial subscription only), so I spent a couple of evenings to
implement the desired functionality as a &lt;a href="https://github.com/galaxy4public/nginx-upstream-resolve" title="A patch to introduce the dynamic resolution of the upstream servers"&gt;patch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/galaxy4public/nginx-upstream-resolve" title="A patch to introduce the dynamic resolution of the upstream servers"&gt;patch&lt;/a&gt; implements the dynamic DNS resolution of the specified upstream
servers in the upstream compatible way: we are re-using the very same &amp;ldquo;resolve&amp;rdquo;
keyword on the server line as the commercial version of nginx does ensuring
that if you ever decide to switch to the commercial subscription you would not
need to change your configs.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/galaxy4public/nginx-upstream-resolve" title="A patch to introduce the dynamic resolution of the upstream servers"&gt;patch&lt;/a&gt; was originally created for nginx 0.8.6 and was used in
production for the last couple of years. The work on the patch was sponsored by
&lt;a href="https://openwall.com.au"&gt;Openwall (Australia)&lt;/a&gt; and Data Solutions
Group.&lt;/p&gt;
&lt;p&gt;Enjoy! :)&lt;/p&gt;</content><category term="hacking"/><category term="nginx"/><category term="patch"/></entry><entry><title>Transparent SSH host-jumping (Expert)</title><link href="https://dmitry.khlebnikov.net/2016/07/26/transparent-ssh-host-jumping-expert/" rel="alternate"/><published>2016-07-26T10:00:00+10:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2016-07-26:/2016/07/26/transparent-ssh-host-jumping-expert/</id><summary type="html">&lt;p&gt;This post presents an expert-level solution for transparent SSH
host-jumping that enhances a previous method by allowing different usernames
and ports for each host in the connection chain. The author created a custom
Bash script to be used with the ProxyCommand directive in the SSH config, which
parses a new connection syntax like userA^hostA/userB^hostB:port/hostC. This
script dynamically handles the unique credentials for each intermediate host
while still leveraging SSH&amp;rsquo;s connection multiplexing features like
ControlMaster and ControlPersist to reuse connections and improve performance.
The solution addresses the limitations of the prior &amp;ldquo;Advanced&amp;rdquo; method, offering
greater flexibility for navigating complex network environments with varied
host configurations.&lt;/p&gt;</summary><content type="html">&lt;p&gt;A while ago in the &lt;a href="https://dmitry.khlebnikov.net/2015/08/06/transparent-ssh-host-jumping-advanced/"&gt;Transparent SSH host-jumping (Advanced)&lt;/a&gt; post I
described a technique on how one could jump quite effortlessly through a chain
of intermediate hosts. However, there was a catch: the user names and ports
across the whole chain should be the same and there was no easy way to change
that.&lt;/p&gt;
&lt;p&gt;Given that I &lt;a href="https://dmitry.khlebnikov.net/2016/07/25/ssh-interactive-proxycommand/"&gt;recently&lt;/a&gt; paid quite a lot of attention to the ProxyCommand
directive I decided to look into the implementation of the helper script that
will allow one to tweak parameters for the hosts in the chain.&lt;/p&gt;
&lt;p&gt;You can read the &lt;a href="https://dmitry.khlebnikov.net/2015/08/06/transparent-ssh-host-jumping-advanced/"&gt;original&lt;/a&gt; post for the details of how this host-jumping
technique works, here I am only going to provide the proxy script and the
corresponding ssh config parameter block to use the script.&lt;/p&gt;
&lt;p&gt;The goal was to support the following syntax:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu9a5c562a971348a0aaeed0ea6dd72f61a3000602" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu9a5c562a971348a0aaeed0ea6dd72f61a3000602" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;default_user@userA^hostA/userB^hostB:port/hostC&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It was a little challenge to come up with the character for identifying the
user part for intermediate hosts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@&lt;/code&gt; - cannot be used since SSH parses the command line before providing the
    string to the ProxyCommand script&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%&lt;/code&gt; - cannot be used since SSH was thinking that I&amp;rsquo;m trying to do an
    expansion of the internal SSH variable&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt;, &lt;code&gt;(&lt;/code&gt;, &lt;code&gt;)&lt;/code&gt;, and &lt;code&gt;&amp;amp;&lt;/code&gt; - are all shell unfriendly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I decided on the &lt;code&gt;^&lt;/code&gt; character as a delimiter.&lt;/p&gt;
&lt;p&gt;In the proposed command above the &amp;ldquo;default_user&amp;rdquo; is the user name we ultimately
want to use for logging into the last host in the chain (it happens that this
user name will be used for any host in the chain where no alternative name is
provided).&lt;/p&gt;
&lt;p&gt;Each host in the chain could also be provided with the relevant port or, if the
port is omitted, it will use the global port configuration (usually 22/tcp but
can be changed with the &lt;code&gt;-p&lt;/code&gt; argument to ssh).  The script is a bit not optimised
(bash is really slow on string processing, but I decided to stick with pure
bash where it was possible):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu54647306fde3214c0b5d0a8a049578410d87023f" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu54647306fde3214c0b5d0a8a049578410d87023f" class="line-numbers icon list-numbered"&gt;&lt;code class="language-bash" data-file="~/bin/ssh-helper.sh"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-eu&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;pipefail
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&amp;lt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&amp;lt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;DEFAULT_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;DEFAULT_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOST_NEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="p"&gt;%%/*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOST_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_NEXT&lt;/span&gt;&lt;span class="p"&gt;%%^*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_USER&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_NEXT&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HOST_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$DEFAULT_USER&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;:
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOST_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_NEXT&lt;/span&gt;&lt;span class="p"&gt;##*:&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_PORT&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_NEXT&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HOST_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$DEFAULT_PORT&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;:
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;TARGET_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="p"&gt;##*/&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;TARGET_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET_HOST&lt;/span&gt;&lt;span class="p"&gt;##*:&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;TARGET_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET_HOST&lt;/span&gt;&lt;span class="p"&gt;%:*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_PORT&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_HOST&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;TARGET_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$DEFAULT_PORT&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;:
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;TARGET_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TARGET_HOST&lt;/span&gt;&lt;span class="p"&gt;#*^&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOST_NEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_NEXT&lt;/span&gt;&lt;span class="p"&gt;#*^&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOST_NEXT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_NEXT&lt;/span&gt;&lt;span class="p"&gt;%:*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="p"&gt;%/*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_CHAIN&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="p"&gt;#*/&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="p"&gt;#*/&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_NEXT&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOST_CHAIN&lt;/span&gt;&lt;span class="p"&gt;:+/&lt;/span&gt;&lt;span class="nv"&gt;$HOST_CHAIN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.ssh/.sessions&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Creating the sessions directory&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-m700&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.ssh/.sessions&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;CONTROL_SOCKET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_USER&lt;/span&gt;&lt;span class="s2"&gt;@HOST_CHAIN:&lt;/span&gt;&lt;span class="nv"&gt;$HOST_PORT&lt;/span&gt;&lt;span class="s2"&gt;\n&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;shasum&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cut&lt;span class="w"&gt; &lt;/span&gt;-f1&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&amp;lt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ControlMaster auto&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ControlPath ~/.ssh/.sessions/&lt;/span&gt;&lt;span class="nv"&gt;$CONTROL_SOCKET&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ControlPersist 120s&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_USER&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_PORT&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$HOST_CHAIN&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;-W&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_HOST&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$TARGET_PORT&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above is a proof of the concept and it &amp;ldquo;works for me&amp;rdquo; :). I am using it
every day when I need to access boxes behind a bastion host. Your mileage may
vary and you are free to create your own version of the script that would
perform better (I would be really glad if a version of such a script could be
shared with me).&lt;/p&gt;
&lt;p&gt;The corresponding configuration block in the ssh config file looks as follows:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menue6ec32bacd6346926f265fe9da3c9babbed570db" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menue6ec32bacd6346926f265fe9da3c9babbed570db" class="line-numbers icon list-numbered"&gt;&lt;code class="language-ssh_config" data-file="~/.ssh/config"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*/*&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uncomment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ControlPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;also&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uncomment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ControlMaster&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;ControlMaster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;auto&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;OpenSSH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uncomment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;following&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;long&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;chains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;ControlPath&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sessions&lt;/span&gt;&lt;span class="o"&gt;/%&lt;/span&gt;&lt;span class="nt"&gt;r&lt;/span&gt;&lt;span class="o"&gt;@%&lt;/span&gt;&lt;span class="nt"&gt;h&lt;/span&gt;&lt;span class="o"&gt;:%&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;OpenSSH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uncomment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;following&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;ControlPath&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sessions&lt;/span&gt;&lt;span class="o"&gt;/%&lt;/span&gt;&lt;span class="nt"&gt;C&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ProxyCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="nt"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;ssh-helper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, this is a bit messy since OpenSSH introduced the &lt;code&gt;%C&lt;/code&gt; macro in version 6.7
and without &lt;code&gt;%C&lt;/code&gt; the &lt;code&gt;ControlPath&lt;/code&gt; string gets too long for OpenSSH to create a
socket on the filesystem for long chains of hosts.&lt;/p&gt;</content><category term="ssh"/><category term="console"/><category term="ssh"/><category term="howto"/></entry><entry><title>SSH: Interactive ProxyCommand</title><link href="https://dmitry.khlebnikov.net/2016/07/25/ssh-interactive-proxycommand/" rel="alternate"/><published>2016-07-25T10:00:00+10:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2016-07-25:/2016/07/25/ssh-interactive-proxycommand/</id><summary type="html">&lt;p&gt;This post demonstrates a solution to a common misconception that SSH&amp;rsquo;s
ProxyCommand does not support interactive scripts, a problem the author
encountered while working on the sshephalopod authentication project. The
author proves interactivity is possible by creating a wrapper script that
cleverly manipulates file descriptors. The script first saves the original
standard input (descriptor 0) and standard output (descriptor 1), which are
used for the SSH protocol, to new descriptors (10 and 11). It then redirects
standard input and output to the user&amp;rsquo;s terminal (via standard error,
descriptor 2), allowing for interactive prompts. After the interaction is
complete, the script restores the original file descriptors before executing
the final command (like nc) to establish the proxied SSH connection, thus
enabling interactive authentication or other user inputs within the
ProxyCommand workflow.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was involved in the creation of the &lt;a href="https://github.com/realestate-com-au/sshephalopod"&gt;sshephalopod&lt;/a&gt; project, which was an
attempt to build an enterprise level authentication framework for SSH
authentication using the SSH &lt;abbr title="Certificate Authority"&gt;CA&lt;/abbr&gt; feature.&lt;/p&gt;
&lt;p&gt;The project is based on a wrapper script that signs a user via a &lt;abbr title="Security Assertion Markup Language"&gt;SAML&lt;/abbr&gt; identity
provider and gets user&amp;rsquo;s public key signed for the further usage.&lt;/p&gt;
&lt;p&gt;In one of the discussions I pointed out that such a wrapper script is not good
for the end user experience and I proposed to provide the users with an excerpt
for their ssh config file, so the functionality of sshephalopod would be
transparent to the general usage scenario of the ssh tool.&lt;/p&gt;
&lt;p&gt;The response was that ProxyCommand do not support interactivity. Well, as they
say: The challenge is accepted :)&lt;/p&gt;
&lt;p&gt;The following is my train of thoughts before I came up with a general solution
on how to allow an interactive command to be used as the ProxyCommand in the
ssh config file.&lt;/p&gt;
&lt;p&gt;Before we start solving the problem at hand we need to create a test
environment, so we would be able to confirm when we reached success. The task
itself was very simple: we needed a host we could ssh into (an sshd daemon
running on the local host would be sufficient), then we needed an interactive
script, and a configuration block for the connection.&lt;/p&gt;
&lt;p&gt;The configuration block is pretty simple (%h expands to localhost and %p
expands to the port specified on the command line or to &amp;ldquo;22&amp;rdquo; otherwise):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu8cc48c5311c9933fb2d44c79dbfc6754794aa621" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu8cc48c5311c9933fb2d44c79dbfc6754794aa621" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;-A1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Host localhost&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/.ssh/config
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Host localhost&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; ProxyCommand ~/bin/interactive.script.sh %h %p&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since most of our research is going to be inside the interactive script you
will see several incarnations of script&amp;rsquo;s body. The very first one was the
following:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuaf0a0f8b097c88ad2d8cf3bf7c4678aedc7fef1c" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuaf0a0f8b097c88ad2d8cf3bf7c4678aedc7fef1c" class="line-numbers icon list-numbered"&gt;&lt;code class="language-bash" data-file="~/bin/interactive.script.sh"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point we just need to confirm that our test environment works as
expected &amp;ndash; the ssh session should be proxied through the nc command and we
should be able to login under our own account via ssh to the localhost (my
private key was added to the key manager with ssh-add, hence no password prompt
was displayed):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu8256b384d892095c2be72f12aa50e341fba35d2e" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu8256b384d892095c2be72f12aa50e341fba35d2e" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;galaxy@localhost
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Last login: Thu Jul 21 01:30:21 2016&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK, we confirmed that we can establish a proxied connection and tunnel our ssh
session through it.&lt;/p&gt;
&lt;p&gt;Each interactive script or program relies on the communication channel with the
user otherwise it could not be interactive. This channel comprises at least of
two file descriptors: one for standard input and the other for standard output,
so let&amp;rsquo;s check what descriptors are available for our script:&lt;/p&gt;
&lt;pre class="highlight" data="file="~/bin/interactive.script.sh""&gt;&lt;input type="radio" name="code_menu716cdc83a627f660bf2a36793fecfd49c9a49002" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu716cdc83a627f660bf2a36793fecfd49c9a49002" class="line-numbers icon list-numbered"&gt;&lt;code class="language-bash"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# on Linux the following line would be much simpler: ls -l /proc/$$/fd/, but&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# on OS X they do not expose the open file descriptors through /proc, so I&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# used &amp;quot;lsof&amp;quot; instead.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;lsof&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we try to connect now we should see something like the following (I am
writing this article on an OS X machine so I provide the output from OS X,
however this also works for Linux):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu0428b03b95771df5b5bf252202c8f28e18b0839d" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu0428b03b95771df5b5bf252202c8f28e18b0839d" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;galaxy@localhost
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;COMMAND   PID   USER   FD   TYPE             DEVICE  SIZE/OFF     NODE NAME&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45225   user  cwd    DIR                1,2       612   893854 /Users/user/.ssh&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45225   user  txt    REG                1,2    628640  2329236 /bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45225   user  txt    REG                1,2    625712 13892061 /usr/lib/dyld&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45225   user  txt    REG                1,2 385393734 13894121 /private/var/run/dyld_shared_cache_x86_64&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45225   user    0   PIPE 0x44d71099589485df     16384          -&amp;gt;0x44d7109951223d0f&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45225   user    1   PIPE 0x44d710995122454f     16384          -&amp;gt;0x44d71099512246af&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45225   user    2u   CHR               16,1  0t631830     9705 /dev/ttys001&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45225   user  255r   REG                1,2       219 15455259 /Users/user/bin/interactive.script.sh&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Last login: Mon Jul 25 17:52:40 2016 from localhost&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are interested in file descriptors 0 (standard input), 1 (standard output),
and 2 (standard error). As you can see the standard input and output are part
of the pipes (presumably linking them to the parent ssh process) and standard
error is pointing to our terminal session.&lt;/p&gt;
&lt;p&gt;I could have occupied a bit more of the page space showcasing that if you try
to communicate on standard input and/or output the ssh client will terminate
since you will be messing with the SSH protocol flow, but I believe you will
trust me on this :).&lt;/p&gt;
&lt;p&gt;What can we do to interact with the user, yet to preserve the channel with the
parent ssh process? Well, the answer is quite obvious:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we have a pointer to the terminal session (file descriptor 2, the standard
    error, points to the terminal),&lt;/li&gt;
&lt;li&gt;so we just need to save pointers to the pipes&amp;rsquo; ends,&lt;/li&gt;
&lt;li&gt;re-open standard input and output with the terminal before we interact with
    the user,&lt;/li&gt;
&lt;li&gt;and restore these file descriptors back once we are ready to hand over the
    ssh session.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following version of the script demonstrates the implementation of the
above logic:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu03051b3fe53092d6099050f5d029ca890e76e839" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu03051b3fe53092d6099050f5d029ca890e76e839" class="line-numbers icon list-numbered"&gt;&lt;code class="language-bash" data-file="~/bin/interactive.script.sh"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&amp;lt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&amp;lt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# start of the interactive behaviour&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;lsof&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Type something: &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;I
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;You typed: &lt;/span&gt;&lt;span class="nv"&gt;$I&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# finish of the interactive behaviour&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&amp;lt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nc&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think it is time to test it :) :&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu5f0e59480f98ee3b556f16d6f341317f85ac7260" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu5f0e59480f98ee3b556f16d6f341317f85ac7260" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;galaxy@localhost
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;COMMAND   PID   USER   FD   TYPE             DEVICE  SIZE/OFF     NODE NAME&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user  cwd    DIR                1,2       612   893854 /Users/user/.ssh&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user  txt    REG                1,2    628640  2329236 /bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user  txt    REG                1,2    625712 13892061 /usr/lib/dyld&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user  txt    REG                1,2 385393734 13894121 /private/var/run/dyld_shared_cache_x86_64&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user    0u   CHR               16,1  0t640407     9705 /dev/ttys001&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user    1u   CHR               16,1  0t640407     9705 /dev/ttys001&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user    2u   CHR               16,1  0t640407     9705 /dev/ttys001&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user   10   PIPE 0x44d710995122378f     16384          -&amp;gt;0x44d71099589494ff&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user   11   PIPE 0x44d7109951223d0f     16384          -&amp;gt;0x44d710995122454f&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bash    45238   user  255r   REG                1,2       135 15455285 /Users/user/bin/interactive.script.sh&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Type something: This is a test&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;You typed: This is a test&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Last login: Mon Jul 25 17:57:05 2016 from localhost&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;logout&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Connection to localhost closed.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mission accomplished! :)&lt;/p&gt;
&lt;p&gt;I hope this small article would help somebody to design better wrappers around
SSH. Keep in mind that you could always optimise it further. For example, recent
versions of OpenSSH support passing of a file descriptor from the ProxyCommand
script, so if you have a decent netcat tool that supports the &amp;ldquo;-F&amp;rdquo; option
(fdpass) you could get native performance for the ssh communication link with
no proxy process hanging around.&lt;/p&gt;
&lt;p&gt;P.S. if you have any questions do not hesitate to comment.&lt;/p&gt;</content><category term="ssh"/><category term="ssh"/><category term="console"/></entry><entry><title>Raspberry Pi 3 toolchain on CentOS 7</title><link href="https://dmitry.khlebnikov.net/2016/05/22/raspberry-pi-3-toolchain-on-centos-7/" rel="alternate"/><published>2016-05-22T12:00:00+10:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2016-05-22:/2016/05/22/raspberry-pi-3-toolchain-on-centos-7/</id><summary type="html">&lt;p&gt;This post provides a detailed walkthrough of building a
cross-compilation toolchain for a Raspberry Pi 3 on a CentOS 7 server to
accelerate software development. Motivated by a project to modify his car&amp;rsquo;s CAN
bus system using a Raspberry Pi, the author found on-device compilation to be
too slow and opted to create a toolchain on a more powerful AWS EC2 instance.
The process involves using the crosstool-ng project to configure and build the
toolchain, with specific configuration adjustments detailed for targeting the
ARMv8-A architecture and Cortex-A53 CPU of the Raspberry Pi 3. After the
hour-long build process, the author demonstrates the toolchain&amp;rsquo;s success by
compiling a simple &amp;ldquo;Hello&amp;rdquo; world program and verifying that the resulting
binary is a 64-bit ARM executable, ready for use on the target device.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I heard a lot about Raspberry Pi boards but until now I had no need nor time to
work with one.&lt;/p&gt;
&lt;p&gt;However, recently I purchased a Dodge Journey R/T and found that although I
love the car I am so disappointed with its software and hard-wired logic that I
decided to experiment a bit and fix the most annoying things.&lt;/p&gt;
&lt;p&gt;Since almost everything inside the car is talking over the CAN bus I needed
some kind of a enclave inside the car where I could run my code and
inject/intercept CAN messages.&lt;/p&gt;
&lt;p&gt;I looked around and found that I can build the desired appliance using
Raspberry Pi 3 (Model B) + PiCAN 2 HAT board.&lt;/p&gt;
&lt;p&gt;Once the hardware was delivered to my home the time came to start building the
software side of things. My distribution of choice for this project became
CentOS 7 (userland), however, building stuff on the Raspberry Pi itself was a
painful and long process, so I needed a proper toolchain to be able to utilise
much more powerful hardware and do builds quicker.&lt;/p&gt;
&lt;p&gt;The following is a session dump (with some notes) on how I built my toolchain
on an AWS EC2 instance which was running a minimal CentOS 7 as its OS.&lt;/p&gt;
&lt;p&gt;I spawned an EC2 instance (you need at least 8GB of free space there to be able
to build the toolchain), logged in, and ensured that my system is up to date
(and update it if it was not):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuc93f6e2dbc100543f60023f66ff1161af7c43f2c" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuc93f6e2dbc100543f60023f66ff1161af7c43f2c" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;update
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Loaded plugins: at-exit, fastestmirror, post-transaction-actions&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Loading mirror speeds from cached hostfile&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; * base: mirror.domain.tld&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; * extras: mirror.domain.tld&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; * updates: mirror.domain.tld&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;No packages marked for update&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The easiest way to build a toolchain is to use the crosstool-ng project, so I
went ahead and downloaded the latest version with the corresponding GPG
signature, verified the signature (although the verification was very
superficial since Bryan Hundven is not in my Web of Trust and the crosstool-ng
website was not providing an HTTPS page to confirm the fingerprint of the key),
and unpacked the sources:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu19c6c0f19f8844fd6be4ab3cb10034ad5a0ca697" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu19c6c0f19f8844fd6be4ab3cb10034ad5a0ca697" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;yum&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;bison&lt;span class="w"&gt; &lt;/span&gt;bzip2&lt;span class="w"&gt; &lt;/span&gt;flex&lt;span class="w"&gt; &lt;/span&gt;gcc&lt;span class="w"&gt; &lt;/span&gt;gcc-c++&lt;span class="w"&gt; &lt;/span&gt;glibc-static&lt;span class="w"&gt; &lt;/span&gt;gperf&lt;span class="w"&gt; &lt;/span&gt;help2man&lt;span class="w"&gt; &lt;/span&gt;libstdc++-static&lt;span class="w"&gt; &lt;/span&gt;libtool&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;ncurses-devel&lt;span class="w"&gt; &lt;/span&gt;texinfo&lt;span class="w"&gt; &lt;/span&gt;wget
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;useradd&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;su&lt;span class="w"&gt; &lt;/span&gt;-&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.22.0.tar.xz&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;-q&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.22.0.tar.xz.sig&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;gpg&lt;span class="w"&gt; &lt;/span&gt;--recv-keys&lt;span class="w"&gt; &lt;/span&gt;35B871D1&lt;span class="w"&gt; &lt;/span&gt;--keyserver&lt;span class="w"&gt; &lt;/span&gt;pgp.surfnet.nl
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: &amp;quot;--keyserver&amp;quot; not a key ID: skipping&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: &amp;quot;pgp.surfnet.nl&amp;quot; not a key ID: skipping&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: requesting key 35B871D1 from hkp server keys.gnupg.net&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: /home/build/.gnupg/trustdb.gpg: trustdb created&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: key 35B871D1: public key &amp;quot;Bryan Hundven &amp;quot; imported&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: Total number processed: 1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg:               imported: 1  (RSA: 1)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;gpg&lt;span class="w"&gt; &lt;/span&gt;--verify&lt;span class="w"&gt; &lt;/span&gt;crosstool-ng-1.22.0.tar.xz.sig
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: Signature made Fri Nov 20 13:09:26 2015 UTC using RSA key ID 35B871D1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: Good signature from &amp;quot;Bryan Hundven &amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg:                 aka &amp;quot;Bryan Hundven &amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg:                 aka &amp;quot;Bryan Hundven &amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg:                 aka &amp;quot;Bryan Hundven &amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg:                 aka &amp;quot;[jpeg image of size 3080]&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg:                 aka &amp;quot;[jpeg image of size 16246]&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg: WARNING: This key is not certified with a trusted signature!&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;gpg:          There is no indication that the signature belongs to the owner.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Primary key fingerprint: 561E D9B6 2095 88ED 23C6  8329 CAD7 C8FC 35B8 71D1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;xJSf&lt;span class="w"&gt; &lt;/span&gt;crosstool-ng-1.22.0.tar.xz
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;crosstool-ng-1.22.0.tar.xz*&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next step was to prepare the environment for the crosstool-ng build process:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuff3fa604b25a6641bfc22d2486f1821222b11e99" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuff3fa604b25a6641bfc22d2486f1821222b11e99" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;crosstool-ng
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;/configure&lt;span class="w"&gt; &lt;/span&gt;--prefix&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/ct-ng
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;checking build system type... x86_64-pc-linux-gnu&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[skipped]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;configure: creating ./config.status&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;config.status: creating Makefile&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;make
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  SED    &amp;#39;ct-ng&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[skipped]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  SED    &amp;#39;docs/ct-ng.1&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  GZIP   &amp;#39;docs/ct-ng.1.gz&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;install
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;  GEN    &amp;#39;config/configure.in&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[skipped]&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For auto-completion, do not forget to install &lt;code&gt;ct-ng.comp&lt;/code&gt; into your &lt;code&gt;bash&lt;/code&gt;
completion directory (usually &lt;code&gt;/etc/bash_completion.d/&lt;/code&gt;)&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menucbcff75052f939b1b7ac98e44d83026b0c57e899" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menucbcff75052f939b1b7ac98e44d83026b0c57e899" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/ct-ng/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the above was done we are ready to actually configure and build our toolchain:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu1eef3216b0f049c03b4e18e9e96fe071fd92de56" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu1eef3216b0f049c03b4e18e9e96fe071fd92de56" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;~/builddir
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/builddir
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;ct-ng&lt;span class="w"&gt; &lt;/span&gt;menuconfig&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was quite happy with the defaults and the only changes I made were the
following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Paths and misc options -&amp;gt; Try features marked as EXPERIMENTAL -&amp;gt; check&lt;/p&gt;
&lt;p&gt;You may skip this but I prefer to see all available options when I go
through the list of configurable parameters, hence I enabled this;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Target options -&amp;gt; Target Architecture -&amp;gt; arm&lt;/p&gt;
&lt;p&gt;My board is Raspberry Pi 3 (Model B) which runs on ARMv8 Cortex-A53, yours
may be different but most likely it is also ARM based :)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Target options -&amp;gt; Bitness -&amp;gt; 64-bit&lt;/p&gt;
&lt;p&gt;There is actually almost no point in building a 64-bit version when you are
running on such a limited device as Raspberry, but at the time I was
writing this blog post I was building the AArch64 version of the toolchain.
I would recommend to stick with the 32-bit version of the toolchain if you
do not have any specific requirements for the 64-bit one;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Target options -&amp;gt; Architecture level -&amp;gt; armv8-a&lt;/p&gt;
&lt;p&gt;Since my only target was Raspberry Pi 3 (Model B) and the toolchain was
created for the personal use I did additional optimisation for my board.&lt;/p&gt;
&lt;p&gt;If you are not sure or if you want a toolchain that would produce more
compatible code for a range of ARM devices, then leave this setting at its
default, i.e. blank&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Target options -&amp;gt; Emit assembly for CPU -&amp;gt; cortex-a53&lt;/p&gt;
&lt;p&gt;Actually, this supersedes the previous option since it is more explicit. If
you are not optimising for a specific hardware, then leave it at the default
setting (which is blank).&lt;/p&gt;
&lt;p&gt;Also, there are more optimisations, e.g. you can specify that FPU for this
board is neon-fp-armv8, etc. but to keep it simple I decided not to
showcase that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Toolchain options -&amp;gt; Tuple&amp;rsquo;s vendor string -&amp;gt; rpi3&lt;/p&gt;
&lt;p&gt;This is optional but I like my toolchain to be as descriptive as possible,
e.g. with this setting binaries will be prefixed with
&lt;code&gt;aarch64-rpi3-linux-gnueabi-&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Operating System -&amp;gt; Target OS -&amp;gt; linux&lt;/p&gt;
&lt;p&gt;It is essential to set this to &amp;ldquo;linux&amp;rdquo; otherwise &lt;code&gt;crosstool-ng&lt;/code&gt; will assume
that you are building a toolchain for a bare-bone hardware and the produced
binaries would not be properly linked to work with the OS on the board&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once we are comfortable with the configuration of the future toolchain we exit
the configuration menu (saving the changes) and launch the build process (it
would take approximately an hour on AWS t2.small instance):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menudba6bf8d5ce39bd085750bdc4c3125f36b88e6e0" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menudba6bf8d5ce39bd085750bdc4c3125f36b88e6e0" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;configuration written to .config&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;*** End of the configuration.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;*** Execute &amp;#39;ct-ng build&amp;#39; to start the build or try &amp;#39;ct-ng help&amp;#39;.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;ct-ng&lt;span class="w"&gt; &lt;/span&gt;build
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[INFO ]  Performing some trivial sanity checks&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[INFO ]  Build started 20160522.070915&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[skipped]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[INFO ]  Build completed at 20160522.080629&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[INFO ]  (elapsed: 57:14.18)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[INFO ]  Finishing installation (may take a few seconds)...&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[57:15] /&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Seems that our Raspberry Pi 3 toolchain is ready to be used. Let&amp;rsquo;s compile
something and see how it goes:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu8a0e371575d812d74f66a9d57b40ed84983420a5" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu8a0e371575d812d74f66a9d57b40ed84983420a5" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/x-tools/aarch64-rpi3-linux-gnueabi/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;aarch64-rpi3-linux-gnueabi-gcc&lt;span class="w"&gt; &lt;/span&gt;--version
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;aarch64-rpi3-linux-gnueabi-gcc (crosstool-NG crosstool-ng-1.22.0) 5.2.0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Copyright (C) 2015 Free Software Foundation, Inc.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;This is free software; see the source for copying conditions.  There is NO&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#include \nint main(){printf(&amp;quot;Hello\\n&amp;quot;); return 0;}\n&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;sample.c
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;aarch64-rpi3-linux-gnueabi-gcc&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;sample&lt;span class="w"&gt; &lt;/span&gt;sample.c
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;sample
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rwxr-xr-x. 1 build build 7672 May 22 08:19 sample&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[build@localhost ~]$ &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;sample
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;sample: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 4.3.0, not stripped&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Well, it is time to build some awesome stuff for Raspberry Pi 3, I guess, but
this will be in another post. :)&lt;/p&gt;</content><category term="linux"/><category term="console"/><category term="howto"/><category term="linux"/><category term="raspberry pi"/></entry><entry><title>Building a firewall? Simple and easy!</title><link href="https://dmitry.khlebnikov.net/2016/05/22/building-a-firewall-simple-and-easy/" rel="alternate"/><published>2016-05-22T10:00:00+10:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2016-05-22:/2016/05/22/building-a-firewall-simple-and-easy/</id><summary type="html">&lt;p&gt;This post advocates for building simple, elegant, and secure firewall
rulesets, arguing that overly complex configurations are difficult to manage
and audit. The author suggests that most firewall needs can be met with fewer
than 20 well-defined rules by focusing on four key prerequisites: understanding
networking fundamentals, mapping traffic flows with diagrams, clearly defining
firewall goals (e.g., allowing inbound access and restricting outbound), and
knowing the specific firewall toolset, like iptables. A real-world example is
provided with a 13-rule iptables configuration for a NAT gateway, which
establishes a default-deny policy for incoming traffic while allowing
established connections, essential ICMP traffic, and specific services like
SSH, demonstrating that a secure and effective firewall doesn&amp;rsquo;t need to be
convoluted.&lt;/p&gt;</summary><content type="html">&lt;p&gt;I strive for simplicity since I am a strong believer that achieving a goal with
the most simplest solution looks elegant, proves that you have deep knowledge
on the subject, and overall is beautiful by itself.  Additionally to this, a
simple solution is easier to comprehend and to audit, hence it is much easier
to ensure the security of such a solution.&lt;/p&gt;
&lt;p&gt;Over the last decade, I stumbled upon numerous complicated firewalls erected on
the NAT boxes with tens (sometimes, hundreds!) of rules describing the traffic
flows and punched holes for some edge cases.  Every time I wondered: what kind
of a bug has bitten the person who composed such a convoluted ruleset that is a
nightmare to manage?&lt;/p&gt;
&lt;p&gt;In 99% of the cases, I was able to come up with a ruleset of usually less than
20 rules for the whole firewall to achieve exactly the same result.  So, in
this article I will explain my approach on building firewalls that are easy to
support and to understand.&lt;/p&gt;
&lt;p&gt;Before you dive into optimising (or creating) your firewall, there are some
things you need to have a clear understanding of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Networking&lt;/p&gt;
&lt;p&gt;You do not need to be an expert or a guru, but you should know fundamentals
about the protocols, standards, and internals of the networking related to
the area you are going to secure with the firewall.&lt;/p&gt;
&lt;p&gt;When you are in doubt, do not rely on an assumption: use search engines and
locate the corresponding RFC, standard, and/or any other authoritative
source of information documenting the protocol you are working with.&lt;/p&gt;
&lt;p&gt;It is impossible to know everything, but the common fallacy is that people
often create things based on wrong assumptions when it was a couple of
clicks away to research and understand the subject.  I have seen so many
times when people are &amp;ldquo;designing&amp;rdquo; their firewalls without a slightest clue
in regard to the standards covering interoperability of the hosts in the IP
network.&lt;/p&gt;
&lt;p&gt;For example, you can easily spot these &amp;ldquo;creations&amp;rdquo; if you look at the
ruleset and see that there is a rule that drops ICMP packets
unconditionally (or do not define any rules dealing with ICMP when the
default policy is &amp;ldquo;deny&amp;rdquo;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Traffic flows&lt;/p&gt;
&lt;p&gt;Think of a firewall as a filter that manages traffic flows (Figure 1 shows
a good visual representation of how traffic passes through the Linux
netfilter subsystem).&lt;/p&gt;
&lt;p&gt;Before you can make a decision in regard to whether to allow or deny
something you must know what your are dealing with and what outcome you
want to achieve.&lt;/p&gt;
&lt;p&gt;It would tremendously help you later if you could simply draw a diagram of
your network and overlay it with a layer documenting the inbound and
outbound flows of each node on the diagram.  Such a &amp;ldquo;data flow&amp;rdquo; diagram is
supposed to be a part of any solution documentation involving network
infrastructure, but in reality most of the enterprises (at least the ones I
worked with) simply forget to generate one.&lt;/p&gt;
&lt;p&gt;On such a diagram you would be able to see straight away what kind of
legitimate traffic is going to cross your firewall and what traffic is not
supposed to reach your firewall at all.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Goals&lt;/p&gt;
&lt;p&gt;This may sound obvious, but one of the most important things is to know
exactly what you are trying to achieve.  You are not building a firewall for
the sake of building a firewall, are you?&lt;/p&gt;
&lt;p&gt;Frankly speaking, there are just a handful number of scenarios for creating
a firewall (I fail to come up with more than five at this point) and all of
them are very simple.&lt;/p&gt;
&lt;p&gt;If your network provides any services to the external network (e.g.
Internet) there are just two options I can think of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Allow external entities to access services on defined public endpoints
   (an inbound flow) and allow unrestricted outbound traffic from your
   network to the external one (an outbound flow);&lt;/li&gt;
&lt;li&gt;Allow external entities to access services on defined public endpoints
   and restrict outbound traffic from your network to a defined set of
   external endpoints.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If your network does not provide any services to the external network and
only consumes resources from it, I see only two options:&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Disallow any access from the external entities to your resources, but
   allow your network to access the external endpoints and receive
   responses from there;&lt;/li&gt;
&lt;li&gt;The same as above, but restrict the outbound traffic to a selected set
   of the external endpoints.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The fifth scenario is the one I personally do not use and is the blacklist
approach:&lt;/p&gt;
&lt;ol start="5"&gt;
&lt;li&gt;allow everything in each direction and block communication for the
   specific endpoints only (be they external and/or internal).&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tools&lt;/p&gt;
&lt;p&gt;Last, but not least, is the requirement to know the corresponding toolset
you are going to utilise to achieve your goals.&lt;/p&gt;
&lt;p&gt;There are numerous high-level frameworks which are supposed to make systems
administrator&amp;rsquo;s life easier (e.g.  RHEL/CentOS/Fedora are using firewalld
on top of iptables), however, personally I prefer to work with iptables
directly (thus, my systems do not have firewalld installed).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before you start implementing the rules it often helps to describe your
firewall ruleset in simple sentences, e.g.:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;deny any incoming traffic to our network through the firewall unless it is
     explicitly allowed by the rules;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;allow any outbound traffic from our network through the firewall unless it
     is explicitly denied by the rules;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;deny any forwarding of traffic through the firewall unless it is
     explicitly allowed by the rules;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;allow anyone on the Internet to connect to the webserver on ports 80/tcp
     and 443/tcp;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;allow our office to connect to the firewall via SSH on port 22/tcp;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;allow traffic from our network to pass the firewall toward the Internet;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;allow the back channel traffic from the resources on the Internet to our
     network;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And then research how each of these sentences can be implemented using the
configuration language of the tool.  The point here is that you need to take
one step at a time and translate the logic exactly so you do not deviate from
your goals &amp;ndash; you will be able to optimise the result at a later stage.&lt;/p&gt;
&lt;p&gt;Well, it is time for a real world example, I guess.  The following snippet is
used as the skeleton for the iptables ruleset on systems I manage (this snippet
assumes that 192.168.0.0/16 is the internal network and that the ruleset is
installed on the NAT instance which is a gateway to the Internet):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu4a0911465a0d4c64a731c582de972a92a06c2c90" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu4a0911465a0d4c64a731c582de972a92a06c2c90" class="line-numbers icon list-numbered"&gt;&lt;code class="language-iptables" data-file="/etc/sysconfig/iptables"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;INPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;FORWARD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;OUTPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;INPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;INVALID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DROP&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;already&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;established&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;through&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;INPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RELATED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;ESTABLISHED&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;traffic&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;INPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Let&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MTU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;discovery&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;management&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;work&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;properly&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;INPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;icmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;icmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;icmp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;remote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SSH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;specified&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;would&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sshd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;standard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;INPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tcp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NEW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tcp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;dport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DHCP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;its&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;information&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;INPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eth0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;udp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NEW&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;udp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;sport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;dport&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Reject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;anything&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;did&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;specifically&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;above&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;INPUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;REJECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;icmp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;prohibited&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NAT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;so&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;accept&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;packets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;designated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;but&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;forwarding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FORWARD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eth0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;192.168.0.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;192.168.0.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FORWARD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eth0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;192.168.0.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;192.168.0.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;there&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MTU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mismatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;between&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;NAT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;box&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hosts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;behind&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;want&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ICMP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MTU&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;discovery&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FORWARD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;eth0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;icmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;icmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;icmp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;192.168.0.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;192.168.0.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;We&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;may&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;interested&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;anything&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rejected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;so&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FORWARD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LOG&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;We&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;forward&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;anything&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FORWARD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;REJECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;icmp&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;prohibited&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;COMMIT&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;nat&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;POSTROUTING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ACCEPT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;POSTROUTING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;192.168.0.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m m-Double"&gt;192.168.0.0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MASQUERADE&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nx"&gt;COMMIT&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a few things to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;default policies for INPUT and FORWARD are set to ACCEPT while the last
    rule in each chain is set to REJECT.  The reason for this is that the
    default policy can be either ACCEPT or DROP with the latter silently
    dropping packets.  Dropping packets makes it hard to investigate network
    issues, hence I prefer REJECT instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if you need to open more ports the best place to insert your rules would be
    between SSH and DHCP rules in the INPUT chain&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if you are setting up a firewall on the server (so no traffic forwarding is
    needed) you may want to either drop all rules from the FORWARD chain and
    set the default policy for that chain to DROP, or you can simply leave the
    last two rules (LOG and REJECT) in the chain removing the rest.  Also, the
    nat table is not needed on the servers, so should be omitted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the above ruleset is not strict enough in regard to filtering ICMP traffic
    and allows all types of ICMP, perhaps it could be further tightened to
    allow only ICMP types 0 (reply), 3 (destination unreachable), 8 (request),
    11 (time exceeded), and 30 (traceroute) to pass through the firewall.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, here you have it a solid firewall ruleset consisting of just 13 rules.&lt;/p&gt;</content><category term="linux"/><category term="console"/><category term="howto"/><category term="iptables"/><category term="linux"/><category term="security"/></entry><entry><title>Transparent SSH host-jumping (Advanced)</title><link href="https://dmitry.khlebnikov.net/2015/08/06/transparent-ssh-host-jumping-advanced/" rel="alternate"/><published>2015-08-06T10:00:00+10:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2015-08-06:/2015/08/06/transparent-ssh-host-jumping-advanced/</id><summary type="html">&lt;p&gt;This post presents an advanced SSH configuration to transparently
connect to a target host through one or more intermediate &amp;ldquo;jump&amp;rdquo; hosts,
addressing the inconvenience of managing numerous LocalForward entries in the
~/.ssh/config file. The solution uses a &amp;ldquo;Host &lt;em&gt;/&lt;/em&gt;&amp;rdquo; pattern and a ProxyCommand
directive, allowing a user to connect with a simple command like ssh
user@hostA/hostB/hostC. To overcome the inefficiency of creating new
connections for every session, the author further refines the ProxyCommand to
leverage SSH&amp;rsquo;s connection multiplexing features, ControlMaster, ControlPath,
and ControlPersist. This improved setup reuses already established connections,
significantly speeding up subsequent logins and keeping idle background
connections alive for a set period, which optimises the workflow for accessing
hosts in complex, non-directly reachable network environments.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In this brief article I am going to describe how I resolved a nagging issue I
had with setting up access to hosts which are not directly reachable, but where
you need to forward your connection through an intermediate host.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://dmitry.khlebnikov.net/2010/12/10/ssh-port-forwarding-intermediate/"&gt;Previously&lt;/a&gt;, I was using the local SSH port-forwarding technique (although I
was configuring hosts I connect to in the &lt;code&gt;~/.ssh/config&lt;/code&gt; file instead of
using the command-line options). However, this approach turned out to be quite
inconvenient since every time I wanted to connect to a new host (and, possibly,
through a new intermediate host) I had to edit my SSH configuration file and
add something like the following:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menua21a570a00ed6f02086673c0966a710dbe704486" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menua21a570a00ed6f02086673c0966a710dbe704486" class="line-numbers icon list-numbered"&gt;&lt;code class="language-ssh_config"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Host intermediate
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    HostName 192.168.1.1
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    HostKeyAlias intermediate
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    LocalForward 10001 target:22
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Host target
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    HostName 127.0.0.1
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    HostKeyAlias target
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    Port 10001&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Upon closer examination of my day-to-day routine I found two things that
frustrated me the most:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;My &lt;code&gt;~/.ssh/config&lt;/code&gt; file was growing uncontrollably and became hard to
     navigate;&lt;/li&gt;
&lt;li&gt;Each time I needed to connect to the target host through the intermediate
     host I had to open two sessions with one of them being idle most of the
     time.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After a while I stumbled upon an article describing quite a generic way to
tunnel through an intermediate host and found the approach quite convenient for
the day-to-day use. So, I have added the following block into my &lt;code&gt;~/.ssh/config&lt;/code&gt;
file just before the &lt;code&gt;Host *&lt;/code&gt; section:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu3b419c95e019b2d0fee193ee0f20a54b1ac137d5" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu3b419c95e019b2d0fee193ee0f20a54b1ac137d5" class="line-numbers icon list-numbered"&gt;&lt;code class="language-ssh_config"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Host &lt;span class="gs"&gt;*/*&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;    ProxyCommand ssh $(dirname %h) -W $(basename %h):%p&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From that point on, I could connect to a target host via an intermediate one
by simply executing the following command:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu5f7eb05b3740384784e4230f2b3cd7102e9cb7b5" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu5f7eb05b3740384784e4230f2b3cd7102e9cb7b5" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;user@intemediate/target&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The configuration with the &lt;code&gt;ProxyCommand&lt;/code&gt; directive was spawning two &lt;code&gt;ssh&lt;/code&gt;
processes with one connected to the intermediate host in the background and the
other proxied through the intermediate host and connected to the target running
in the foreground, so from my point of view I had just one terminal session
open. The configuration allowed to chain as many hosts as I wanted, e.g.:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu91c3ba8826a1b4a89bc536b4fd741ca30f83f28c" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu91c3ba8826a1b4a89bc536b4fd741ca30f83f28c" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;user@hostA/hostB/hostC/hostD&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above would result in three &lt;code&gt;ssh&lt;/code&gt; processes running in the background (the
first connected to hostA, the second connected to &lt;em&gt;hostB&lt;/em&gt; proxied through &lt;em&gt;hostA&lt;/em&gt;,
and the third connected to &lt;em&gt;hostC&lt;/em&gt; proxied through &lt;em&gt;hostB&lt;/em&gt;) and one foreground
process which was connected to &lt;em&gt;hostD&lt;/em&gt; proxied via &lt;em&gt;hostC&lt;/em&gt;. This is great and quite
flexible to use, however, this approach has a number of limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you cannot specify different ports for different hosts in a chain;&lt;/li&gt;
&lt;li&gt;neither can you use different login names for different hosts in the chain;&lt;/li&gt;
&lt;li&gt;establishing connection to different chains sharing a part of the chain
    would not reuse already established connections, i.e. slow connection times.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Personally, I am using the same login name and the same ports on hosts I am
accessing, so the first two items were not an issue for me, but the last one
was irritating enough and I decided to figure out whether it is possible to
optimise it. After a bit of reading the documentation and a few attempts I came
up with the following configuration block in my &lt;code&gt;~/.ssh/config&lt;/code&gt; file (remember,
this block should be placed &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;Host *&lt;/code&gt; one):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu80d227972a59ddbefc52a46bff679940cc729f08" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu80d227972a59ddbefc52a46bff679940cc729f08" class="line-numbers icon list-numbered"&gt;&lt;code class="language-ssh_config"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nt"&gt;Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*/*&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ControlMaster&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;auto&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ControlPath&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ssh&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sessions&lt;/span&gt;&lt;span class="o"&gt;/%&lt;/span&gt;&lt;span class="nt"&gt;r&lt;/span&gt;&lt;span class="o"&gt;@%&lt;/span&gt;&lt;span class="nt"&gt;h&lt;/span&gt;&lt;span class="o"&gt;:%&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ProxyCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;sh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mkdir -p -m700 ~/.ssh/.sessions/&amp;quot;%r@$(dirname %h)&amp;quot; &amp;amp;&amp;amp; exec ssh -o &amp;quot;ControlMaster auto&amp;quot; -o &amp;quot;ControlPath   ~/.ssh/.sessions/%r@$(dirname %h):%p&amp;quot; -o &amp;quot;ControlPersist 120s&amp;quot; -l %r -p %p $(dirname %h) -W $(basename %h):%p&amp;#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;rsquo;s review it line by line, so the logic is clear:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu87a7dc8c713912d2cbeec9cb203de1334d1c024b" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu87a7dc8c713912d2cbeec9cb203de1334d1c024b" class="line-numbers icon list-numbered"&gt;&lt;code class="language-ssh_config"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Host &lt;span class="gs"&gt;*/*&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This host definition block would catch any host specified on the &lt;code&gt;ssh&lt;/code&gt; command
line when the host name matches the &lt;code&gt;*/*&lt;/code&gt; pattern, so &lt;code&gt;ssh hostA/hostB/hostC&lt;/code&gt;
will be matched as &lt;code&gt;hostA/hostB&lt;/code&gt; being the first part before &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;hostC&lt;/code&gt; as
the second part after &lt;code&gt;/&lt;/code&gt;. Due to a recursive call to &lt;code&gt;ssh&lt;/code&gt; (see below) this
block will be recursively applied to all hosts in the specified chain.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menud1a89b83ec2df1169fa56aa0562d180eaa1a4f98" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menud1a89b83ec2df1169fa56aa0562d180eaa1a4f98" class="line-numbers icon list-numbered"&gt;&lt;code class="language-ssh_config"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;ControlMaster auto&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This directive instructs &lt;code&gt;ssh&lt;/code&gt; to try to reuse an existing control channel to
communicate with the remote host, and if such a channel does not exist it will
be created, so further connections to the same remote host would benefit from
a speedup provided by the already established connection.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuc04a10476da6ad2294914fee89c623e52abe4a19" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuc04a10476da6ad2294914fee89c623e52abe4a19" class="line-numbers icon list-numbered"&gt;&lt;code class="language-ssh_config"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;ControlPath ~/.ssh/.sessions/%r@%h:%p&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This directive provides &lt;code&gt;ssh&lt;/code&gt; with the location of the control channel socket
file. The socket file should be unique for each remote host. Since we are
reusing the existing connection and skipping the authentication the socket file
should be tagged with the corresponding login name, this is why we are using %r
(remote login name), %h (the remote host name), and %p (the remote port) as
part of the file name. Please note that due to our usage of &amp;ldquo;/&amp;rdquo; as a host
separator in the chain the path constructed here will have a subdirectory
defined in the middle of the %h expansion. &lt;code&gt;ssh&lt;/code&gt; would not automatically create
that subdirectory, so it is something we need to address (see below)&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menud15bbc7664b2bb28985ec776946973f7d0e43b8b" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menud15bbc7664b2bb28985ec776946973f7d0e43b8b" class="line-numbers icon list-numbered"&gt;&lt;code class="language-ssh_config"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;ProxyCommand …&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the heart of the whole block. I am starting this proxy command with
&lt;code&gt;/bin/sh -c '…'&lt;/code&gt; since &lt;code&gt;ssh&lt;/code&gt; is &lt;code&gt;exec()&lt;/code&gt;uting the specified command (this
replaces the spawned shell and makes it impossible to conditionally chain
commands), therefore I am using the shell binary as the proxy command to get
the ability to script my logic. Then I am creating the required directory
structure for the control channels under &lt;code&gt;~/.ssh/.sessions&lt;/code&gt; (note the &lt;code&gt;-p&lt;/code&gt;
argument to &lt;code&gt;mkdir&lt;/code&gt;, this will create all the missing parts of the specified
tree, but also would silence &lt;code&gt;mkdir&lt;/code&gt; in case all of the directories already
exist). It is worth to mention that with this &lt;code&gt;mkdir&lt;/code&gt; command I am creating the
subdirectory for the &lt;code&gt;ControlPath&lt;/code&gt; defined for the enclosing &lt;code&gt;Host */*&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;The second part of the command line is conditionally executing &lt;code&gt;ssh&lt;/code&gt; if &lt;code&gt;mkdir&lt;/code&gt;
did not report any issues. It is good to execute &lt;code&gt;ssh&lt;/code&gt; here since we do not
need a redundant shell hanging around in the process tree. In this recursive
&lt;code&gt;ssh&lt;/code&gt; call we explicitly specify that we also need multiplexing of the control
channels created by the parent connections (they are &amp;ldquo;parent&amp;rdquo; since this is the
connection that established first and which enables access to the hosts further
down the specified chain) as well as we explicitly specify the location of the
control channel (note that since it is a parent connection we are stripping
the rest of host names from the &lt;code&gt;%h&lt;/code&gt; macro using &lt;code&gt;dirname&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Finally, the third explicitly specified directive is &lt;code&gt;ControlPersist&lt;/code&gt; which is
set to &lt;code&gt;120s&lt;/code&gt;. This directive instructs &lt;code&gt;ssh&lt;/code&gt; to stay in the background and
maintain the control channel in case we decide to reuse it, but if no activity
on the control channel is detected for 2 minutes the &lt;code&gt;ssh&lt;/code&gt; process would
terminate. Without this directive the moment you close the connection which was
the master connection all dependent connections would also be closed, e.g. if
you have two sessions: one to &lt;code&gt;hostA/hostB&lt;/code&gt; and the other to &lt;code&gt;hostA/hostC&lt;/code&gt;, the
moment you closed the first connection the second one will be immediately
terminated if you do not have the &lt;code&gt;ControlPersist&lt;/code&gt; configured.&lt;/p&gt;
&lt;p&gt;The rest of the &lt;code&gt;ssh&lt;/code&gt; arguments is obvious: we connect to the first host in the
provided host chain (we are extracting that part with &lt;code&gt;dirname %h&lt;/code&gt;) and we are
proxying stdin/stdout to the last host in the supplied chain with the &lt;code&gt;-W&lt;/code&gt;
option.&lt;/p&gt;
&lt;p&gt;Basically, the control flow when you do &lt;code&gt;ssh user@hostA/hostB/hostC&lt;/code&gt; is the
following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ssh&lt;/code&gt; matches the &lt;code&gt;*/*&lt;/code&gt; pattern against the provided host name
     (&lt;code&gt;hostA/hostB/hostC&lt;/code&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ssh&lt;/code&gt; tries to reuse the control channel by attempting to open the
     &lt;code&gt;~/.ssh/.sessions/user@hostA/hostB/hostC:22&lt;/code&gt; socket, if successful the
     connection is established and the command prompt is displayed to the
     calling user, otherwise the execution continues&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ssh&lt;/code&gt; executes the defined &lt;code&gt;ProxyCommand&lt;/code&gt; command&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the first part of the command creates &lt;code&gt;~/.ssh/.sessions/hostA/hostB&lt;/code&gt; if it
     is not there&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the second part executes&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu491750152d3ca7ae8a0e19bad28d7d865ff2a1dd" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu491750152d3ca7ae8a0e19bad28d7d865ff2a1dd" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;ssh … -o &amp;quot;ControlPath ~/.ssh/.sessions/user@hostA/hostB:22&amp;quot; … hostA/hostB -W hostC:22&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will initiate another round of the above steps, but with a shorter chain
 and it will be recursive until there is just a single host left, e.g. when
 we ascend to &lt;code&gt;hostA&lt;/code&gt; as the host to connect to.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;now, with connected stdin/stdout to port 22 on &lt;code&gt;hostC&lt;/code&gt; (in the last
     iteration) &lt;code&gt;ssh&lt;/code&gt; performs the authentication against &lt;code&gt;hostC&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if authentication is successful &lt;code&gt;ssh&lt;/code&gt; creates the
     &lt;code&gt;~/.ssh/.sessions/user@hostA/hostB/hostC:22&lt;/code&gt; control channel socket and
     becomes the master of that control channel&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;a command prompt is displayed to the calling user&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I hope this little trick will save you some time and will make your life
easier. :)&lt;/p&gt;</content><category term="ssh"/><category term="console"/><category term="ssh"/><category term="howto"/></entry><entry><title>Should we use "sudo" for day-to-day activities?</title><link href="https://dmitry.khlebnikov.net/2015/07/18/should-we-use-sudo-for-day-to-day-activities/" rel="alternate"/><published>2015-07-18T20:04:00+10:00</published><updated>2026-07-19T06:11:02+10:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2015-07-18:/2015/07/18/should-we-use-sudo-for-day-to-day-activities/</id><summary type="html">&lt;p&gt;This article challenges the common belief that using sudo for daily
administrative tasks is more secure than directly logging in as a
privileged user via SSH.&lt;/p&gt;</summary><content type="html">&lt;div class="toc"&gt;&lt;span class="toctitle"&gt;Table of Contents&lt;/span&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#history"&gt;History&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#people-are-saying-sudo-is-good-is-it-true"&gt;People are saying &amp;ldquo;sudo&amp;rdquo; is good. Is it true?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#what-is-the-problem-with-the-sudo-approach"&gt;What is the problem with the &amp;ldquo;sudo&amp;rdquo; approach?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-light-side-of-sudo-is-there-one"&gt;The light side of &amp;ldquo;sudo&amp;rdquo;. Is there one?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-can-we-improve-the-security-of-our-systems-in-relation-to-the-sudo-usage"&gt;How can we improve the security of our systems in relation to the &amp;ldquo;sudo&amp;rdquo; usage?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;None of the systems I administer or supervise have &lt;code&gt;sudo&lt;/code&gt; installed with the
SUID bit set.&lt;/p&gt;
&lt;p&gt;Every time I answer a question on how to do privileged work on these systems
(i.e. do tasks that require administrator privileges) with a proposal to SSH
under a privileged account directly to do such a work, whoever asked the
question starts to blabber about how insecure that is, that one should use &lt;code&gt;sudo&lt;/code&gt;,
and that nobody should ever login directly as root.&lt;/p&gt;
&lt;p&gt;I have spent quite some time explaining the misconception behind so-called
&amp;ldquo;secure way to access systems through sudo&amp;rdquo;, so I decided to write up an
article that describes the issues of using that approach and why using &lt;code&gt;sudo&lt;/code&gt;
is actually &lt;em&gt;less secure&lt;/em&gt; than a direct SSH access approach.&lt;/p&gt;
&lt;h2 id="history"&gt;History&lt;/h2&gt;
&lt;p&gt;The following is based on my personal recollection of the history around early
90’s with some references to documents I could quickly find.  Unfortunately, this
is one of the topics that is not so easy to reconstruct in full detail.
Although there may be minor inaccuracies, the outlined view on the history
should be pretty close to the true events that took place.&lt;/p&gt;
&lt;p&gt;Many years ago (but not that far in the past) system administrators were using
&lt;code&gt;telnet&lt;/code&gt; and &lt;code&gt;rsh&lt;/code&gt; to access and administer their servers.  Networks were
simpler and traffic sniffing techniques were common.  Therefore, it was very
easy to eavesdrop on a root account login and to automate the task of gathering
the credentials.  To mitigate the issue to some extent the following approach
was proposed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;login as a non-privileged account first;&lt;/li&gt;
&lt;li&gt;do some stuff;&lt;/li&gt;
&lt;li&gt;if you need privileged account access (e.g. root) switch to it using &lt;code&gt;su&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The idea behind this proposal was that such a sequence makes it harder for a
traffic listener to catch root’s credentials on the wire due to the login
sequence mixed in to the stream of other activities the systems administrator
was doing before escalating their account to the privileged user.&lt;/p&gt;
&lt;p&gt;At approximately the same time discussions started in regard to the dangers
of working as root, that people were tending to work as root for prolonged
periods of time performing tasks that did not require escalated privileges
without proper justification for that behaviour.  As the result of these
discussions the &lt;code&gt;sudo&lt;/code&gt; utility was born.  The utility allowed to bind root
privileges to a restricted set of commands and maintained access controls
through its configuration file in order to allow or deny access to the defined
functionality to specific users/groups.  This allowed systems administrators to
delegate some of the privileged routines to less privileged user/groups. (See
more on the &lt;a href="https://www.sudo.ws/sudo/history.html"&gt;history of sudo&lt;/a&gt; if you are interested).&lt;/p&gt;
&lt;p&gt;Somewhere in early 90’s the two approaches were merged, so the approach became:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;login as a non-privileged account and do your every day tasks;&lt;/li&gt;
&lt;li&gt;once a privileged operation is required execute it through &lt;code&gt;sudo&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Although it seems like a minor optimisation, in fact, this introduced a &lt;em&gt;major
security issue&lt;/em&gt;: previously, the attacker should have needed to listen for
traffic in attempt to figure out when the victim is executing &lt;code&gt;su&lt;/code&gt; and is
providing root&amp;rsquo;s password, but now they did not need to do anything except of
capturing user&amp;rsquo;s password at the beginning of the session (the same problem the
&amp;ldquo;su-after-normal-login&amp;rdquo; approach was trying to solve just reappeared :) ) since
once you know user&amp;rsquo;s password you can use &lt;code&gt;sudo&lt;/code&gt; which authenticates you with
&lt;em&gt;user&amp;rsquo;s&lt;/em&gt; password!&lt;/p&gt;
&lt;p&gt;In 1995, &lt;a href="https://en.wikipedia.org/wiki/Tatu_Yl%C3%B6nen"&gt;Tatu Ylönen&lt;/a&gt; as a response to the issues related to exchanging the
credentials over non-protected, easily sniffable networks released his first
implementation of the SSH protocol as freeware to the public.  Over the next
five years SSH was adopted worldwide and it eventually replaced
&lt;code&gt;telnet&lt;/code&gt;/&lt;code&gt;rlogin&lt;/code&gt;/&lt;code&gt;rsh&lt;/code&gt; for the remote access and management activities in most
places around the globe.&lt;/p&gt;
&lt;p&gt;However, due to inertia the recommended approach of logging in as a
non-privileged account and escalating privileges later somehow survived, is
still followed, and often people don’t even try to analyse and see the flaws of
the approach.&lt;/p&gt;
&lt;h2 id="people-are-saying-sudo-is-good-is-it-true"&gt;People are saying &amp;ldquo;sudo&amp;rdquo; is good. Is it true?&lt;/h2&gt;
&lt;p&gt;&lt;sup&gt;(or looking critically into some common misconceptions re: &amp;ldquo;sudo&amp;rdquo;)&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;So let’s look at the most commonly used &amp;ldquo;pros&amp;rdquo; in favour of using &lt;code&gt;sudo&lt;/code&gt;. For
example, &lt;a href="https://help.ubuntu.com/community/RootSudo"&gt;Ubuntu’s community help page&lt;/a&gt; provides a nice, aggregated list of
benefits provided by &lt;code&gt;sudo&lt;/code&gt; usage in their default installation. Let’s walk
through all of them and see if they are real benefits at all:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The installer has fewer questions to ask.&lt;/p&gt;
&lt;p&gt;This is a very questionable &amp;ldquo;benefit&amp;rdquo; to the end user since it implies
 that the system would do more stuff with escalated privileges behind the
 scenes without the user even knowing it. From the security standpoint it
 just silently expands the attack surface.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Users don&amp;rsquo;t have to remember an extra password for occasional use (i.e.
     the root password). If they did, they&amp;rsquo;d be likely to forget it (or record
     it unsafely, allowing anyone to easily crack into their system).&lt;/p&gt;
&lt;p&gt;This is another hard to comprehend &amp;ldquo;benefit&amp;rdquo;. Given that we now have SSH
 and the recommended best practice is to use SSH keys instead of passwords,
 &lt;code&gt;sudo&lt;/code&gt; requires users to memorise yet another password.&lt;/p&gt;
&lt;p&gt;Moreover, the argument in regard to users’ likely behaviour is purely
 speculative and assumptive — to the same extent we can assume that
 password policies make it hard for users to memorise their passwords and
 they would be likely to forget the password (or record it unsafely, …).&lt;/p&gt;
&lt;p&gt;The truth is, the key based authentication solves the issue of the
 requirement to have multiple passwords for multiple accounts.&lt;/p&gt;
&lt;p&gt;There is one place, however, where the discussed &amp;ldquo;benefit&amp;rdquo; is actually
 applicable — local console access (no key authentication there, usually),
 but even there the best practice would be to login directly as root on
 the virtual console if there is such a need (the reason for that is
 quite complicated and in short could be described as the following: there
 are multiple checks and assumptions in the kernel code and the
 accompanying C library on allocating a terminal, spawning a process, etc.
 for root over the same actions performed for a non-privileged user).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It avoids the &amp;ldquo;I can do anything&amp;rdquo; interactive login by default. You will
     be prompted for a password before major changes can happen, which should
     make you think about the consequences of what you are doing.&lt;/p&gt;
&lt;p&gt;This statement also assumes that for some reason people would prefer to
 always login as root and do all of their work under that account.&lt;/p&gt;
&lt;p&gt;In reality users are so used to prefix almost any failed command with
 &lt;code&gt;sudo&lt;/code&gt; that this &amp;ldquo;benefit&amp;rdquo; can be considered as the quite opposite item,
 against &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In a properly configured environment you explicitly need to login as the
 privileged account to do functions that require privileges.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sudo adds a log entry of the command(s) run (in /var/log/auth.log). If you
     mess up, you can go back and see what commands were run.&lt;/p&gt;
&lt;p&gt;This statement is also somewhat true, but it does not defend the &lt;code&gt;sudo&lt;/code&gt;
 usage. A proper auditing subsystem is what keeps audit logs no matter how
 activity was performed. Logging of executed commands for user&amp;rsquo;s history
 reasons is the job for the shell.&lt;/p&gt;
&lt;p&gt;The ability of logging is so limited in &lt;code&gt;sudo&lt;/code&gt; that it cannot be used for
 anything except a substitute for the shell history. Just imagine the
 following scenario — a user executes:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menue0873a6b3bbbd7dc24737a7fa65e758791335387" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menue0873a6b3bbbd7dc24737a7fa65e758791335387" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;less&lt;span class="w"&gt; &lt;/span&gt;/var/log/messages&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and then types &amp;ldquo;!&amp;rdquo; followed by the Enter/Return key — the user effectively
 now in the root shell and what &lt;code&gt;sudo&lt;/code&gt; will log into its logs has nothing
 to do with what user actually did.&lt;/p&gt;
&lt;p&gt;Instead of relying on &lt;code&gt;sudo&lt;/code&gt;&amp;rsquo;s logging abilities, one should configure
 &lt;code&gt;auditd&lt;/code&gt; and send events to a centralised log aggregator to get audit logs
 that can be trusted.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;On a server, every cracker trying to brute-force their way in will know it
     has an account named root and will try that first. What they don&amp;rsquo;t know is
     what the usernames of your other users are. Since the root account
     password is locked, this attack becomes essentially meaningless, since
     there is no password to crack or guess in the first place.&lt;/p&gt;
&lt;p&gt;This is such a weak attempt to bring host security into the play that it
 is hard to comment on it without dropping a tear :).&lt;/p&gt;
&lt;p&gt;First, use SSH keys and disable the password authentication on the server
 — this (and not some security through obscurity) will defend the system
 from the brute force attacks.&lt;/p&gt;
&lt;p&gt;Second, protect your remote access entry points with properly configured
 firewall and allow remote access from a defined list of locations only
 (i.e. whitelist authorised locations) — this will shrink the possible
 attack surface of the SSH service considerably.&lt;/p&gt;
&lt;p&gt;Third, nobody actually prevents you from having a different user name with
 uid 0.  The &lt;code&gt;root&lt;/code&gt; account is just an accepted default, the system does not
 really care since it works with numerical UIDs, not with names.  See the
 next point for an option how to achieve that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Allows easy transfer for admin rights by adding and removing users from
     groups. When you use a single root password, the only way to de-authorize
     users is to change the root password.&lt;/p&gt;
&lt;p&gt;This is an interesting one: half of the statement is true, another is
 misleading.&lt;/p&gt;
&lt;p&gt;The true part is that you can easily delegate privileged operations using
 &lt;code&gt;sudo&lt;/code&gt;.  All in all, it was the primary goal and requirement of the tool
 creation to provide access delegation.&lt;/p&gt;
&lt;p&gt;The misleading part is that the alternative is to use a single root
 account/password.  The truth is that nobody is limited by a single root
 account: you can create as many as you want and each of them could have
 their own distinct password, e.g.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu80241711cfa88755cd6da161ef3c23749590471e" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu80241711cfa88755cd6da161ef3c23749590471e" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;useradd&lt;span class="w"&gt; &lt;/span&gt;-om&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/bin/sh&lt;span class="w"&gt; &lt;/span&gt;new_root
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[root@localhost ~]# &lt;/span&gt;passwd&lt;span class="w"&gt; &lt;/span&gt;new_root&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Better yet, do not set the password and lock the account instead with
 &lt;code&gt;usermod -L new_root&lt;/code&gt; since we are using keys, remember?&lt;/p&gt;
&lt;p&gt;This approach also provides additional accountability since users will
 have separate shell histories, their login attempts will be clearly logged
 under separate names, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;sudo can be setup with a much more fine-grained security policy.&lt;/p&gt;
&lt;p&gt;Another half-truth in the list of &amp;ldquo;benefits&amp;rdquo;. The statement is incomplete
 and lacks the part it is comparing the functionality to.  If we are
 comparing a legacy Unix access control system with &lt;code&gt;sudo&lt;/code&gt;, then yes &lt;code&gt;sudo&lt;/code&gt;
 is much more configurable. If we compare &lt;code&gt;sudo&lt;/code&gt; with, say, SELinux or
 GRsecurity’s RBAC &amp;ndash; &lt;code&gt;sudo&lt;/code&gt; will lose since both have much more
 fine-grained security controls than &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The root account password does not need to be shared with everybody who
     needs to perform some type of administrative task(s) on the system (see
     the previous bullet).&lt;/p&gt;
&lt;p&gt;As with item #6 this statement assumes for some obscure reason that there
 can be just a single root account in the system.  Therefore, the result of
 such a logical exercise is also questionable since it is based on a wrong
 assumption.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The authentication automatically expires after a short time (which can be
     set to as little as desired or 0); so if you walk away from the terminal
     after running commands as root using sudo, you will not be leaving a root
     terminal open indefinitely.&lt;/p&gt;
&lt;p&gt;This statement also compares &lt;code&gt;sudo&lt;/code&gt; with something reader cannot compare
 to.  Moreover, it mixes up two logically unrelated things: the credentials
 expiration and the terminal security.  While it is great that &lt;code&gt;sudo&lt;/code&gt; implements
 the former, the latter is usually addressed by entirely different means:
 starting with auto-logout functionality, lock screen, physical security,
 etc.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Well, we can continue to critically assess other common statements in regard to
&lt;code&gt;sudo&lt;/code&gt; made mostly by people who do not have any strong Information Security
background, but it would be a waste of time for the readers.&lt;/p&gt;
&lt;p&gt;You can always raise a question regarding some particular claim and/or
assumption related to &lt;code&gt;sudo&lt;/code&gt; and if it is interesting I would add it (and the
corresponding response) to this article.&lt;/p&gt;
&lt;h2 id="what-is-the-problem-with-the-sudo-approach"&gt;What is the problem with the &amp;ldquo;sudo&amp;rdquo; approach?&lt;/h2&gt;
&lt;p&gt;Well, there are several in fact. The most pressing issue is that the usage of
&lt;code&gt;sudo&lt;/code&gt; (or &lt;code&gt;su&lt;/code&gt;, or any other utility that has its SUID bit set) is crossing
the security boundary from the less privileged account to the more privileged
account. This opens doors (or widens the attack surface) to privilege
escalation techniques. In plain English it makes the non-privileged account to
be essentially equal to the privileged one, let me explain by a fairly simple
example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;imagine if you are a developer and you work on a server under your
     non-privileged account;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;as a part of your daily routine you need to download some third party
     package and install it (we are going to leave out all the security
     complexities involved with such an activity like verifying signatures,
     using a separate instance to prepare a package for deployment, etc.);&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;the installation of the package usually requires executing some third
     party code under your non-privileged account. This code was not written by
     you and there is a high chance that you did not read/verify the foreign
     code line by line in order to ensure that it does not do anything
     malicious since this would be quite time consuming, would require a lot of
     effort, and your team has more important priorities than this (remember,
     this is an example based on situations you would encounter in the real
     world, which is by no means perfect);&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;it happens, that that particular third party was compromised and some
     malicious code has been injected into the package installation routines;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;after the execution of the installation routine (and the malicious code
     for that matter) your &lt;code&gt;~/.bashrc&lt;/code&gt; (for example) is modified in such a way
     that each time you login it starts up a key logger or some other kind of
     remotely controllable piece of software that talks back to its master;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So far we just got an issue localised to this non-privileged account only (with
a possibility to spread across the entire fleet of servers that non-privileged
account has access to in case of the NFS mounted home directories). Is this
bad? Yes, it is since it may disrupt this particular project, steal reachable
sensitive information, could be used as a trampoline to jump start further
research and exploitation of other vulnerable resources. Is it critically bad?
Not necessarily. If the systems are built properly with host-based security in
mind, if the proper privilege separation techniques are used throughput the
company infrastructure, etc. the impact is localised and with proper monitoring
systems it would be detected eventually and investigated (keep in mind that it
is really hard for a non-privileged account to hide their activities from more
privileged processes).&lt;/p&gt;
&lt;p&gt;Now, let’s add &lt;code&gt;sudo&lt;/code&gt; to the mix, e.g. suddenly the developer decided to
install an additional library package into the system. So, what would happen
next? You guessed it: the developer would need to type in their password to
convince &lt;code&gt;sudo&lt;/code&gt; that they are &amp;ldquo;allowed&amp;rdquo; to do such a privileged thing as
installing a system package, the malicious software installed by the attacker
would happily intercept that and send it back to its master.&lt;/p&gt;
&lt;p&gt;From this point on, the attacker has the account password of the account where
their software runs and which they control. The attacker can now utilise &lt;code&gt;sudo&lt;/code&gt;
powers at their will. The security impact would be ranging from &amp;ldquo;high&amp;rdquo; to
&amp;ldquo;extreme&amp;rdquo; depending on how committed the attackers are.&lt;/p&gt;
&lt;p&gt;Following the logic, why would we want to introduce an additional complexity
that does not address the issue it was supposed to address, which is &amp;ldquo;to limit
exposure of the root account&amp;rdquo;?&lt;/p&gt;
&lt;h2 id="the-light-side-of-sudo-is-there-one"&gt;The &lt;em&gt;light&lt;/em&gt; side of &amp;ldquo;sudo&amp;rdquo;. Is there one?&lt;/h2&gt;
&lt;p&gt;So, is &lt;code&gt;sudo&lt;/code&gt; any good for anything? Actually, yes, it is. All in all, &lt;code&gt;sudo&lt;/code&gt;
is a tool that attracts a lot of attention from the security researchers and
auditors, its codebase got numerous peer reviews and the functionality the tool
provides can be used for good. The following scenarios come to my mind right
away:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;On a SELinux enabled system it seems that &lt;code&gt;sudo&lt;/code&gt; is the only properly
   implemented and reliable way to assume a different SELinux role. All other
   mechanisms are either lacking in the functionality or just do it half way
   leaving some artefacts behind;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unless some effort is put into tweaking the way PAM (Pluggable
   Authentication Modules, an authentication framework used on Linux, Solaris,
   and some other Unix-like systems) authenticates users on LDAP enabled
   systems there is no easy way to have two separate accounts (a privileged and
   a non-privileged account) for the same LDAP user. This could be addressed
   administratively (e.g. by defining additional privileged users in LDAP) or
   technically by implementing account names’ prefixes. However, if there are
   budgetary and/or time constraints to implement the proper security framework
   &lt;code&gt;sudo&lt;/code&gt; with a quite restricted configuration would be an acceptable
   compromise.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, personally, I failed to find any other justified applications of
&lt;code&gt;sudo&lt;/code&gt; in a secure environment and would be happy to get some feedback if you
have something in mind worth including into the list above.&lt;/p&gt;
&lt;p&gt;It is worth to mention that in the first scenario (SELinux roles), &lt;code&gt;sudo&lt;/code&gt; should
not have the SUID bit set since normal SELinux users could assume roles with
&lt;code&gt;newrole&lt;/code&gt;.  The difficulty arises when an administrator tries to assume a user
role (e.g. such a role transition is not prescribed in the policy).  One could
argue that either the SELinux policy should be amended and, then, &lt;code&gt;newrole&lt;/code&gt; could
be used or such a behaviour of the administrator account should not be allowed at
all.  However, in real life, administrators need to see the world through users&amp;rsquo;
eyes from time to time to expedite the troubleshooting process, in these cases
&lt;code&gt;sudo&lt;/code&gt; will help, but since it is going to be executed by an administrator (a
privileged account) to switch to a user (a low privileged account) there is no
need for any other permissions as &lt;code&gt;0100&lt;/code&gt; (execute for the owner) on the &lt;code&gt;sudo&lt;/code&gt;
binary.&lt;/p&gt;
&lt;h2 id="how-can-we-improve-the-security-of-our-systems-in-relation-to-the-sudo-usage"&gt;How can we improve the security of our systems in relation to the &amp;ldquo;sudo&amp;rdquo; usage?&lt;/h2&gt;
&lt;p&gt;OK, so we got to the point where we are standing at a crossroad and we kind
of established that excessive usage of &lt;code&gt;sudo&lt;/code&gt; is a bad thing (for
security-paranoid readers - read: &amp;ldquo;almost any usage of &lt;code&gt;sudo&lt;/code&gt; except for
changing SELinux roles on a SELinux-enabled system is a bad thing&amp;rdquo;). So, what
is the alternative way of doing things? Well, there is a complex approach on
addressing and minimising the privilege escalation risks and roughly it can be
summarised as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;apply the &lt;a href="https://en.wikipedia.org/wiki/Principle_of_least_privilege"&gt;least privilege principle&lt;/a&gt; to everything&lt;/p&gt;
&lt;p&gt;For example, if you need to work on content, the account you use to log
into the remote server should be allowed to do just that. This sounds a bit
extreme, so we may re-phrase it as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a name="quote-least-privilege" href="#quote-least-privilege"&gt;&lt;/a&gt;
Each account should be provided the least number of required privileges
to do specific tasks under that account and should not be used for
anything else outside the defined set of activities.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Again, if you include &amp;ldquo;becoming root&amp;rdquo; into the list of defined activities
it would kind of defeat the purpose, so please resist this temptation. :)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;there should be a defined list of accounts which are allowed to do
    system-wide modifications (e.g. privileged accounts)&lt;/p&gt;
&lt;p&gt;In line with the previous bullet point, these accounts must be used for
these activities only (system updates, installing new software, modifying
system-wide configuration).  The expectation is that the privileged
accounts are used on an occasion only;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;privileged accounts during their activities should not cross the security
    boundary with the less privileged accounts.&lt;/p&gt;
&lt;p&gt;This is needed to avoid attacks from the less privileged accounts toward
more privileged (e.g. process hijacking, file races, etc.). Unfortunately,
this point is a bit confusing without a proper explanation and that may take
several blog posts to be fully covered, but in a nutshell it means that
root should not touch anything writable by the less privileged account.
Some examples of the bad and insecure behaviour include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;changing directory to a directory writable by non-root,&lt;/li&gt;
&lt;li&gt;executing scripts from a directory writable by non-root accounts or
    scripts that are writable by non-root,&lt;/li&gt;
&lt;li&gt;copying a file/directory from/to a directory writable by non-root
    accounts,&lt;/li&gt;
&lt;li&gt;and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;if possible (and this is &lt;em&gt;highly&lt;/em&gt; recommended) privileged accounts should be
    accessed from the trusted and secure machines.&lt;/p&gt;
&lt;p&gt;It is really hard to define what the &amp;ldquo;trusted and secure machine&amp;rdquo; is, but
generally it should be assumed&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;that it is a laptop or a desktop station that is used entirely for the
    work purposes (no free time surfing on the leisure sites and stuff :) ),&lt;/li&gt;
&lt;li&gt;that proper firewall rules and protection techniques were used to secure
    the machine, and&lt;/li&gt;
&lt;li&gt;that the operator is security-minded person and does everything they
    can to ensure integrity and security of their machine and software
    installed on it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I would be really happy to answer any question in regard to this article and
provide any possible help in making our everyday system level activities more
secure, so do not hesitate to comment or &lt;a href="/contact"&gt;contact me&lt;/a&gt;.&lt;/p&gt;</content><category term="sudo"/><category term="sudo"/><category term="console"/></entry><entry><title>SSH port-forwarding (Intermediate)</title><link href="https://dmitry.khlebnikov.net/2010/12/10/ssh-port-forwarding-intermediate/" rel="alternate"/><published>2010-12-10T10:00:00+11:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2010-12-10:/2010/12/10/ssh-port-forwarding-intermediate/</id><summary type="html">&lt;p&gt;This post introduces the basics of SSH port forwarding as a secure
alternative to exposing services like a MySQL database to the public internet.
The author explains that instead of binding a database to a public IP address,
which poses security risks, one can keep it running on a private or loopback
interface (like 127.0.0.1:3306) on the remote server. By using a local port
forwarding command, such as ssh -L3306:127.0.0.1:3306 user@server, a secure
tunnel is created over the SSH connection. This makes the remote database
accessible on the user&amp;rsquo;s local machine as if it were running locally, allowing
the use of local database management tools like MySQL WorkBench without
compromising security.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In my previous blog entry I described some basic functionality of SSH in terms
of port-forwarding. Now it&amp;rsquo;s time for a little bit more complex stuff.&lt;/p&gt;
&lt;p&gt;In this article I will highlight:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;(forward) piercing of a firewall (getting access to resources behind it);&lt;/li&gt;
&lt;li&gt;dynamic port-forwarding (AKA proxy);&lt;/li&gt;
&lt;li&gt;(reverse) piercing of a firewall (exposing your local services on the remote side).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="forward-firewall-piercing"&gt;Forward firewall piercing&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with the forward firewall piercing, since it is the easiest and was
somewhat already described in my previous blog entry on this topic. Now,
imagine that you already have SSH access to some host which is multi-home
connected (e.g. the host is connected to more than one network). Let&amp;rsquo;s also
assume that the host is a firewall and is masquerading other hosts in the
internal network and is translating just a handful set of ports to the servers
(looks familiar, doesn&amp;rsquo;t it? :) ). In other words, we are speaking of a
standard firewall/NAT router.&lt;/p&gt;
&lt;p&gt;Now, how can you access port 12345 on host behind the firewall given that this
port is not &amp;ldquo;exported&amp;rdquo; by the NAT on the firewall? This is quite simple. Open a
terminal window on your local computer and type the following:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu139b31acf6275129f3a07a213c3df1038dfe8053" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu139b31acf6275129f3a07a213c3df1038dfe8053" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-L12345:192.168.1.2:12345&lt;span class="w"&gt; &lt;/span&gt;joe@firewall.domain.tld&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From now on, as long as your SSH session is up and running you will be able to
reach &lt;code&gt;192.168.1.2:12345&lt;/code&gt; by connecting to &lt;code&gt;localhost:12345&lt;/code&gt; (i.e. the &lt;code&gt;12345&lt;/code&gt;
port on your local computer). Indeed, for this to work you need SSH access
anywhere inside the protected network (not necessarily on the firewall itself)
and if the firewall blocks any SSH access, you are out of luck.&lt;/p&gt;
&lt;h2 id="dynamic-port-forwarding"&gt;Dynamic port-forwarding&lt;/h2&gt;
&lt;p&gt;There are at least two usage patterns where I find SSH&amp;rsquo;s ability to forward
requests to many ports useful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I need to connect to different services (possibly on different hosts) inside
  the protected network (as per the configuration described above) and I don&amp;rsquo;t
  want to specify all of them on the command line;&lt;/li&gt;
&lt;li&gt;I need to access some resource which is Geo-protected (e.g. allows access
  from a particular part of the world), e.g. want to watch US Netflix being
  physically in Australia&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In both cases, you use the following SSH command in your terminal window:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu0461f5eb29c7995922feee6d7515b3f847ea0412" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu0461f5eb29c7995922feee6d7515b3f847ea0412" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-D3128&lt;span class="w"&gt; &lt;/span&gt;joe@relay.domain.tld&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As long as you SSH session is active, you can use port &lt;code&gt;3128&lt;/code&gt; on your local
machine as SOCKS4/SOCKS5 proxy (e.g. you can configure proxy settings in your
browser to use &lt;code&gt;localhost:3128&lt;/code&gt;) and browse the Net through your SSH connection,
and all your requests will look like they are coming from &lt;code&gt;relay.domain.tld&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are many uses for this: for example, some payment processors won&amp;rsquo;t allow
you to pay for goods if you are trying to pay through them from some countries
even if you are a legitimate user, another use case is when you are concerned
re: your privacy &amp;ndash; you can conceal your actual location by building a chain of
SSH tunnels and access the desired web site through this chain :) .&lt;/p&gt;
&lt;h2 id="reverse-firewall-piercing"&gt;Reverse firewall piercing&lt;/h2&gt;
&lt;p&gt;Finally, what if you are behind a very strict firewall that limits almost
everything, but you need to provide some services to the outside world from
your computer (e.g. sharing your access to company&amp;rsquo;s confidential information
to folks from WikiLeaks&amp;hellip; just kidding :) )?&lt;/p&gt;
&lt;p&gt;To achieve this you need to have an SSH account somewhere in the Net &amp;ndash; just
Google for &amp;ldquo;free ssh account&amp;rdquo; and you will surely find one for yourself).&lt;/p&gt;
&lt;p&gt;Now, when you have the account, you can execute the following on your local
computer (which is inside that highly secure network :) ):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuc96f20bfd9632d6f274b97c5f42f1d00e1ba9109" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuc96f20bfd9632d6f274b97c5f42f1d00e1ba9109" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-R:60000:127.0.0.1:22&lt;span class="w"&gt; &lt;/span&gt;joe@friendly.domain.tld&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The above command will setup such a configuration that connecting on port &lt;code&gt;60000&lt;/code&gt;
at &lt;code&gt;friendly.domain.tld&lt;/code&gt; will forward traffic to your local machine&amp;rsquo;s port 22
(which is behind a firewall) &amp;ndash; this will work as long as your SSH session to
the &lt;code&gt;friendly.domain.tld&lt;/code&gt; is active.  Unfortunately, there are several pitfalls
in this approach, but they are all resolvable.&lt;/p&gt;
&lt;p&gt;Firstly, you need to ensure that &lt;code&gt;friendly.domain.tld&lt;/code&gt; is using a recent version
of SSH daemon, otherwise you will be limited to bind only to the loopback
interface on the remote host.&lt;/p&gt;
&lt;p&gt;Secondly, even if they are using a recent version of the SSH daemon, they can
disallow such binding (e.g. setting &amp;ldquo;GatewayPorts no&amp;rdquo; in &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;),
and, again, you will be restricted to the loopback interface only.&lt;/p&gt;
&lt;p&gt;Finally, you need to find such a friendly host which allows you to connect to
the bound ports from the outside (many public ones have a firewall rule
preventing such access in order to prevent abuses of their services).&lt;/p&gt;
&lt;p&gt;All in all, the best option to try this is to have your own host somewhere (e.g.
purchase a small virtual environment from some hosting provider or create an
instance on AWS EC2), then you will be able to configure the remote side the
way you want it!&lt;/p&gt;
&lt;p&gt;Anyway, even if you fail to find a host that allows you to expose your service
to the public, you still should be able to access it yourself &amp;ndash; using the
forward firewall piercing technique described at the beginning of this post:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu79048bbafb540a4dbe2092403beb568a4d0e4775" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu79048bbafb540a4dbe2092403beb568a4d0e4775" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;# &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;your&lt;span class="w"&gt; &lt;/span&gt;home&lt;span class="w"&gt; &lt;/span&gt;computer
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-L60000:127.0.0.1:60000&lt;span class="w"&gt; &lt;/span&gt;joe@friendly.domain.tld&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once it is done your computer in the highly secure network should be reachable
via SSH on your &lt;code&gt;localhost:60000&lt;/code&gt;, e.g.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu002709e21abf8c28749cf117ba70a46c05e3ab4a" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu002709e21abf8c28749cf117ba70a46c05e3ab4a" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;[user@localhost ~] ssh -p6000 my_account@localhost&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Where &lt;code&gt;my_account&lt;/code&gt; is a user allowed to SSH into your computer in the highly
secure network.&lt;/p&gt;</content><category term="ssh"/><category term="ssh"/><category term="console"/></entry><entry><title>SSH port-forwarding (Basic)</title><link href="https://dmitry.khlebnikov.net/2010/11/29/ssh-port-forwarding-basic/" rel="alternate"/><published>2010-11-29T10:00:00+11:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2010-11-29:/2010/11/29/ssh-port-forwarding-basic/</id><summary type="html">&lt;p&gt;An introduction into the world of SSH port-forwarding and how to
leverage it to access resources in private subnets&lt;/p&gt;</summary><content type="html">&lt;p&gt;I think all of you are using SSH in your daily routines. However, do you use
its full potential? Today&amp;rsquo;s topic is the SSH port-forwarding feature and how
it can be use to achieve some interesting configurations.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m sure most of you are aware of the feature, but how many of you are using
it? Personally, I&amp;rsquo;m a bit obsessed with it and have found numerous cases where
this feature of SSH is a real life saver.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with simple things and imagine that you have a server where you
are running MySQL (as a backend for your website) and that you are not
comfortable to work with MySQL neither through the MySQL command line interface
nor through a web-interace like phpMyAdmin. Instead you prefer to use, say,
MySQL WorkBench or something similar running on your local computer.&lt;/p&gt;
&lt;p&gt;How one could do this? Well, I&amp;rsquo;ve seen a lot that people tend to configure the
MySQL server to listen on a public interface (i.e. an interface that is
reachable from the Internet), then access it from their computer directly.
It&amp;rsquo;s not that bad if you have a static &lt;abbr title="Internet Protocol"&gt;IP&lt;/abbr&gt; assigned to your computer and you
added a firewall rule protecting the MySQL port on the server to be reachable
from your &lt;abbr title="Internet Protocol"&gt;IP&lt;/abbr&gt; address only, but what if you are behind some kind of NAT and the
same &lt;abbr title="Internet Protocol"&gt;IP&lt;/abbr&gt; address is shared among others in your network? What if you don&amp;rsquo;t have
a static &lt;abbr title="Internet Protocol"&gt;IP&lt;/abbr&gt; and the dynamic &lt;abbr title="Internet Protocol"&gt;IP&lt;/abbr&gt; range assigned by your ISP is too broad?
Overall, I personally don&amp;rsquo;t like running MySQL on a public interface since
there were publicly known security vulnerabilities in the MySQL binary
protocol, and I can assure you there will be more discovered over the time.&lt;/p&gt;
&lt;p&gt;So, what should we do to access our remote database securely and conveniently?
Run the MySQL server on 127.0.0.1:3306 (well, if your web server is located on
another machine, you may want to bind MySQL to some internal interface &amp;ndash; the
network that links your servers only, e.g. 192.168.1.1:3306), and then use the
SSH port-forwarding feature to setup a tunnel between your remote database and
your local computer:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu4257876e24f7e442b77692efa962d9b97da0e477" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu4257876e24f7e442b77692efa962d9b97da0e477" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;-L3306:127.0.0.1:3306&lt;span class="w"&gt; &lt;/span&gt;user@server&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, &lt;code&gt;-L3306:127.0.0.1:3306&lt;/code&gt; says that we want &lt;code&gt;127.0.0.1:3306&lt;/code&gt; to be &amp;ldquo;mapped&amp;rdquo;
to our local host to port &lt;code&gt;3306&lt;/code&gt;. While the SSH session is active any packet
sent to &lt;code&gt;localhost:3306&lt;/code&gt; (on your computer) will be forwarded over the SSH
encrypted channel to the remote server and then will be fed to remote&amp;rsquo;s
&lt;code&gt;127.0.0.1:3306&lt;/code&gt;.  This means that as long as your SSH session is alive you
will be able to work with the remote MySQL server like it&amp;rsquo;s running on your
local computer and you can use any MySQL tools locally.&lt;/p&gt;
&lt;p&gt;Well, the above command was given for Linux, macOS, and other Unix-based
systems. For Windows, you can use PuTTY to archive the same. I found the
following &lt;a href="https://www.akadia.com/services/ssh_putty.html"&gt;instructions for PuTTY&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;OK, this is not a new topic and it was described many times here and there on
the Internet, but I decided to write this article as a foundation for my
further articles on this topic which will cover more advanced usage of the SSH
port-forwarding feature. For example, I intend to document a configuration of
the MySQL replication setup between two servers located at two different data
centres where each server is a part of a server farm protected by a firewall.&lt;/p&gt;</content><category term="ssh"/><category term="ssh"/><category term="console"/><category term="bastion"/></entry><entry><title>HOWTO: VMware Player as a remote console (VNC)</title><link href="https://dmitry.khlebnikov.net/2010/11/24/howto-vmware-player-as-a-remote-console-vnc/" rel="alternate"/><published>2010-11-24T10:00:00+11:00</published><updated>2025-10-18T02:45:38+11:00</updated><author><name>(GalaxyMaster)</name></author><id>tag:dmitry.khlebnikov.net,2010-11-24:/2010/11/24/howto-vmware-player-as-a-remote-console-vnc/</id><summary type="html">&lt;p&gt;Turning VMware Player into a non-privileged VNC client to access&lt;/p&gt;</summary><content type="html">&lt;p&gt;VMware VMs from a Linux-based PC.
Tags: howto, hack
Alias: /2010/11/howto-vmware-player-as-remote-console.html&lt;/p&gt;
&lt;p&gt;Since I am doing a lot of remote systems administration tasks due to the nature
of my IT consulting work and since I am also running Linux on all my computers
I was looking for a native way how to get a remote console to VMware VMs from
linux.&lt;/p&gt;
&lt;p&gt;After some searching I found that &lt;a href="http://www.vmware.com/go/downloadplayer/"&gt;VMware Player&lt;/a&gt; (which has native binaries for
Linux) can be used as a VNC client to get to VMs consoles. However, once I have
downloaded VMware Player&amp;rsquo;s bundle and was faced with its requirement to run the
installation script as root I became quite unhappy with an idea of running some
proprietary software on my machine as root, especially after looking into the
bundle and the way the installation script was written. Moreover, there was no
need for other parts of &lt;a href="http://www.vmware.com/go/downloadplayer/"&gt;VMware Player&lt;/a&gt; &amp;ndash; I just wanted to have a small tool to
be able to hook the remote consoles up under my lovely Linux environment.
Therefore, I decided to take a challenge and to tweak the installation so it
will be possible to install the whole thing as a non-privileged user. Another
sub-goal was to strip the installation further down and prepare a small package
with only components needed for remote console sessions.&lt;/p&gt;
&lt;p&gt;If you are not concerned about security (and integrity) of your system, e.g.
you are fine with the re-installation of the whole system, then it will be
cheaper to just install the &lt;a href="http://www.vmware.com/go/downloadplayer/"&gt;VMware Player&lt;/a&gt; under the root account. In this case
you don&amp;rsquo;t need to read any further since what I am describing below is for
those brave hearts who value their systems and who do not want to give a chance
to mess their systems up by running low-quality custom installation scripts as
root.&lt;/p&gt;
&lt;p&gt;Well, if you are still reading, then I hope that my research on this topic and
the how-to I have spent considerable time to come up with is worth something
and will be of some help to you.&lt;/p&gt;
&lt;p&gt;Our starting point is a Linux-based system (it does not matter what
distribution you are running, but I did everything on a customised &lt;a href="https://en.altlinux.org/"&gt;ALT Linux&lt;/a&gt;&amp;rsquo;s
RPM-based distribution) running on an x86 compatible hardware (mine was 32-bit,
but I see no issues with 64-bit ones).&lt;/p&gt;
&lt;p&gt;The first step is to download &lt;a href="http://www.vmware.com/go/downloadplayer/"&gt;VMware Player&lt;/a&gt; for your architecture, set proper
permissions on the downloaded file, and then extract the payload as follows
(you need to ensure that you have at least 40MB of free space on &lt;code&gt;/tmp&lt;/code&gt;, BTW):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu2e76674af4879279e4d4d498794eabce33892036" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu2e76674af4879279e4d4d498794eabce33892036" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./VMware-Player-3.1.3.324285.i386.bundle
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;./VMware-Player-3.1.3-324285.i386.bundle&lt;span class="w"&gt; &lt;/span&gt;--console&lt;span class="w"&gt; &lt;/span&gt;-x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;/vmplayer
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Extracting VMware Installer...done.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;No protocol specified&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;No protocol specified&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;vmplayer
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;total 20&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x  8 vmware vmware 4096 Nov 23 09:01 vmware-installer&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x  4 vmware vmware 4096 Nov 23 09:01 vmware-ovftool&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x  5 vmware vmware 4096 Nov 23 09:01 vmware-player&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x 11 vmware vmware 4096 Nov 23 09:01 vmware-player-app&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwxr-xr-x  3 vmware vmware 4096 Nov 23 09:01 vmware-player-setup&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So far so good. We now have the whole bundle unpacked into the specified
directory and we are interested in just two subdirectories: &lt;code&gt;vmware-player&lt;/code&gt; and
&lt;code&gt;vmware-player-app&lt;/code&gt;, the rest is not related to the functionality we are
looking for.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s pick up all parts from which we will build our future &amp;ldquo;VMware remote
console&amp;rdquo; tool. To make it easier create a dedicated subdirectory, e.g.
&lt;code&gt;vmrconsole&lt;/code&gt;, with the following structure:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu27ff562624e23d9ed8bf8d00eb9eb89517278f48" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu27ff562624e23d9ed8bf8d00eb9eb89517278f48" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-m700&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-m700&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/&lt;span class="o"&gt;{&lt;/span&gt;bin,etc,lib,share&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;total 12&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwx------ 2 vmware vmware 4096 Nov 24 01:21 bin&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwx------ 2 vmware vmware 4096 Nov 24 01:21 etc&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwx------ 2 vmware vmware 4096 Nov 24 01:21 lib&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;drwx------ 2 vmware vmware 4096 Nov 24 01:21 share&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From now on we are going to populate these directories with files from the
unpacked bundle.&lt;/p&gt;
&lt;p&gt;The first file we are interested in is &lt;code&gt;appLoader&lt;/code&gt; &amp;ndash; this is the primary
executable by the way, we need to copy it to our bin directory and then try to
run it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu7c5f59b547ffaab0c13a46a9736700b3964bdeab" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu7c5f59b547ffaab0c13a46a9736700b3964bdeab" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer/vmware-player-app/lib/bin/appLoader&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/appLoader
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;appLoader&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmplayer
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;~/vmrconsole/bin/vmplayer&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;255&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Huh, this is not very informative, is it? The binary silently exits with error
code of 255. Well, you may get other errors at this stage if you don&amp;rsquo;t have all
the required shared libraries installed on you system &amp;ndash; however I doubt it
since the requirements of this binary are pretty reasonable: glibc and zlib.&lt;/p&gt;
&lt;p&gt;OK, let&amp;rsquo;s take a peek inside and figure out what is going on (originally I used
&lt;code&gt;strace&lt;/code&gt; with logging to a file, but to keep this article reasonable short I am
highlighting important things only):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuaa0b583c9a7e72c3c505376eddb4c628a157763f" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuaa0b583c9a7e72c3c505376eddb4c628a157763f" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;strace&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-eopen&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmplayer&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-2
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;open(&amp;quot;/etc/localtime&amp;quot;, O_RDONLY)        = 6&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;open(&amp;quot;/etc/vmware/config&amp;quot;, O_RDONLY|O_LARGEFILE) = -1 EACCES (Permission denied)&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It looks that &lt;code&gt;vmplayer&lt;/code&gt; wants to access a global config file and does not try to
look for an alternative, home directory based one. Well, this is understandable
since VMware folks did not expect it to be run as a non-privileged process, but
we need to deal with this somehow. What are our options here? The simplest
option I could think of at the moment is to substitute the hardcoded absolute
path inside the binary with something relative:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu794b2233518640f6514a97385bb3f977e52445f2" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu794b2233518640f6514a97385bb3f977e52445f2" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;strings&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/appLoader&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;/etc/vmware&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uniq&lt;span class="w"&gt; &lt;/span&gt;-c
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;      1 /etc/vmware/config&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;      1 /etc/vmware/icu&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;      1 /etc/vmware/ssl/rui.crt&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;      1 /etc/vmware/ssl/rui.key&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;      1 /etc/vmware/ssl/dh512.pem&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;      1 /etc/vmware/ssl/dh1024.pem&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/etc/vmware,..//////etc,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/appLoader&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The trick here is to substitute one string with another of the same length (we
are modifying a binary so we do not want to mess offsets up) and luckily enough
we can use whatever number of slashes we want &amp;ndash; they all are considered as a
single separator nevertheless. OK, we could have used a hex editor and could
have terminated strings with a NULL byte, but the point is that the approach I
took is the quickest and is working well. Let&amp;rsquo;s run the modified binary through
&lt;code&gt;strace&lt;/code&gt; again, but this time we need to be prepared for the changed behaviour:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu38737404bf6e2051eab6c931fe95615ec3aee0ab" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu38737404bf6e2051eab6c931fe95615ec3aee0ab" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/bin]$ &lt;/span&gt;touch&lt;span class="w"&gt; &lt;/span&gt;../etc/config
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/bin]$ &lt;/span&gt;strace&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-emkdir,lstat,open,write&lt;span class="w"&gt; &lt;/span&gt;./vmplayer&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-12
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;mkdir(&amp;quot;/tmp/.private/vmware/vmware-vmplayer&amp;quot;, 0700) = -1 EEXIST (File exists)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;open(&amp;quot;/tmp/.private/vmware/vmware-vmplayer/appLoader-9245.log&amp;quot;, O_RDWR|O_CREAT|O_APPEND|O_LARGEFILE, 0644) = 5&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;open(&amp;quot;/etc/localtime&amp;quot;, O_RDONLY)        = 6&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;Nov 24 01:53:09.824: app-3077760&amp;quot;..., 121) = 121&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;Nov 24 01:53:09.824: app-3077760&amp;quot;..., 60) = 60&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;Nov 24 01:53:09.824: app-3077760&amp;quot;..., 71) = 71&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;Nov 24 01:53:09.824: app-3077760&amp;quot;..., 57) = 57&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;\&amp;quot;\n&amp;quot;, 2)                     = 2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;Nov 24 01:53:09.825: app-3077760&amp;quot;..., 82) = 82&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;open(&amp;quot;..//////etc/config&amp;quot;, O_RDONLY|O_LARGEFILE) = 6&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;Nov 24 01:53:09.825: app-3077760&amp;quot;..., 89) = 89&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;Nov 24 01:53:09.825: app-3077760&amp;quot;..., 73) = 73&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looks better, does not it? Our binary found the config file and was able to
open it, however it still produces no output, but it reports something to a log
file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu39308905c96b2532dba33a632ddb2e7f210d3bfa" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu39308905c96b2532dba33a632ddb2e7f210d3bfa" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/bin]$ &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-7&lt;span class="w"&gt; &lt;/span&gt;/tmp/.private/vmware/vmware-vmplayer/appLoader-9245.log
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Nov 24 01:53:09.824: app-3077760704| Log for VMware Workstation pid=9245 version=7.1.3 build=build-324285 option=Release&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Nov 24 01:53:09.824: app-3077760704| The process is 32-bit.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Nov 24 01:53:09.824: app-3077760704| Host codepage=utf8 encoding=UTF-8&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Nov 24 01:53:09.824: app-3077760704| Calling: &amp;quot;./vmplayer&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Nov 24 01:53:09.825: app-3077760704| Using configuration file ..//////etc/config.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Nov 24 01:53:09.825: app-3077760704| libdir entry was not present in ..//////etc/config.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Nov 24 01:53:09.825: app-3077760704| Unable to lookup library directory.&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I do not know much about VMware Players config file but according to the log
message it wants some variable called &lt;code&gt;libdir&lt;/code&gt; and this variable should point
to the library directory, so let&amp;rsquo;s introduce such a variable and try to execute
the binary again:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuf83b6fccdfa5acae5501e590fb361d674e192407" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuf83b6fccdfa5acae5501e590fb361d674e192407" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/bin]$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libdir = ..&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;../etc/config
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/bin]$ &lt;/span&gt;strace&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-emkdir,lstat,open,write&lt;span class="w"&gt; &lt;/span&gt;./vmplayer&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tail&lt;span class="w"&gt; &lt;/span&gt;-4
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;LOG NOT INITIALIZED | LoadLibrar&amp;quot;..., 73) = 73&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;open(&amp;quot;../lib/libvmplayer.so/libvmplayer.so&amp;quot;, O_RDONLY) = -1 ENOENT (No such file or directory)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;LOG NOT INITIALIZED | Error load&amp;quot;..., 153) = 153&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;write(5, &amp;quot;LOG NOT INITIALIZED | Could not &amp;quot;..., 74) = 74&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I hope you have noticed that I have used a relative path for the library
directory in the config file and this means that we always should run the
binary with its directory being the current working directory. This is a bit
inconvenient, but we will solve this with a wrapper script later. Right now, we
need to get it working and we see that it tried to dynamically load some
library from the library directory. OK, let&amp;rsquo;s search for this library in the
unpacked bundle directory and copy the library file over to our tree:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu357e3f044265ba3fb39a06532937864354508d4b" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu357e3f044265ba3fb39a06532937864354508d4b" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/bin]$ &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;libvmplayer.so
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/home/vmware/vmplayer/vmware-player-app/lib/lib/libvmplayer.so&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/home/vmware/vmplayer/vmware-player-app/lib/lib/libvmplayer.so/libvmplayer.so&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer/vmware-player-app/lib/lib/libvmplayer.so&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ldd&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/libvmplayer.so/libvmplayer.so&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;not found&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; libview.so.2 =&amp;gt; not found&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; libcds.so =&amp;gt; not found&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; libvmwarebase.so.0 =&amp;gt; not found&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; libvmwareui.so.0 =&amp;gt; not found&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt; libgvmomi.so.0 =&amp;gt; not found&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The last command showed that &lt;code&gt;libvmplayer.so&lt;/code&gt; depends on some libraries and
that their locations are currently unknown to the system. In order to solve
this there are two things we need to do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We need to tell the system where it should search for the libraries;&lt;/li&gt;
&lt;li&gt;We need to locate these libraries and put them into a directory where the
   system will find them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To accomplish the first thing we need to create a wrapper script around
&lt;code&gt;vmplayer&lt;/code&gt; and use this script for fine-tuning later. Here is the very basic
script for this purpose (created as &lt;code&gt;~/vmrconsole/bin/loader.sh&lt;/code&gt;):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu4e354732137fc6713490e741fbd8dcc08e8d5c4b" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu4e354732137fc6713490e741fbd8dcc08e8d5c4b" class="line-numbers icon list-numbered"&gt;&lt;code class="language-bash" data-file="~/vmrconsole/bin/loader.sh"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# We must run vmplayer from the directory it resides in since all&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# relative paths are solved from there.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;ORIG_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;##*/&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;ABS_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;readlink&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$BASH_SOURCE&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;VMW_BINDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ABS_NAME&lt;/span&gt;&lt;span class="p"&gt;%/*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;VMW_LIBDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_BINDIR&lt;/span&gt;&lt;span class="s2"&gt;/../lib&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# check that directories exist, if not notify the user&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_BINDIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ERROR: cannot determine the directory where this script resides!&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# preserve the current working directory&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;OLD_PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_LIBDIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ERROR: the &amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_LIBDIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; directory does not exist!&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# resolve the library directory path (to get rid off ../ inside of it)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;VMW_LIBDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-P&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-ne&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_LIBDIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ERROR: could not resolve the &amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_LIBDIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; directory path!&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# return back since it possible that we were called as ./vmrconsole&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$OLD_PWD&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ERROR: could not return to the original &amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$OLD_PWD&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; directory!&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# we don&amp;#39;t need the following variable anymore&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;unset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;OLD_PWD
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# change the current directory to $VMW_BINDIR&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_BINDIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ERROR: failed to change directory to &amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_BINDIR&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;!&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# set the library search path so the dynamic linker will be able&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# to locate locally installed libraries.&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$VMW_LIBDIR&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="p"&gt;:+:&lt;/span&gt;&lt;span class="nv"&gt;$LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;LD_LIBRARY_PATH
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="c1"&gt;# execute the real binary and pass the supplied arguments to it&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$ORIG_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./appLoader&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This wrapper script should not be called directly, instead we need to create a
symbolic link to this wrapper, e.g. for &lt;code&gt;vmplayer&lt;/code&gt; the following should be
performed:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu56c705feb3b2ef3c3644f82cc9b50f5ee961155e" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu56c705feb3b2ef3c3644f82cc9b50f5ee961155e" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-sf&lt;span class="w"&gt; &lt;/span&gt;loader.sh&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmplayer&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since our &lt;code&gt;vmplayer&lt;/code&gt; is not a full scale VMware Player I suggest to create
another small wrapper script and name it &lt;code&gt;vmrconsole&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu4bd3c98b9c45f706ecfd119090e3fbb32582cb14" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu4bd3c98b9c45f706ecfd119090e3fbb32582cb14" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmrconsole
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;#&lt;/span&gt;!/bin/bash
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;ABS_NAME=$(readlink -e $BASH_SOURCE) || exit 1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;BINDIR=&amp;quot;${ABS_NAME%/*}&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;exec &amp;quot;$BINDIR/vmplayer&amp;quot; -h &amp;quot;$@&amp;quot;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmrconsole&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we need to populate our library directory with the needed libraries. It is
a bit tricky to describe since on different systems you will likely end up with
different sets of libraries inside our local directory. For example, on my
system I have quite a few of the libraries installed from the distribution
repositories and these versions of libraries are fresher and with many bug
fixes in comparison to the VMWare provided ones.&lt;/p&gt;
&lt;p&gt;Anyway, the general approach to install missing libraries is the following &amp;ndash;
we start with the libraries we determined as missing during our &lt;code&gt;ldd
~/vmrconsole/lib/libvmplayer.so/libvmplayer.so | fgrep 'not found'&lt;/code&gt; step (we
need to locate and copy them over to our library directory):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu725eee1c7a6d67cde0b6c02bfba615b396127b7e" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu725eee1c7a6d67cde0b6c02bfba615b396127b7e" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ldd&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/libvmplayer.so/libvmplayer.so&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;not found&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{ print $1; }&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xargs&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer&lt;span class="w"&gt; &lt;/span&gt;-type&lt;span class="w"&gt; &lt;/span&gt;f&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-execdir&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-avL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;`libview.so.2&amp;#39; -&amp;gt; `/home/vmware/vmrconsole/lib/libview.so.2&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;`libcds.so&amp;#39; -&amp;gt; `/home/vmware/vmrconsole/lib/libcds.so&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;`libvmwarebase.so.0&amp;#39; -&amp;gt; `/home/vmware/vmrconsole/lib/libvmwarebase.so.0&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;`libvmwareui.so.0&amp;#39; -&amp;gt; `/home/vmware/vmrconsole/lib/libvmwareui.so.0&amp;#39;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;`libgvmomi.so.0&amp;#39; -&amp;gt; `/home/vmware/vmrconsole/lib/libgvmomi.so.0&amp;#39;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once this is done we need to follow the following loop until there is no output
from the command listed below (in fact, on my system this step was not needed
since I had all dependencies in place already, but it is harmless to execute
this command anyway):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menub5b118cc426291e479f21a6676a5e1d8d4e96e1f" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menub5b118cc426291e479f21a6676a5e1d8d4e96e1f" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;&lt;span class="nv"&gt;LD_LIBRARY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/vmrconsole/lib&lt;span class="w"&gt; &lt;/span&gt;ldd&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;not found&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;awk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{ print $1; }&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;xargs&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer&lt;span class="w"&gt; &lt;/span&gt;-type&lt;span class="w"&gt; &lt;/span&gt;f&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-execdir&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-avL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\;&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we try to access any remote VM&amp;rsquo;s console it will be clear that some parts
are still missing:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu12b3a1528958643c60e3e975652eeccede4f87bd" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu12b3a1528958643c60e3e975652eeccede4f87bd" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;vmrconsole&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.70.31
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Failed to open file &amp;#39;/usr/lib/vmware/share/pixmaps/progress.png&amp;#39;: No such file or directory&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Failed to open file &amp;#39;/usr/lib/vmware/share/pixmaps/eula.png&amp;#39;: No such file or directory&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Failed to open file &amp;#39;/usr/lib/vmware/share/pixmaps/stream-spinner.png&amp;#39;: No such file or directory&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;Failed to open file &amp;#39;/usr/lib/vmware/share/pixmaps/stream-spinner-stopped.png&amp;#39;: No such file or directory&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;SSLLoadSharedLibrary: Failed to load library libcrypto.so.0.9.8:/home/vmplayer/vmrconsole/bin/libdir/lib/libcrypto.so.0.9.8/libcrypto.so.0.9.8: cannot open shared object file: No such file or directory&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;strace&lt;/code&gt; is our best friend here, just run the command through &lt;code&gt;strace&lt;/code&gt;,
examine the log file, and fix stuff properly:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menua1e35f475048bddeba359b00c88a7673fa82b459" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menua1e35f475048bddeba359b00c88a7673fa82b459" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;strace&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;-eopen&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;~/strace.log&lt;span class="w"&gt; &lt;/span&gt;vmrconsole&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.70.31&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;less&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;~/strace.log
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;-lr&lt;span class="w"&gt; &lt;/span&gt;/vmware&lt;span class="w"&gt; &lt;/span&gt;*
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;libcds.so&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;libgvmomi.so.0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;libvmplayer.so/libvmplayer.so&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;libvmwarebase.so.0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;libvmwareui.so.0&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;strings&lt;span class="w"&gt; &lt;/span&gt;libcds.so&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;/vmware
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/bootstrap&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware-installer/bootstrap&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware-installer/database&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/etc/vmware/,..//////etc/,&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;libcds.so&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/etc/vmware-installer/,..//////etc/installer/,&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;libcds.so&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;strings&lt;span class="w"&gt; &lt;/span&gt;libgvmomi.so.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;/vmware
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/hostd/proxy.xml&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/icu&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/rui.crt&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/rui.key&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/dh512.pem&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/dh1024.pem&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/etc/vmware/,..//////etc/,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;libgvmomi.so.0
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;strings&lt;span class="w"&gt; &lt;/span&gt;libvmplayer.so/libvmplayer.so&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;/vmware
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/lib/vmware&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bin/vmware-vmrc-daemon&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;strings&lt;span class="w"&gt; &lt;/span&gt;libvmwarebase.so.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;/vmware
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/config&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/lib/vmware&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/icu&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/CVP&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;vmauthd/vmware-authd&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/sbin/vmware-authd&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/var/run/vmware/authd_&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/var/run/vmware&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/rui.crt&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/rui.key&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/dh512.pem&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/dh1024.pem&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/var/run/vmware/usbarbitrator-socket&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/lib/vmware/settings&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/lib/vmware/config&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bin/vmware-vmx&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bin/vmware-vmx-debug&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;bin/vmware-vmx-stats&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;%&lt;/span&gt;s/vmware-speedtest-%d.tmp
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/vmware-ace/host.vmpl&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/vmware-ace/host-update.vmpl&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/etc/vmware/,..//////etc/,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;libvmwarebase.so.0&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/etc/vmware,..//////etc,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;libvmwarebase.so.0&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/usr/lib/vmware/,..//////////etc/,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;libvmwarebase.so.0&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/var/run/vmware,..//////var/run,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;libvmwarebase.so.0&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;strings&lt;span class="w"&gt; &lt;/span&gt;libvmwareui.so.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/vmware
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/lib/vmware&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;strings&lt;span class="w"&gt; &lt;/span&gt;libvmwareui.so.0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;/var/run/vmware
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/var/run/vmware/fuse/%Lu.info&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole/lib]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/var/run/vmware,..//////var/run,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;libvmwareui.so.0&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ufff, we did a lot of binary patching &amp;ndash; luckily, VMware binaries and libraries
are not calculating their checksums. Now, we need to put all this stuff we have
seen in the &lt;code&gt;strings&lt;/code&gt; output and during the execution of the program in place (in
accordance to our new relative paths):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu864b5d126d253e2f1531c9e077d44dcb90d3ebb3" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu864b5d126d253e2f1531c9e077d44dcb90d3ebb3" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;icu
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/home/vmware/vmplayer/vmware-player-app/lib/icu&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-aL&lt;span class="w"&gt; &lt;/span&gt;/home/vmware/vmplayer/vmware-player-app/lib/icu&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/etc/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-m700&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/etc/ssl
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;CVP
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-m700&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/var
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-m700&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/var/run
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;pixmaps
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/home/vmware/vmplayer/vmware-player/lib/share/pixmaps&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/home/vmware/vmplayer/vmware-player-app/lib/share/pixmaps&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-m700&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/share/pixmaps
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-aL&lt;span class="w"&gt; &lt;/span&gt;/home/vmware/vmplayer/vmware-player-app/lib/share/pixmaps/*&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/share/pixmaps/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-aL&lt;span class="w"&gt; &lt;/span&gt;/home/vmware/vmplayer/vmware-player-app/lib/share/EULA.txt&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/share/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-aL&lt;span class="w"&gt; &lt;/span&gt;/home/vmware/vmplayer/vmware-player-app/lib/share/*.ui&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/share/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-aL&lt;span class="w"&gt; &lt;/span&gt;/home/vmware/vmplayer/vmware-player-app/lib/share/icons&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/share/&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this stage we should be able to launch the &lt;code&gt;vmplayer&lt;/code&gt; program via our wrapper
script (I am launching it on a remote machine through SSH, but here I am
describing how it should look on the local console):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menuacb486bd06e0974db80b234299ec61178b85578c" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menuacb486bd06e0974db80b234299ec61178b85578c" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;~/vmrconsole/bin/vmplayer&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="VMware Player: main window" class="figure" src="https://dmitry.khlebnikov.net/images/howto-vmware-player-as-remote-console-01.jpg"&gt;&lt;/p&gt;
&lt;p&gt;So far so good, but we need to resolve the issue with the OpenSSL library
dependency. To resolve the issue we need to create symbolic links from our
local library directory to the system-wide version of the OpenSSL library:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu831263f14399d603040e80e8f67f0252c1bbdb19" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu831263f14399d603040e80e8f67f0252c1bbdb19" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/libcrypto.so.0.9.8&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/usr/lib/libssl.so.0.9.8&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-ld&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/lib/lib*.so.0.9.8&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 vmware vmware 27 Nov 24 05:27 /home/vmware/vmrconsole/lib/libcrypto.so.0.9.8 -&amp;gt; /usr/lib/libcrypto.so.0.9.8&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 vmware vmware 27 Nov 24 05:27 /home/vmware/vmrconsole/lib/libssl.so.0.9.8 -&amp;gt; /usr/lib/libssl.so.0.9.8&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&amp;rsquo;s try to connect to remote VM&amp;rsquo;s console again:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu38cab1477cc447e0548c6534d5c0d2884ec8e591" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu38cab1477cc447e0548c6534d5c0d2884ec8e591" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;vmrconsole&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.70.31&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="VMware Player: credentials window" class="figure" src="https://dmitry.khlebnikov.net/images/howto-vmware-player-as-remote-console-02.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="VMware Player: MKS error message" class="figure" src="https://dmitry.khlebnikov.net/images/howto-vmware-player-as-remote-console-03.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Well, this error message does not say much except that we are not getting our
console :). If we check the log file directory we would see there is a file
called &lt;code&gt;player-XXXXX.log&lt;/code&gt; (where XXXXX is the PID of the VMware Player that
produced this log file). Inside the log file we may see some interesting parts
like the following (I have included only those messages which are related to our
task):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menub211ec4dd96ef8c94e73b9a8f3a787fe05b173df" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menub211ec4dd96ef8c94e73b9a8f3a787fe05b173df" class="line-numbers icon list-numbered"&gt;&lt;code class="language-syslog"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:40:42.002: player| CDS error: Cannot locate VMIS, bootstrap file /etc/vmware-installer/bootstrap unavailable!
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:40:42.072: player| Unable to launch vmplayer-daemon: File does not exist.
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:40:42.072: player| Unable to find /home/vmplayer/bin/vmware-unity-helper in attempt to launch daemon.
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:04.630: player| Player dispatch: Opening VM while not connected to the daemon.
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:05.437: player| ConnectMksClient - calling VMClient_ConnectMksClientEx
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:05.437: player| VMClient_ConnectMksClientEx - trying local socket connection
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:05.444: player| Cnx_Connect: Returning false because CnxConnectAuthd failed
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:05.444: player| Cnx_Connect: Error message: Connection terminated by server
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:05.444: player| VMClient_ConnectMksClientEx - trying remote socket connection
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:05.611: player| VMClient_ConnectMksClientEx - connecting the MKS client
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:05.616: player| vmdbPipe_Streams: Couldn&amp;#39;t read
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;Nov 24 03:41:15.941: player| Gdk: losing last reference to undestroyed window&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From the messages quoted above it is quite clear that the program is trying to
launch some helper binaries/daemons in the background and to delegate the
actual remote console task to them. You can &lt;code&gt;strace&lt;/code&gt;/&lt;code&gt;ltrace&lt;/code&gt; the whole thing,
review the resulting logs and you will find that it tries to execute two helper
binaries: &lt;code&gt;vmware-authd&lt;/code&gt; and &lt;code&gt;vmware-remotemks&lt;/code&gt;, the latter is the remote
console engine, while the former is some kind of an authentication daemon and I
do not think it is needed for our purposes. I am not going to describe in
details how I arrived at the following (it is all clear once you have studied
the &lt;code&gt;strace&lt;/code&gt;/&lt;code&gt;ltrace&lt;/code&gt; log files):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menud8448c376b80966c037527855537d01debf82f68" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menud8448c376b80966c037527855537d01debf82f68" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;vmware-authd&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;-name&lt;span class="w"&gt; &lt;/span&gt;vmware-remotemks
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/home/vmware/vmplayer/vmware-player-app/lib/bin/vmware-remotemks&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/home/vmware/vmplayer/vmware-player-app/sbin/vmware-authd&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-m700&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/vmauthd
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;which&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/vmauthd/vmware-authd
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-ld&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/vmauthd/vmware-authd
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;lrwxrwxrwx 1 vmware vmware 9 Nov 24 04:15 /home/vmware/vmrconsole/vmauthd/vmware-authd -&amp;gt; /bin/true&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-aL&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer/vmware-player-app/lib/bin/vmware-remotemks&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmware-remotemks
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;strings&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmware-remotemks&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;fgrep&lt;span class="w"&gt; &lt;/span&gt;/vmware
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/lib/vmware&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/config&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/icu&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/rui.crt&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/rui.key&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/dh512.pem&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/ssl/dh1024.pem&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/etc/vmware/CVP&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;vmauthd/vmware-authd&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/usr/sbin/vmware-authd&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/var/run/vmware/authd_&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/var/run/vmware&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/var/run/vmware,..//////var/run,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmware-remotemks
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s,/etc/vmware/,..//////etc/,g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/bin/vmware-remotemks&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another attempt:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu38cab1477cc447e0548c6534d5c0d2884ec8e591" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu38cab1477cc447e0548c6534d5c0d2884ec8e591" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;vmrconsole&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.70.31&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="VMware Player: hints messages" class="figure" src="https://dmitry.khlebnikov.net/images/howto-vmware-player-as-remote-console-04.jpg"&gt;&lt;/p&gt;
&lt;p&gt;Ouch, we are presented with a bunch of hints &amp;ndash; your mileage may vary since it
depends on the environment, but you surely will see the hint/error message
presented on the above screenshot.&lt;/p&gt;
&lt;p&gt;This is not a showstopper if you are an English-only user - just click on the
OK button and you should be able to work with the console, however I think it
is just a right thing to do to fix this little bugger:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu8b9f8879b24695834f55e79a516e2d5ce4a0a9b3" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu8b9f8879b24695834f55e79a516e2d5ce4a0a9b3" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;~/vmplayer&lt;span class="w"&gt; &lt;/span&gt;-iname&lt;span class="w"&gt; &lt;/span&gt;xkeymap
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;/home/vmware/vmplayer/vmware-player-app/lib/xkeymap&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-aL&lt;span class="w"&gt; &lt;/span&gt;/home/vmware/vmplayer/vmware-player-app/lib/xkeymap&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-aL&lt;span class="w"&gt; &lt;/span&gt;/home/vmware/vmplayer/vmware-player-app/lib/vnckeymap&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole/
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;vmrconsole&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.70.31&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="VMware Player: remote console window" class="figure" src="https://dmitry.khlebnikov.net/images/howto-vmware-player-as-remote-console-05.jpg"&gt;&lt;/p&gt;
&lt;p&gt;We are almost done! At least we achieved the goal we set at the beginning of
this article.&lt;/p&gt;
&lt;p&gt;Now, it is time to ensure that all file/directory permissions are strict enough
and to create a tarball, then place it somewhere so we will be able to use our
new tool when the right time comes:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;input type="radio" name="code_menu10da6f1d9c0bc0367213f984844b152970c8e97e" class="no-line-numbers icon list-numbered"&gt;&lt;input type="radio" name="code_menu10da6f1d9c0bc0367213f984844b152970c8e97e" class="line-numbers icon list-numbered"&gt;&lt;code class="language-console"&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole]$ &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;-type&lt;span class="w"&gt; &lt;/span&gt;d&lt;span class="w"&gt; &lt;/span&gt;-execdir&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole]$ &lt;/span&gt;find&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;-type&lt;span class="w"&gt; &lt;/span&gt;f&lt;span class="w"&gt; &lt;/span&gt;-execdir&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\;&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~/vmrconsole]$ &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0700&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bin/*
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;vmrconsole&lt;span class="w"&gt; &lt;/span&gt;vmrconsole-3.1.3.324285
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;cjpf&lt;span class="w"&gt; &lt;/span&gt;~/vmrconsole-3.1.3.324285.i386.tar.bz2&lt;span class="w"&gt; &lt;/span&gt;vmrconsole-3.1.3.324285
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="gp"&gt;[user@localhost ~]$ &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-lh&lt;span class="w"&gt; &lt;/span&gt;vmrconsole-3.1.3.324285.i386.tar.bz2&lt;span class="w"&gt; &lt;/span&gt;
&lt;/div&gt;&lt;div class="line"&gt;&lt;a&gt;&lt;/a&gt;&lt;span class="go"&gt;-rw-r--r-- 1 vmware vmware 13M Nov 24 07:06 vmrconsole-3.1.3.324285.i386.tar.bz2&lt;/span&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;P.S. Oh, boy, it takes 2 hours to investigate and to come up with a solution,
then 8 hours to write an article to describe steps to reproduce! I hope that
somebody has found this article useful and I would appreciate any comments.&lt;/p&gt;</content><category term="hacking"/></entry></feed>