<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>My notes and ramblings</title><link href="https://www.shore.co.il/blog/" rel="alternate"/><link href="https://www.shore.co.il/blog/feeds/all.atom.xml" rel="self"/><id>https://www.shore.co.il/blog/</id><updated>2024-11-29T00:00:00+02:00</updated><entry><title>My experience with security researchers</title><link href="https://www.shore.co.il/blog/security-researchers/" rel="alternate"/><published>2024-11-29T00:00:00+02:00</published><updated>2024-11-29T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2024-11-29:/blog/security-researchers/</id><summary type="html">&lt;p class="first last"&gt;My experience with security researchers.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been sitting on this blog post for a long while. I have a history of
working with (so called) security researchers that I would describe as poor. I
don't want to besmirch the profession of security research. I enjoy reading
security research write ups, I follow a lot of the security best practices, I
subscribe to the security mailing lists of the OSes I use and overall have high
regard for the professionals in the field.&lt;/p&gt;
&lt;p&gt;On the other hand, over my career I have many different interactions with
security companies and researchers working in those companies that have all
been bad. I worked at a cyber security company along side security researchers
from the IDF's 8200 unit. I received notices on security vulnerabilities on
sites I or the companies I work at run (especially after publishing a
&lt;tt class="docutils literal"&gt;security.txt&lt;/tt&gt; policy). But best of all is my experience with the Israeli
National Cyber Directorate. Let me get started.&lt;/p&gt;
&lt;div class="section" id="security-audits-and-certifications"&gt;
&lt;h2&gt;Security audits and certifications&lt;/h2&gt;
&lt;p&gt;A few of the companies I worked at went through security audits to get a
certification (SOC2 or HIPAA). As the person responsible for the
infrastructure and our CI/CD pipelines I was a part of the audit from beginning
to end and when the audit report was delivered, I addressed some of the
findings. From the few audits I took part in, I can say that the worst was a
company that ran a few automated scanners in the vein of &lt;a class="reference external" href="https://www.ssllabs.com/ssltest/"&gt;SSL Test&lt;/a&gt; and the better ones ran something akin to
&lt;a class="reference external" href="https://github.com/semgrep/semgrep"&gt;Semgrep&lt;/a&gt; and maybe checking the OWASP
top 10.&lt;/p&gt;
&lt;p&gt;All of the audits I've been part of had not produced any worthwhile results.
No actual vulnerabilities were ever found and most the time a few publicly
available security scanners were used (the screenshot from the SSL Test is
still vivid in my mind).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="working-with-security-researchers"&gt;
&lt;h2&gt;Working with security researchers&lt;/h2&gt;
&lt;p&gt;I worked at a cyber security company with an actual cyber security product.
There we had a security research team with people from the IDF 8200 unit. From
my dealings with them, they have poor knowledge of things you would expect (on
the level of not knowing the difference between symmetrics and asymmetric
encryption) and their research can boiled down to running Nmap and Metasploit.&lt;/p&gt;
&lt;p&gt;When one of them learned that I run my own mail server, he claimed to be able
to break in to my server. I said go for it, hoping to learn something new and
fix whatever vulnerability my server may have. Looking over his shoulder, I saw
that he was running Metasploit with a preset for mail servers. Having found
nothing (not because I'm that good, I just install security updates and have
sane settings) he turned quiet.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-israeli-national-cyber-directorate"&gt;
&lt;h2&gt;The Israeli National Cyber Directorate&lt;/h2&gt;
&lt;p&gt;I saved the best for last, the reason I felt the urge to write this post. Over
the last 3 or 4 years I was contacted 3 times by the INCD to let me know of
vulnerabilities they found in my personal sites and services.&lt;/p&gt;
&lt;p&gt;The first time I was contacted by phone. I was a little surprised and took the
matter seriously. I was told that my mail server had an RCE. Asking for
details, I was told the CVE and the person on the other end explained to me
that I need to update my mail server. I quickly checked the CVE and I found
that Debian had backported the patch but the server version stayed the same (or
maybe some suffix was added, I don't remember). I tried to explain that I had a
patched server but it fell on deaf ears and they were adamant that the version
I was using vulnerable and I had to update ASAP. I thanked them for letting me
know and promised to look in to it.&lt;/p&gt;
&lt;p&gt;The second time I was again contacted by phone. This time I was less surprised.
I was told that my GitLab instance was misconfigured, although it required
logging in, repositories were exposed through the &lt;a class="reference external" href="https://git.shore.co.il/explore"&gt;/explore&lt;/a&gt; URL. I explained that it was deliberate,
that I develop opensource software and that is were I store it and make it
available for others (if you take a look, all of the repositories have an
opensource license and my blog even links to them). Again, it didn't convince
the person on the other side. I thanked them for letting me know and promised
to look in to it.&lt;/p&gt;
&lt;p&gt;The third time I was again contacted by phone. This time I was not a bit
surprised. I was told that my SSH server is vulnerable and I have to update it.
I explained that I am running OpenSSH on an OpenBSD machine and that the
vulnerability in question only happens on Linux machines. The person on the
other end didn't know what OpenBSD is (I tried explaining that the developers
of OpenBSD also develop OpenSSH, they didn't seem to get it). Showing my age, I
complained that this is a waste of the taxes I pay. The person on the other end
didn't appreciate it and ended the call.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="closing-thoughts"&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;When I was growing up and the internet was becoming accessible to everyone a
new phenomenon named script kiddies started. People scanning ports, open
Windows shares and guessing SSH usernames and passwords. Then somebody got the
bright idea of making a career out of it by selling people some scary stories
and exaggerating their own capabilities and calling it security research. While
true that there are unpatched and vulnerable machines on the internet, this is
not security research and because I have sensible security practices I only
encountered false positives due to rudimentary scanners flagging my servers as
vulnerable without checking if they are indeed vulnerable.&lt;/p&gt;
&lt;p&gt;I don't remember which company it was, but I remember one such company had an
realtime map of the internet showing realtime attacks. Looking closely, each
ping and each new connection to port 22 was an attack. The field is now filled
with charlatans that instead of trying to break in to your servers now try to
bill you for running Nmap or verifying your DMARC record. They've turned this
in a very successful industry and the Israeli government seems to have fallen
to this trap as well (as I'm pretty sure other goverments have as well).&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Copy a drive on a running machine</title><link href="https://www.shore.co.il/blog/onlide-disk-copy/" rel="alternate"/><published>2024-06-08T00:00:00+03:00</published><updated>2024-06-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2024-06-08:/blog/onlide-disk-copy/</id><summary type="html">&lt;p class="first last"&gt;Copying from an old drive to a new drive while the machine is running&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I was close to running out of disk space on the homelab machine I use for
running services with personal data that I run in my home (I feel more
comfortable having physical control). I was too lazy to connect the machine to
a monitor and keyboard and reboot from a thumbdrive so I tried to copy the old
drive to a new drive while the machine was running. Here's what I did:&lt;/p&gt;
&lt;ol class="arabic"&gt;
&lt;li&gt;&lt;p class="first"&gt;Stop running processes that have open files. For me it was stopping Docker
containers, some services (I have most things running in containers so most
of the services are ones that come with a standard OS installation).&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
docker&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;ls&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;'NR&amp;gt;1 {print $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;xargs&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;stop&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;stop&lt;span class="w"&gt; &lt;/span&gt;fwupd.service&lt;span class="w"&gt; &lt;/span&gt;systemd-timesyncd.service&lt;span class="w"&gt; &lt;/span&gt;docker.service&lt;span class="w"&gt; &lt;/span&gt;docker.socket&lt;span class="w"&gt; &lt;/span&gt;containerd.service&lt;span class="w"&gt; &lt;/span&gt;cron.service&lt;span class="w"&gt; &lt;/span&gt;systemd-journald.service&lt;span class="w"&gt; &lt;/span&gt;systemd-journald.socket&lt;span class="w"&gt; &lt;/span&gt;systemd-journald-dev-log.socket
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;&lt;code&gt;cd&lt;/code&gt; to a directory on a different drive to avoid keeping the path
open (&lt;code&gt;cd /tmp&lt;/code&gt;). Remount all partitions as read-only: &lt;code&gt;sudo mount
--all -o remount,ro -t vfat,btrfs,ext4&lt;/code&gt;. This may fail and you will see an
error with a path that couldn't be remounted due to open files. Run
&lt;code&gt;lsof /path/that/has/open/files&lt;/code&gt;, find the processes that still have
open files and either stop that service or kill the process (stopping the
service is better as it won't get restarted). Repeat until the partitions
are mounted read-only. Verify with the &lt;code&gt;mount&lt;/code&gt; command. Run
&lt;code&gt;sudo sync&lt;/code&gt; and now you should be able to copy the drive as there
won't writes to it while copying and the copy will be consistent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Start a session on the host using &lt;code&gt;tmux&lt;/code&gt; and copy
&lt;code&gt;dd if=/dev/sda of=/dev/nvme0n1&lt;/code&gt;. Now we wait.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Once the copy is complete, run &lt;code&gt;sudo sync&lt;/code&gt; again and &lt;code&gt;echo 1 |
sudo tee /sys/block/nvme0n1/device/rescan&lt;/code&gt;. Now we need to resize the data
partition, &lt;code&gt;sudo parted /dev/nvme0n1 --script resizepart 3 100%&lt;/code&gt; (in
my case the data partition was the 3rd partition after the EFI and root
partitions). Open the encrypted partition &lt;code&gt;sudo cryptsetup open
/dev/nvme0n1p3 _dev_nvme0n1p2&lt;/code&gt; and resize the encrypted partition
&lt;code&gt;sudo cryptsetup resize _dev_nvme0n1p3&lt;/code&gt;. Last resize is the filesystem
in the encrypted partition, we mount it &lt;code&gt;sudo mount
/dev/mapper/_dev_nvme0n1p3 /mnt&lt;/code&gt; and resize it &lt;code&gt;sudo btrfs filesystem
resize max /mnt&lt;/code&gt;. Umount the data partition &lt;code&gt;sudo umount /mnt&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Let's reinstall the boot loader.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;/dev/nvme0n1p2&lt;span class="w"&gt; &lt;/span&gt;/mnt&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;/dev/nvme0n1p1&lt;span class="w"&gt; &lt;/span&gt;/mnt/boot/efi&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;dir&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;dev&lt;span class="w"&gt; &lt;/span&gt;proc&lt;span class="w"&gt; &lt;/span&gt;sys&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;--bind&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;done&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chroot&lt;span class="w"&gt; &lt;/span&gt;/mnt&lt;span class="w"&gt;
&lt;/span&gt;grub-install&lt;span class="w"&gt; &lt;/span&gt;/dev/nvme0n1&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="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dir&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;dev&lt;span class="w"&gt; &lt;/span&gt;proc&lt;span class="w"&gt; &lt;/span&gt;sys&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/mnt/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&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;done&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/mnt/boot/efi&lt;span class="w"&gt;
&lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/mnt
&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p class="first"&gt;Because we copied the existing partitions, their UUIDs will remain the same
so there's no need to update mounts. Stop the machine, remove the old drive
and boot. With a little luck the UEFI BIOS will not need configuration to
use the new drive and the machine will boot happily.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content><category term="misc"/></entry><entry><title>PEP 621 and Setuptools</title><link href="https://www.shore.co.il/blog/pep-621-and-setuptools/" rel="alternate"/><published>2022-04-28T00:00:00+03:00</published><updated>2022-04-28T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2022-04-28:/blog/pep-621-and-setuptools/</id><summary type="html">&lt;p class="first last"&gt;PEP 621 and Setuptools&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In the world of Python packaging, the trend is moving to using
&lt;code&gt;pyproject.toml&lt;/code&gt; to store all of the package's metadata as well as
settings for different tools. Until recently, this trend meant adding another
tool like &lt;a class="reference external" href="https://python-poetry.org/"&gt;Poetry&lt;/a&gt;, &lt;a class="reference external" href="https://flit.pypa.io/"&gt;Flit&lt;/a&gt; or &lt;a class="reference external" href="https://pdm.fming.dev/"&gt;PDM&lt;/a&gt; instead of using
the regular Setuptools (and maybe &lt;a class="reference external" href="https://pypa-build.readthedocs.io/"&gt;build&lt;/a&gt;).  But with Setuptools 61.0.0, you can
still use Setuptools and have a modern Python package.&lt;/p&gt;
&lt;div class="section" id="why"&gt;
&lt;h2&gt;Why?&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Avoid having a plethora of Python package managers installed. This is more
noticeable when some are wrappers or replacements of virtualenv and then you
need to install them globally.&lt;/li&gt;
&lt;li&gt;With Setuptools you keep close to the state of &lt;a class="reference external" href="https://peps.python.org/pep-0621/"&gt;PEP 621&lt;/a&gt; and your &lt;code&gt;pyproject.toml&lt;/code&gt; generic&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="why-not"&gt;
&lt;h2&gt;Why not?&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;You still need to manage virtual environments manually.&lt;/li&gt;
&lt;li&gt;You need a very recent version of Setuptools. At the time of writing this
post I didn't find a distribution that had a new enough version of Setuptools
packaged.&lt;/li&gt;
&lt;li&gt;You need a supported version of Python. Currently this means Python 3.7 or
later.&lt;/li&gt;
&lt;li&gt;You can get very close with an equivalent &lt;code&gt;setup.cfg&lt;/code&gt; and get backwards
compatibility.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="how"&gt;
&lt;h2&gt;How?&lt;/h2&gt;
&lt;p&gt;I created a &lt;a class="reference external" href="https://git.shore.co.il/nimrod/samplepyproject"&gt;sample Python project&lt;/a&gt; that has everything you need
to get started. It's tested to make sure a valid and working package, that
editable installation work, etc. You can skip directly to the &lt;a class="reference external" href="https://git.shore.co.il/nimrod/samplepyproject/-/blob/main/pyproject.toml"&gt;pyproject.toml&lt;/a&gt;
file.&lt;/p&gt;
&lt;p&gt;A few notes. The only part that's Setuptools specific is specifying the
attribute for getting the version dynamically. You can specify the version
explicitly and be totally generic. The other settings that can be set
dynamically need to be read from other files which isn't great. I would like to
see the description be an attribute like version so I can point it to the
docstring like Flit does. For editable installations you still need a
&lt;code&gt;setup.py&lt;/code&gt; file but it's a 2 line file that calls &lt;code&gt;setup()&lt;/code&gt;. You
don't need a &lt;code&gt;setup.cfg&lt;/code&gt; file at all.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="looking-forward"&gt;
&lt;h2&gt;Looking forward&lt;/h2&gt;
&lt;p&gt;I'm pretty sure that once PEP 621 is approved, other package managers will
adapt and tool specific configuration will be dropped. Also, support for using
&lt;code&gt;pyproject.toml&lt;/code&gt; in Setuptools will stop being experimental. I think that
once that's done and the only difference will be the 2 lines specifying the
build system we will see projects that want easier onboarding adopt Setuptools
to avoid forcing more tools on users. Over time distributions will package
newer versions of Setuptools and you will be able to have a working Python
environment just with the distribution packages.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="appendix-pep-582"&gt;
&lt;h2&gt;Appendix - PEP 582&lt;/h2&gt;
&lt;p&gt;Another PEP that's yet to be approved is &lt;a class="reference external" href="https://peps.python.org/pep-0582/"&gt;PEP 582&lt;/a&gt;. This propsal is for  a package directory
that's local to a project (in the spirit of &lt;code&gt;node_modules&lt;/code&gt;). The reason I
mention it here is that this removes the need for virtual environments (and
thus the other major selling point for the different package managers). Now,
while it's not been approved and there's no version of Python that supports it
out of the box, we can mimic it with a little creative shell scripting:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nv"&gt;PYTHON_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'from sys import version_info as v; print(f"{v[0]}.{v[1]}")'&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&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;PYTHONPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/__pypackages__/&lt;/span&gt;&lt;span class="nv"&gt;$PYTHON_VERSION&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PYTHONPATH&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&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="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/__pypackages__/&lt;/span&gt;&lt;span class="nv"&gt;$PYTHON_VERSION&lt;/span&gt;&lt;span class="s2"&gt;/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;With the above snippet we point Python to load packages from the
&lt;code&gt;__pypackages__&lt;/code&gt; directory and add the &lt;code&gt;bin&lt;/code&gt; directory to the
&lt;code&gt;PATH&lt;/code&gt; so executables are available. Lastly to install packages in that
location we run &lt;code&gt;python3 -m pip install -t
"__pypackages__/$PYTHON_VERSION" foo&lt;/code&gt;. I use &lt;a class="reference external" href="https://direnv.net/"&gt;direnv&lt;/a&gt;
quite a lot and I have the following snippet in a few projects:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nv"&gt;PYTHON_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'from sys import version_info as v; print(f"{v[0]}.{v[1]}")'&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&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;PYTHONPATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/__pypackages__/&lt;/span&gt;&lt;span class="nv"&gt;$PYTHON_VERSION&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PYTHONPATH&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&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="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/__pypackages__/&lt;/span&gt;&lt;span class="nv"&gt;$PYTHON_VERSION&lt;/span&gt;&lt;span class="s2"&gt;/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"__pypackages__/&lt;/span&gt;&lt;span class="nv"&gt;$PYTHON_VERSION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/pre&gt;
&lt;p&gt;This performs an editable installation of the package and all it dependencies,
so from that moment on I'm set and I don't any other package manager or even a
virtual environment.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Setting up my ns4 instance</title><link href="https://www.shore.co.il/blog/setting-up-ns4/" rel="alternate"/><published>2022-02-26T00:00:00+02:00</published><updated>2022-02-26T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2022-02-26:/blog/setting-up-ns4/</id><summary type="html">&lt;p class="first last"&gt;Setting up ns4.shore.co.il on online.net&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is one of the posts that is more of me writing what I did so I can do it
again later and isn't geared toward the general public, you've been warned. A
little bit of background. The instance in question is my bare-metal Dedibox
instance. I use it for the following:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://git.shore.co.il/shore/secondary-dns-docker"&gt;Secondary DNS instance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://git.shore.co.il/shore/registry-docker"&gt;Docker registry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Some static web sites.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://git.shore.co.il/shore/gitlab-runner-docker"&gt;GitLab CI runner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Remote &lt;a class="reference external" href="https://git.shore.co.il/shore/workbench"&gt;Workbench&lt;/a&gt; instance.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A bare-metal instance is very performant, allows me to run KVM and VirtualBox
VMs and I feel a little better on the security front. Although I can due a
remote KVM install of Debian, so far I've been using the online installation.
This makes me wonder how stock is the installation and what I'll encounter if I
do an in-place upgrade of Debian to a new release. The only data I care about
are the images in the registry and they are backed up weekly to a machine in my
house that I have off-site backups for. &lt;tt class="docutils literal"&gt;/var&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;/home&lt;/tt&gt; are subvolumes
in a btrfs volume in a LUKS encrypted partition. I enter the encryption key
using KVM over IP. I have some private information but nothing I can't just git
clone back to existence. I acknowledge that it's not as secure as can be but
it's good enough for me and I judge the tradeoffs to be valid.&lt;/p&gt;
&lt;p&gt;Everything listed above is deployed using GitLab CI pipelines but there's a
chicken and egg situation here that some of the services depend on each other
and at the very least I can't deploy the GitLab runner using GitLab pipelines.
So here's what I did.&lt;/p&gt;
&lt;div class="section" id="os-installation"&gt;
&lt;h2&gt;OS installation&lt;/h2&gt;
&lt;p&gt;OS installation takes ~15 minutes but afterwards KVM over IP is disabled for
another hour. I work around this limitation by opening the KVM session first
and then go ahead with the OS installation. In the online.net console, install
Debian using the wizard. It's pretty standard stuff but the partitioning
default to a RAID-1 setup which I don't want. Instead have &lt;tt class="docutils literal"&gt;/boot/efi&lt;/tt&gt; at 512
MB, &lt;tt class="docutils literal"&gt;/boot&lt;/tt&gt; at 2048 MB and &lt;tt class="docutils literal"&gt;/&lt;/tt&gt; using the rest on the first drive (the order
is important as changes don't boot for some reason I'm not interested enough to
find). Because I must have a partition on each drive, keep &lt;tt class="docutils literal"&gt;/data&lt;/tt&gt; on the
second using all of the space.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bootstrapping"&gt;
&lt;h2&gt;Bootstrapping&lt;/h2&gt;
&lt;p&gt;Remove the existing SSH host key:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;ns4.shore.co.il&lt;span class="w"&gt;
&lt;/span&gt;ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-R&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;dig&lt;span class="w"&gt; &lt;/span&gt;ns4.shore.co.il&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Connect using KVM as root and setup sudo.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
apt&lt;span class="w"&gt; &lt;/span&gt;update&lt;span class="w"&gt;
&lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;sudo&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="s1"&gt;'nimrod ALL=(ALL) NOPASSWD: ALL'&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;tee&lt;span class="w"&gt; &lt;/span&gt;/etc/sudoers.d/nimrod
&lt;/pre&gt;
&lt;p&gt;Now SSH as normal and check sudo (&lt;tt class="docutils literal"&gt;sudo whoami&lt;/tt&gt;). Now remove the passwords and
the SSH public key from the root account.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;passwd&lt;span class="w"&gt; &lt;/span&gt;--delete&lt;span class="w"&gt; &lt;/span&gt;nimrod&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;passwd&lt;span class="w"&gt; &lt;/span&gt;--delete&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;/root/.ssh
&lt;/pre&gt;
&lt;p&gt;Setting up the encrypted partition.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/data&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;--dir&lt;span class="w"&gt; &lt;/span&gt;/data&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &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;'#/data#d'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/fstab&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;btrfs-progs&lt;span class="w"&gt; &lt;/span&gt;cryptsetup&lt;span class="w"&gt; &lt;/span&gt;parted&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;parted&lt;span class="w"&gt; &lt;/span&gt;/dev/sdb&lt;span class="w"&gt; &lt;/span&gt;--script&lt;span class="w"&gt; &lt;/span&gt;mklabel&lt;span class="w"&gt; &lt;/span&gt;gpt&lt;span class="w"&gt; &lt;/span&gt;mkpart&lt;span class="w"&gt; &lt;/span&gt;primary&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;100&lt;/span&gt;%&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;cryptsetup&lt;span class="w"&gt; &lt;/span&gt;luksFormat&lt;span class="w"&gt; &lt;/span&gt;/dev/sdb1&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;cryptsetup&lt;span class="w"&gt; &lt;/span&gt;luksOpen&lt;span class="w"&gt; &lt;/span&gt;/dev/sdb1&lt;span class="w"&gt; &lt;/span&gt;data&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mkfs&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;/dev/mapper/data&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mount&lt;span class="w"&gt; &lt;/span&gt;/dev/mapper/data&lt;span class="w"&gt; &lt;/span&gt;/mnt&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;subvolume&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;/mnt/builds&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;subvolume&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;/mnt/home&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;btrfs&lt;span class="w"&gt; &lt;/span&gt;subvolume&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;/mnt/var&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/builds&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;lsblk&lt;span class="w"&gt; &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;UUID&lt;span class="w"&gt; &lt;/span&gt;--list&lt;span class="w"&gt; &lt;/span&gt;--noheadings&lt;span class="w"&gt; &lt;/span&gt;--nodeps&lt;span class="w"&gt; &lt;/span&gt;/dev/sdb1&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&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;"data UUID=&lt;/span&gt;&lt;span class="nv"&gt;$UUID&lt;/span&gt;&lt;span class="s2"&gt; none luks,discard"&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;sudo&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/etc/crypttab&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;"/dev/mapper/data /builds btrfs defaults,compress=zstd,discard,relatime,subvol=builds"&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;sudo&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/etc/fstab&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;"/dev/mapper/data /home btrfs defaults,compress=zstd,discard,relatime,subvol=home"&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;sudo&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/etc/fstab&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;"/dev/mapper/data /var btrfs defaults,compress=zstd,discard,relatime,subvol=var"&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;sudo&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;/etc/fstab&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;--archive&lt;span class="w"&gt; &lt;/span&gt;/home/*&lt;span class="w"&gt; &lt;/span&gt;/mnt/home/&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;--archive&lt;span class="w"&gt; &lt;/span&gt;/var/*&lt;span class="w"&gt; &lt;/span&gt;/mnt/var/&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/mnt&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;cryptsetup&lt;span class="w"&gt; &lt;/span&gt;luksClose&lt;span class="w"&gt; &lt;/span&gt;data&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;systemc&lt;span class="w"&gt; &lt;/span&gt;reboot&lt;span class="w"&gt; &lt;/span&gt;--now
&lt;/pre&gt;
&lt;p&gt;The instance will now reboot, enter the encryption key in the KVM console. From
the &lt;tt class="docutils literal"&gt;homelab&lt;/tt&gt; repository:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./Ansible&lt;span class="w"&gt;
&lt;/span&gt;ansible-playbook&lt;span class="w"&gt; &lt;/span&gt;bootstrap.yaml&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;ns4&lt;span class="w"&gt;
&lt;/span&gt;ansible-playbook&lt;span class="w"&gt; &lt;/span&gt;debian_server.yaml&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;ns4
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="services"&gt;
&lt;h2&gt;Services&lt;/h2&gt;
&lt;p&gt;From the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;secondary-dns-docker&lt;/span&gt;&lt;/tt&gt; repository:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;ns4.shore.co.il&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt;
&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt;
&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d
&lt;/pre&gt;
&lt;p&gt;Same as above, from the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;web-proxy-docker&lt;/span&gt;&lt;/tt&gt; repository, branch &lt;tt class="docutils literal"&gt;ns4&lt;/tt&gt;. Some
secret environment variables are set in the GitLab pipeline, so for them to
exist re-run the deploy job in GitLab afterwards.&lt;/p&gt;
&lt;p&gt;From the &lt;tt class="docutils literal"&gt;homelab&lt;/tt&gt; repository:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./Ansible&lt;span class="w"&gt;
&lt;/span&gt;ansible-playbook&lt;span class="w"&gt; &lt;/span&gt;renew-certs.yaml&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;ns4
&lt;/pre&gt;
&lt;p&gt;Setup the Docker registry, but the &lt;tt class="docutils literal"&gt;cron&lt;/tt&gt; container uses an image from the
registry. Start just the registry container and afterwards run the GitLab
deploy job. From the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;registry-docker&lt;/span&gt;&lt;/tt&gt; repository:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;ns4.shore.co.il&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt; &lt;/span&gt;registry&lt;span class="w"&gt;
&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;registry&lt;span class="w"&gt;
&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;registry.shore.co.il/registry-backup&lt;span class="w"&gt; &lt;/span&gt;./backup
&lt;/pre&gt;
&lt;p&gt;Copying the registry backup to ns4 (it's going to take a long while).&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
scp&lt;span class="w"&gt; &lt;/span&gt;-3Cr&lt;span class="w"&gt; &lt;/span&gt;host01.shore.co.il:/var/backups/registry&lt;span class="w"&gt; &lt;/span&gt;ns4.shore.co.il:/home/nimrod/
&lt;/pre&gt;
&lt;p&gt;On ns4:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--rm&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;nobody&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;/home/nimrod/registry:/registry:ro&lt;span class="w"&gt; &lt;/span&gt;registry.shore.co.il/registry-backup&lt;span class="w"&gt; &lt;/span&gt;restore&lt;span class="w"&gt; &lt;/span&gt;/registry&lt;span class="w"&gt; &lt;/span&gt;registry.shore.co.il&lt;span class="w"&gt;
&lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;/home/nimrod/registry
&lt;/pre&gt;
&lt;p&gt;From the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;gitlab-runner-docker&lt;/span&gt;&lt;/tt&gt; repository (delete the old runner in the
&lt;a class="reference external" href="https://git.shore.co.il/admin/runners"&gt;GitLab admin area&lt;/a&gt;):&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;ns4.shore.co.il&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;pull&lt;span class="w"&gt;
&lt;/span&gt;./deploy&lt;span class="w"&gt; &lt;/span&gt;ns4
&lt;/pre&gt;
&lt;p&gt;From here on out it's just running GitLab jobs and pipelines for the static web
sites. etc. &lt;strong&gt;Remember to run the deploy jobs for web-proxy-docker and
registry-docker&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="remote-workbench"&gt;
&lt;h2&gt;Remote workbench&lt;/h2&gt;
&lt;p&gt;Follow the instructions in the &lt;a class="reference external" href="https://git.shore.co.il/nimrod/rcfiles/"&gt;rcfiles repository&lt;/a&gt; and finish it off with &lt;tt class="docutils literal"&gt;wb &lt;span class="pre"&gt;-u&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="closing-thoughts"&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;Along with my previous &lt;a class="reference external" href="https://www.shore.co.il/blog/openbsd-on-sg-2440/"&gt;post on my OpenBSD machine&lt;/a&gt; covers the more involved
parts of &lt;a class="reference external" href="https://www.shore.co.il/blog/homelab-early-2021/"&gt;my infrastructure&lt;/a&gt;. Overall, I think that I'm
in a pretty good state here. My setup is cost conscious, relies very little on
external services and is more pets than cattle. The coverage of infrastructure
code is high and the parts that aren't covered are done rarely (like
reinstalling OpenBSD which I do about once a year or reinstalling Debian which
I do about every 2 years) and are either trivial or nicely documented.&lt;/p&gt;
&lt;p&gt;Going forward, I'm in the process of merging the different *-docker
repositories in to the &lt;a class="reference external" href="https://git.shore.co.il/shore/homelab"&gt;homelab repository&lt;/a&gt;. Once done, I may copy the blog posts
there and maintain them ongoing as changes are done.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Installing OpenBSD on Netgate's SG-2440</title><link href="https://www.shore.co.il/blog/openbsd-on-sg-2440/" rel="alternate"/><published>2021-09-19T00:00:00+03:00</published><updated>2021-09-19T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-09-19:/blog/openbsd-on-sg-2440/</id><summary type="html">&lt;p class="first last"&gt;Installing OpenBSD on Netgate's SG-2440&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is documentation on how to install OpenBSD (in this case 6.9 but the
procedure hasn't changed for as long as I can remember). Since the SG-2400 only
has a serial connection (no monitor output), about half of the is over the
serial console and the rest is over SSH. This post is for me to help me remember
what did I do last time.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Setting up the serial console&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On the laptop:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;screen&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;modprobe&lt;span class="w"&gt; &lt;/span&gt;cp210x&lt;span class="w"&gt;
&lt;/span&gt;dmesg&lt;span class="w"&gt; &lt;/span&gt;--follow
&lt;/pre&gt;
&lt;p&gt;Now connect the cable and watch the &lt;code&gt;dmesg&lt;/code&gt; output to see the serial
connection being added (should be at &lt;code&gt;/dev/ttyUSB0&lt;/code&gt;) and then:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;screen&lt;span class="w"&gt; &lt;/span&gt;/dev/ttyUSB0&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;115200&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Insert the USB drive with the installer and reboot (&lt;code&gt;shutdown -r now&lt;/code&gt;).
To enable the serial connection in the installer, in the boot prompt run the
following commands:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
stty&lt;span class="w"&gt; &lt;/span&gt;com1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;115200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tty&lt;span class="w"&gt; &lt;/span&gt;com1&lt;span class="w"&gt;
&lt;/span&gt;boot
&lt;/pre&gt;
&lt;p&gt;The interactive installer prompts and answers:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
Terminal&lt;span class="w"&gt; &lt;/span&gt;type?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;vt220&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;System&lt;span class="w"&gt; &lt;/span&gt;hostname?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;short&lt;span class="w"&gt; &lt;/span&gt;form,&lt;span class="w"&gt; &lt;/span&gt;e.g.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'foo'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ns1&lt;span class="w"&gt;
&lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;network&lt;span class="w"&gt; &lt;/span&gt;interfaces&lt;span class="w"&gt; &lt;/span&gt;are:&lt;span class="w"&gt; &lt;/span&gt;em0&lt;span class="w"&gt; &lt;/span&gt;em1&lt;span class="w"&gt; &lt;/span&gt;em2&lt;span class="w"&gt; &lt;/span&gt;em3&lt;span class="w"&gt; &lt;/span&gt;em4&lt;span class="w"&gt; &lt;/span&gt;em5&lt;span class="w"&gt; &lt;/span&gt;vlan0.&lt;span class="w"&gt;
&lt;/span&gt;Which&lt;span class="w"&gt; &lt;/span&gt;network&lt;span class="w"&gt; &lt;/span&gt;interface&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;wish&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;configure?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'done'&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;em0&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;em5&lt;span class="w"&gt;
&lt;/span&gt;IPv4&lt;span class="w"&gt; &lt;/span&gt;address&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;em5?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'dhcp'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'none'&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;dhcp&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.3.1&lt;span class="w"&gt;
&lt;/span&gt;Netmask&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;em5?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;.255.255.0&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;IPv6&lt;span class="w"&gt; &lt;/span&gt;address&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;em5?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'autoconf'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'none'&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;none&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;network&lt;span class="w"&gt; &lt;/span&gt;interfaces&lt;span class="w"&gt; &lt;/span&gt;are:&lt;span class="w"&gt; &lt;/span&gt;em0&lt;span class="w"&gt; &lt;/span&gt;em1&lt;span class="w"&gt; &lt;/span&gt;em2&lt;span class="w"&gt; &lt;/span&gt;em3&lt;span class="w"&gt; &lt;/span&gt;em4&lt;span class="w"&gt; &lt;/span&gt;em5&lt;span class="w"&gt; &lt;/span&gt;vlan0.&lt;span class="w"&gt;
&lt;/span&gt;Which&lt;span class="w"&gt; &lt;/span&gt;network&lt;span class="w"&gt; &lt;/span&gt;interface&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;wish&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;configure?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'done'&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="k"&gt;done&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Default&lt;span class="w"&gt; &lt;/span&gt;IPv4&lt;span class="w"&gt; &lt;/span&gt;route?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;IPv4&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;none&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;A&lt;span class="w"&gt; &lt;/span&gt;response&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;required.&lt;span class="w"&gt;
&lt;/span&gt;Default&lt;span class="w"&gt; &lt;/span&gt;IPv4&lt;span class="w"&gt; &lt;/span&gt;route?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;IPv4&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;none&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;none&lt;span class="w"&gt;
&lt;/span&gt;DNS&lt;span class="w"&gt; &lt;/span&gt;domain&lt;span class="w"&gt; &lt;/span&gt;name?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;e.g.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'example.com'&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;my.domain&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;shore.co.il&lt;span class="w"&gt;
&lt;/span&gt;DNS&lt;span class="w"&gt; &lt;/span&gt;nameservers?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;IP&lt;span class="w"&gt; &lt;/span&gt;address&lt;span class="w"&gt; &lt;/span&gt;list&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'none'&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;none&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;.9.9.9&lt;span class="w"&gt;

&lt;/span&gt;Password&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;root&lt;span class="w"&gt; &lt;/span&gt;account?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Password&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;root&lt;span class="w"&gt; &lt;/span&gt;account?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;again&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;The&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;password&lt;span class="w"&gt; &lt;/span&gt;must&lt;span class="w"&gt; &lt;/span&gt;be&lt;span class="w"&gt; &lt;/span&gt;set.&lt;span class="w"&gt;
&lt;/span&gt;Password&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;root&lt;span class="w"&gt; &lt;/span&gt;account?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Password&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;root&lt;span class="w"&gt; &lt;/span&gt;account?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;again&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Start&lt;span class="w"&gt; &lt;/span&gt;sshd&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;default?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;yes&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;the&lt;span class="w"&gt; &lt;/span&gt;default&lt;span class="w"&gt; &lt;/span&gt;console&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;com1?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;yes&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;speeds&lt;span class="w"&gt; &lt;/span&gt;are:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;19200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;38400&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;57600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;115200&lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;Which&lt;span class="w"&gt; &lt;/span&gt;speed&lt;span class="w"&gt; &lt;/span&gt;should&lt;span class="w"&gt; &lt;/span&gt;com1&lt;span class="w"&gt; &lt;/span&gt;use?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'done'&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="m"&gt;115200&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Setup&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;user?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;enter&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;lower-case&lt;span class="w"&gt; &lt;/span&gt;loginname,&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'no'&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;no&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;nimrod&lt;span class="w"&gt;
&lt;/span&gt;Full&lt;span class="w"&gt; &lt;/span&gt;name&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;user&lt;span class="w"&gt; &lt;/span&gt;nimrod?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;nimrod&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Nimrod&lt;span class="w"&gt; &lt;/span&gt;Adar&lt;span class="w"&gt;
&lt;/span&gt;Password&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;user&lt;span class="w"&gt; &lt;/span&gt;nimrod?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;will&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Password&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;user&lt;span class="w"&gt; &lt;/span&gt;nimrod?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;again&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;WARNING:&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;targeted&lt;span class="w"&gt; &lt;/span&gt;by&lt;span class="w"&gt; &lt;/span&gt;password&lt;span class="w"&gt; &lt;/span&gt;guessing&lt;span class="w"&gt; &lt;/span&gt;attacks,&lt;span class="w"&gt; &lt;/span&gt;pubkeys&lt;span class="w"&gt; &lt;/span&gt;are&lt;span class="w"&gt; &lt;/span&gt;safer.&lt;span class="w"&gt;
&lt;/span&gt;Allow&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;login?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;yes,&lt;span class="w"&gt; &lt;/span&gt;no,&lt;span class="w"&gt; &lt;/span&gt;prohibit-password&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;no&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yes&lt;span class="w"&gt;

&lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;disks&lt;span class="w"&gt; &lt;/span&gt;are:&lt;span class="w"&gt; &lt;/span&gt;sd0&lt;span class="w"&gt; &lt;/span&gt;sd1&lt;span class="w"&gt; &lt;/span&gt;sd2.&lt;span class="w"&gt;
&lt;/span&gt;Which&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;disk?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'?'&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;details&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;sd0&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;?&lt;span class="w"&gt;
&lt;/span&gt;sd0:&lt;span class="w"&gt; &lt;/span&gt;ATA,&lt;span class="w"&gt; &lt;/span&gt;Micron_M600_MTFD,&lt;span class="w"&gt; &lt;/span&gt;MU04&lt;span class="w"&gt; &lt;/span&gt;naa.500a0751122dae7a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;119&lt;/span&gt;.2G&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;sd1:&lt;span class="w"&gt; &lt;/span&gt;SanDisk,&lt;span class="w"&gt; &lt;/span&gt;Cruzer&lt;span class="w"&gt; &lt;/span&gt;Blade,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.26&lt;span class="w"&gt; &lt;/span&gt;serial.07815567071025103004&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;.7G&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;sd2:&lt;span class="w"&gt; &lt;/span&gt;Generic,&lt;span class="w"&gt; &lt;/span&gt;Ultra&lt;span class="w"&gt; &lt;/span&gt;HS-COMBO,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.98&lt;span class="w"&gt; &lt;/span&gt;serial.04242240000000225001&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;.5G&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;disks&lt;span class="w"&gt; &lt;/span&gt;are:&lt;span class="w"&gt; &lt;/span&gt;sd0&lt;span class="w"&gt; &lt;/span&gt;sd1&lt;span class="w"&gt; &lt;/span&gt;sd2.&lt;span class="w"&gt;
&lt;/span&gt;Which&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;disk?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'?'&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;details&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;sd0&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sd2&lt;span class="w"&gt;
&lt;/span&gt;Disk:&lt;span class="w"&gt; &lt;/span&gt;sd2&lt;span class="w"&gt;       &lt;/span&gt;Usable&lt;span class="w"&gt; &lt;/span&gt;LBA:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;59768768&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;59768832&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Sectors&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="c1"&gt;#: type                                 [       start:         size ]
&lt;/span&gt;------------------------------------------------------------------------&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;EFI&lt;span class="w"&gt; &lt;/span&gt;Sys&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="m"&gt;64&lt;/span&gt;:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;960&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="m"&gt;3&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;OpenBSD&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="m"&gt;1024&lt;/span&gt;:&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;59767745&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;Use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;W&lt;span class="o"&gt;)&lt;/span&gt;hole&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;MBR,&lt;span class="w"&gt; &lt;/span&gt;whole&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;G&lt;span class="o"&gt;)&lt;/span&gt;PT,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;O&lt;span class="o"&gt;)&lt;/span&gt;penBSD&lt;span class="w"&gt; &lt;/span&gt;area&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;E&lt;span class="o"&gt;)&lt;/span&gt;dit?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;OpenBSD&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;w&lt;span class="w"&gt;
&lt;/span&gt;Setting&lt;span class="w"&gt; &lt;/span&gt;OpenBSD&lt;span class="w"&gt; &lt;/span&gt;MBR&lt;span class="w"&gt; &lt;/span&gt;partition&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;whole&lt;span class="w"&gt; &lt;/span&gt;sd2...done.&lt;span class="w"&gt;
&lt;/span&gt;The&lt;span class="w"&gt; &lt;/span&gt;auto-allocated&lt;span class="w"&gt; &lt;/span&gt;layout&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;sd2&lt;span class="w"&gt; &lt;/span&gt;is:&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;#                size           offset  fstype [fsize bsize   cpg]
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;a:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;.0M&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;b:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1919&lt;/span&gt;.9M&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;2097216&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;swap&lt;span class="w"&gt;
  &lt;/span&gt;c:&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;29184&lt;/span&gt;.0M&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;unused&lt;span class="w"&gt;
  &lt;/span&gt;d:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1591&lt;/span&gt;.9M&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;6029088&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /tmp
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;e:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;2471&lt;/span&gt;.8M&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;9289248&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /var
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;f:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;3339&lt;/span&gt;.8M&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;14351488&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /usr
&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;936&lt;/span&gt;.0M&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;21191488&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /usr/X11R6
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;h:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;3783&lt;/span&gt;.8M&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;23108320&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /usr/local
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;i:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;1668&lt;/span&gt;.0M&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;30857472&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /usr/src
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;j:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;5855&lt;/span&gt;.9M&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;34273472&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /usr/obj
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;k:&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="m"&gt;6589&lt;/span&gt;.5M&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="m"&gt;46266432&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;.2BSD&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;16384&lt;/span&gt;&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;&lt;span class="c1"&gt;# /home
&lt;/span&gt;Use&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;A&lt;span class="o"&gt;)&lt;/span&gt;uto&lt;span class="w"&gt; &lt;/span&gt;layout,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;E&lt;span class="o"&gt;)&lt;/span&gt;dit&lt;span class="w"&gt; &lt;/span&gt;auto&lt;span class="w"&gt; &lt;/span&gt;layout,&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;C&lt;span class="o"&gt;)&lt;/span&gt;ustom&lt;span class="w"&gt; &lt;/span&gt;layout?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;a&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2a:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;.0MB&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="m"&gt;2097152&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2k:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6589&lt;/span&gt;.5MB&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="m"&gt;13495360&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;33&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2d:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1591&lt;/span&gt;.9MB&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="m"&gt;3260160&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2f:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3339&lt;/span&gt;.8MB&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="m"&gt;6840000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2g:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;936&lt;/span&gt;.0MB&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="m"&gt;1916832&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2h:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3783&lt;/span&gt;.8MB&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="m"&gt;7749152&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2j:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5855&lt;/span&gt;.9MB&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="m"&gt;11992960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2i:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1668&lt;/span&gt;.0MB&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="m"&gt;3416000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&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;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;/dev/rsd2e:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2471&lt;/span&gt;.8MB&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="m"&gt;5062240&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sectors&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;512&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bytes&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="m"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cylinder&lt;span class="w"&gt; &lt;/span&gt;groups&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;202&lt;/span&gt;.50MB,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;12960&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;blocks,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;25920&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;inodes&lt;span class="w"&gt; &lt;/span&gt;each&lt;span class="w"&gt;
&lt;/span&gt;Available&lt;span class="w"&gt; &lt;/span&gt;disks&lt;span class="w"&gt; &lt;/span&gt;are:&lt;span class="w"&gt; &lt;/span&gt;sd0&lt;span class="w"&gt; &lt;/span&gt;sd1.&lt;span class="w"&gt;
&lt;/span&gt;Which&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;you&lt;span class="w"&gt; &lt;/span&gt;wish&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;initialize?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'done'&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="k"&gt;done&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.a&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2k&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.k&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt/home&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;local,&lt;span class="w"&gt; &lt;/span&gt;nodev,&lt;span class="w"&gt; &lt;/span&gt;nosuid&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2d&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.d&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt/tmp&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;local,&lt;span class="w"&gt; &lt;/span&gt;nodev,&lt;span class="w"&gt; &lt;/span&gt;nosuid&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2f&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.f&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt/usr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;local,&lt;span class="w"&gt; &lt;/span&gt;nodev&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2g&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.g&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt/usr/X11R6&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;local,&lt;span class="w"&gt; &lt;/span&gt;nodev&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2h&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.h&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt/usr/local&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;local,&lt;span class="w"&gt; &lt;/span&gt;nodev&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2j&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.j&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt/usr/obj&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;local,&lt;span class="w"&gt; &lt;/span&gt;nodev,&lt;span class="w"&gt; &lt;/span&gt;nosuid&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.i&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt/usr/src&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;local,&lt;span class="w"&gt; &lt;/span&gt;nodev,&lt;span class="w"&gt; &lt;/span&gt;nosuid&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;/dev/sd2e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;46c9b63f83d3fd95.e&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;/mnt/var&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ffs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;rw,&lt;span class="w"&gt; &lt;/span&gt;asynchronous,&lt;span class="w"&gt; &lt;/span&gt;local,&lt;span class="w"&gt; &lt;/span&gt;nodev,&lt;span class="w"&gt; &lt;/span&gt;nosuid&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;Let&lt;span class="s1"&gt;'s install the sets!
Location of sets? (disk http nfs or '&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="s1"&gt;') [http] disk
Is the disk partition already mounted? [yes] no
Available disks are: sd0 sd1 sd2.
Which disk contains the install media? (or '&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="s1"&gt;') [sd0] sd1
  a:          1358848             1024  4.2BSD   2048 16384 16142
  i:              960               64   MS-DOS
Available sd1 partitions are: a i.
Which sd1 partition has the install sets? (or '&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="s1"&gt;') [a]
Pathname to the sets? (or '&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="s1"&gt;') [6.9/amd64]

Select sets by entering a set name, a file name pattern or '&lt;/span&gt;all&lt;span class="s1"&gt;'. De-select
sets by prepending a '&lt;/span&gt;-&lt;span class="s1"&gt;', e.g.: '&lt;/span&gt;-game*&lt;span class="s1"&gt;'. Selected sets are labelled '&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;X&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="s1"&gt;'.
    [X] bsd           [X] base69.tgz    [X] game69.tgz    [X] xfont69.tgz
    [X] bsd.mp        [X] comp69.tgz    [X] xbase69.tgz   [X] xserv69.tgz
    [X] bsd.rd        [X] man69.tgz     [X] xshare69.tgz
Set name(s)? (or '&lt;/span&gt;abort&lt;span class="s1"&gt;' or '&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="s1"&gt;') [done] -x*
    [X] bsd           [X] base69.tgz    [X] game69.tgz    [ ] xfont69.tgz
    [X] bsd.mp        [X] comp69.tgz    [ ] xbase69.tgz   [ ] xserv69.tgz
    [X] bsd.rd        [X] man69.tgz     [ ] xshare69.tgz
Set name(s)? (or '&lt;/span&gt;abort&lt;span class="s1"&gt;' or '&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="s1"&gt;') [done] -game*
    [X] bsd           [X] base69.tgz    [ ] game69.tgz    [ ] xfont69.tgz
    [X] bsd.mp        [X] comp69.tgz    [ ] xbase69.tgz   [ ] xserv69.tgz
    [X] bsd.rd        [X] man69.tgz     [ ] xshare69.tgz
Set name(s)? (or '&lt;/span&gt;abort&lt;span class="s1"&gt;' or '&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="s1"&gt;') [done]
Directory does not contain SHA256.sig. Continue without verification? [no] yes
Installing bsd          100% |**************************| 20423 KB    00:01
Installing bsd.mp       100% |**************************| 20515 KB    00:01
Installing bsd.rd       100% |**************************|  4107 KB    00:00
Installing base69.tgz   100% |**************************|   291 MB    00:54
Extracting etc.tgz      100% |**************************|   254 KB    00:00
Installing comp69.tgz   100% |**************************| 85958 KB    00:26
Installing man69.tgz    100% |**************************|  7560 KB    00:06
Location of sets? (disk http nfs or '&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="s1"&gt;') [done]

What timezone are you in? ('&lt;/span&gt;?&lt;span class="err"&gt;'&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;list&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;Canada/Mountain&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Israel&lt;span class="w"&gt;
&lt;/span&gt;Saving&lt;span class="w"&gt; &lt;/span&gt;configuration&lt;span class="w"&gt; &lt;/span&gt;files...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;Making&lt;span class="w"&gt; &lt;/span&gt;all&lt;span class="w"&gt; &lt;/span&gt;device&lt;span class="w"&gt; &lt;/span&gt;nodes...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;Multiprocessor&lt;span class="w"&gt; &lt;/span&gt;machine&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;bsd.mp&lt;span class="w"&gt; &lt;/span&gt;instead&lt;span class="w"&gt; &lt;/span&gt;of&lt;span class="w"&gt; &lt;/span&gt;bsd.&lt;span class="w"&gt;
&lt;/span&gt;Exit&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;hell,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;H&lt;span class="o"&gt;)&lt;/span&gt;alt&lt;span class="w"&gt; &lt;/span&gt;or&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt;eboot?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;reboot&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;With this part done, I should be able to login as the root account over SSH (or
over the serial console). Now I should bootstrap the instance in the following
ways: setting up &lt;code&gt;doas&lt;/code&gt; for the regular user, setting up the internet
connection and adding the SSH public keys for the regular user. Then I can run
Ansible and setup everything else.&lt;/p&gt;
&lt;p&gt;For the internet connection, I don't have any nice way of doing it, so I just
copy the connection details from the &lt;code&gt;router&lt;/code&gt; role in the &lt;code&gt;homelab&lt;/code&gt;
repository (get the password from the Keepass password database with &lt;code&gt;ph
show --field Password 'Web Sites/Bezeq International'&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Add the public SSH keys:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
ssh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.3.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'mkdir -p .ssh; chmod 700 .ssh; touch .ssh/authorized_keys; chmod 600 .ssh/authorized_keys'&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;ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-yf&lt;span class="w"&gt; &lt;/span&gt;~/.ssh/shore_ecdsa&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ssh-keygen&lt;span class="w"&gt; &lt;/span&gt;-yf&lt;span class="w"&gt; &lt;/span&gt;~/.ssh/shore_ed25519&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ssh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192&lt;/span&gt;.168.3.1&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'tee .ssh/authorized_keys'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Bootstrap the instance (in the &lt;code&gt;homelab&lt;/code&gt; repository):&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
ansible-playbook&lt;span class="w"&gt; &lt;/span&gt;bootstrap.yaml&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;ns1&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;-k&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'ansible_host=192.168.3.1'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Setup the router (still in the &lt;code&gt;homelab&lt;/code&gt; repository):&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
ansible-playbook&lt;span class="w"&gt; &lt;/span&gt;router.yaml&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'ansible_host-192.168.3.1'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;ansible-playbook&lt;span class="w"&gt; &lt;/span&gt;update.yaml&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;ns1
&lt;/pre&gt;
&lt;p&gt;Boom! Done.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Parsing Apache logs</title><link href="https://www.shore.co.il/blog/parsing-apache-logs/" rel="alternate"/><published>2021-09-18T00:00:00+03:00</published><updated>2021-09-18T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-09-18:/blog/parsing-apache-logs/</id><summary type="html">&lt;p class="first last"&gt;Parsing Apache logs in Python&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Due to a clerical error at my ISP, my internet connection is down for the
weekend. So what's an activity that doesn't require a working internet
connection? Blogging. Earlier this year I was interviewing and on 3 different
occasions I was presented with a technical challenge of parsing Apache access
logs. I know from experience that the goal of the challenge is to gauge how well
I can parse text files with the standard Unix command line tools (mainly
&lt;code&gt;awk&lt;/code&gt; but also &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;wc&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt; and some plain shell
scripting).&lt;/p&gt;
&lt;p&gt;Now &lt;code&gt;awk&lt;/code&gt; is really a great tool and its performance is better than any
Hadoop cluster (as long as you can fit the data in a single machine) and usually
the challenge is limited enough that it's doable with &lt;code&gt;awk&lt;/code&gt;. However, it
may be easier to read and debug to write the solution using Python and obviously
with Python being a much more flexible tool, we can solve any problem (even a
real-life one) that has to do with parsing Apache's access logs.&lt;/p&gt;
&lt;p&gt;For this I'm going to use &lt;a class="reference external" href="https://github.com/r1chardj0n3s/parse"&gt;Parse&lt;/a&gt;.
With Parse we can write specifications (sort of the reverse of f-strings) and
with them we can parse log lines and get back nicely structured data. Also, for
bonus points, I'm going to use generators (it should also improve performance a
bit).&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="sd"&gt;"""Reads the file handler line by line and returns a dictionary of the
    log fields.
    """&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="sd"&gt;'''{ip} {logname} {user} [{date:th}] "{request}" {status:d} {bytes} "{referer}" "{user_agent}"'''&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;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;This function will work with any file opened with &lt;code&gt;open&lt;/code&gt; or with
&lt;code&gt;sys.stdin&lt;/code&gt;. Let's grab an example log file from &lt;a class="reference external" href="https://github.com/elastic/examples"&gt;Elastic's examples repo&lt;/a&gt; and print the 10 most frequent client IP
addresses.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;urllib.request&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;ipaddresses&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;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s2"&gt;"https://github.com/elastic/examples/blob/master/Common%20Data&lt;/span&gt;&lt;span class="si"&gt;%20F&lt;/span&gt;&lt;span class="s2"&gt;ormats/apache_logs/apache_logs?raw=true"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parse_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ipaddresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;ipaddresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ipaddresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;ipaddresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;sorted_addresses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;ipaddresses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sorted_addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&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="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sorted_addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Obviously this is a simple example, but this method is not limited in any way.
There's no messing around with delimiters or worrying about long strings inside
quotation marks nor checking the &lt;code&gt;awk&lt;/code&gt; man page for functions you never
used before. The resulting code is pretty clear and the performance is on-par
with any shell script you can whip together.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Terraform project structure</title><link href="https://www.shore.co.il/blog/tf-project-structure/" rel="alternate"/><published>2021-09-14T00:00:00+03:00</published><updated>2021-09-14T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-09-14:/blog/tf-project-structure/</id><summary type="html">&lt;p class="first last"&gt;My preferred Terraform project structure.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Recently I've been using &lt;a class="reference external" href="https://terragrunt.gruntwork.io/"&gt;Terragrunt&lt;/a&gt; and I
have thoughts on what it offers and is it useful. My usage has been in an
existing project that follows the Gruntworks guidelines closely and with the
paid subscription to the Gruntworks library. These opinions are my own and
they're based on my recent experience with Terragrunt as well as managing small
and medium infrastructure with Terraform for the last few years both as a single
developer and part of small team.&lt;/p&gt;
&lt;p&gt;The main point of Terragrunt as I understand it is keeping from repeating
yourself in code. I am not a fan of copying and pasting big blocks of code nor
of having to change the same value in a few different places. So for me keeping
code DRY is a worthwhile endeavor.&lt;/p&gt;
&lt;div class="section" id="keeping-modules-dry"&gt;
&lt;h2&gt;Keeping modules DRY&lt;/h2&gt;
&lt;p&gt;Terragrunt works by using modules. I like Terraform modules. Even the Terraform
documentation suggests that you don't have a single top level module for your
entire infrastructure. It makes development more difficult with more merge
conflicts. It makes deploying for testing purposes more difficult because
Terraform will keep trying to delete resources that aren't in your code (because
someone else working in a different branch has made changes for some other
reason). You can work around that by specifying the target you're interested in
but that is error-prone and is tiresome after a while.&lt;/p&gt;
&lt;p&gt;In a previous project I worked on we had a module for roughly each service. We
had quite a lot of code that was copied from one module to another (like
when creating a new RDS instance we also created the subnet group, the security
group for the client, etc.). Over time we saw clearly what code was shared
between the different modules, we created a &lt;code&gt;library&lt;/code&gt; directory and
started adding sub-modules there and after a while we had a nice library of
reusable sub-modules and things were good.&lt;/p&gt;
&lt;p&gt;Because we waited a bit before creating a new sub-module they were pretty
stable. When we did have a change to the a sub-module that we wanted to deploy
across the entire infrastructure, we would open a branch, work on all the needed
changes there, test them in one of the testing environments and then open a PR
that has all of the changes (the sub-module changes, the calling modules
changes, any fallout from those changes).&lt;/p&gt;
&lt;p&gt;This process fitted us nicely. The PR had the entire picture and we could really
see if the change improved anything (like adding an output to a module to be
used in a different module would be clear if you see it being used). We did on
occasion had conflicting changes and we did had to use targeted &lt;code&gt;plan&lt;/code&gt; and
&lt;code&gt;apply&lt;/code&gt; but as far as I can remember less than once a quarter.&lt;/p&gt;
&lt;p&gt;Terragrunt recommends splitting the repository in 2, one for sub-modules and one
for actually deployed modules. Then you create &lt;code&gt;terragrunt.hcl&lt;/code&gt; files that
list the sub-modules needed with the Git ref used. This allows you to use the
RDS database sub-module from today but the auto-scaling group from last year. I
see little point in this.&lt;/p&gt;
&lt;p&gt;The change process goes as follows, 1 PR for the sub-modules repository and 1
for the live repository (or more, we haven't gotten around to discussing
environments yet) Now I hear that the recommendation has changed. The new
recommendation is that each sub-module will be in a separate repository. So more
PRs for each change (that one change of adding an output and using it became
less obvious but requires more work, I wouldn't call it a win).  I wonder if
there's any place that has 2 repositories, 1 for code 1 for the tests and you
change the code, and when it's merged you go to the tests repo and update the
tests there to use the new code to see if it passes?&lt;/p&gt;
&lt;p&gt;Another outcome from this way of working I keep seeing is that because changes
are not applied (or planned) before merging the changes to the sub-module,
errors and issues are only found out later which triggers more PRs.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="environments-remote-states-and-workspaces-oh-my"&gt;
&lt;h2&gt;Environments, remote states and workspaces, oh my&lt;/h2&gt;
&lt;p&gt;Another way that Terragrunt keeps your code DRY is by generating the Terraform
backend configuration, because you can't use variables there with Terrafrom. So
you save less than 10 lines. Cool. Also, you won't have by accident (because you
copied that code from another module) used the same location for 2 modules and
have them delete each others resources. It happened to me more than once, but
you see it clearly when you first run &lt;code&gt;terraform plan&lt;/code&gt; so it's very easy
to catch.&lt;/p&gt;
&lt;p&gt;Now, the folks at Gruntworks suggest you create a directory for each
environment. From what I can see, that means you copy your
&lt;code&gt;terragrunt.hcl&lt;/code&gt; file to each directory and you modify it slightly (I
think you can see where I'm going with this). If your project has a different
module for each environment, this is a win. no doubt about it. I've seen
projects like that and it's really a pain to manage.&lt;/p&gt;
&lt;p&gt;Before I ever heard about Terragrunt, I had this exact problem. I solved it
using Terraform workspaces and a simple convention. Each environment would have
its own workspace (let's say that the default workspace is the sandbox but
that's up to you). Each module would have a bunch of &lt;code&gt;tfvars&lt;/code&gt; files for
each environment. The workflow for deploying to the &lt;code&gt;dev&lt;/code&gt; environment
would look like this:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
terraform&lt;span class="w"&gt; &lt;/span&gt;workspace&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="w"&gt;
&lt;/span&gt;terraform&lt;span class="w"&gt; &lt;/span&gt;plan&lt;span class="w"&gt; &lt;/span&gt;-tfvars&lt;span class="w"&gt; &lt;/span&gt;dev.tfvars&lt;span class="w"&gt; &lt;/span&gt;-out&lt;span class="w"&gt; &lt;/span&gt;tfplan&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Review the changes.
&lt;/span&gt;terraform&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;tfplan
&lt;/pre&gt;
&lt;p&gt;For making life a little easier I also added the following snippet to each
module:&lt;/p&gt;
&lt;pre class="code terraform literal-block"&gt;
&lt;span class="nb"&gt;locals&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="na"&gt;module&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;"${basename(path.module)}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="na"&gt;env&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;"${terraform.workspace == "default" ? "sandbox" : terraform.workspace}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Yes, this is copied code and along with the backend configuration, over 10 lines
of code mostly that is mostly duplicated. However, when I compare it to the
&lt;code&gt;terragrunt.hcl&lt;/code&gt; files, this is peanuts. I checked a few modules in the
codebase I'm working on and we have &lt;code&gt;terragrunt.hcl&lt;/code&gt; files that are 100s
of lines long and share all but a few lines.&lt;/p&gt;
&lt;p&gt;I found that this convention is easy to document, easy for new developers to
pick up, uses existing tools so you can use your existing knowledge and all of
the benefits of avoiding to use another tool in your workflow.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="workflow"&gt;
&lt;h2&gt;Workflow&lt;/h2&gt;
&lt;p&gt;Terragrunt builds a directory for each module (and each environment obviously),
clones the Git repos you mentioned with refs you specified and then mucks about
with the Terraform commands and plan files to stich everything togethere. Even
on paper this doesn't look like a good idea and it isn't one in practice, making
debugging issues difficult.&lt;/p&gt;
&lt;p&gt;It also suggests that you can have different versions of the sub-modules in use
across different environments, putting emphais on having the &lt;code&gt;main&lt;/code&gt; branch
match exactly what is each environment instead of putting emphasis of avoiding
drift between the different environments.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;This post is a critique of the Gruntworks recommended setup and workflow and I
think that if you read it all you would see that I think that there are better
and easier ways. You can compare Terragrunt to a badly managed Terraform project
and find that it helps you. But when you compare to it one that uses the suggested
convention, it makes things more difficult, doesn't deliver on the promise of
keeping your code DRY and promotes bad habits.&lt;/p&gt;
&lt;p&gt;I didn't plan on reviewing Terragrunt until I used it. Terragrunt makes life
less enjoyable. It has a convoluted workflow locally (with those bloody git
clones), it makes debugging issues difficult and the upside is just not there. I
would recommend to anyone who thinks about adopting Terrgrunt to first read the
&lt;a class="reference external" href="https://www.terraform.io/docs/cli/workspaces/index.html"&gt;workspaces documentation&lt;/a&gt; before going with
Terragrunt and think hard on the code review, the testing and development
workflows.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Misc. projects and updates</title><link href="https://www.shore.co.il/blog/misc-updates-may-2021/" rel="alternate"/><published>2021-05-11T00:00:00+03:00</published><updated>2021-05-11T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-11:/blog/misc-updates-may-2021/</id><summary type="html">&lt;p class="first last"&gt;A few small project and an update for May 2021.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I have a few smaller projects that I never blogged about and now might be a good
time.&lt;/p&gt;
&lt;div class="section" id="presentation-toolkit"&gt;
&lt;h2&gt;Presentation toolkit&lt;/h2&gt;
&lt;p&gt;I like Markdown and Vim, I don't like PowerPoint. On enough occasions I was
asked to present and I built a workflow to make my life easier. For that I have
a &lt;a class="reference external" href="https://git.shore.co.il/nimrod/presentation"&gt;container image&lt;/a&gt; with all of
the tools that I require. There's an example presentation in that repo and
there's also a GitLab &lt;a class="reference external" href="https://git.shore.co.il/shore/ci-templates/-/blob/master/templates/presentation.yml"&gt;CI template&lt;/a&gt;
for those using GitLab and wish to integrate it in their pipeline. For me,
creating presentations in Markdown and DOT is much faster and I find the results
(especially when dealing with code) much nicer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="cloudwatch-logs-container-image"&gt;
&lt;h2&gt;CloudWatch logs container image&lt;/h2&gt;
&lt;p&gt;When I started working with AWS ECS I had trouble debugging deployment issues.
The logs in the ECS console were not complete and the container logs didn't hold
the answers either. After a few times I had to login to EC2 instances and tail
the ECS agent logs, I decided that sending those to CloudWatch logs is needed to
better deal with such issues. I created &lt;a class="reference external" href="https://git.shore.co.il/nimrod/cw-logs-docker"&gt;another container image&lt;/a&gt; that has the CloudWatch plugin
for the AWS CLI. With that you can send the ECS agent logs (or any other log
file) to CloudWatch Logs. Deploying it as an ECS service (set the scheduling to
daemon) and having it run on all of the ECS instances solved that issue for me.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="cron-container-image"&gt;
&lt;h2&gt;Cron container image&lt;/h2&gt;
&lt;p&gt;Several services that I run need containerized Cron. However, regular Cron
daemons aren't suited for running in a container (they remove environment
variables before starting jobs, they log to syslog, etc.). Aptible created a
Cron daemon suitable for containers named &lt;a class="reference external" href="https://github.com/aptible/supercronic"&gt;Supersonic&lt;/a&gt;, however there's ready container
image so I created one. You can see it at in my &lt;a class="reference external" href="https://git.shore.co.il/nimrod/cron-docker"&gt;GitLab instance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The image can be used in 2 ways. You can it in a multi-stage build and copy the
Supersonic binary from it.&lt;/p&gt;
&lt;pre class="code Dockerfile literal-block"&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;registry.shore.co.il/cron&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;supersonic&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;alpine:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--from&lt;span class="o"&gt;=&lt;/span&gt;supersonic&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/supersonic&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/
&lt;/pre&gt;
&lt;p&gt;Or you can build on top of it (&lt;tt class="docutils literal"&gt;ONBUILD&lt;/tt&gt; instructions copy the &lt;tt class="docutils literal"&gt;crontab&lt;/tt&gt;
file and validate it).&lt;/p&gt;
&lt;pre class="code Dockerfile literal-block"&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;registry.shore.co.il/cron&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;script&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apk&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;--update&lt;span class="w"&gt; &lt;/span&gt;--no-cache&lt;span class="w"&gt; &lt;/span&gt;aws-cli&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nobody&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="lastly-updates"&gt;
&lt;h2&gt;Lastly, updates&lt;/h2&gt;
&lt;p&gt;Subscribers to my blog might have noticed that I've been blogging rather
profusely this last week. The reason is that I've finished the contract I had
with my last employer. I've taken a short break and now I'm looking for work
again. Right now I'm available for freelance work if something interesting comes
up. Otherwise, I'm looking for long-term work. If anyone you know (or you are
yourself) is looking for DevOps/ SRE/ infrastructure engineer with a preference
for simple and effective tools and a strong tendency for open source software,
drop me a message at &lt;a class="reference external" href="mailto:nimrod@shore.co.il"&gt;nimrod@shore.co.il&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The reason for the excessive blogging (apart from the fact that I now have free
time and I hope that someone somewhere finds my work interesting) is that I hope
save time. I see that although almost all of what I wrote is publicly available
in &lt;a class="reference external" href="https://git.shore.co.il/explore"&gt;my GitLab instance&lt;/a&gt;, people apparently
don't look too closely go just by my &lt;a class="reference external" href="https://www.shore.co.il/about"&gt;résumé&lt;/a&gt;.
I hope that these last few blog posts make it more apparent who I am, what it is
that I do and my approach to things. Thus saving both the interviewers and me
some time.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Deploying a Hugo blog with gitreceive</title><link href="https://www.shore.co.il/blog/hugo-gitreceive/" rel="alternate"/><published>2021-05-10T00:00:00+03:00</published><updated>2021-05-10T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-10:/blog/hugo-gitreceive/</id><summary type="html">&lt;p class="first last"&gt;How to deploy a blog built with Hugo using gitreceive&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This blog post is actually one I meant to write a long time ago, but always put
off. I'm going to describe how to use gitreceive to deploy a blog that's built
using Hugo. I find the Heroku-style of deploying using &lt;tt class="docutils literal"&gt;git push&lt;/tt&gt; nice in
some cases and this is a nice example of using gitreceive as any. Obviously
other static blogging tools can be used.&lt;/p&gt;
&lt;div class="section" id="first-setup-the-server"&gt;
&lt;h2&gt;First, setup the server&lt;/h2&gt;
&lt;p&gt;We need to create a user on the server, grant it access to deploy the blog and
add the SSH public keys.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;useradd&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;hugo&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;755&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;hugo&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;hugo&lt;span class="w"&gt; &lt;/span&gt;/var/www/html/blog&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-d&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;700&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;hugo&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;hugo&lt;span class="w"&gt; &lt;/span&gt;~hugo/.ssh&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;hugo&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;hugo&lt;span class="w"&gt; &lt;/span&gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;~hugo/.ssh/authorized_keys&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="s1"&gt;'command="GITUSER=hugo gitreceive run %s %s" ssh-ed25519 AAAAC3....'&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;sudo&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;~hugo/.ssh/authorized_keys
&lt;/pre&gt;
&lt;p&gt;Now we'll install git, gitreceive and Hugo.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
sudo&lt;span class="w"&gt; &lt;/span&gt;apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;git&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;/usr/local/bin&lt;span class="w"&gt;
&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;https://github.com/gohugoio/hugo/releases/download/v0.83.1/hugo_0.83.1_Linux-64bit.tar.gz&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;-xz&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;https://raw.github.com/progrium/gitreceive/master/gitreceive&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;gitreceive&lt;span class="w"&gt;
&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;gitreceive
&lt;/pre&gt;
&lt;p&gt;Create the receiver script with the following content at &lt;tt class="docutils literal"&gt;/home/hugo/receiver&lt;/tt&gt;
and mark it as executable:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/sh
&lt;/span&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;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/var/tmp/gitreceive/&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&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="s1"&gt;'----&amp;gt; Unpacking'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;tar&lt;span class="w"&gt; &lt;/span&gt;-x&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="s1"&gt;'----&amp;gt; Building blog'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;hugo&lt;span class="w"&gt; &lt;/span&gt;--cleanDestinationDir&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="s1"&gt;'----&amp;gt; Deploying blog'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;/var/www/html/blog/*&lt;span class="w"&gt;
&lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;public/*&lt;span class="w"&gt; &lt;/span&gt;/var/www/html/blog/&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="s1"&gt;'----&amp;gt; Done'&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="second-the-client-side"&gt;
&lt;h2&gt;Second, the client side&lt;/h2&gt;
&lt;p&gt;Actually, this is quite simple, just adding a git remote.&lt;/p&gt;
&lt;pre class="code shell literal-block"&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;deploy&lt;span class="w"&gt; &lt;/span&gt;hugo@example.com:blog
&lt;/pre&gt;
&lt;p&gt;And now let's give it a test.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
$&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt; &lt;/span&gt;deploy&lt;span class="w"&gt;
&lt;/span&gt;Counting&lt;span class="w"&gt; &lt;/span&gt;objects:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;Delta&lt;span class="w"&gt; &lt;/span&gt;compression&lt;span class="w"&gt; &lt;/span&gt;using&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;threads.&lt;span class="w"&gt;
&lt;/span&gt;Compressing&lt;span class="w"&gt; &lt;/span&gt;objects:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;31&lt;/span&gt;/31&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;Writing&lt;span class="w"&gt; &lt;/span&gt;objects:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;%&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;/39&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;.55&lt;span class="w"&gt; &lt;/span&gt;KiB&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="m"&gt;2&lt;/span&gt;.18&lt;span class="w"&gt; &lt;/span&gt;MiB/s,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;Total&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;delta&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;reused&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="o"&gt;(&lt;/span&gt;delta&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;----&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Unpacking&lt;span class="w"&gt;
&lt;/span&gt;----&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Building&lt;span class="w"&gt; &lt;/span&gt;blog&lt;span class="w"&gt;
&lt;/span&gt;Start&lt;span class="w"&gt; &lt;/span&gt;building&lt;span class="w"&gt; &lt;/span&gt;sites&lt;span class="w"&gt; &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;EN&lt;span class="w"&gt;
&lt;/span&gt;-------------------+-----&lt;span class="w"&gt;
  &lt;/span&gt;Pages&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="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;Paginator&lt;span class="w"&gt; &lt;/span&gt;pages&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="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;Non-page&lt;span class="w"&gt; &lt;/span&gt;files&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="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;Static&lt;span class="w"&gt; &lt;/span&gt;files&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="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;Processed&lt;span class="w"&gt; &lt;/span&gt;images&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="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;Aliases&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="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;Sitemaps&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="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;Cleaned&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="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;Total&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="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ms&lt;span class="w"&gt;
&lt;/span&gt;----&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Deploying&lt;span class="w"&gt; &lt;/span&gt;blog&lt;span class="w"&gt;
&lt;/span&gt;----&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Done&lt;span class="w"&gt;
&lt;/span&gt;To&lt;span class="w"&gt; &lt;/span&gt;blog:blog&lt;span class="w"&gt;
 &lt;/span&gt;*&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;new&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;master
&lt;/pre&gt;
&lt;p&gt;That's it, the blog is deployed. Obviously having the output from the script is
useful. We can change the &lt;tt class="docutils literal"&gt;receiver&lt;/tt&gt; script to do a lot of other things.
Running &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; build &amp;amp;&amp;amp; &lt;span class="pre"&gt;docker-compose&lt;/span&gt; pull &amp;amp;&amp;amp; &lt;span class="pre"&gt;docker-compose&lt;/span&gt; up &lt;span class="pre"&gt;-d&lt;/span&gt;&lt;/tt&gt;
can produce a simple and straightforward dev environment.  We can make it more
general by running a script inside the repo and build a makeshift CI tool.
Lastly, I have an Ansible role to do all of the server configuration for you in
&lt;a class="reference external" href="https://git.shore.co.il/ansible/gitreceive"&gt;my GitLab instance&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Spam me - Update #2</title><link href="https://www.shore.co.il/blog/spam-me-notifications/" rel="alternate"/><published>2021-05-10T00:00:00+03:00</published><updated>2021-05-10T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-10:/blog/spam-me-notifications/</id><summary type="html">&lt;p class="first last"&gt;Another update on the spam me page.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Previously &lt;a class="reference external" href="../spam_me/"&gt;I wrote&lt;/a&gt; about the &lt;a class="reference external" href="https://www.shore.co.il/spam"&gt;spam me page&lt;/a&gt; and later an &lt;a class="reference external" href="../spam-me-update"&gt;update&lt;/a&gt; on
how things were going.&lt;/p&gt;
&lt;p&gt;Things were stable and I didn't get any spam. But something bothered me. I would
only get notifications on my laptop and only when it was on. Notifications would
be lost if the laptop was offline. Also, &lt;a class="reference external" href="https://patchbay.pub/"&gt;Patchbay&lt;/a&gt; had
downtime a few months back and I didn't get notifications at all. Lastly, I'm
keen on self-hosting.&lt;/p&gt;
&lt;div class="section" id="nextcloud-notifier"&gt;
&lt;h2&gt;Nextcloud Notifier&lt;/h2&gt;
&lt;p&gt;Nextcloud has a notification mechanism that is used internally. I get
notifications on pending updates on my cellphone and laptop. Pushing
notifications over HTTP requires authentication, but using the &lt;tt class="docutils literal"&gt;occ&lt;/tt&gt; CLI
doesn't require authentication.&lt;/p&gt;
&lt;p&gt;I wrote a &lt;a class="reference external" href="https://git.shore.co.il/nimrod/nextcloud-notifier"&gt;simple web service&lt;/a&gt; to post messages through
the Nextcloud notification mechanism. The service runs the &lt;tt class="docutils literal"&gt;occ&lt;/tt&gt; command using
&lt;tt class="docutils literal"&gt;docker exec&lt;/tt&gt; so usage is anonymous. Notifications are received on both
cellphone and laptop and I'm content. Now you can &lt;a class="reference external" href="https://www.shore.co.il/spam"&gt;send me messages&lt;/a&gt;
and I'll always get it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="future-uses"&gt;
&lt;h2&gt;Future uses&lt;/h2&gt;
&lt;p&gt;Now that I have a notification service, I saw more uses than I originally
envisioned. I send notification on failed Cron jobs, for example:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
backup&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;wget&lt;span class="w"&gt; &lt;/span&gt;--spider&lt;span class="w"&gt; &lt;/span&gt;https://notify.shore.co.il/send?message&lt;span class="o"&gt;=&lt;/span&gt;Backup%20failed.
&lt;/pre&gt;
&lt;p&gt;I have CI jobs that run on a schedule (rebuilding container images) so now I
have a &lt;a class="reference external" href="https://git.shore.co.il/shore/ci-templates/-/blob/master/templates/notify.yml"&gt;GitLab CI template&lt;/a&gt;
that sends me a message if the CI pipeline failed.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Collecting metrics</title><link href="https://www.shore.co.il/blog/collecting-metrics/" rel="alternate"/><published>2021-05-08T00:00:00+03:00</published><updated>2021-05-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-08:/blog/collecting-metrics/</id><summary type="html">&lt;p class="first last"&gt;Integrating metric collection in your application&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A few startups I worked at had a similar story. When they got started they
didn't have any metric collection (maybe some system metric from their cloud
provider, but nothing more). After a few times where they had to debug an issue
where metrics were needed they decided to start collecting metrics from the
application. Since they were a small team with little experience in setting up
the needed infrastructure or the man power to handle such a task they decided to
use a SaaS product (NewRelic and Datadog are both good choices here). Then as
the company grew and the number of users, processes, components and instances
grew so did the bill from the that SaaS. This is usually the time where they
decide that they need an DevOps person on the team (not just because of the
metrics issue, but as the company matures, the customer base grows, uptime
requirements increase, scaling is an issue, etc.). What follows is my advice on
such undertakings.&lt;/p&gt;
&lt;p&gt;First of all, use StatsD. Not specifically the StatsD daemon but the protocol.
It's mature, flexible enough for most tasks, supported in all languages and in
all metrics collection software. You can even use netcat to push metrics from
shell scripts.&lt;/p&gt;
&lt;p&gt;You don't have to setup storage for the metrics you collect, CloudWatch, Librato
or similar services can be used and are cheaper than the more integrated SaaS
offering. If you have Elasticsearch already for log collection, you can use that
for storing metrics as well (great for a few dozen instances). InfluxDB and
Gnocchi support the StatsD protocol. As you can see, it's a flexible solution.&lt;/p&gt;
&lt;p&gt;I think that most people associate the StatsD protocol with the Graphite
protocol and software, but there is a key difference: StatsD uses UDP. It's
faster and there's less overhead. You can default to sending the metrics to
localhost and if nothing collects them that's still fine (great for local
development and CI jobs where you don't want to run metrics collection software
or where it doesn't make sense). I know that some would say that TCP is more
reliable than UDP and you can lose metrics using it. To that I would say that
the lower overhead of UDP, the lack of connection is actually an advantage. No
&lt;code&gt;connection closed&lt;/code&gt; errors, that single packet is more likely to reach its
destination in case the system is under heavy load than opening a new TCP
connection. You can read this &lt;a class="reference external" href="https://github.blog/2015-06-15-brubeck/"&gt;GitHub blog post&lt;/a&gt; to see how they dealt with their
metric loss.&lt;/p&gt;
&lt;p&gt;I know that Prometheus is the new hotness and very popular when running
Kubernetes and it's not a bad choice. But there are a few things that you need
to keep in mind when considering it. It's best when you have service discovery
available for it (if you're already using Kubernetes or running in a cloud
provider that's not an issue but that's not everybody). Also, for short lived
tasks (like Cron or processes that pull from a job queue) or for processes that
can't open a listening socket (or if you have many of the same processes running
on the same host), you need to setup a push gateway. The process pushes metrics
to the gateway and Prometheus collects them afterwards (sounds an awful like the
StatsD daemon, doesn't it). Prometheus has a StatsD exporter so you can still
use StatsD along with Prometheus.&lt;/p&gt;
&lt;p&gt;If you want some ad-hoc or more lightweight metrics collection, &lt;a class="reference external" href="https://github.com/rapidloop/statsd-vis"&gt;statsd-vis&lt;/a&gt; is a good solution. It holds the
data in memory for a configurable time and has a builtin web UI to watch the
graphs. I have a a very small &lt;a class="reference external" href="https://hub.docker.com/r/adarnimrod/statsd-vis"&gt;Docker image&lt;/a&gt; for that.&lt;/p&gt;
&lt;p&gt;Lastly, for Python applications I recommend the &lt;a class="reference external" href="https://markus.readthedocs.io/"&gt;Markus&lt;/a&gt; library. It's easy to use, reliable and you
add more metrics as needed. 2 thumbs up.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Database resource handling</title><link href="https://www.shore.co.il/blog/db_resources/" rel="alternate"/><published>2021-05-08T00:00:00+03:00</published><updated>2021-05-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-08:/blog/db_resources/</id><summary type="html">&lt;p class="first last"&gt;Handling and versioning of database resources&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A few years ago I was working at a company where part of the product was an
online encyclopedia. The content was curated by a team in the company using an
homegrown CMS. Deployments of it were done by dumping the content of a few
database tables, zipping them up, uploading to S3, and in the production
database, dropping those tables and restoring from content of the zip file in
S3.&lt;/p&gt;
&lt;p&gt;This was a Laravel app and deployments were done using Ansible. We had Ansible
code for that, but the database dump and uploading to S3 was manual and the
configuration was in the repo where the Ansible roles were maintained, not the
repo were the app was developed.&lt;/p&gt;
&lt;p&gt;I know most people would jump up and say that this causes downtime that can be
avoided, but this was the way it was done when I joined. It avoided the need to
care about database migrations for those tables. And given that this company was
based in Israel and we work on Sundays, nobody cared that the entire product
(not just the encyclopedia) was down for ~10 minutes on Sundays. As a side note,
you'd be surprised at how many Israeli startups do weekly deployments on
Sundays.&lt;/p&gt;
&lt;p&gt;This caused a few issues for the company. First of all, developers had
individual instances on Azure that were initially set up by the Ops team. At the
time we had Azure credits but those were going to run out in a few months and we
wanted to move developers to use their local machines entirely.&lt;/p&gt;
&lt;p&gt;Another thing was that the process of updating the resources was involved and
required somebody from the Ops team. It meant that once a breaking change was
merged to the master branch of the application, tests would break for all
developers until the database resources were updated.&lt;/p&gt;
&lt;p&gt;Yet another downside was automated testing. Previous attempts at having a CI
pipeline required sharing the database between all of the jobs. It needed to be
maintained by the Ops team. And it caused to jobs to fail if a breaking change
was deployed.&lt;/p&gt;
&lt;p&gt;The solution I came up with was extending the Laravel app to do most of the
work. First I added a new config array for the version of the resources we
currently want. IIRC we decided on the format of year and week number. Then I
wrote a new Artisan command to download the the zip file, drop the tables and
restore from the zip file. Laravel offers a way to save what it calls system
variables in the database, so the currently deployed version was stored in the
database and I could skip this process entirely if the currently version and
wanted version match making the process usually faster and idempotent.&lt;/p&gt;
&lt;p&gt;With the command in place, it was added to to all of the relevant places.
Developers had a short script that they maintained which ran all of the tasks
that were needed for updating the app (like database migrations, SCSS builds,
Gulp builds) so the command was added to the script. It replaced a few Ansible
tasks with a single task to run that command.&lt;/p&gt;
&lt;p&gt;The developers maintaining the CMS added the functionality to upload a database
dump of the those tables and exposed it in the admin UI and the loop was
complete.&lt;/p&gt;
&lt;p&gt;The benefit of this approach was the developer workflow. Instead of working with
the Ops team to update the resources, they would just update the config to
point to the new version. It allowed downgrades in cases we wanted to revert to
an older version.  It made the PRs much clearer to everybody. The Dev
team was self-sufficient, the Ops team had one thing less to worry about. We
were one step closer to having developers develop using their local machines
(using Docker and Docker Compose) instead of those instances in Azure. We were
using Bitbucket and they just came out with the open beta for Bitbucket
Pipelines and I was able to set up automated testing that was completely
independent for each run. Overall, a lot upsides and time saved for about 200
lines of PHP code (it took me around 2 days since it was the first time I wrote
more than 2 lines of PHP and the code review had a lot of issues for me to
address).&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Saving sent messages with Exim</title><link href="https://www.shore.co.il/blog/exim-sent-messages/" rel="alternate"/><published>2021-05-08T00:00:00+03:00</published><updated>2021-05-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-08:/blog/exim-sent-messages/</id><summary type="html">&lt;p class="first last"&gt;Saving sent messages with Exim&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In shore.co.il I use the Exim mail transfer agent (SMTP server) along with the
Dovecot IMAP server. Messages received in Exim are sent to Dovecot over LMTP. So
far, pretty standard stuff. All of the configuration and setup can be seen in my
&lt;a class="reference external" href="https://www.shore.co.il/blog/ldap-auth/"&gt;GitLab instance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I wanted to save sent messages to a folder (like GMail does) but didn't find a
way to do that over LMTP documented anywhere so here's what I did. I created a
router in Exim that if the sender is one of the local domains (domains Exim
receives messages for), then they're redirected to the LMTP transport but with
&lt;tt class="docutils literal"&gt;+Sent&lt;/tt&gt; suffix attached so they're saved to the &lt;tt class="docutils literal"&gt;Sent&lt;/tt&gt; folder. You can see
the configuration &lt;a class="reference external" href="https://git.shore.co.il/shore/mail-docker/-/blob/master/exim4/exim4.conf#L665"&gt;here&lt;/a&gt;.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Accessing the GitLab remote Terraform state from Ansible</title><link href="https://www.shore.co.il/blog/gitlab-tf-state-ansible/" rel="alternate"/><published>2021-05-08T00:00:00+03:00</published><updated>2021-05-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-08:/blog/gitlab-tf-state-ansible/</id><summary type="html">&lt;p class="first last"&gt;Accessing the GitLab remote Terraform state from Ansible.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Note to self: How to get the outputs from a GitLab remote Terrafrom state from
Ansible.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Get Terraform outputs from the GitLab remote state&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;localhost&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;local&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;become&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;gather_facts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;amilive&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;tf_workspace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lookup('env',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'TF_WORKSPACE')|default('default',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;true)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;gitlab_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lookup('env',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'GITLAB_TOKEN')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;gitlab_base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lookup('env',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'GITLAB_BASE_URL')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Projects&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;PRIVATE-TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitlab_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;GET&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitlab_base_url&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/projects"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;projects&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Request&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nt"&gt;PRIVATE-TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitlab_token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;GET&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;return_content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;200&lt;/span&gt;&lt;span class="p-Indicator"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitlab_base_url&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/projects/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project_id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/terraform/state/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tf_workspace&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(projects.json|selectattr('path',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'equalto',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project_name))[0].id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;tf_state&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Env output&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;(tf_state.content|from_json)["outputs"]["env"]["value"]&lt;/span&gt;
&lt;/pre&gt;
</content><category term="misc"/></entry><entry><title>shore.co.il infrastructure - May 2021 edition</title><link href="https://www.shore.co.il/blog/homelab-early-2021/" rel="alternate"/><published>2021-05-08T00:00:00+03:00</published><updated>2021-05-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-08:/blog/homelab-early-2021/</id><summary type="html">&lt;p class="first last"&gt;Description of the shore.co.il infrastructure as it exists in May
2021.&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="hardware"&gt;
&lt;h2&gt;Hardware&lt;/h2&gt;
&lt;p&gt;The hardware I'm using consists of:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Netgate SG-2440 running OpenBSD.&lt;/li&gt;
&lt;li&gt;Linksys EA6350 running OpenWrt.&lt;/li&gt;
&lt;li&gt;ASrock N3150-NUC running Debian.&lt;/li&gt;
&lt;li&gt;An online.net (now Scaleway) Dedibox running Debian.&lt;/li&gt;
&lt;li&gt;An ADSL modem provided by my ISP.&lt;/li&gt;
&lt;li&gt;A purpose built PC in the living room running Debian.&lt;/li&gt;
&lt;li&gt;APC UPS (I don't remember the exact model I don't feel like getting and
checking).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The OpenBSD box is &lt;tt class="docutils literal"&gt;ns1.shore.co.il&lt;/tt&gt;. It's the router for the home network,
the primary DNS server for the &lt;tt class="docutils literal"&gt;shore.co.il&lt;/tt&gt; zone, DNS resolver for the
network, DHCP server and running HAProxy. The local Debian box has a 0.5TB drive
and is the LDAP server, mail server, Nextcloud, GitLab. I store all of my
private information on it encrypted. I have off-site backups I take about every
2 weeks that I store in my mom's house (also encrypted). The living room PC runs
Kodi, Transmission and my podcast downloader. It has a magnetic 8TB drive that
holds music, movies and other such media. The OpenWrt box is the wireless access
point. Lastly, the Dedibox is a bare metal instance which obviously has a faster
internet connection than I have locally. It runs this blog, a few other web
sites, the container registry, the secondary DNS server for &lt;tt class="docutils literal"&gt;shore.co.il&lt;/tt&gt; and
most GitLab CI jobs run on it. Also, it runs my workbench (a container that has
all of the tools I need and use) that benefits from the faster internet
connection, faster drives, abundant memory and beefy CPU. The data drive is also
encrypted, but not backed up (everything on it can be recreated in less than a
day).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="deployments"&gt;
&lt;h2&gt;Deployments&lt;/h2&gt;
&lt;p&gt;Initial setup is done using Ansible. Services on the different Debian boxes run
in Docker containers and deployed using GitLab. The other OSes are maintained
using just Ansible. There's no redundancy since it would take more money than I
would like to spend. There's no infrastructure-as-code (no Terraform) since the
only thing I could code is the single Dedibox instance (and it's not supported
by the Scaleway provider last time I checked). All of the Debian instances run a
GitLab Runner in a Docker container and have access to the &lt;tt class="docutils literal"&gt;dockerd&lt;/tt&gt; socket
so they can create containers, run jobs in them and build and push images. I
know this is a security risk, but since only I use this GitLab instance I'm
worried about it. The deployments are pretty consistent, projects have a
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose.yaml&lt;/span&gt;&lt;/tt&gt; file, the GitLab runner runs &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; build&lt;/tt&gt;,
&lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; pull&lt;/tt&gt; and &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;docker-compose&lt;/span&gt; up&lt;/tt&gt; to deploy services. All of the
code is in the &lt;a class="reference external" href="https://git.shore.co.il/shore"&gt;Shore group in my GitLab instance&lt;/a&gt;. The templates for the CI pipelines that I use
are also in &lt;a class="reference external" href="https://git.shore.co.il/shore/ci-templates"&gt;my GitLab instance&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="security"&gt;
&lt;h2&gt;Security&lt;/h2&gt;
&lt;p&gt;Most services are only available over SSL (apart from services that don't
support it like DNS, DHCP, etc.). I'm using Let's Encrypt to issue globally
valid certificates. In my home network this has presented a problem. I wanted to
have multiple hosts using SSL with the single IP address and not rely on the
router to decrypt the traffic. I wanted the traffic to remain encrypted until
it reaches the host and that the certificate to globally valid. For that I run
HAProxy which uses SNI to identify the requested host and forwards the traffic
accordingly.&lt;/p&gt;
&lt;p&gt;The SSH servers all have rate limits and only allow public key authentication. I
rely on SSH keys to authenticate and login instead of LDAP, I prefer it since if
the LDAP server is down I'm not locked out entirely.  Further more the only
services that use the LDAP server are on the same host and connect to it via a
Unix socket, further securing the access. The LDAP server is not available on
the network.&lt;/p&gt;
&lt;p&gt;For regular tasks like renewing the SSL certificates or updating the hosts I
have written Ansible playbooks. I routinely rotate the SSL keys and also the DH
parameters. I run them manually from my laptop, I don't want them to be updated
automatically by the hosts themselves. Also, rebooting the NUC and Dedibox
requires a manual step to unlock the encrypted drives. I can update all of the
hosts, rebuild all of the container images and deploy in about 2 hours and with
very little interaction and I do so every few weeks.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="changes-from-the-previous-iteration"&gt;
&lt;h2&gt;Changes from the previous iteration&lt;/h2&gt;
&lt;p&gt;There are a few changes in the infrastructure sine the last versions.  First,
instead of the Dedibox I used an EC2 instance. The Dedibox costs more, but the
time saving from the beefier instance, from the CI improvements and being able
to run VMs on it are worth it.&lt;/p&gt;
&lt;p&gt;I used to use Ansible for everything, including deploying services which I do
now with Docker Compose and GitLab runners. The development workflow with Docker
is easier and faster than using Ansible along with Vagrant and Molecule. I can
honestly say that I'm surprised more people don't this more often. It's really
easy, simple, secure and reliable. I find that this approach is useful for
simple setups like mine (or dev or QA environments), especially since the same
Docker Compose setup can be used for local development.&lt;/p&gt;
&lt;p&gt;The addition of the Nextcloud and GitLab services have made me entirely
self-hosted. I use online.net and a DNS registrar but apart from that I don't
rely on any external service and I hold all of my private data (except that most
of the emails I send end up in Google's servers anyway). My sites are indexed by
Google, Bing and Yandex but I'm not sure what can I do about that.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="future-improvements"&gt;
&lt;h2&gt;Future improvements&lt;/h2&gt;
&lt;p&gt;The UPS isn't supported by NUT or anything else available on Linux or OpenBSD. I
will replace it sometime in the future with one that does so that I can trigger a
clean shutdown when there's a power outage and the battery is running low. Also,
I would like to replace the NUC with a newer and faster one.&lt;/p&gt;
&lt;p&gt;I have external monitoring on services but I've yet to setup internal log
aggregation or metrics collection. I plan on setting up an EFK stack (I have
some POC code laying around but I need to update it and bring it in line with
the rest of the infrastructure). I also want to investigate Sensu for running
checks locally (a Nagios replacement), I have my eye on Testinfra for the host
checks.&lt;/p&gt;
&lt;p&gt;I'm using Z-Push along with Nextcloud for Activesync but it doesn't work with my
phone so I want to evaluate SOGo as a replacement. I want to try the
Dropbear-initramfs integration so I can unlock encrypted drives remotely over
SSH. I want to replace the workbench using Docker with the toolbox project.&lt;/p&gt;
&lt;p&gt;I avoided using a VPN for now and I don't want to go down that route. But I've
been in very closed networks so I want to setup a Websocket proxy to my SSH
server on the Dedibox so I can connect over port 443 and tunnel out from such
networks.&lt;/p&gt;
&lt;p&gt;Lastly, I see XMPP, Matrix or Mastodon (or maybe 2 of them) in the future for
secure and self-hosted chatting with friends.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>LDAP authentication for web services</title><link href="https://www.shore.co.il/blog/ldap-auth/" rel="alternate"/><published>2021-05-08T00:00:00+03:00</published><updated>2021-05-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-08:/blog/ldap-auth/</id><summary type="html">&lt;p class="first last"&gt;LDAP authentication for web services&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Some web services I run don't offer integration with LDAP for authentication.
One possible way to have authentication is to use the &lt;a class="reference external" href="https://github.com/vouch/vouch-proxy"&gt;Vouch proxy&lt;/a&gt;. I used it along with Nextcloud (which
has integration with LDAP) providing OAuth. But I encountered a limitation to
this approach. Some clients only support basic authentication and don't support
the newer JWT tokens and OAuth flows (clients for the Transmission torrent
clients are an example for that). I didn't want to deal with secret management
or with &lt;tt class="docutils literal"&gt;.htaccess&lt;/tt&gt; files. I wanted users to be able to authenticate using
their LDAP password.&lt;/p&gt;
&lt;p&gt;First attempt was using the &lt;a class="reference external" href="https://httpd.apache.org/docs/2.4/mod/mod_authnz_ldap.html"&gt;LDAP authnz module for Apache&lt;/a&gt;. But either I
didn't set it up correctly or that connecting to the LDAP server over a Unix
socket doesn't work as expected. Anyway, authentication always succeeded when
using the Unix socket and I didn't want to change the LDAP setup I have (I
prefer using the Unix socket with containers as I can easily limit which
containers have access to the LDAP server by cross-mounting the socket only
to containers I want to have access).&lt;/p&gt;
&lt;p&gt;I ended up creating a small service in Python with Flask and &lt;a class="reference external" href="https://flask-simpleldap.readthedocs.io/"&gt;Flask-SimpleLDAP&lt;/a&gt;. The service exposes just a single
endpoint &lt;tt class="docutils literal"&gt;/validate&lt;/tt&gt; which returns a 200 code when basic authentication
succeeds or a 401 code when it fails. Authentication uses the LDAP server over
the Unix socket as I wanted. It can be easily integrated with Nginx using the
&lt;a class="reference external" href="http://nginx.org/en/docs/http/ngx_http_auth_request_module.html#auth_request"&gt;auth_request directive&lt;/a&gt;.
An example can be seen &lt;a class="reference external" href="https://git.shore.co.il/shore/web-proxy-docker/-/blob/master/snippets/ldap-auth.conf"&gt;here&lt;/a&gt;.
The entire service is available on my &lt;a class="reference external" href="https://git.shore.co.il/shore/ldap-auth"&gt;GitLab instance&lt;/a&gt;. There's even a Docker image you can
use in my &lt;a class="reference external" href="https://registry.shore.co.il/"&gt;container registry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For now I'm using a fork of Flask-SimpleLDAP (until &lt;a class="reference external" href="https://github.com/alexferl/flask-simpleldap/pull/86"&gt;my PR&lt;/a&gt; for adding support for
accessing the LDAP server over a Unix socket is merged).&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Monitoring shore.co.il</title><link href="https://www.shore.co.il/blog/monitoring-shore/" rel="alternate"/><published>2021-05-08T00:00:00+03:00</published><updated>2021-05-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-08:/blog/monitoring-shore/</id><summary type="html">&lt;p class="first last"&gt;Monitoring shore.co.il&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Recently, I had some time to work on a project I had on my to-do list for a long
time, monitoring services in &lt;a class="reference external" href="https://www.shore.co.il/"&gt;shore.co.il&lt;/a&gt;. The
project is now done and is available in my &lt;a class="reference external" href="https://git.shore.co.il/shore/amilive"&gt;GitLab instance&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="requirements"&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;When I write monitoring, I mean periodic checks on services and alerts if they
fail. I had a specific requirement set in mind with this project. I wanted the
monitoring to be reliable, meaning that if anything and everything in my
infrastructure failed, I would still get alerts. This was critical for me since
I run a lot of my infrastructure at home and a prolonged internet or power
outage would bring down many services. Cheap and easy would also be nice.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="architecture"&gt;
&lt;h2&gt;Architecture&lt;/h2&gt;
&lt;p&gt;I decided on using Lambda functions along with SMS notifications from SNS on
AWS. Lambda functions can be reliably triggered using CloudWatch Events on a
schedule (every x minutes) and failures can be published to a SNS topic that has
a target that sends SMS messages to my cellphone. So far, very reliable, no
dependency on anything in my infrastructure. For added reliability, I added
CloudWatch alerts in case a function failed to be invoked recently or if the
invocation failed. Said alerts would also send me an SMS message. SMS messages
cost a little (hopefully there would little of those), I don't have enough
Lambda function invocation or runtime to go over the free tier and the price for
the code in S3 isn't great either. For me, it was easier, cheaper and more
reliable than setting up Nagios, Sensu or similar.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="solution"&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;p&gt;I wrote a few Python functions to test the different services I run (DNS, SMTP,
IMAP, SSH, different web services). To deploy them I wrote a Terraform module
that does everything from creating the SNS topic, upload the Python code and
hook up the Lambda functions. Everything is ran inside a GitLab CI pipeline and
uses the &lt;a class="reference external" href="https://docs.gitlab.com/ee/user/infrastructure/terraform_state.html"&gt;GitLab remote Terraform state&lt;/a&gt; (I
recently had reason to try it out and I was impressed).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;I don't think I would set up this specific solution for a company. A company
would most likely have an on-call schedule. Maybe using a SaaS product would be
easier and better in some aspects (like running checks from multiple locations).
But for my small infrastructure and considerations it was a success. The project
can be adapted to use a service like PagerDuty to have an on-call schedule and
it can be deployed to multiple regions to run checks from multiple regions.
Lastly, Nagios and Sensu have a library of ready checks in Ruby or Perl so you
don't have to write them yourself. This project has been live for more than a
week now and has been reliable. The AWS Cost Explorer predicts that the cost for
this month would be a few dollars. I call it a success.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Human and structured logs</title><link href="https://www.shore.co.il/blog/structured-logging/" rel="alternate"/><published>2021-05-08T00:00:00+03:00</published><updated>2021-05-08T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2021-05-08:/blog/structured-logging/</id><summary type="html">&lt;p class="first last"&gt;Output both human readable and structured logs&lt;/p&gt;
</summary><content type="html">&lt;p&gt;As anyone who's heard of the 12 factor app knows, logs are best outputted to the
standard output of the process and have the environment handle the logs
accordingly. A while back I worked at a company where we collected logs with the
EFK stack. We had quite a bit of logs and ingested daily and tracking down
issues in Kibana using regular expressions became unwieldy. Structured logging
(read json lines, where every line is a json object) was the answer. It would
allow us to more easily find all of the logs for a single transaction or account
and our EFK stack could parse out-of-the-box.&lt;/p&gt;
&lt;p&gt;There was a previous attempt at structured logs before I joined the team that
failed to be merged. It was too invasive, the diff too big, it never worked
correctly and it required too much work to carry the diff forward and eventually
the branch was dropped.&lt;/p&gt;
&lt;p&gt;Another complaint I heard from the developers about that previous attempt was
that it made reading the logs when developing locally (reading the logs from the
Docker container) or for failing CI builds more difficult. Indeed, it was not
human-readable.&lt;/p&gt;
&lt;p&gt;Others at the company (with experience log4j and enterprise software) wanted to
output all of the logs to 2 files with different formats. I saw this as a
mistake. It would make handling logs inside of containers more difficult. It
would require dealing with log rotation.&lt;/p&gt;
&lt;p&gt;Another goal for me was to be able gradually convert components to the new log
handling so we won't end up with a huge diff that's never merged again.&lt;/p&gt;
&lt;p&gt;I went with the &lt;a class="reference external" href="http://www.structlog.org/"&gt;structlog&lt;/a&gt; package and after
delving in to it, I created a module that only requires importing to use it.
The module configures the log format (human or json) and level according to
environment variables but it doesn't require any change to the logging calls.
The structlog package also comes with a great looking console renderer where
ever control character are valid. Here's how it looks:&lt;/p&gt;
&lt;img alt="Example rendered logs in a console" src="http://structlog.readthedocs.io/en/stable/_images/console_renderer.png"/&gt;
&lt;p&gt;And here's the module:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging.config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;structlog&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;structlog.stdlib&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;structlog.processors&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;structlog.dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;emptyLogRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makeLogRecord&lt;/span&gt;&lt;span class="p"&gt;({})&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_extra_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'_record'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;'stack_info'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;emptyLogRecord&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__dict__&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;event_dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# noqa: E501&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="n"&gt;event_dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;event_dict&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;pre_chain&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="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StackInfoRenderer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositionalArgumentsFormatter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_log_level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_logger_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TimeStamper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt; %H:%M.%S'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_exc_info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;add_extra_params&lt;/span&gt;&lt;span class="p"&gt;,&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="n"&gt;processor&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="s1"&gt;'human'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConsoleRenderer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'json'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONRenderer&lt;/span&gt;&lt;span class="p"&gt;(),&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="n"&gt;logFormat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LOG_FORMAT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'human'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;logFormat&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;logLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LOG_LEVEL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'WARN'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;logLevel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WARN&lt;/span&gt;  &lt;span class="c1"&gt;# The default log level.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;config&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="s1"&gt;'version'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'disable_existing_loggers'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'formatters'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s1"&gt;'json'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'()'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorFormatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'processor'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'json'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'foreign_pre_chain'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pre_chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'format'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="si"&gt;%(message)s&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="s1"&gt;'human'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'()'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessorFormatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'processor'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'human'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'foreign_pre_chain'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pre_chain&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'handlers'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'formatter'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logFormat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'class'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'logging.StreamHandler'&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;'loggers'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'handlers'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'level'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;'propegate'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&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="p"&gt;},&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="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dictConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;processors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;logFormat&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;context_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="n"&gt;logger_factory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;structlog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoggerFactory&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;I called this module &lt;code&gt;logging_config&lt;/code&gt; and if a module had imported it,
then logs would be human readable by default and if the &lt;code&gt;LOG_FORMAT&lt;/code&gt;
environment variable was set to &lt;code&gt;json&lt;/code&gt; (it would be in the staging and
production environment where we had an EFK stack running and collecting logs)
the output was in json format.&lt;/p&gt;
&lt;p&gt;This was the first step and it could be merged without breaking existing
components. However, most logging calls still had formatted strings which didn't
expose all of the data in a structured format. We had to go module by module and
change to have a single, static log message and pass all of the variables in the
&lt;code&gt;extra&lt;/code&gt; parameter. It would mean changing calls that looked like this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Failed to send message &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; to user &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;to something like this:&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Failed to send message."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"uid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;We could add fields to the output easily, deal with missing parameters more
easily, output the Flask request context more easily, etc. We could do this
piecemeal, going through one module at a time and not have to keep a growing
diff maintained. It would keep the human readable logs where needed and have
easily searchable logs in Kibana where available. We kept to the 12 factor app
methodology and the benefits of it. It required little change to our setup and
deployment. It had noticeable benefits out of gate which made the Dev team pitch
in with converting modules to the new logging calls convention.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Spam me - Update</title><link href="https://www.shore.co.il/blog/spam-me-update/" rel="alternate"/><published>2020-11-20T00:00:00+02:00</published><updated>2020-11-20T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2020-11-20:/blog/spam-me-update/</id><summary type="html">&lt;p class="first last"&gt;Update on the direct messaging experiment&lt;/p&gt;
</summary><content type="html">&lt;p&gt;In &lt;a class="reference external" href="https://www.shore.co.il/blog/spam_me"&gt;last month' post&lt;/a&gt; I wrote about the
new direct messaging I setup and the concern about abuse from that (spam and
privacy concerns). While I can't speak on the privacy part (I don't know if
anybody is listening to that channel besides me), I can update on the spam part.
Since going live I received exactly 0 unsolicited messages. Nada, zilch, bupkis,
גורנישט. I'm a little disappointed with that since it shows that my blog is not
read by millions of people (a shock, I know). But it also seems that automated
scanners, scanning repos in Github don't act on this kind of information. With
that, I plan on keeping everything running for the foreseeable future.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Spam me</title><link href="https://www.shore.co.il/blog/spam_me/" rel="alternate"/><published>2020-10-24T00:00:00+03:00</published><updated>2020-10-24T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2020-10-24:/blog/spam_me/</id><summary type="html">&lt;p class="first last"&gt;An experiment in spam&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A while back I saw an interesting project, &lt;a class="reference external" href="https://patchbay.pub/"&gt;Patchbay&lt;/a&gt;.
At first I wanted to use it when I run long tasks on remote machines (as the
example shows). I would obviously script the desktop part, commit it to my
&lt;a class="reference external" href="https:/git.shore.co.il/nimrod/rcfiles.git"&gt;rcfiles repo&lt;/a&gt; and have it run on
startup.  As a security/ privacy concern, I planned on keeping the full URL
private.  So I shelved it until I would have a proper secret management system
in place for such things.&lt;/p&gt;
&lt;p&gt;A few months went by and I remembered that project and started to play around
with receiving such messages but sending them from a webpage. The outcome is
&lt;a class="reference external" href="https://www.shore.co.il/spam"&gt;shore.co.il/spam&lt;/a&gt;. I'm announcing this on my
blog as I'm actually interested to see if I get any spam this way. The desktop
side of things is in this &lt;a class="reference external" href="https://git.shore.co.il/nimrod/rcfiles/-/commit/1e912443df1d8066f074a4addb1b443ada9ee36e"&gt;rcfiles commit&lt;/a&gt;
and the source for web page is in my &lt;a class="reference external" href="https://git.shore.co.il/nimrod/blog/-/commit/b99513ebadc5f39c77d109597804b76b79e5a2c0"&gt;blog commit&lt;/a&gt;,
both are quite public.&lt;/p&gt;
&lt;p&gt;There isn't something technically interesting here (apart from Patchbay). But
the experiment aspect is interesting to me. I would like to see who reads my
blog and will send me messages (hopefully interesting ones). I'm not going to
advertise this in any other way. And I would like to see if I get any spam as a
result of this blog entry or from having the URL public in my Git repos. I'll
post an update in a few weeks with initial results.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Docker socket over SSH</title><link href="https://www.shore.co.il/blog/docker_socket_over_ssh/" rel="alternate"/><published>2018-01-09T00:00:00+02:00</published><updated>2018-01-09T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2018-01-09:/blog/docker_socket_over_ssh/</id><summary type="html">&lt;p class="first last"&gt;Docker socket over SSH&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Yesterday I described how to connect to a remote &lt;code&gt;dockerd&lt;/code&gt; over TCP. I
didn't touch security considerations at all (firewall, TLS certificate). This
because, for my use, I prefer a different method, forwarding the Unix socket
over SSH. Here's how.&lt;/p&gt;
&lt;p&gt;First, you need OpenSSH version 6.7 or later (both client and server). Also,
the login user on the remote instance must have permissions to access the Docker
socket (in other words, be a member of the &lt;code&gt;docker&lt;/code&gt; group).&lt;/p&gt;
&lt;p&gt;Here's how to forward the remote socket:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
ssh&lt;span class="w"&gt; &lt;/span&gt;-fNTo&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ExitOnForwardFailure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;yes&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ServerAliveInterval&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-L&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.ssh/docker.sock:/var/run/docker.sock&lt;span class="w"&gt; &lt;/span&gt;host&lt;span class="w"&gt;
&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;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.ssh/docker.sock
&lt;/pre&gt;
&lt;p&gt;And to close the connection and return to the local &lt;code&gt;dockerd&lt;/code&gt; kill the
&lt;code&gt;ssh&lt;/code&gt; process that's running in the background, &lt;code&gt;rm&lt;/code&gt; the docker
socket under &lt;code&gt;$HOME/.ssh&lt;/code&gt; and unset &lt;code&gt;DOCKER_HOST&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The reason I prefer this method is that it's easier to setup for ad-hoc tasks
and arguably more secure since you not only authenticate the user and host with
SSH, but you limit access to only those that are part of the &lt;code&gt;docker&lt;/code&gt;
group.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Bind dockerd to a TCP port</title><link href="https://www.shore.co.il/blog/docker_tcp_socket/" rel="alternate"/><published>2018-01-08T00:00:00+02:00</published><updated>2018-01-08T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2018-01-08:/blog/docker_tcp_socket/</id><summary type="html">&lt;p class="first last"&gt;Bind dockerd to a TCP port&lt;/p&gt;
</summary><content type="html">&lt;p&gt;On a modern system (one running Systemd) when installing Docker, the
&lt;code&gt;dockerd&lt;/code&gt; daemon is run using Systemd' socket activation. By default the
socket is &lt;code&gt;/var/run/docker.sock&lt;/code&gt;. If you want to connect to a remote
machine over TCP, the obvious thing to do is to create
&lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; and set the &lt;code&gt;hosts&lt;/code&gt; list there. But that
will conflict with the command line flags for socket activation. The correct way
is to override Systemd' socket activation config. Here's how (all command are as
&lt;code&gt;root&lt;/code&gt;):&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/etc/systemd/system/docker.socket.d&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="s1"&gt;'[Socket]'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/systemd/system/docker.socket.d/tcp.conf&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="s1"&gt;'ListenStream=2375'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/etc/systemd/system/docker.socket.d/tcp.conf&lt;span class="w"&gt;
&lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;daemon-reload&lt;span class="w"&gt;
&lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;restart&lt;span class="w"&gt; &lt;/span&gt;docker
&lt;/pre&gt;
</content><category term="misc"/></entry><entry><title>Bundling a binary file into a shell script</title><link href="https://www.shore.co.il/blog/shell_binary_bundle/" rel="alternate"/><published>2017-12-06T00:00:00+02:00</published><updated>2017-12-06T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2017-12-06:/blog/shell_binary_bundle/</id><summary type="html">&lt;p class="first last"&gt;Bundling a binary file into a shell script&lt;/p&gt;
</summary><content type="html">&lt;p&gt;When creating an auto-scaling group in EC2 I often try to package the deployment
script into the user data. Installing some packaged software is easy to do but
bundling configuration files that are needed is less straightforward.
If the files are not confidential in any way, I either clone a Git repository
or download a tarball from our static assets domain. But this leads to a
dependency on external services and a slightly more complex deployment
procedure. A few days ago I was faced with the same options again but it didn't
sit right with me to do all this for a couple of files that are a few K's in
size totally. I remembered that some software have installation scripts that
bundle the binary blob inside the script.&lt;/p&gt;
&lt;div class="section" id="first-version"&gt;
&lt;h2&gt;First version&lt;/h2&gt;
&lt;p&gt;I searched and found an article in the &lt;a class="reference external" href="http://www.linuxjournal.com/content/add-binary-payload-your-shell-scripts"&gt;Linux Journal&lt;/a&gt;
that seemed to show what I wanted to (and seems to be copied everywhere). You
could download a single file that was a shell script with the binary blob
inside. Your usage will be close to this&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
wget&lt;span class="w"&gt; &lt;/span&gt;http://hostname.tld/bundle&lt;span class="w"&gt;
&lt;/span&gt;sh&lt;span class="w"&gt; &lt;/span&gt;bundle
&lt;/pre&gt;
&lt;p&gt;or this&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
wget&lt;span class="w"&gt; &lt;/span&gt;http://hostname.tld/bundle&lt;span class="w"&gt;
&lt;/span&gt;chmod&lt;span class="w"&gt; &lt;/span&gt;+x&lt;span class="w"&gt; &lt;/span&gt;bundle&lt;span class="w"&gt;
&lt;/span&gt;./bundle
&lt;/pre&gt;
&lt;p&gt;Which is fine. However the code was a bit longer than it should have been and
I felt it could be done better. A little more research and I found an answer in
&lt;a class="reference external" href="https://stackoverflow.com/a/10491738"&gt;Stack Overflow&lt;/a&gt; that mentioned
&lt;code&gt;uuencode&lt;/code&gt; and &lt;code&gt;uudecode&lt;/code&gt;. Reading the man page I saw it was closer
to what I wanted. The code I wrote is available on my &lt;a class="reference external" href="https://git.shore.co.il/nimrod/bundle/-/tree/first_implementation"&gt;GitLab instance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The implementation works as follows. The bundle has the script at the start of
the file with the encoded binary at the end. The shell executes the script part
(which ends with exit as to not continue any further, causing errors) and
&lt;code&gt;uudecode&lt;/code&gt; only starts processing after it sees the relevant header. The
script feeds itself to &lt;code&gt;uudecode&lt;/code&gt; (&lt;code&gt;uudecode "$0"&lt;/code&gt;) which decodes
the binary and outputs it to disk which the script can then use. The code has
both the build instruction in the &lt;code&gt;Makefile&lt;/code&gt; and usage example in the
&lt;code&gt;bats&lt;/code&gt; tests.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="second-version"&gt;
&lt;h2&gt;Second version&lt;/h2&gt;
&lt;p&gt;However something kept nagging me. I wanted a simple invocation method like so:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
curl&lt;span class="w"&gt; &lt;/span&gt;http://hostname.tld/bundle&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/pre&gt;
&lt;p&gt;And in the case of the user data in EC2, I could simply use the bundle.
Otherwise I would need to host it somewhere and in the user data I would
download and run the bundle. Which means that if the bundle was unavailable the
instance would fail to provision.&lt;/p&gt;
&lt;p&gt;Everything I found assumed that the file was present in the file system for
&lt;code&gt;uudecode&lt;/code&gt; to decode. If it was piped there was no file that
&lt;code&gt;uudecode&lt;/code&gt; could then decode. I kept mauling over it and a came up with
a short, clean solution to this problem, which is available &lt;a class="reference external" href="https://git.shore.co.il/nimrod/bundle/-/tree/second_implementation"&gt;here&lt;/a&gt;, again
with build instruction and test examples.&lt;/p&gt;
&lt;p&gt;This time I used AWK to replace a single line in the script with the file,
encoded using &lt;code&gt;uuencode&lt;/code&gt; but this time in base64 (to keep the script valid
without any characters with special meanings). That is piped to &lt;code&gt;uudecode&lt;/code&gt;
which decodes and saves it to disk. The script can then continue with the
binary blob present.&lt;/p&gt;
&lt;p&gt;This method is less space efficient and the build procedure is less obvious. But
the ability to use resulting script as the user data (or piping the output from
&lt;code&gt;curl&lt;/code&gt; to &lt;code&gt;sh&lt;/code&gt;) is worth it in my opinion.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Building inside a Docker container with the correct user</title><link href="https://www.shore.co.il/blog/docker_uid/" rel="alternate"/><published>2017-11-26T00:00:00+02:00</published><updated>2017-11-26T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2017-11-26:/blog/docker_uid/</id><summary type="html">&lt;p class="first last"&gt;Building inside a Docker container with the correct user&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Lately I've been using Docker container as clean, easily portable and easily
removable build environments. In those cases the image contains the needed build
tools and the project is mounted to a volume inside the container. The artifacts
are then built inside the container but are placed inside the volume. However
a small problem arises, the artifacts (and whatever other files are created,
like cache) are owned by the default user, &lt;code&gt;root&lt;/code&gt;, making editing or
removing said files less straightforward.&lt;/p&gt;
&lt;div class="section" id="the-trivial-solution"&gt;
&lt;h2&gt;The trivial solution&lt;/h2&gt;
&lt;p&gt;The trivial solution is to run the container with the correct user id, like so&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;:/volume"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--user&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$uid&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$gid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;buildimage&lt;span class="w"&gt; &lt;/span&gt;make
&lt;/pre&gt;
&lt;p&gt;I personally find it a tiresome after the 3rd time I had to &lt;cite&gt;sudo chown&lt;/cite&gt; the
project because I forgot to specify the uid and gid and it's a (low) barrier
of entry for new users.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-better-solution"&gt;
&lt;h2&gt;A better solution&lt;/h2&gt;
&lt;p&gt;The solution I've come up with is this small script that sets the uid and gid
values to those of the owner and group for the volume and then execute the
commands.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/sh
&lt;/span&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;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;id&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&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="s2"&gt;"0"&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;||&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;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not running as root, continuing as the current user."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&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="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;stat&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null&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;{&lt;/span&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;"Can't find stat, exiting."&lt;/span&gt;&lt;span class="p"&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;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="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;gosu&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null&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;{&lt;/span&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;"Can't find gosu, exiting."&lt;/span&gt;&lt;span class="p"&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;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="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;stat&lt;span class="w"&gt; &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;'%u'&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;stat&lt;span class="w"&gt; &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;'%g'&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gosu&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$uid&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$gid&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The script is also available for &lt;a class="reference external" href="https://www.shore.co.il/blog/static/runas"&gt;download&lt;/a&gt;. The only dependency is
&lt;a class="reference external" href="https://github.com/tianon/gosu"&gt;gosu&lt;/a&gt;.  You can download and check it to
your VCS and incorporate it into your Dockerfile, or download it via the
&lt;code&gt;ADD&lt;/code&gt; directive, like so:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
FROM&lt;span class="w"&gt; &lt;/span&gt;buildpack-deps&lt;span class="w"&gt;
&lt;/span&gt;RUN&lt;span class="w"&gt; &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://github.com/tianon/gosu/releases/download/1.10/gosu-amd64&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;gosu-amd64&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="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;755&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gosu-amd64&lt;span class="w"&gt; &lt;/span&gt;/usr/local/bin/gosu&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="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;gosu-amd64&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="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-fsSL&lt;span class="w"&gt; &lt;/span&gt;https://www.shore.co.il/blog/static/runas&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;runas&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="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;-g&lt;span class="w"&gt; &lt;/span&gt;root&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;755&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;runas&lt;span class="w"&gt; &lt;/span&gt;/entrypoint&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="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;runas&lt;span class="w"&gt;
&lt;/span&gt;ENTRYPOINT&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;"/entrypoint"&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;VOLUME&lt;span class="w"&gt; &lt;/span&gt;/volume&lt;span class="w"&gt;
&lt;/span&gt;WORKDIR&lt;span class="w"&gt; &lt;/span&gt;/volume&lt;span class="w"&gt;
&lt;/span&gt;ENV&lt;span class="w"&gt; &lt;/span&gt;HOME&lt;span class="w"&gt; &lt;/span&gt;/volume
&lt;/pre&gt;
&lt;p&gt;Setting the home directory to the mounted volume will result in some files (like
package managers cache) to be created there, which you may or may not want. And
then finally, to build run&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;:/volume"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;buildimage&lt;span class="w"&gt; &lt;/span&gt;make
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>How to fix the PocketCHIP for Vim</title><link href="https://www.shore.co.il/blog/pocketchip-xmodmap/" rel="alternate"/><published>2017-04-22T00:00:00+03:00</published><updated>2017-04-22T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2017-04-22:/blog/pocketchip-xmodmap/</id><summary type="html">&lt;p class="first last"&gt;How to fix the PocketCHIP for Vim&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I ordered me a PocketCHIp to have a cheap, portable Linux computer with a
physical keyboard with the added benefit if it running Debian out of the box. I
quickly discovered that in Vim the dash or minus key is not what you'd expect. A
quick search turned up that the key is mapped the numpad minus via
&lt;code&gt;xmopmap&lt;/code&gt;. To change the mapping to what is for me a better setting run
&lt;code&gt;sed -i 's/KP_Subtract/minus/g' ~/.Xmodmap&lt;/code&gt; and to apply the setting
afterward run &lt;code&gt;xmodmap ~/.Xmodmap&lt;/code&gt;.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>VirtualBox extensions</title><link href="https://www.shore.co.il/blog/vbox_extenstions/" rel="alternate"/><published>2017-01-17T00:00:00+02:00</published><updated>2017-01-17T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2017-01-17:/blog/vbox_extenstions/</id><summary type="html">&lt;p class="first last"&gt;Installing the VirtualBox extension pack&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I happened to run into a Vagrant image today that required the USB2 controller
extension to VirtualBox. The procedure to install the extension pack was not
immediately clear to me.&lt;/p&gt;
&lt;div class="section" id="installing-through-the-virtualbox-gui"&gt;
&lt;h2&gt;Installing through the VirtualBox GUI&lt;/h2&gt;
&lt;p&gt;When trying to go through the VirtualBox GUI, the
application would close when I clicked on 'add new package' in the preferences
window. Guessing it had something to do with root privileges I tried to run the
GUI with &lt;code&gt;sudo&lt;/code&gt; but that failed because I'm using Wayland and it could
not find display :0. Using Gnome3, &lt;code&gt;gksudo&lt;/code&gt; is no longer available and
the replacement is &lt;code&gt;pkexec&lt;/code&gt; which uses PolicyKit. running &lt;code&gt;pkexec
VirtualBox&lt;/code&gt; opened the nice Gnome3 authentication prompt but resulted in the
same error. Giving up on starting the VirtualBox GUI as root, I moved my
efforts to the &lt;code&gt;VBoxManage&lt;/code&gt; CLI.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="installing-with-the-vboxmanage-cli"&gt;
&lt;h2&gt;Installing with the VBoxManage CLI&lt;/h2&gt;
&lt;p&gt;Here are the steps I took to successfully install the extension pack:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Find the version of VirtualBox you have installed by running
&lt;code&gt;VBoxManage --version&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Download the matching version of the extension pack from
&lt;a class="reference external" href="https://www.virtualbox.org/wiki/Downloads"&gt;https://www.virtualbox.org/wiki/Downloads&lt;/a&gt; if you're using the latest version
or from &lt;a class="reference external" href="https://www.virtualbox.org/wiki/Download_Old_Builds"&gt;https://www.virtualbox.org/wiki/Download_Old_Builds&lt;/a&gt; if you're using
an older version.&lt;/li&gt;
&lt;li&gt;Assuming you're using version 5.1.10, install the extension pack by running
&lt;code&gt;VBoxManage extpack install
Oracle_VM_VirtualBox_Extension_Pack-5.1.10-112026.vbox-extpack&lt;/code&gt;. For me,
this opened the same nice Gnome3 authentication prompt.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Testing DNS with a clean cache</title><link href="https://www.shore.co.il/blog/resolver/" rel="alternate"/><published>2016-11-01T00:00:00+02:00</published><updated>2016-11-01T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-11-01:/blog/resolver/</id><summary type="html">&lt;p class="first last"&gt;Testing DNS with a clean cache&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Every so often I make changes to a DNS record, test it, find out it's wrong, fix
it and still get the old response because of caching somewhere along the line.
After it happened to me and a colleague during a launch of a new version of a
website, I decided to address the issue. I wanted a way to test DNS quickly and
easily (preferably locally on command line), for it to be lightweight, doesn't
require changes to my existing setup and doesn't require learning new tools. I
decided to create a Docker image that has its own DNS resolver and each new
container from that image has a clean cache and doesn't depend on other DNS
servers or is affected from their caching.&lt;/p&gt;
&lt;div class="section" id="usage"&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;To create a new container:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;-it&lt;span class="w"&gt; &lt;/span&gt;registry.shore.coil/resolver
&lt;/pre&gt;
&lt;p&gt;Inside the container you have access to &lt;code&gt;nslookup&lt;/code&gt;, &lt;code&gt;dig&lt;/code&gt; and
&lt;code&gt;mail&lt;/code&gt; for testing purposes. If you need to test new changes,
&lt;code&gt;exit&lt;/code&gt; the container and create a new one with no cache.&lt;/p&gt;
&lt;p&gt;If you want to run just a single command (like getting the MX record for
&lt;code&gt;shore.co.il&lt;/code&gt;):&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;registry.shore.co.il/resolver&lt;span class="w"&gt; &lt;/span&gt;dig&lt;span class="w"&gt; &lt;/span&gt;+short&lt;span class="w"&gt; &lt;/span&gt;shore.co.il&lt;span class="w"&gt; &lt;/span&gt;mx
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="how-does-it-work"&gt;
&lt;h2&gt;How does it work&lt;/h2&gt;
&lt;p&gt;On launch, the container runs and uses its own DNS resolver (in this case NSD).
This way the OS caching or upstream caching don't interferes with querying and
every new container starts with an empty cache.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Self service AWS IAM policy</title><link href="https://www.shore.co.il/blog/aws_change_own_password/" rel="alternate"/><published>2016-09-01T00:00:00+03:00</published><updated>2016-09-01T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-09-01:/blog/aws_change_own_password/</id><summary type="html">&lt;p class="first last"&gt;AWS IAM policy to allow users to change their own password and manage
their own keys.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A common practice for me when a new member joins the team or when someone
forgets his/ her AWS account password is to change the account password myself,
send the new password over an insecure channel (email, Slack) but force the
account to change the password on first login. Also, I prefer to have users
manage their own keys to AWS themselves. But without the correct IAM policy
users aren't able to perform either action. Here's an IAM to allow both:&lt;/p&gt;
&lt;pre class="code json literal-block"&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&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="s2"&gt;"iam:GetAccountPasswordPolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:ListAccount*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:GetAccountSummary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:GetAccountPasswordPolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:ListUsers"&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="nt"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nt"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&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="s2"&gt;"iam:ChangePassword"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:*LoginProfile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:*AccessKey*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"iam:*SSHPublicKey*"&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="nt"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::&lt;insert account="" aws="" here="" id=""&gt;:user/${aws:username}"&lt;/insert&gt;&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="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;If you want a little script with the AWS CLI, here's one for you:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="nv"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;accountid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;aws&lt;span class="w"&gt; &lt;/span&gt;ec2&lt;span class="w"&gt; &lt;/span&gt;describe-security-groups&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;--group-names&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Default'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;--query&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'SecurityGroups[0].OwnerId'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;--output&lt;span class="w"&gt; &lt;/span&gt;text&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;https://www.shore.co.il/blog/static/policy.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s/&lt;insert account="" aws="" here="" id=""&gt;/&lt;/insert&gt;&lt;/span&gt;&lt;span class="nv"&gt;$accountid&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$tempfile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;aws&lt;span class="w"&gt; &lt;/span&gt;iam&lt;span class="w"&gt; &lt;/span&gt;create-policy&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;--policy-name&lt;span class="w"&gt; &lt;/span&gt;change-own-password&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;--policy-document&lt;span class="w"&gt; &lt;/span&gt;file://&lt;span class="nv"&gt;$tempfile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$tempfile&lt;/span&gt;
&lt;/pre&gt;
</content><category term="misc"/></entry><entry><title>Ad-hoc serving of git repositories</title><link href="https://www.shore.co.il/blog/git_serve/" rel="alternate"/><published>2016-08-16T00:00:00+03:00</published><updated>2016-08-16T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-08-16:/blog/git_serve/</id><summary type="html">&lt;p class="first last"&gt;Ad-hoc serving of git repositories&lt;/p&gt;
</summary><content type="html">&lt;p&gt;On some occasion you want to serve your git repo from your local copy (perhaps
your git repository is quite large and your internet connection is slow or your
build process would benefit from pulling from an intermediary without
authentication). Here are 2 ways to serve your git repository without any
configuration or software installation. Both ways serve a single repository
without authentication or encryption but read-only (no push).&lt;/p&gt;
&lt;div class="section" id="using-the-git-protocol"&gt;
&lt;h2&gt;Using the git protocol&lt;/h2&gt;
&lt;p&gt;The git executable is itself a git server using the native git protocol. Inside
the root of the repository run the following command&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
git&lt;span class="w"&gt; &lt;/span&gt;daemon&lt;span class="w"&gt; &lt;/span&gt;--reuseaddr&lt;span class="w"&gt; &lt;/span&gt;--verbose&lt;span class="w"&gt;  &lt;/span&gt;--base-path&lt;span class="o"&gt;=&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;--export-all&lt;span class="w"&gt; &lt;/span&gt;./.git
&lt;/pre&gt;
&lt;p&gt;And on the client you can clone by running&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;git://servername/&lt;span class="w"&gt; &lt;/span&gt;reponame
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="using-the-http-protocol"&gt;
&lt;h2&gt;Using the HTTP protocol&lt;/h2&gt;
&lt;p&gt;This way serves the repo over HTTP using Python 2's SimpleHTTPServer. Run the
following in the rot of the git repo&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
git&lt;span class="w"&gt; &lt;/span&gt;update-server-info&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;.git&lt;span class="w"&gt;
&lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;SimpleHTTPServer
&lt;/pre&gt;
&lt;p&gt;And on the client clone by running&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;http://servername:8000/&lt;span class="w"&gt; &lt;/span&gt;reponame
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="final-words"&gt;
&lt;h2&gt;Final words&lt;/h2&gt;
&lt;p&gt;I've added both ways as git aliases in my &lt;a class="reference external" href="https://git.shore.co.il/nimrod/rcfiles/-/blob/master/.config/git/config"&gt;rcfiles repo&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>SSH security</title><link href="https://www.shore.co.il/blog/ssh_security/" rel="alternate"/><published>2016-07-05T00:00:00+03:00</published><updated>2016-07-05T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-07-05:/blog/ssh_security/</id><summary type="html">&lt;p class="first last"&gt;My best practices regarding SSH security&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Over the years I'd heard many people share their best practices regarding
securing SSH access and have been asked by friends and colleagues how to secure
their servers. So here are my opinions and practices regarding SSH security. The
main point I try to get across is balance. Balance between security and
functionality.&lt;/p&gt;
&lt;div class="section" id="practices-i-avoid"&gt;
&lt;h2&gt;Practices I avoid&lt;/h2&gt;
&lt;p&gt;First, changing the listening port. The upside is that a high random port is
scanned less often and the various script kiddies sometimes fails to notice it,
thus reducing the noise in the logs. This however is no real security measure
as any capable attacker will quickly spot the daemon listening on a different
and all benefits will be lost. The downside is that by not using the default
port you need to configure all clients accordingly. So, no substantial wins
and minor loss. I pass on this idea.&lt;/p&gt;
&lt;p&gt;The second most common is allowing access only from the office IP or a few
select IP addresses. The security benefit is high but the risk is also high. I
view SSH access to servers as critical and limiting access puts you in risk of
locking yourself out in case of trouble/ emergency. Not having management access
to your servers is more dangerous to your business than allowing whomever to
try (not succeed) to access your servers. Therefore I prefer to limit the access
in different ways.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="practices-i-employ"&gt;
&lt;h2&gt;Practices I employ&lt;/h2&gt;
&lt;p&gt;Most importantly, allow only key-based authentication. This works without
relying on 3rd party services and is extremely secure when done properly. The
most important issue keeping your private key private. Personally I keep my home
directory on a separate partition that is encrypted with LUKS so the keys are
encrypted when in rest. If you don't have encrypted storage for your keys,
password encrypt the keys (consult the ssh-keygen manual for instructions).&lt;/p&gt;
&lt;p&gt;Another measure of security is limiting the number of authentication attempts
any single IP can perform in a given time. This is achieved by 2 actions. By
ensuring that &lt;code&gt;MaxAuthTries&lt;/code&gt; in your sshd config is not set too high (the
default is 6 which is damn reasonable) and by limiting the number of TCP
connections to port 22 any IP can initiate. With UFW on Linux this is done by
running &lt;code&gt;ufw limit ssh&lt;/code&gt; and for OpenBSD or FreeBSD I'd refer you to Peter
Hansteen's &lt;a class="reference external" href="https://home.nuug.no/~peter/pf/en/bruteforce.html"&gt;great PF tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These 2 steps will create a barrier to entry that no brute-force attack will be
able to overcome in anyone's lifetime. This is what I do on all of my servers
and it has served me well. However, although the commonly used OpenSSH is
extensively used, researched and tested, bugs still happen. Keeping current
with updates is vital.&lt;/p&gt;
&lt;p&gt;Also, some other good practices are disabling root login, SSH protocol version 1
is deprecated, insecure and must be turned off (is off by default, but I felt
it's worth mentioning). To further simplify things, here is a short Ansible
playbook that covers the actions mentioned above for Debian based systems.&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;all&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Restart SSH&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&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-Scalar-Plain"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;restarted&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;APT install and update&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;with_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;openssh-server&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;ufw&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;apt&lt;/span&gt;&lt;span class="p"&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="s"&gt;'{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;latest&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;update_cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;yes&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;cache_valid_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;3600&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Configure SSHd&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;with_dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;MaxAuthTries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;PasswordAuthentication&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;no&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;PermitRootLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;no&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;lineinfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;/etc/ssh/sshd_config&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;regexp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item.key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item.key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item.value&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;present&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;Restart SSH&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Enable UFW&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;ufw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;enabled&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Rate limit SSH&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;ufw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;limit&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;tcp&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Expanding variables and shell expressions in parameters to Docker entrypoint</title><link href="https://www.shore.co.il/blog/docker_entrypoint/" rel="alternate"/><published>2016-06-13T00:00:00+03:00</published><updated>2016-06-13T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-06-13:/blog/docker_entrypoint/</id><summary type="html">&lt;p class="first last"&gt;Expanding variables and shell expressions in parameters to Docker entrypoint&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A known best practice when creating Docker images is when you need to run
commands in runtime before starting the actual application/ daemon is to create
an entrypoint script and pass the command as parameters in the &lt;code&gt;CMD&lt;/code&gt;
instruction. Another best practice is to exec the final command so it would be
PID 1 and receive the signals passed to it. Let's create a small example. Here's
the &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
FROM alpine
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
ENV var value
CMD ["echo", "$var"]
&lt;/pre&gt;
&lt;p&gt;And the &lt;code&gt;entrypoint.sh&lt;/code&gt; script:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/sh
&lt;/span&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;&lt;span class="c1"&gt;# Perform any needed tasks here.
&lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now let's build and run this container:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--tag&lt;span class="w"&gt; &lt;/span&gt;entrypoint&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&gt;
&lt;/span&gt;Sending&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;context&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;Docker&lt;span class="w"&gt; &lt;/span&gt;daemon&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt;.67&lt;span class="w"&gt; &lt;/span&gt;kB&lt;span class="w"&gt;
&lt;/span&gt;Step&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="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;alpine&lt;span class="w"&gt;
 &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;5f05d2ba9e65&lt;span class="w"&gt;
&lt;/span&gt;Step&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;:&lt;span class="w"&gt; &lt;/span&gt;COPY&lt;span class="w"&gt; &lt;/span&gt;entrypoint.sh&lt;span class="w"&gt; &lt;/span&gt;/entrypoint.sh&lt;span class="w"&gt;
 &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;f59f4d7f3546&lt;span class="w"&gt;
&lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;intermediate&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;27ca546c6b6c&lt;span class="w"&gt;
&lt;/span&gt;Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ENTRYPOINT&lt;span class="w"&gt; &lt;/span&gt;/entrypoint.sh&lt;span class="w"&gt;
 &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Running&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;98c65b63948a&lt;span class="w"&gt;
 &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;1de45b33021b&lt;span class="w"&gt;
&lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;intermediate&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;98c65b63948a&lt;span class="w"&gt;
&lt;/span&gt;Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;ENV&lt;span class="w"&gt; &lt;/span&gt;var&lt;span class="w"&gt; &lt;/span&gt;value&lt;span class="w"&gt;
 &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Running&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;133a8781f0ac&lt;span class="w"&gt;
 &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;bba451334fb2&lt;span class="w"&gt;
&lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;intermediate&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;133a8781f0ac&lt;span class="w"&gt;
&lt;/span&gt;Step&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;CMD&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="nv"&gt;$var&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;Running&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;e8436c6c3202&lt;span class="w"&gt;
 &lt;/span&gt;---&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;a49d9b335b74&lt;span class="w"&gt;
&lt;/span&gt;Removing&lt;span class="w"&gt; &lt;/span&gt;intermediate&lt;span class="w"&gt; &lt;/span&gt;container&lt;span class="w"&gt; &lt;/span&gt;e8436c6c3202&lt;span class="w"&gt;
&lt;/span&gt;Successfully&lt;span class="w"&gt; &lt;/span&gt;built&lt;span class="w"&gt; &lt;/span&gt;a49d9b335b74&lt;span class="w"&gt;
&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;entrypoint&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$var&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;As we can see the variable &lt;code&gt;var&lt;/code&gt; wasn't expanded to it's content. After a
bit of head scratching, The following simple change was made to the entrypoint
script.&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/sh
&lt;/span&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;&lt;span class="c1"&gt;# Perform any needed tasks here.
&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exec &lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The change is to first evaluate the expression (expanding any variable and
expression found), then &lt;code&gt;exec&lt;/code&gt; it. The outcome is what you'd expect.&lt;/p&gt;
&lt;pre class="code literal-block"&gt;
$ docker build --tag entrypoint .
Sending build context to Docker daemon 28.67 kB
Step 0 : FROM alpine
 ---&amp;gt; 5f05d2ba9e65
Step 1 : COPY entrypoint.sh /entrypoint.sh
 ---&amp;gt; b874d862999d
Removing intermediate container fb6483ff00e3
Step 2 : ENTRYPOINT /entrypoint.sh
 ---&amp;gt; Running in 82adf0b2c4c7
 ---&amp;gt; 6674f336c5e1
Removing intermediate container 82adf0b2c4c7
Step 3 : ENV var value
 ---&amp;gt; Running in 599f3f98c11d
 ---&amp;gt; 980f1e1e1ad5
Removing intermediate container 599f3f98c11d
Step 4 : CMD echo $var
 ---&amp;gt; Running in e29f1948480a
 ---&amp;gt; e27fd79143f8
Removing intermediate container e29f1948480a
Successfully built e27fd79143f8
$ docker run entrypoint
value
&lt;/pre&gt;
</content><category term="misc"/></entry><entry><title>An example Ansible role</title><link href="https://www.shore.co.il/blog/ansible-example-role/" rel="alternate"/><published>2016-05-19T00:00:00+03:00</published><updated>2016-05-19T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-05-19:/blog/ansible-example-role/</id><summary type="html">&lt;p class="first last"&gt;An example Ansible role.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;A few weeks ago I started a new job and a lot of time was spent on refactoring
as well as adding to an existing Ansible automation code base. For me this was a
chance to work more with &lt;a class="reference external" href="https://molecule.readthedocs.org/"&gt;Molecule&lt;/a&gt; for
testing. Molecule is a infrastructure-as-code testing tool that is
inspired by Test-kitchen and the tests can be written using &lt;a class="reference external" href="http://testinfra.readthedocs.io/"&gt;Testinfra&lt;/a&gt; which in turn is using &lt;a class="reference external" href="http://pytest.org/"&gt;pytest&lt;/a&gt;. The reasons for me to choose this combination is that
the tools are written in Python and that they're focused on Ansible. However I
quickly grew tired of copying files from role to role or making the same
changes to files again and again. So in that spirit I created a new Git repo
with an empty Ansible role (no tasks, variables, handlers etc.) but has all of
my changes and tweaks already applied and working tests out of the box.&lt;/p&gt;
&lt;div class="section" id="usage"&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;To work on the role install VirtualBox and Vagrant (I use the versions in
Debian's repos) and from PyPI Ansible, Molecule and Testinfra. Now, fork the
repo. As you can see there are already README and LICENSE files. If you ever
ran &lt;code&gt;ansible-galaxy init&lt;/code&gt; or &lt;code&gt;molecule init&lt;/code&gt; you'll notice that
indeed the repo was created with those tools.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="dependencies"&gt;
&lt;h2&gt;Dependencies&lt;/h2&gt;
&lt;p&gt;There's an example dependency present in &lt;code&gt;meta/main.yml&lt;/code&gt; but instead of
the declaring the dependencies in &lt;code&gt;meta/main.yml&lt;/code&gt; and the sources of the
dependencies in &lt;code&gt;requirements.yml&lt;/code&gt; which leads to repeating yourself, the
example shows how to declare the source of the dependent role directly in
&lt;code&gt;meta/main.yml&lt;/code&gt; (which I haven't seen mentioned clearly in the Ansible
documentation. For repositories with playbooks I'd still add a
&lt;code&gt;requirements.yml&lt;/code&gt; file since there's no meta directory. Pulling the
dependencies took some thought and what I came up with is:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
ansible-galaxy&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;git+file://&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;,&lt;span class="k"&gt;$(&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;rev-parse&lt;span class="w"&gt; &lt;/span&gt;--abbrev-ref&lt;span class="w"&gt; &lt;/span&gt;HEAD&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;This is a workaround for installing the dependencies as it actually uses
ansible-galaxy to install the git repo of the role and the dependencies as well.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="testing"&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;First, I configured &lt;a class="reference external" href="http://pre-commit.com/"&gt;pre-commit&lt;/a&gt; hooks that check,
among other things, the validity of the YAML files and the does a syntax check
of the Ansible playbook.&lt;/p&gt;
&lt;p&gt;As for Molecule, the configuration of the test environment is mainly under
&lt;code&gt;molecule.yml&lt;/code&gt;. That is were you'd go to change the Vagrant box to test.
You can add multiple boxes and specify which box to test like so
&lt;code&gt;molecule test --platform &lt;box_name&gt;&lt;/box_name&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Also worth mentioning is the Ansible configuration in &lt;code&gt;ansible.cfg&lt;/code&gt;.
This is some what of a workaround as well because many of the options can be
configured in &lt;code&gt;molecule.yml&lt;/code&gt; which is used to generate its own
&lt;code&gt;ansible.cfg&lt;/code&gt;. However since Testinfra runs the tests over Ansible and
Molecule doesn't pass the configuration along to it, the configuration isn't
honored during testing. This caused me some grief as tests were constantly
failing because Ansible would the host SSH key and fail as it was not known.
The way I did is create an &lt;code&gt;ansible.cfg&lt;/code&gt; at the root of the repo where
Testinfra would look and passed that as the template to Molecule.&lt;/p&gt;
&lt;p&gt;The playbook that is run in at &lt;code&gt;tests/playbook.yml&lt;/code&gt; and the tests are
under &lt;code&gt;tests/&lt;/code&gt; as well. There's an simple example test but the Testinfra
documentation quite good. Just remember to that both the filename and function
name should start with &lt;code&gt;test_&lt;/code&gt; and you won't have tests that aren't found.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-word-on-ci"&gt;
&lt;h2&gt;A word on CI&lt;/h2&gt;
&lt;p&gt;Now you have all of the different pieces and workflow to run complete tests on
roles the next obvious step is setting up a CI pipeline. In my tests and as I
know the various CI services (I tried Travis-CI and CircleCI) disable the
option to run any hypervisor. For me it's a deal breaker because I depend on
VirtualBox (I need to test on different OSes, not just Linux). If LXC serves
your needs than you should be able to run Vagrant with the LXC provider and
therefore Molecule. For me it's a deal breaker.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-final-word-on-boiler-plate"&gt;
&lt;h2&gt;A final word on boiler-plate&lt;/h2&gt;
&lt;p&gt;In a previous post I mentioned that I have several repositories that have the
same boiler-plate and how I plan on dealing with that. Now, this is the first
attempt at this. The idea is having a base repo that I clone, add another remote
and voilà, a new project with the scaffolding already there. For bonus points, I
can update the base repo and pull those changes in all projects. Here's how I do
it:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://git.shore.co.il/ansible/ansible-role-example.git&lt;span class="w"&gt; &lt;/span&gt;ansible-role-name&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;ansible-role-name&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;file&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="k"&gt;$(&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;-l&lt;span class="w"&gt; &lt;/span&gt;ansible-role-example&lt;span class="k"&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;do&lt;/span&gt;&lt;span class="w"&gt; &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;'s/ansible-role-example/ansible-role-name/g'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.&lt;span class="w"&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="s2"&gt;"- Renamed ansible-role-example to ansible-role-name."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;rename&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;ansible-role-example&lt;span class="w"&gt;
&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@example.com/path/to/repo&lt;span class="w"&gt;
&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;/pre&gt;
&lt;p&gt;And in case I update the ansible-role-example repo than I pull the updates by
running &lt;code&gt;git pull ansible-role-example master&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Pre-commit hooks</title><link href="https://www.shore.co.il/blog/pre-commit/" rel="alternate"/><published>2016-03-12T00:00:00+02:00</published><updated>2016-03-12T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-03-12:/blog/pre-commit/</id><summary type="html">&lt;p class="first last"&gt;New pre-commit hooks I wrote&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;a class="reference external" href="http://www.pre-commit.com"&gt;Pre-commit&lt;/a&gt; is a nice, simple tool to add Git
hooks to your project. The primary goal is running fast checks on commits
(before committing them), mainly linters and syntax checkers. Today I've 2 of my
own, for Ansible playbooks and shell scripts. The Ansible playbooks hook is
located at &lt;a class="reference external" href="https://git.shore.co.il/ansible/ansible-pre-commit.git"&gt;https://git.shore.co.il/ansible/ansible-pre-commit.git&lt;/a&gt; and the shell
scripts hook is at &lt;a class="reference external" href="https://git.shore.co.il/nimrod/shell-pre-commit.git"&gt;https://git.shore.co.il/nimrod/shell-pre-commit.git&lt;/a&gt;. Both
have a short README which describes installation and usage.&lt;/p&gt;
&lt;div class="section" id="my-view-on-testing"&gt;
&lt;h2&gt;My view on testing&lt;/h2&gt;
&lt;p&gt;I find that Pre-commit suites my view on proportionate testing. The smaller the
change, the faster the test (and as a result, more trivial). Personally, I
prefer to structure my work as small commits that are easier to revert, these
deserve fast (and more trivial) tests which Pre-commit provides. The bigger the
change, the more rigorous (and thus longer) the test. In my opinion this helps
in creating a good workflow which quickly finds small errors while developing
and reduces the number of times one must ran the full test suite because he/she
had a typo that failed the test. This is why I prefer to separate the test
suite so that I can the ability to run the simpler and faster locally and get
rid of simple error quickly.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>bundle certs</title><link href="https://www.shore.co.il/blog/bundle_certs/" rel="alternate"/><published>2016-03-02T00:00:00+02:00</published><updated>2016-03-02T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-03-02:/blog/bundle_certs/</id><summary type="html">&lt;p class="first last"&gt;Announce a new tool, bundle_certs&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Like I said in a previous blog post, I rarely blog but I run &lt;code&gt;git init
project-name&lt;/code&gt; pretty regularly. So here's a new such repo, &lt;a class="reference external" href="https://git.shore.co.il/nimrod/bundle_certs.git"&gt;bundle_certs&lt;/a&gt;. A simple shell script for
bundling (in the correct order) SSL certificates.&lt;/p&gt;
&lt;div class="section" id="how-i-start-new-projects"&gt;
&lt;h2&gt;How I start new projects&lt;/h2&gt;
&lt;p&gt;This little tool, along with &lt;a class="reference external" href="https://git.shore.co.il/nimrod/ssl-ca.git"&gt;ssl-ca&lt;/a&gt; and &lt;a class="reference external" href="https://git.shore.co.il/nimrod/ssh-ca.git"&gt;ssh-ca&lt;/a&gt; have some commonality in how I use
them and this seems like a good opportunity to share. I keep my rc files (like
&lt;code&gt;.vimrc&lt;/code&gt;) in the &lt;cite&gt;rcfiles repo
&lt;https: cite="" git.shore.co.il="" nimrod="" rcfiles.git&lt;=""&gt;&amp;gt;_.  However I don't install them as
mentioned in the documentation. Instead I add them as Git sub modules and now I
can be reasonably sure that when I clone the rcfiles repository, the aliases and
sourced files mentioned in &lt;code&gt;.bashrc&lt;/code&gt; are present. Here's how:&lt;/https:&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
ssh&lt;span class="w"&gt; &lt;/span&gt;cgit&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'git init --bare /srv/git/REPONAME'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;submodule&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;master&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;https://www.shore.co.il/cgit/REPONAME
&lt;/pre&gt;
&lt;p&gt;First I create the remote repository (most of you would probably use Github but
I prefer self hosting). Then I add it as a Git submodule.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="repository-boiler-plate"&gt;
&lt;h2&gt;Repository boiler-plate&lt;/h2&gt;
&lt;p&gt;Truth be told, there are more line of tests, documentation, license, etc. than
there is actual code in these repositories. It happened to a few times that I
added something nice to a repository that I wanted to have in all (or most) of
my other repositories and in new repositories going forward.&lt;/p&gt;
&lt;p&gt;One solution I thought of is creating a base template repository that all
others are forked from. The upside is if I change something in the base
repository I can fetch it in all other repositories. The downside is not all
repositories are the same (different license, programming language, pre-commit
and git hooks).&lt;/p&gt;
&lt;p&gt;Another option I know of are tools that manage a specific aspect of the repo,
for example the license, or &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A third option is using a project management tool like &lt;a class="reference external" href="http://doc.crates.io/"&gt;Cargo&lt;/a&gt; for Rust or &lt;a class="reference external" href="http://leiningen.org/"&gt;Leiningen&lt;/a&gt;
for Clojure. But not all aspects or languages have such tools.&lt;/p&gt;
&lt;p&gt;The fourth option I'm thinking of is using a scaffolding tool, mainly &lt;a class="reference external" href="http://yeoman.io/"&gt;Yeoman&lt;/a&gt; as it seems to the most popular one but its focus is on
JS and webapps.&lt;/p&gt;
&lt;p&gt;As of now, my plan is to try and maintain a base repo for certain project types
and see how it goes (Yeoman would just take more time to get started with).&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Finding is a script sourced or not?</title><link href="https://www.shore.co.il/blog/sourced_or_not/" rel="alternate"/><published>2016-02-29T00:00:00+02:00</published><updated>2016-02-29T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2016-02-29:/blog/sourced_or_not/</id><summary type="html">&lt;p class="first last"&gt;How to find if a shell script is sourced or not.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've recently written a shell script that contained several functions and I
wanted to support 2 usage methods. The first is quite regular, marking it as an
executable and running it. The second is to source the script and gain the
functions declared. The problem is not actually performing any tasks (or
outputting anything) if the script is being sourced. It took a bit of fiddling
but I found a short one-liner to add at the top of the script that solves this
in a POSIX-compliant way (at least on my test machines, Debian with Bash and Dash
and KSH on OpenBSD). Here is an example usage:&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
&lt;span class="ch"&gt;#!/bin/sh -e
&lt;/span&gt;&lt;span class="c1"&gt;# Check if the script is being sourced or not.
&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_&lt;/span&gt;&lt;span class="s2"&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="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&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="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;expr&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$-&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".*i.*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null&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;sourced&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&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="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$sourced&lt;/span&gt;&lt;span class="s2"&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="k"&gt;then&lt;/span&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;Sourced&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&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;Run&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;The solution is using 2 heuristics. If the last argument (&lt;code&gt;$_&lt;/code&gt;) if
different from the command name (must be first command run, otherwise the last
argument will be overwritten). The second is if the option flags (&lt;code&gt;$-&lt;/code&gt;)
contain &lt;code&gt;i&lt;/code&gt; for interactive. This works when both marking the script as
executable and passing the name as a parameter to the shell.&lt;/p&gt;
</content><category term="misc"/></entry><entry><title>Sharing Ansible modules</title><link href="https://www.shore.co.il/blog/ansible-modules/" rel="alternate"/><published>2015-11-15T00:00:00+02:00</published><updated>2015-11-15T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2015-11-15:/blog/ansible-modules/</id><summary type="html">&lt;p class="first last"&gt;How to share Ansible modules&lt;/p&gt;
</summary><content type="html">&lt;p&gt;With Ansible you're expected to share roles with the Ansible Galaxy tool (either
through the &lt;a class="reference external" href="https://galaxy.ansible.com/"&gt;Ansible Galaxy hub&lt;/a&gt; or just using
straight git repositories). This works well enough (and personally I am using
&lt;code&gt;ansible-galaxy init&lt;/code&gt; to start each new role, even those that I'm not
going to share with the community). However, for sharing modules there is no
such easy solution, or is it?&lt;/p&gt;
&lt;div class="section" id="sharing-with-git-submodule"&gt;
&lt;h2&gt;Sharing with git submodule&lt;/h2&gt;
&lt;p&gt;I'd like to start by saying that git submodule is the poor man's package manager
and it's lack of popularity is (somewhat) justified. However, this is a nice
demonstration of a case where there is no package manager available and of using
git submodule instead. Also, I've only been able to use this technique for
modules written in Python, which is nice considering the lack of boiler-plate
that Ansible provides and that Python is my personal preference.&lt;/p&gt;
&lt;p&gt;The whole story is really quite simple, create a separate git repository with
the modules in it. You can put them in sub-directories and as a far as I know,
there's no restriction on the hierarchy depth. In your playbook directory create
a &lt;code&gt;library&lt;/code&gt; directory (the Ansible default, so you can change this in
&lt;code&gt;ansible.cfg&lt;/code&gt;) and create an empty &lt;code&gt;__init__.py&lt;/code&gt; file inside that
directory. Add a git submodule inside that directory and you're done. Let's see
an example&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
git&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;ansible-modules&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;ansible-modules&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c1"&gt;# Write great module
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt;
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;push&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;/path/to/your/ansible/playbook/repository&lt;span class="w"&gt;
&lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;library&lt;span class="w"&gt;
&lt;/span&gt;touch&lt;span class="w"&gt; &lt;/span&gt;library/__init__.py&lt;span class="w"&gt;
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;submodule&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;host:/path/to/ansible-modules&lt;span class="w"&gt; &lt;/span&gt;library/my_modules&lt;span class="w"&gt;
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;.gitmodules&lt;span class="w"&gt;
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;commit&lt;span class="w"&gt;
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;push
&lt;/pre&gt;
&lt;p&gt;Really, not that complicated. The only magic (undocumented) bit is creating a
&lt;code&gt;__init__.py&lt;/code&gt; file inside the &lt;code&gt;library&lt;/code&gt; directory, which is a shame
that the Ansible documentation doesn't cover that. If you want to see a
real-life example, checkout my &lt;a class="reference external" href="https://git.shore.co.il/ansible/ansible-playbooks.git"&gt;ansible-playbooks&lt;/a&gt; and &lt;a class="reference external" href="https://git.shore.co.il/ansible/ansible-modules.git"&gt;ansible-modules&lt;/a&gt; git repos.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Why I don't write on my blog often?</title><link href="https://www.shore.co.il/blog/why-no-blogging/" rel="alternate"/><published>2015-11-10T00:00:00+02:00</published><updated>2015-11-10T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2015-11-10:/blog/why-no-blogging/</id><summary type="html">&lt;p class="first last"&gt;Why I don't write on my blog often&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I often criticize myself on not blogging more often. The process goes like this:
I'm doing something mildly interesting and I say to myself 'This is mildly
interesting, maybe someone else will find this mildly interesting.'. But 9 out
of 10 times, what ever I'm doing has some code (when I say code I usually mean
an Ansible playbook, a shell script or something similar) accompanying.  Instead
of a lengthy blog post, I publish a git repo. The repo has a &lt;code&gt;README&lt;/code&gt;
file, the code is documented, there's a &lt;code&gt;Makefile&lt;/code&gt; or &lt;code&gt;fabfile&lt;/code&gt;, you
can clone and fork the repo. It's almost always better than a blog post.&lt;/p&gt;
&lt;p&gt;However now I have many repositories and just a few blog posts. What I'm going
to do from now on is I'll publish the git repo, but add a short post announcing
the repo.&lt;/p&gt;
&lt;div class="section" id="ssl-ca"&gt;
&lt;h2&gt;ssl-ca&lt;/h2&gt;
&lt;p&gt;I'm announcing ssl-ca, a tool to generate a certificate authority, keys and
signed certificates. The main use case is an internal network (like a
development or staging environment, but not just) where you control all nodes.
For that goal, it's as close to a real CA as needed and somewhat secure. There's
no OCSP or CRL, the certs serial is random, but the default hash, bit length and
algorithms are modern and secure. You can get it at:
&lt;a class="reference external" href="https://git.shore.co.il/nimrod/ssl-ca/"&gt;https://git.shore.co.il/nimrod/ssl-ca/&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Using Ansible as a Python module</title><link href="https://www.shore.co.il/blog/ansible-python/" rel="alternate"/><published>2015-01-01T00:00:00+02:00</published><updated>2015-01-01T00:00:00+02:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2015-01-01:/blog/ansible-python/</id><summary type="html">&lt;p class="first last"&gt;Using Ansible as a Python module when playbooks are not enough.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;At my current employer we have several servers in production with various
providers, some of them with multiple IP addresses. When configuring the
firewall to allow traffic from other servers I reached for Ansible. The
obvious solution was to use a nested loop, something like this:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="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-Scalar-Plain"&gt;Allow other servers&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;ufw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;allow&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nt"&gt;from_ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item[1]&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;with_nested&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;all_hosts&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item.ansible_all_ipv4_addresses&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;However, this syntax is invalid (and other variations I tried). Using
&lt;code&gt;include&lt;/code&gt; with &lt;code&gt;with_items&lt;/code&gt; is deprecated and I didn't manage
to get it to work with registering variables as well. What I had left was
programmatically generating a playbook, but investigating further I found that
Ansible can be imported as a Python module.&lt;/p&gt;
&lt;div class="section" id="incorporating-ansible-in-python"&gt;
&lt;h2&gt;Incorporating Ansible in Python&lt;/h2&gt;
&lt;p&gt;To retrieve all of the ip addresses I'd ran the setup module to gather the
information&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;ansible.runner&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;struct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Runner&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'setup'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'all_hosts'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;Now we have a complex data structure that is the output of Ansible's fact
gathering module. Running it in the interpreter and examining the structure is
not hard at all and that is how I managed to write the following code to extract
a list of all of our server's ip addresses.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="n"&gt;ipaddresses&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;for&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'contacted'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'contacted'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'ansible_facts'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'ansible_all_ipv4_addresses'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;ipaddresses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="putting-that-information-to-good-use"&gt;
&lt;h2&gt;Putting that information to good use&lt;/h2&gt;
&lt;p&gt;Now that we have a list of the ip addresses, we can start running Ansible
commands right from with Python (just like we did) or build a playbook by
outputting a YAML file. I chose the latter.&lt;/p&gt;
&lt;pre class="code python literal-block"&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;safe_dump&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'all_ipv4'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ipaddresses&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;safe_dump&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'vars.yml'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;This will create a vars.yml file with the all_ipv4 variable already defined
there to be imported to any playbook and run. For example:&lt;/p&gt;
&lt;pre class="code yaml literal-block"&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;all_hosts&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;vars_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;vars.yml&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="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-Scalar-Plain"&gt;Allow other servers&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;with_items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;all_ipv4&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nt"&gt;ufw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l-Scalar-Plain"&gt;allow&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nt"&gt;from_ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;'{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;With this much little code we were able to query all of our hosts, extract the
needed information and output it back to Ansible for further use. I see this as
a product of the good decisions the Ansible developers choose early on (YAML,
Python, SSH). As always, for any feedback you may have, &lt;a class="reference external" href="mailto:nimrod@shore.co.il"&gt;email me&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>SSL/TLS ciphers</title><link href="https://www.shore.co.il/blog/ssl/" rel="alternate"/><published>2014-07-12T00:00:00+03:00</published><updated>2014-07-12T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2014-07-12:/blog/ssl/</id><summary type="html">&lt;p class="first last"&gt;Which ciphers to enable and in which order.&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="the-problem-at-hand"&gt;
&lt;h2&gt;The problem at hand&lt;/h2&gt;
&lt;p&gt;You have a website and you want to encrypt the traffic going in and out of your
webserver. Since you heard about the attacks currently known at SSL and TLS, you
want to configure your server to not be vulnerable to any. In a perfect world
(or if you control your clients) all you have to do is allow TLS 1.2 and AES-GCM
with elliptic-curve Diffie-Hellman key exchange only (AESGCM+ECDH when using
openssl) and you're set. This combination is secure, fast, offers perfect
forward secrecy and at the time of writing there are no known attacks that make
it crackable in a reasonable time. So what's the problem? With a public website
you don't control the web browser the visitor uses. If he or she is using IE on
Windows XP or Android 2.x the browser doesn't support TLS 1.2 or AES-GCM and the
visitor can't access the website. How do you keep your website secure yet
reasonably accessible?&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="known-attacks-on-ssl-and-tls"&gt;
&lt;h2&gt;Known attacks on SSL and TLS&lt;/h2&gt;
&lt;p&gt;First, SSL 2.0 is insecure (it's even disabled by default in IE7) so we'll not
be using it. Version roll back attacks allow a man in the middle to change the
response from the client to force a lower grade (read the lowest grade possible)
cipher suite.  The BEAST attack exploits a weakness in CBC ciphers in TLS 1.0.
But fixes all major browsers have been released for quite some time, so we're
going to assume that the client is secure and CBC ciphers are safe to use
(reasonable assumption, but still an assumption).  CRIME and BREACH exploit a
weakness in compression and RC4 is considered to be weak although not broken
like DES or MD5.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ie-in-windows-xp"&gt;
&lt;h2&gt;IE in Windows XP&lt;/h2&gt;
&lt;p&gt;All version of IE that are available on Windows XP offer RC4 and 3DES as the
best ciphers available. Unfortunately Chrome uses the Windows scrypt library so
it has the same limitation. For a user this means that if you're on Windows XP
you should be using an up-to-date version of Firefox to have the best experience
until you can move from Windows XP (or Windows in general). For the website
manager it leaves you with 2 options, either add support for either 3DES or RC4
ciphers with SHA1 hashes (for openssl, add RC4-SHA or 3DES-SHA at the end of the
cipher list) or ask users to use Firefox if they're still on XP. I chose the
latter rather then the former, but I have that luxury.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-are-we-left-with"&gt;
&lt;h2&gt;What are we left with?&lt;/h2&gt;
&lt;p&gt;Since modern browser browsers that support SSL 3.0 support TLS 1.0, we'll be
using TLS 1.0 or newer. Any AES cipher (AES-GCM preferred) with ECDH key
exchange (preferred) or DH key exchange and SHA2 (preferred) or SHA1 hashes and
disable compression. On my server (OpenBSD firewall/ load-balancer/ SSL
terminator and reverse-proxy) with the included OpenSSL and Nginx the
configuration is as followed&lt;/p&gt;
&lt;pre class="literal-block"&gt;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers !kRSA:!3DES:!RC4:!DES:!MD5:!aNULL:!NULL:AESGCM+ECDH:AES256+ECDH:AES128:+SHA1;
ssl_prefer_server_ciphers on;
&lt;/pre&gt;
&lt;p&gt;Take note that I first disable what I don't want, then allow what I do want in
the order I prefer. I've also disabled DH key exchange with AES-GCM since all
browsers that support AES-GCM support ECDH so I've opted for that (the reasoning
being that ECDH is faster than DH so it's preferable).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="final-words"&gt;
&lt;h2&gt;Final words&lt;/h2&gt;
&lt;p&gt;This is not enough to call your site secure. I haven't mentioned secure cookies,
HSTS, input sanitation, cross-site scripting, OCSP, certificate strength,
implementation vulnerabilities (such as OpenSSL's heartbleed) or any of the
other security considerations. For testing purposes I used &lt;a class="reference external" href="http://sourceforge.net/projects/sslscan/"&gt;sslscan&lt;/a&gt; and &lt;a class="reference external" href="https://calomel.org/firefox_ssl_validation.html"&gt;Calomel's SSL validation
add-on for Firefox&lt;/a&gt;. You can
also &lt;a class="reference external" href="https://www.ssllabs.com/ssltest/index.html"&gt;SSLLabs' SSL test&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry><entry><title>Blogging with Pelican</title><link href="https://www.shore.co.il/blog/pelican/" rel="alternate"/><published>2014-04-19T00:00:00+03:00</published><updated>2014-04-19T00:00:00+03:00</updated><author><name>Nimrod Adar</name></author><id>tag:www.shore.co.il,2014-04-19:/blog/pelican/</id><summary type="html">&lt;p class="first last"&gt;How I blog with Pelican&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="what-is-pelican"&gt;
&lt;h2&gt;What is Pelican?&lt;/h2&gt;
&lt;p&gt;&lt;a class="reference external" href="http://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; is a static site generator.  It's
written in Python, focusing on blogs, using reStructuredText, Jinja2 and Fabric
(but you can use Markdown and makefiles and has provisions for normal web pages
as well).  It's a pythonic tool that's easy to use and was a breeze to setup.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="installing-pelican"&gt;
&lt;h2&gt;Installing Pelican&lt;/h2&gt;
&lt;p&gt;As Pelican is a static blog/ website generator, all we're doing is in your
workstation. All you need to have server-wise is a bog-standard web server (like
Apache or Nginx). Everything else is done on your local machine. I installed
Pelican from Debian (it's currently available in testing)&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
apt-get&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;python-pelican&lt;span class="w"&gt; &lt;/span&gt;fabric
&lt;/pre&gt;
&lt;p&gt;Alternatively, you can use pip&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pelican&lt;span class="w"&gt; &lt;/span&gt;fabric
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-a-blog"&gt;
&lt;h2&gt;Creating a blog&lt;/h2&gt;
&lt;p&gt;Create a blog directory and an empty blog&lt;/p&gt;
&lt;pre class="literal-block"&gt;
$ mkdir blog
$ cd blog
$ pelican-quickstart
Welcome to pelican-quickstart v3.4.0.

This script will help you create a new Pelican-based website.
Please answer the following questions so this script can generate the files
needed by Pelican.


&amp;gt; Where do you want to create your new web site? [.]
&amp;gt; What will be the title of this web site? My Blog
&amp;gt; Who will be the author of this web site? &lt;insert here="" name="" you=""&gt;
&amp;gt; What will be the default language of this web site? [en]
&amp;gt; Do you want to specify a URL prefix? e.g., http://example.com   (Y/n)
&amp;gt; What is your URL prefix? (see above example; no trailing slash) &lt;insert blog="" slash="" trailing="" url="" without=""&gt;
&amp;gt; Do you want to enable article pagination? (Y/n)
&amp;gt; How many articles per page do you want? [10]
&amp;gt; Do you want to generate a Fabfile/Makefile to automate generation and publishing? (Y/n)
&amp;gt; Do you want an auto-reload &amp;amp; simpleHTTP script to assist with theme and site development? (Y/n)
&amp;gt; Do you want to upload your website using FTP? (y/N)
&amp;gt; Do you want to upload your website using SSH? (y/N) y
&amp;gt; What is the hostname of your SSH server? [localhost] &lt;insert address="" server="" ssh=""&gt;
&amp;gt; What is the port of your SSH server? [22]
&amp;gt; What is your username on that server? [root] &lt;insert ssh="" username=""&gt;
&amp;gt; Where do you want to put your web site on that server? [/var/www] &lt;insert blog's="" directory="" full="" path="" to="" your=""&gt;
&amp;gt; Do you want to upload your website using Dropbox? (y/N)
&amp;gt; Do you want to upload your website using S3? (y/N)
&amp;gt; Do you want to upload your website using Rackspace Cloud Files? (y/N)
&amp;gt; Do you want to upload your website using GitHub Pages? (y/N)
Done. Your new project is available at blog
&lt;/insert&gt;&lt;/insert&gt;&lt;/insert&gt;&lt;/insert&gt;&lt;/insert&gt;&lt;/pre&gt;
&lt;p&gt;Since Pelican uses OpenSSH, you can use servers defined in your SSH preferences.
Now, lets configure the blog to our liking.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration"&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;In the blog directory there are the 2 configuration files: pelicanconf.py for
configuring Pelican and publishconf.py for configuration that are only for
publishing using Make or Fabric. Pelican also creates standard Makefile and
fabfile.py for you. I've made the following modifications to pelicanconf.py:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
TIMEZONE = 'Asia/Jerusalem'
PATH = "content"
DIRECT_TEMPLATES = ('index', 'archives')
DISPLAY_CATEGORIES_ON_MENU = False
DISPLAY_PAGES_ON_MENU = True
TAGS_SAVE_AS = ''
TAG_SAVE_AS = ''
STATIC_PATH = ['static']
&lt;/pre&gt;
&lt;p&gt;And to publishconf.py:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
CATEGORY_FEED_ATOM = None
&lt;/pre&gt;
&lt;p&gt;I've set the timezone to mine (so that the time of published articles is
correct), add everything under contents/static as static contents to be uploaded
to the server, disabled showing of categories of articles and creating feeds for
them, disabled saving of articles by tags and set pages (which are simple web
pages unlike articles which are blog entries) to show on the menu. Next, themes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="themes"&gt;
&lt;h2&gt;Themes&lt;/h2&gt;
&lt;p&gt;Pelican comes with a default theme (the same as used by Pelican's website) but I
wanted something more understated so I took at look at
&lt;a class="reference external" href="https://github.com/getpelican/pelican-themes"&gt;https://github.com/getpelican/pelican-themes&lt;/a&gt; and chose pelican-mockingbird.
Either clone it or add it as a git submodule (depends on if you're using Git to
version control your blog or not)&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/wrl/pelican-mockingbird.git&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#If you're not using Git.
&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;submodule&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;https://github.com/wrl/pelican-mockingbird.git&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;#If you're using Git.&lt;/span&gt;
&lt;/pre&gt;
&lt;p&gt;and set the theme to that by adding the following to pelicanconf.py:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
THEME = "./pelican-mockingbird"
&lt;/pre&gt;
&lt;p&gt;I've also edited &lt;code&gt;base.html&lt;/code&gt; and &lt;code&gt;article.html&lt;/code&gt; inside of
&lt;code&gt;pelican-mockingbird/templates&lt;/code&gt; to suite my liking. Next, let us add a new
entry.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="adding-an-entry"&gt;
&lt;h2&gt;Adding an entry&lt;/h2&gt;
&lt;p&gt;Create a ReStructuredText file inside of contents. The filename is for personal
use and not critical. The heading is the article name and you can add the
following for Pelican to use:&lt;/p&gt;
&lt;pre class="literal-block"&gt;
:date: 2014-04-19
:slug:  this-will-the-filename
:author: &lt;insert here="" name="" your=""&gt;
:summary: &lt;insert here="" summary=""&gt;
&lt;/insert&gt;&lt;/insert&gt;&lt;/pre&gt;
&lt;p&gt;After we added the content we want to upload it to our web server (I use fabric)&lt;/p&gt;
&lt;pre class="code shell literal-block"&gt;
fab&lt;span class="w"&gt; &lt;/span&gt;publish
&lt;/pre&gt;
&lt;p&gt;If you don't have keys set for the server it will ask you for your password to
the server.  Last thing, you can create pages, create a pages directory inside
contents and save the files there. Their format is the same as articles but
they'll have a somewhat template applied and they will be shown in the menu. A
good example will an 'About Me' page.&lt;/p&gt;
&lt;p&gt;That's it, you now have Pelican installed, configured and published to your web
site. If you want to see a real life example, clone &lt;a class="reference external" href="https://git.shore.co.il/nimrod/blog"&gt;my blog&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"/></entry></feed>