<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Orange Sun</title><link href="https://potyarkin.com/" rel="alternate"></link><link href="https://potyarkin.com/feeds/all.atom.xml" rel="self"></link><id>https://potyarkin.com/</id><updated>2024-01-09T00:00:00+03:00</updated><subtitle>Unsorted ramblings, sometimes related to programming</subtitle><entry><title>Declaring bankruptcy on Advent of Purescript 2023</title><link href="https://potyarkin.com/posts/2024/aoc2023-bankruptcy/" rel="alternate"></link><published>2024-01-09T00:00:00+03:00</published><updated>2024-01-09T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2024-01-09:/posts/2024/aoc2023-bankruptcy/</id><summary type="html">&lt;p&gt;Advent of Code is a &lt;a href="https://potyarkin.com/posts/2023/aoc2022/"&gt;fun challenge&lt;/a&gt; and this year I decided to attempt
&lt;a href="https://sio.github.io/advent-of-code/2023/"&gt;solving it in Purescript&lt;/a&gt;. Today I'm declaring this attempt a
failure. This post will serve as a postmortem.&lt;/p&gt;
&lt;h2 id="choosing-purescript"&gt;&lt;a class="toclink" href="#choosing-purescript"&gt;Choosing Purescript&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Last year I solved my first Advent of Code using Go. It was fun and I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Advent of Code is a &lt;a href="https://potyarkin.com/posts/2023/aoc2022/"&gt;fun challenge&lt;/a&gt; and this year I decided to attempt
&lt;a href="https://sio.github.io/advent-of-code/2023/"&gt;solving it in Purescript&lt;/a&gt;. Today I'm declaring this attempt a
failure. This post will serve as a postmortem.&lt;/p&gt;
&lt;h2 id="choosing-purescript"&gt;&lt;a class="toclink" href="#choosing-purescript"&gt;Choosing Purescript&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Last year I solved my first Advent of Code using Go. It was fun and I knew
immediately that I will come back for the next AoC. I also knew that I won't
be solving it using the same language - learning on the go (pun intended)
contributed significantly to my enjoyment of AoC 2022.&lt;/p&gt;
&lt;p&gt;In November 2023 I listened to an &lt;a href="https://corecursive.com/teaching-fp-with-richard-feldman/"&gt;old podcast&lt;/a&gt; where Richard Feldman
evangelises Elm programming language. I got interested and initially decided
to use Elm for the upcoming Advent of Code. While I was learning the basics of
the language I also learned about the ongoing old controversy in Elm community
related to how its BDFL handles communication and development. I decided that
that's too much drama for my liking and that it has too little hope of
resolving anytime soon
- and that it's better to stay away from Elm.&lt;/p&gt;
&lt;p&gt;That's how I ended up with &lt;a href="https://www.purescript.org/"&gt;Purescript&lt;/a&gt;. It was probably the only other
frontend language that offered functional programming with a strong type
system and a &lt;a href="https://github.com/purescript-halogen/purescript-halogen"&gt;nice UI framework&lt;/a&gt;. I hoped that using an
unconventional language would introduce me to frontend development without
dealing with unpleasant Javascript ecosystem.&lt;/p&gt;
&lt;h2 id="learning-purescript"&gt;&lt;a class="toclink" href="#learning-purescript"&gt;Learning Purescript&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My only prior experience with functional programming was Microsoft's Power
Query &lt;a href="https://potyarkin.com/tags/m/"&gt;M language&lt;/a&gt; which is rather narrowly focused on data
processing and is not a general purpose language.&lt;/p&gt;
&lt;p&gt;So, with effectively zero prior knowledge I started learning from &lt;a href="https://book.purescript.org/"&gt;The
Purescript Book&lt;/a&gt; but quickly switched to
&lt;a href="https://leanpub.com/fp-made-easier"&gt;Functional Programming Made Easier&lt;/a&gt; by
Charles Scalfani - the former was too fast paced for me.
Scalfani's book was an enjoyable read even if a little too verbose.
I did not follow author's advice to type out and run all code snippets from
the book - that may have contributed to my eventual failure but I don't think
that it was a major factor.&lt;/p&gt;
&lt;h2 id="failing-purescript"&gt;&lt;a class="toclink" href="#failing-purescript"&gt;Failing Purescript&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I enjoyed solving small textbook problems in Purescript. It's a very nice
language. Here are the things I liked most about it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Separation of pure and effectful functions&lt;/li&gt;
&lt;li&gt;Fearless refactoring&lt;/li&gt;
&lt;li&gt;Clean and readable syntax&lt;/li&gt;
&lt;li&gt;Pattern matching with exhaustiveness checking&lt;/li&gt;
&lt;li&gt;Function currying, tail call optimisation and other FP niceties&lt;/li&gt;
&lt;li&gt;Type system&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I had no problems with understanding recursion, currying, pattern matching,
etc. I had relatively little problems with understanding monads and related
concepts.&lt;/p&gt;
&lt;p&gt;My problem was that these nice abstract concepts just did not translate in my
head to applicable programming techniques. Simple practical tasks tripped me
up hard.&lt;/p&gt;
&lt;p&gt;Parsing text was painful. For the first AoC puzzle I decided to &lt;a href="https://github.com/sio/advent-of-code/blob/18c766f757eb71af581ffdeabba056586cf1bd3b/aoc2023/src/Day01/Solve.purs#L137-L147"&gt;tough it out&lt;/a&gt;
with a bespoke char based parser even though I vaguely understood that there
should be a monad based parser combinator library for that. That vague
knowledge was not provided by any of these books, I've picked it up
accidentally from some random blog post on the web.&lt;/p&gt;
&lt;p&gt;For the second AoC day I &lt;a href="https://github.com/sio/advent-of-code/blob/18c766f757eb71af581ffdeabba056586cf1bd3b/aoc2023/src/Day02/Solve.purs#L112-L120"&gt;used the proper library&lt;/a&gt;. It was better but still
felt unnecessarily difficult. When third day's puzzle called for a parser not
based on regular grammar my mind just blanked out. I was loaded up to the brim
with pure theory and I lacked practical knowledge to apply it.&lt;/p&gt;
&lt;p&gt;All this time while I was struggling with text parsing the actual tasks I
picked up Purescript for (frontend experiments and puzzle solving) were
deprioritized to background. I have to commend Purescript and Halogen on this
because if it was anything less than straightforward none of UI and/or puzzle
solutions would get done - I just barely devoted any time to that.&lt;/p&gt;
&lt;p&gt;The project got stalled. I was reluctant to finish the last chapters of
Scalfani's book because I was confident that they won't provide me with
practical knowledge I was lacking. I was hesitant to go web diving for new,
more practical learning materials because I wasn't sure they exist -
Purescript community is rather small, and most learning resources are
enumerated in multiple places. I evaluated those lists the first time I looked.
I probably should've gone looking for more generic functional programming
knowledge - and that would blow my free time budget.&lt;/p&gt;
&lt;p&gt;So I'm just declaring bankruptcy on this project.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Even though I failed to cowboy my way into Purescript I still got &lt;a href="https://sio.github.io/advent-of-code/2023/"&gt;the
solutions to first two days&lt;/a&gt; of Advent of Code to show off -
all logic executed client side, all UI generated on demand, all type safe and
checked at compile time.&lt;/p&gt;
&lt;p&gt;I could probably pour more effort, practise more, find new learning resources
- and complete the remaining challenges. I'm stubborn enough to see this through.
But my hobby time is not unlimited and there are other projects waiting -
I'm certain I will enjoy some of those more.&lt;/p&gt;</content><category term="posts"></category><category term="programming"></category><category term="purescript"></category><category term="advent-of-code"></category></entry><entry><title>Benchmarking ssh-agent performance</title><link href="https://potyarkin.com/posts/2023/ssh-agent-benchmark/" rel="alternate"></link><published>2023-07-19T00:00:00+03:00</published><updated>2023-07-19T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2023-07-19:/posts/2023/ssh-agent-benchmark/</id><summary type="html">&lt;p&gt;I have an application idea that would require calling ssh-agent rather
frequently - but how many requests per second can it handle?&lt;/p&gt;
&lt;p&gt;To answer this question I wrote a &lt;a href="https://github.com/sio/ssh-agent-benchmark/blob/master/ssh_agent_test.go"&gt;small benchmark&lt;/a&gt; in Go.
It runs a tight loop sending messages for ssh-agent to sign.
Turns out the agent is pretty fast …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have an application idea that would require calling ssh-agent rather
frequently - but how many requests per second can it handle?&lt;/p&gt;
&lt;p&gt;To answer this question I wrote a &lt;a href="https://github.com/sio/ssh-agent-benchmark/blob/master/ssh_agent_test.go"&gt;small benchmark&lt;/a&gt; in Go.
It runs a tight loop sending messages for ssh-agent to sign.
Turns out the agent is pretty fast!&lt;/p&gt;
&lt;p&gt;On a cheap cloud machine it was able to reliably sign more than 500 messages
per second with an ED25519 key. RSA signing was about 4-5 times slower, as
expected.
For a personal heuristic I've decided to memorize that ED25519 signatures
cost 2ms and RSA ones 8ms.&lt;/p&gt;
&lt;p&gt;Message size had little effect on throughput because both in ED25519 and in
RSA signatures inputs are hashed with a fast SHA algorithm prior to any other
processing.&lt;/p&gt;
&lt;p&gt;Just in case my random number generator was too slow I checked if it affects
the benchmark results. Tests confirmed that it doesn't.&lt;/p&gt;
&lt;p&gt;Of course, 500 rps does not sound &lt;em&gt;web scale&lt;/em&gt; but for me it's more than
enough. OpenSSH ssh-agent utilizes only a single CPU core, so there is some
potential for performance improvement if you need - but that would mean
doing the signatures in your software. I would rather trust OpenSSH team
(who are known to be just the right amount of paranoid) than touch private key
material with my clumsy hands.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;You can run the same tests yourself: clone the &lt;a href="https://github.com/sio/ssh-agent-benchmark"&gt;repo&lt;/a&gt; and execute &lt;code&gt;make&lt;/code&gt; from
top-level directory.
Benchmark names describe the key being used, message size and whether the
message is unique or the same for each iteration. Here is a sample output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/32B/unique-4          3553      1763363 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/32B/same-4            3568      1708270 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/32B/unique-4           778      7824780 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/32B/same-4             763      7657785 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/64B/unique-4          3456      1752457 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/64B/same-4            3598      1733750 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/64B/unique-4           781      7639828 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/64B/same-4             798      7720210 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/256B/unique-4         3549      1735906 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/256B/same-4           3417      1722301 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/256B/unique-4          698      7738767 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/256B/same-4            787      7625366 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/1024B/unique-4        3555      1703601 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/1024B/same-4          3651      1633226 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/1024B/unique-4         805      7542115 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/1024B/same-4           810      7437307 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/16384B/unique-4       3205      1935190 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_ed25519/16384B/same-4         3296      1907921 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/16384B/unique-4        783      7624776 ns/op&lt;/span&gt;
&lt;span class="go"&gt;BenchmarkSshAgent/key_rsa4096/16384B/same-4          759      7620548 ns/op&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="posts"></category><category term="ssh"></category><category term="cryptography"></category><category term="benchmark"></category><category term="go"></category><category term="ed25519"></category></entry><entry><title>Many-to-many relationships in Excel data model</title><link href="https://potyarkin.com/posts/2023/excel-many-to-many/" rel="alternate"></link><published>2023-06-02T00:00:00+03:00</published><updated>2023-06-02T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2023-06-02:/posts/2023/excel-many-to-many/</id><summary type="html">&lt;p&gt;This is a quick hack to build many-to-many relationships in Excel data model
even though they are not supported out of the box.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create intermediate &lt;a href="https://stackoverflow.com/a/70682229"&gt;calculated table&lt;/a&gt; and use DAX to fill it
    with unique values from related columns on both sides of the relationship:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;EVALUATE
  FILTER(
    DISTINCT(
      UNION(
        VALUES …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;This is a quick hack to build many-to-many relationships in Excel data model
even though they are not supported out of the box.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Create intermediate &lt;a href="https://stackoverflow.com/a/70682229"&gt;calculated table&lt;/a&gt; and use DAX to fill it
    with unique values from related columns on both sides of the relationship:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;EVALUATE
  FILTER(
    DISTINCT(
      UNION(
        VALUES(&amp;#39;TableA&amp;#39;[Field]),
        VALUES(&amp;#39;TableB&amp;#39;[Field])
      )
    ),
    NOT(ISBLANK([Field]))
  )
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create two one-to-many relationships placing this intermediate table in
    between&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For all intents and purposes you may now forget that this intermediate table
exists: it will get updated automatically whenever you update the data model,
and it will not consume much resources. Pivot tables and DAX formulas will
work as if the two original tables were directly connected via many-to-many
link.&lt;/p&gt;
&lt;p&gt;A similar intermediate table may be created with Power Query, but that's a lot
less elegant (unless you're already using Power Query elsewhere in the
workbook) and takes significantly longer to recalculate.&lt;/p&gt;</content><category term="posts"></category><category term="excel"></category></entry><entry><title>A pull request 10 years in the making</title><link href="https://potyarkin.com/posts/2023/10-year-pull-request/" rel="alternate"></link><published>2023-05-03T00:00:00+03:00</published><updated>2023-05-03T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2023-05-03:/posts/2023/10-year-pull-request/</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;Once upon a time there was a bug in a free software project that annoyed me
for long enough that I've learned C to fix it. And it felt good.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now that we've got a TL;DR out of the way, here is the story.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://potyarkin.com/resources/pr893_timeline.svg"&gt;&lt;img alt="story timeline" src="https://potyarkin.com/resources/pr893_timeline.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There is a good torrent …&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;Once upon a time there was a bug in a free software project that annoyed me
for long enough that I've learned C to fix it. And it felt good.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now that we've got a TL;DR out of the way, here is the story.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://potyarkin.com/resources/pr893_timeline.svg"&gt;&lt;img alt="story timeline" src="https://potyarkin.com/resources/pr893_timeline.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There is a good torrent client called &lt;a href="https://transmissionbt.com/"&gt;Transmission&lt;/a&gt;. It's lightweight, fast
and reliable. I have compared it against alternatives several times and
Transmission had always come out on top.&lt;/p&gt;
&lt;p&gt;In 2011 I've found out that Transmission had open file limit essentially
hardcoded to a value of 1024. Given that in Unix-like systems this limit
is also spent on open network sockets the number seemed extremely low.&lt;/p&gt;
&lt;p&gt;Back then FOSS culture was still very foreign to me, so I did what any
consumer would naturally do: I contacted support. Helpful visitors of
Transmission forums told me that open file limit is indeed hardcoded and
pointed me to a &lt;a href="https://trac.transmissionbt.com/ticket/4164"&gt;bug tracker ticket&lt;/a&gt; which discussed this issue and
which was closed several months prior without any real fix.&lt;/p&gt;
&lt;p&gt;Bug tracker discussion pointed out that there was a good reason for setting
such limit, a decades old limitation in glibc - important enough to be
featured first in BUGS section of &lt;a href="https://manpages.debian.org/bullseye/manpages-dev/select.2.en.html#BUGS"&gt;&lt;code&gt;man 2 select&lt;/code&gt;&lt;/a&gt;. Since Transmission
had hit this limitation via intermediate library (libcurl) there wasn't
much else for Transmission developers to do at the time besides to hardcode a
safe low value. Facebook had contributed an alternative libcurl API to work
around that bug only &lt;a href="https://daniel.haxx.se/blog/2012/09/03/introducing-curl_multi_wait/"&gt;a year later&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I did not handle the news well. In fact I did not understand most of it at the
time, only that developers were aware of a problem and that they were not
going to do anything about it. I switched to another torrent client and went
on to moan on local forums about how silly Transmission is and how it's useful
only for toy workloads. For several years every time someone mentioned
Transmission in IRC/XMPP chats I would snarkily introduce them to this bug.
Not my finest hour, I know.&lt;/p&gt;
&lt;p&gt;In the mean time I was hitting the limitations of other torrent clients and
remembered Transmission mostly fondly (if not for that one bug). Some time
later I even returned to Transmission and was sharding workloads between
multiple instances to avoid exhausting open files limit. I reviewed the
alternatives from time to time and have not found any other client to be better enough
to switch to.&lt;/p&gt;
&lt;p&gt;As time went on other people got burned by the same bug. Some of them started
badmouthing Transmission like I did. Forums posts and bug tracker tickets
piled on, but no forward progress was made.&lt;/p&gt;
&lt;p&gt;Unrelated to Transmission, I got introduced to FOSS scene. I've acquired a
habit of checking if I could understand the source code upon encountering a
nasty bug in a piece of software. I submitted a few small PRs to other
projects I used, shared a few projects of my own and experienced being on the
receiving end of an issue/PR. After many years I decided to look at that
Transmission issue again.&lt;/p&gt;
&lt;p&gt;Turned out that thanks to all commenters on Trac and on GitHub, the issue has
been investigated to its root already. Libcurl was the culprit. Quick web
search had introduced me to &lt;code&gt;curl_multi_wait&lt;/code&gt; API, and after some introductory C
tutorials I was able to replace all &lt;code&gt;select()&lt;/code&gt; calls with the new API.&lt;/p&gt;
&lt;p&gt;I submitted the &lt;a href="https://github.com/transmission/transmission/pull/893"&gt;pull request&lt;/a&gt; in April 2019 and the rest is history:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My PR got merged into Transmission in July 2020, providing closure to more
  than a dozen of bug reports&lt;/li&gt;
&lt;li&gt;The first stable version of Transmission featuring my fix (v4.0.0) has
  become available in 2023&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It felt good to finally remove this thorn instead of complaining about it.
I probably should have done that much sooner.&lt;/p&gt;</content><category term="posts"></category><category term="opensource"></category><category term="programming"></category></entry><entry><title>Advent of Code 2022 was fun!</title><link href="https://potyarkin.com/posts/2023/aoc2022/" rel="alternate"></link><published>2023-03-09T00:00:00+03:00</published><updated>2023-03-09T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2023-03-09:/posts/2023/aoc2022/</id><summary type="html">&lt;p&gt;This was the first year I participated in &lt;a href="https://adventofcode.com"&gt;Advent of Code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/sio/advent-of-code/tree/master/aoc2022"&gt;&lt;img alt="" src="https://potyarkin.com/resources/aoc2022.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In case you're not familiar with it, AoC is a Christmas themed programming
competition consisting of 25 challenges published daily (from December 1st to
December 25th). The web site produces personalized puzzle inputs for each user
and expects only …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This was the first year I participated in &lt;a href="https://adventofcode.com"&gt;Advent of Code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/sio/advent-of-code/tree/master/aoc2022"&gt;&lt;img alt="" src="https://potyarkin.com/resources/aoc2022.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In case you're not familiar with it, AoC is a Christmas themed programming
competition consisting of 25 challenges published daily (from December 1st to
December 25th). The web site produces personalized puzzle inputs for each user
and expects only the results to be submitted for validation, not full
algorithms. Participants may use any programming language they like.&lt;/p&gt;
&lt;p&gt;I did not compete for global or community &lt;a href="https://adventofcode.com/2022/leaderboard"&gt;leaderboards&lt;/a&gt; - that would be too
high of a pressure to remain fun. Instead I took a self paced approach and
solved puzzles whenever I felt like it - though I've never skipped ahead to
start the next challenge until I was done with the current one.
&lt;a href="https://github.com/sio/advent-of-code/tree/master/aoc2022"&gt;I completed&lt;/a&gt; the first 18 puzzles in December 2022 and finished the
remaining ones in 2023.&lt;/p&gt;
&lt;p&gt;It was very fun!&lt;/p&gt;
&lt;p&gt;For me Advent of Code turned out to be the best computer game I played in
years (though I'm not much of a gamer). Like many games it requires the player
to develop and hone some arbitrary skills but in this case the skills are not
useless outside of the game. In addition to programming (obviously) AoC
tickled parts of my brain responsible for spatial thinking, math and
creativity. I was reminded of how much I enjoyed similarly spirited
math and physics puzzles when I was at school - it's a shame these experiences are
so rare in adult life.&lt;/p&gt;
&lt;p&gt;Roughly since Day 10 I've started taking notes about each puzzle and my
thought process during solving it. I've intended to include them into this
blog post, but I decided against it. There are enough AoC walkthroughs &lt;a href="https://www.google.com/search?q=%22advent+of+code%22+%222022%22+walkthrough"&gt;out
there&lt;/a&gt; already. Here is a condensed list of bullet points from
my notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Advent of Code is fun! Grid puzzles and mazes are very fun, especially 3D
  ones! Tetris... Yummy! Maze on the surface of a cube... Brilliant!&lt;/li&gt;
&lt;li&gt;At some point I've grown tired. It was beginning to feel more like work and
  less like fun. Taking a (long) break here and there has helped to bring 
  the joy back&lt;/li&gt;
&lt;li&gt;Sometimes I got stuck. There is a large online community around Advent of
  Code, so there are a lot of ways to unblock oneself. I did not actively
  engage with any user group in particular, but on one occasion reading Reddit
  comments has helped to push me in the right direction, and on another one
  I've benefited from GitHub's social networking side.&lt;/li&gt;
&lt;li&gt;AoC is a computer game you can continue playing while away from keyboard. A lot of
  good solution ideas have come to me while I was in shower or in a traffic
  jam.&lt;/li&gt;
&lt;li&gt;A couple of times I felt very clever when I solved Part 2 of the puzzle
  before seeing the prompt.&lt;/li&gt;
&lt;li&gt;Off-by-one errors are truly the bane of programmer's existence :-)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I solved all 2022's puzzles using Go language. I've picked it up less than a
month before the start of Advent of Code, so I went in expecting to learn a
lot and I was not disappointed. I've grown to appreciate the breadth of Golang
standard library and to love type redefining. All of &lt;a href="https://github.com/sio/advent-of-code/tree/master/aoc2022"&gt;my solutions&lt;/a&gt; use only
the standard library - this happened organically, I did not impose any
restrictions in this regard.&lt;/p&gt;
&lt;p&gt;All in all, Go turned out to be exactly what it has promised: a nice language
with a fast compiler and strict type system. From now on I will choose it over
Python for personal projects.&lt;/p&gt;
&lt;p&gt;Working on these 25 puzzles I've gotten used to always having an extra thread of
thought in background, completely unrelated to personal or work life. Even
though I miss it now, I'm not yet sure if I should dive into AoC puzzles
from previous years. Do they introduce enough variety to tickle my mind in
some new ways or are they just more of the same thing? If I ever decide to
try, I've heard that AoC 2019 IntCode puzzles are good - I'll probably start
with those.&lt;/p&gt;</content><category term="posts"></category><category term="programming"></category><category term="go"></category><category term="advent-of-code"></category></entry><entry><title>Negotiating down to 100Mbit between two 1Gbit devices</title><link href="https://potyarkin.com/posts/2022/negotiating-down-to-100mbit/" rel="alternate"></link><published>2022-07-29T00:00:00+03:00</published><updated>2022-07-29T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2022-07-29:/posts/2022/negotiating-down-to-100mbit/</id><summary type="html">&lt;p&gt;Connecting two gigabit-capable devices via 4-wire UTP cable is an abomination,
but sometimes we have to live with it (e.g. when a &lt;a href="https://nevalink.net/"&gt;greedy ISP&lt;/a&gt;
decides to save a few cents and pulls a cheap cable to your apartment).&lt;/p&gt;
&lt;p&gt;The fun starts when the devices try to negotiate Ethernet connection …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Connecting two gigabit-capable devices via 4-wire UTP cable is an abomination,
but sometimes we have to live with it (e.g. when a &lt;a href="https://nevalink.net/"&gt;greedy ISP&lt;/a&gt;
decides to save a few cents and pulls a cheap cable to your apartment).&lt;/p&gt;
&lt;p&gt;The fun starts when the devices try to negotiate Ethernet connection speed.
&lt;a href="https://en.wikipedia.org/wiki/Autonegotiation#Electrical_signals"&gt;Autonegotiation pulses&lt;/a&gt; are essentially 10BASE-T and use only 4 wires with no
checking of cable category, even CAT3 is enough. So autonegotiation will
"succeed" and will settle on 1Gbit since it's supported by both ends, even
though there is not enough physical conductors for it. The link will not work.&lt;/p&gt;
&lt;p&gt;To negotiate a working connection instead we need to remove 1Gbit from
advertised link modes at least on one end. In Linux it should be rather
straightforward with &lt;a href="https://manpages.debian.org/ethtool"&gt;ethtool&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ethtool -s $IFACE advertise 0x00f
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;0x00f is a sum of hex values for all 10Mbit and 100Mbit modes
(0x001 + 0x002 + 0x004 + 0x008), explicitly excluding 1Gbit and everything
above that. Tweaking advertised link modes is better than forcing a particular
connection speed (&lt;code&gt;ethtool -s $IFACE speed 100&lt;/code&gt;) because in absence of
autonegotiation advertisement the other side may play it safe and switch
to half-duplex.&lt;/p&gt;
&lt;p&gt;That's the theory. In practise however, hardware is difficult. Drivers for NICs are
tricky and sometimes incomplete. On D-Link DIR-825 changes made by ethtool
command did not stick: after a short hiccup NIC would immediately return back
to default settings, ignoring ethtool input completely.&lt;/p&gt;
&lt;p&gt;But all is not lost. Through trial and error I found a sequence that worked.
Don't ask me why, I'm not cool enough yet to understand kernel drivers code.
Here is what worked for me:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# Excerpt from /etc/rc.local
# (order of commands and delays matter!)
WAN=eth1
ethtool -s $WAN autoneg off
sleep 1
ethtool -s $WAN speed 100
sleep 1
ethtool -s $WAN advertise 0x00f
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which results in a proper NIC configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Supported ports: [ TP MII ]
Supported link modes:   10baseT/Half 10baseT/Full
                        100baseT/Half 100baseT/Full
                        1000baseT/Half 1000baseT/Full
Supported pause frame use: Symmetric Receive-only
Supports auto-negotiation: Yes
Supported FEC modes: Not reported
Advertised link modes:  10baseT/Half 10baseT/Full
                        100baseT/Half 100baseT/Full
Advertised pause frame use: No
Advertised auto-negotiation: Yes
Advertised FEC modes: Not reported
Link partner advertised link modes:  10baseT/Half 10baseT/Full
                                     100baseT/Half 100baseT/Full
Link partner advertised pause frame use: Symmetric
Link partner advertised auto-negotiation: Yes
Link partner advertised FEC modes: Not reported
Speed: 100Mb/s
Duplex: Full
Port: MII
PHYAD: 4
Transceiver: external
Auto-negotiation: on
Current message level: 0x000000ff (255)
                       drv probe link timer ifdown ifup rx_err tx_err
Link detected: yes
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Let's hope some variation of this will work for your misbehaving device too!
Good luck!&lt;/p&gt;</content><category term="posts"></category><category term="linux"></category><category term="networking"></category></entry><entry><title>"No user exists for uid" when pushing to git repo</title><link href="https://potyarkin.com/posts/2022/no-user-exists-for-uid/" rel="alternate"></link><published>2022-07-21T00:00:00+03:00</published><updated>2022-07-21T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2022-07-21:/posts/2022/no-user-exists-for-uid/</id><summary type="html">&lt;p&gt;Today I tried to automate pushing to a Git repository from a Docker container.
And like many others I failed with an error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ git push
No user exists for uid 2918
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;Today I tried to automate pushing to a Git repository from a Docker container.
And like many others I failed with an error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ git push
No user exists for uid 2918
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Following best practices I was running the container under a random UID to
drop root privileges, and of course there was no user with that UID in the
system. I don't think that's egregious enough to warrant an error instead
of a warning from &lt;code&gt;git push&lt;/code&gt;, so I've started digging.&lt;/p&gt;
&lt;p&gt;I was very surprised to learn where this error &lt;a href="https://github.com/openssh/openssh-portable/blob/c46f6fed419167c1671e4227459e108036c760f8/ssh.c#L659-L664"&gt;comes from&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt; * openssh/ssh.c: Main program for the ssh client.&lt;/span&gt;
&lt;span class="cm"&gt; */&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;av&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="c1"&gt;// ...lines omitted...&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* Get user data. */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pw&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="n"&gt;getpwuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;getuid&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="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pw&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;logit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No user exists for uid %lu&amp;quot;&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="n"&gt;u_long&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;getuid&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;getuid()&lt;/code&gt; is pretty self-explanatory, it returns UID of current user.
Afterwards &lt;code&gt;getpwuid()&lt;/code&gt; attempts to fetch data for the provided UID from
&lt;code&gt;/etc/passwd&lt;/code&gt;. It fails, of course, and returns NULL. OpenSSH client treats
that as a show stopper and exits with an error.&lt;/p&gt;
&lt;p&gt;I was hoping that finding the place where error is generated will help me to
come up with a setup that avoids problematic code branch altogether,
but no luck this time. It's straight in the &lt;code&gt;main()&lt;/code&gt; function of ssh client,
no conditional branching whatsoever.&lt;/p&gt;
&lt;p&gt;I will be &lt;a href="https://github.com/sio/microblog-server/blob/1468a8832805f8a72252473020085495d31efcb9/container/addpasswd.c"&gt;looking into generating&lt;/a&gt; a bogus &lt;code&gt;/etc/passwd&lt;/code&gt; entry on-the-fly prior
to launching my application in container. I would very much like to avoid
hardcoding the UID at build time.
&lt;em&gt;(&lt;strong&gt;UPD&lt;/strong&gt;: proper workaround would be to use &lt;a href="https://cwrap.org/nss_wrapper.html"&gt;libnss-wrapper&lt;/a&gt; like &lt;a href="https://github.com/docker-library/postgres/blob/623c00456eab020e203704232c9bd7703ed7ff34/docker-entrypoint.sh#L76-L82"&gt;postgres does&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Meanwhile, here is a punchline for you:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When current UID is not in /etc/passwd OpenSSH client can not even print a
usage message:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$ ssh
No user exists for uid 3432

$ ssh --help
No user exists for uid 3432
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="posts"></category><category term="linux"></category><category term="ssh"></category><category term="git"></category></entry><entry><title>Pip-installable Pelican themes</title><link href="https://potyarkin.com/posts/2022/pip-install-pelican-theme/" rel="alternate"></link><published>2022-06-10T00:00:00+03:00</published><updated>2022-06-10T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2022-06-10:/posts/2022/pip-install-pelican-theme/</id><summary type="html">&lt;p&gt;Installing &lt;a href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; themes &lt;a href="https://docs.getpelican.com/en/3.6.3/pelican-themes.html#installing-themes"&gt;the default way&lt;/a&gt; is not very
pleasant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to invoke a separate CLI tool&lt;/li&gt;
&lt;li&gt;You may need to create some symlinks and ensure that they don't go stale the
  next time you use Pelican&lt;/li&gt;
&lt;li&gt;Some people (me) have resorted to git submodules instead of official CLI …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Installing &lt;a href="https://blog.getpelican.com/"&gt;Pelican&lt;/a&gt; themes &lt;a href="https://docs.getpelican.com/en/3.6.3/pelican-themes.html#installing-themes"&gt;the default way&lt;/a&gt; is not very
pleasant:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to invoke a separate CLI tool&lt;/li&gt;
&lt;li&gt;You may need to create some symlinks and ensure that they don't go stale the
  next time you use Pelican&lt;/li&gt;
&lt;li&gt;Some people (me) have resorted to git submodules instead of official CLI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a workaround I've been packaging my Pelican themes into simple Python
packages with a sole &lt;code&gt;__init__.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pkg_resources&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resource_filename&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="sd"&gt;    Return path to theme templates and assets&lt;/span&gt;
&lt;span class="sd"&gt;    Use this value for THEME in Pelican settings&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resource_filename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This adds a dependency on &lt;a href="https://setuptools.pypa.io/en/latest/userguide/index.html"&gt;setuptools&lt;/a&gt; but it's already present in most
Python venvs anyways, so not a big deal.
&lt;code&gt;pkg_resources&lt;/code&gt; exposes full path to wherever pip installs the theme. It
also handles unpacking to temporary directory if required (in case of wheels and
zipped installs).&lt;/p&gt;
&lt;p&gt;I have been using this trick for some time already, but only recently I noticed
that Pelican plugins have &lt;a href="https://docs.getpelican.com/en/stable/plugins.html#namespace-plugin-structure"&gt;officially transitioned&lt;/a&gt; to
being pip-installable. They use a clever hack of adding extra packages to
&lt;code&gt;pelican.plugins&lt;/code&gt; namespace and I though it would be cool to use the same
approach with themes.&lt;/p&gt;
&lt;p&gt;Turns out it's not easy to do with setuptools, but is &lt;a href="https://github.com/sio/pelican-smallweb/blob/master/pyproject.toml"&gt;pretty
straightforward&lt;/a&gt; with poetry. As a result I can now publish &lt;a href="https://pypi.org/project/pelican-theme-smallweb/"&gt;my
themes&lt;/a&gt; to PyPI and provide easy invocation instructions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# pelicanconf.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pelican.themes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;smallweb&lt;/span&gt;
&lt;span class="n"&gt;THEME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smallweb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All the end users need to do is to add another line mentioning my theme to
whichever &lt;a href="https://github.com/sio/potyarkin.com/blob/smallweb/requirements.txt#L3"&gt;file they use&lt;/a&gt; to create their Pelican venv.&lt;/p&gt;
&lt;p&gt;On developer side we need to create &lt;code&gt;pelican/themes/themename&lt;/code&gt; folder
structure and point poetry at &lt;code&gt;pelican&lt;/code&gt; for top-level package name. All theme
files should be placed into &lt;code&gt;pelican/themes/themename&lt;/code&gt; and one extra
&lt;code&gt;__init__.py&lt;/code&gt; file should be added there to provide &lt;code&gt;path()&lt;/code&gt; method.
See &lt;a href="https://github.com/sio/pelican-smallweb"&gt;SmallWeb&lt;/a&gt; repository for an example.&lt;/p&gt;</content><category term="posts"></category><category term="pelican"></category><category term="python"></category><category term="pip"></category></entry><entry><title>Unexpected workaround for Libvirt VMs with cgroups v2 in Cirrus CI</title><link href="https://potyarkin.com/posts/2022/libvirt-cirrusci-workaround/" rel="alternate"></link><published>2022-03-02T00:00:00+03:00</published><updated>2022-03-02T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2022-03-02:/posts/2022/libvirt-cirrusci-workaround/</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;Today I wrote a &lt;a href="https://gitlab.com/sio/server_common/-/commit/5777cfae5446e7056fd95408c10e5273cd6529fd"&gt;commit message&lt;/a&gt; that was several screens long.
I think it deserves to be a blog post of its own&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; the commit linked above required some &lt;a href="https://gitlab.com/sio/server_common/-/commit/ebde5a880fc648be382a157454bc1ab17a8e0cd5"&gt;modification&lt;/a&gt; to remove
flakiness, but the workaround still stands. Diff provided in this blog post
was updated to reflect current …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;Today I wrote a &lt;a href="https://gitlab.com/sio/server_common/-/commit/5777cfae5446e7056fd95408c10e5273cd6529fd"&gt;commit message&lt;/a&gt; that was several screens long.
I think it deserves to be a blog post of its own&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; the commit linked above required some &lt;a href="https://gitlab.com/sio/server_common/-/commit/ebde5a880fc648be382a157454bc1ab17a8e0cd5"&gt;modification&lt;/a&gt; to remove
flakiness, but the workaround still stands. Diff provided in this blog post
was updated to reflect current state of affairs&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Recently Cirrus CI builds that were using nested Libvirt VMs have started
failing with the following error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Call to virDomainCreateWithFlags failed: unable to open
&amp;#39;/sys/fs/cgroup/machine/qemu-2-defaultdebian11-server.libvirt-qemu/&amp;#39;:
No such file or directory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;First recorded failure occured on
&lt;a href="https://cirrus-ci.com/task/5115427394158592"&gt;February 25th, 2022&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Error message clearly indicates that the issue is related to Linux control
groups (cgroups), and on a hunch I assumed that Cirrus CI (or Google Cloud)
images were updated to use cgroups v2 by default. Unfortunately I haven't
been recording debug information for cgroups before the failure, so I can
not confirm my guess. Currently cgroups2 are in use, so the hypothesis stands.&lt;/p&gt;
&lt;p&gt;Web search has led me to several useful pages:&lt;/p&gt;
&lt;h4 id="redhat-bugzilla"&gt;&lt;a class="toclink" href="#redhat-bugzilla"&gt;&lt;a href="https://bugzilla.redhat.com/show_bug.cgi?id=1985377#c1"&gt;RedHat bugzilla&lt;/a&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Running Libvirt from inside a container (similar to how Cirrus does) produced
the same error.  The reason is that default cgroups mode was changed in podman
when upgrading from cgroups v1 (host mode) to v2 (private mode).&lt;/p&gt;
&lt;p&gt;I took note of this issue, but I moved on with my research since as a user
I can not change the configuration of container runtime at Cirrus CI.&lt;/p&gt;
&lt;h4 id="libvirt-documentation"&gt;&lt;a class="toclink" href="#libvirt-documentation"&gt;&lt;a href="https://libvirt.org/cgroups.html"&gt;Libvirt documentation&lt;/a&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Libvirt will not auto-create the cgroups directory to back this
partition. In the future, libvirt / virsh will provide APIs / commands
to create custom partitions, but currently this is left as an exercise
for the administrator.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Based on the documentation quoted above I have tried to manually create
the required cgroup with a simple &lt;code&gt;mkdir -p $CGROUP&lt;/code&gt;. Please note that
Libvirt uses different cgroup layouts when running on systems with and
without systemd. Cirrus CI runners use a different (non-systemd) init in
their containers, so the cgroup path in our case is
&lt;code&gt;$MOUNTPOINT/machine/qemu-$ID-$MACHINENAME.libvirt-qemu/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Manual creation of cgroup did not lead to any changes in Libvirt behavior.
Error message stayed the same: no such file or directory.&lt;/p&gt;
&lt;p&gt;Report author at &lt;a href="https://bugzilla.redhat.com/show_bug.cgi?id=1985377#c1"&gt;RedHat bugzilla&lt;/a&gt; had mentioned that they had trouble
with manually moving QEMU process into a different cgroup, so I've tried to do
that and see if the error message will provide any further information.&lt;/p&gt;
&lt;p&gt;I configured Cirrus CI to create a long-running background process and to
migrate it to the newly created cgroup. I was expecting this to fail, so I
did not comment out the code that later would launch a Libvirt VM on CI
runner. &lt;em&gt;Imagine my surprise when the pipeline turned green!&lt;/em&gt; Not only did
the migration succeed, but its success had somehow lead to the success of
Libvirt VM!&lt;/p&gt;
&lt;p&gt;A few trial runs later I noticed that the name of cgroup used in the first
step does not even have to match the cgroup (or cgroups) that will be used
by Libvirt domains.&lt;/p&gt;
&lt;p&gt;This is why I'm adding this meaningless cgroups burn-in to my pipelines.
"It ain't stupid if it works", right?&lt;/p&gt;
&lt;h4 id="full-commit-diff"&gt;&lt;a class="toclink" href="#full-commit-diff"&gt;Full commit diff&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/.cirrus.yml.j2 b/.cirrus.yml.j2&lt;/span&gt;
&lt;span class="gd"&gt;--- a/.cirrus.yml.j2&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/.cirrus.yml.j2&lt;/span&gt;
&lt;span class="gu"&gt;@@ -21,6 +21,8 @@ task:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    # VENVDIR must be absolute path for &amp;#39;cd &amp;amp;&amp;amp; make&amp;#39; approach to work
&lt;span class="w"&gt; &lt;/span&gt;    # VENVDIR should not be cached! Cirrus CI drops some binaries randomly

&lt;span class="gi"&gt;+    CGROUP_WORKAROUND: /sys/fs/cgroup/machine/qemu-cgroup_workaround.libvirt-qemu&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    # Pass values from cirrus-run environment
&lt;span class="w"&gt; &lt;/span&gt;    CLONE_URL: &amp;quot;{{ CI_REPOSITORY_URL }}&amp;quot;
&lt;span class="w"&gt; &lt;/span&gt;    CLONE_SHA: &amp;quot;{{ CI_COMMIT_SHA }}&amp;quot;
&lt;span class="gu"&gt;@@ -61,6 +63,16 @@ task:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  libvirtd_background_script:
&lt;span class="w"&gt; &lt;/span&gt;    - sleep 2 &amp;amp;&amp;amp; /usr/sbin/libvirtd

&lt;span class="gi"&gt;+  # Workaround for cgroups v2&lt;/span&gt;
&lt;span class="gi"&gt;+  # I have no idea why or how this works (see commit message for a longer rant)&lt;/span&gt;
&lt;span class="gi"&gt;+  cgroups_workaround_background_script:&lt;/span&gt;
&lt;span class="gi"&gt;+    - mkdir -p &amp;quot;$CGROUP_WORKAROUND&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+    - ls -lF &amp;quot;$CGROUP_WORKAROUND&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+    - bash -c &amp;#39;echo $$ &amp;gt; /tmp/cgroups_workaround.pid; sleep infinity&amp;#39; &amp;amp;&lt;/span&gt;
&lt;span class="gi"&gt;+    - sleep 1&lt;/span&gt;
&lt;span class="gi"&gt;+    - cat /tmp/cgroups_workaround.pid &amp;gt;&amp;gt; $CGROUP_WORKAROUND/cgroup.procs&lt;/span&gt;
&lt;span class="gi"&gt;+    - cat $CGROUP_WORKAROUND/cgroup.procs&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  # Execute automated tests
&lt;span class="w"&gt; &lt;/span&gt;  test_script:
&lt;span class="w"&gt; &lt;/span&gt;    - cd ansible/tests
&lt;span class="gu"&gt;@@ -70,6 +82,9 @@ task:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;  always:
&lt;span class="w"&gt; &lt;/span&gt;    cache_debug_script:
&lt;span class="w"&gt; &lt;/span&gt;      - find &amp;quot;$HOME/cache&amp;quot; -type f || echo &amp;quot;Exit code: $?&amp;quot;
&lt;span class="gi"&gt;+    cgroups_debug_script:&lt;/span&gt;
&lt;span class="gi"&gt;+      - fgrep cgroup /proc/mounts || echo &amp;quot;Exit code: $?&amp;quot;&lt;/span&gt;
&lt;span class="gi"&gt;+      - find /sys/fs/cgroup -exec ls -ldF {} \; || echo &amp;quot;Exit code: $?&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    kvm_debug_script:
&lt;span class="w"&gt; &lt;/span&gt;      - free -h
&lt;span class="w"&gt; &lt;/span&gt;      - pstree -alT
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="posts"></category><category term="ci"></category><category term="automation"></category><category term="libvirt"></category></entry><entry><title>D-Link DIR-825 (rev.B1) throughput test</title><link href="https://potyarkin.com/posts/2022/d-link-dir-825-revb1-throughput-test/" rel="alternate"></link><published>2022-01-31T00:00:00+03:00</published><updated>2022-01-31T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2022-01-31:/posts/2022/d-link-dir-825-revb1-throughput-test/</id><summary type="html">&lt;p&gt;So, the year is 2022 and I'm still using D-Link &lt;a href="https://openwrt.org/toh/d-link/dir-825"&gt;DIR-825&lt;/a&gt;, rev.B1 as my edge
router at home.&lt;/p&gt;
&lt;p&gt;Thanks to the power of opensource it is running a modern and secure OS
(OpenWRT) long after the manufacturer has abandoned this product.
Even though OpenWRT (and Linux in general) has …&lt;/p&gt;</summary><content type="html">&lt;p&gt;So, the year is 2022 and I'm still using D-Link &lt;a href="https://openwrt.org/toh/d-link/dir-825"&gt;DIR-825&lt;/a&gt;, rev.B1 as my edge
router at home.&lt;/p&gt;
&lt;p&gt;Thanks to the power of opensource it is running a modern and secure OS
(OpenWRT) long after the manufacturer has abandoned this product.
Even though OpenWRT (and Linux in general) has increased the system
requirements, DIR-825 still meets the minimal &lt;a href="https://openwrt.org/supported_devices/864_warning"&gt;8/64&lt;/a&gt; criteria.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Photo of D-Link DIR-825, rev.B1" src="https://potyarkin.com/resources/dir825/router.jpg"&gt;&lt;/p&gt;
&lt;p&gt;To put things into perspective: I bought this very device in November, 2011
for the equivalent of $120. Its WiFi standard is pretty outdated (802.11n),
but that's enough since all my essential devices are hardwired.&lt;/p&gt;
&lt;p&gt;Up until a few weeks ago it was pointless for me to think about router
upgrade. The Ethernet cable coming into my apartment from ISP equipment was
crimped to use only 2 twisted pairs which would never allow for speeds above
100Mbit. And DIR-825 routes 100Mbit just fine. Thankfully, a completely
unrelated incident occurred and an ISP technician was on site - I seized the
opportunity and asked them to recrimp their end (&lt;em&gt;4 missing wires were just
bent aside right before a connector, why would anyone do that?!&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;Now that I have an option to upgrade above 100Mbit, the valid question is:
&lt;em&gt;Will my DIR-825 be able to handle that?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To test router throughput I used two laptops booted into Debian 11 live image,
one of them running a DHCP server and connected to WAN port of the router, the
other one connected to one of LAN ports. I put together all the
required commands into a &lt;a href="https://github.com/sio/router-throughput-test"&gt;Makefile&lt;/a&gt; to avoid having to memorize them - you
might find this repo useful if you ever need to perform a similar test on
your router.&lt;/p&gt;
&lt;p&gt;To establish a &lt;a href="https://potyarkin.com/resources/dir825/baseline.log"&gt;baseline&lt;/a&gt; I connected the laptops directly to each other
without any routers or switches in between. I got the expected near-gigabit
speeds: 932Mbps down, 941Mbps up - nothing unusual here.&lt;/p&gt;
&lt;p&gt;Here are the test results with router in between
(full logs: &lt;a href="https://potyarkin.com/resources/dir825/test1.log"&gt;part one&lt;/a&gt;, &lt;a href="https://potyarkin.com/resources/dir825/test2.log"&gt;part two&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img alt="average: bidir 130/162Mbps, down 262Mbps, up 382Mbps" src="https://potyarkin.com/resources/dir825/results.svg"&gt;&lt;/p&gt;
&lt;p&gt;Maximum throughput was achieved in upload tests (&lt;em&gt;382Mbps&lt;/em&gt;). This is
probably explained by having to process less firewall rules for outgoing
traffic. Download speed even with a very basic iptables configuration was
significantly less (&lt;em&gt;256Mbps&lt;/em&gt;), and bidirectional tests with
simultaneous traffic in both directions confirm that ~300Mbps is a hardware
limit (bidirectional speed was &lt;em&gt;130Mbps down + 162Mbps up =
292Mbps total&lt;/em&gt;). top was showing 99% sirq load during these tests, but
running a monitoring tool did not have any significant impact on the results
(first 5 tests were executed before top was launched).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;These tests show that while DIR-825 is a perfectly capable router for 100Mbps,
it's completely out of its depth with faster connections. Even 300Mbps will
frequently become underutilized if the traffic will happen to flow in both
directions at once.&lt;/p&gt;
&lt;p&gt;Looks like I finally need a new router after almost 11 years with DIR-825...&lt;/p&gt;</content><category term="posts"></category><category term="hardware"></category><category term="router"></category></entry><entry><title>Ansible apt module fails to install python3-apt on Debian Testing</title><link href="https://potyarkin.com/posts/2020/ansible-apt-debian-testing/" rel="alternate"></link><published>2020-12-09T00:00:00+03:00</published><updated>2020-12-09T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2020-12-09:/posts/2020/ansible-apt-debian-testing/</id><summary type="html">&lt;p&gt;I have encountered an unexpected Ansible failure today that turned out to be not
a bug.&lt;/p&gt;
&lt;p&gt;Ansible apt module had failed to auto install the required &lt;code&gt;python3-apt&lt;/code&gt;
package - only on Debian Testing. Same playbook worked fine with Debian
Stable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;TASK [install some apt packages] *********************************************
[WARNING]: Updating cache and auto-installing missing …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;I have encountered an unexpected Ansible failure today that turned out to be not
a bug.&lt;/p&gt;
&lt;p&gt;Ansible apt module had failed to auto install the required &lt;code&gt;python3-apt&lt;/code&gt;
package - only on Debian Testing. Same playbook worked fine with Debian
Stable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;TASK [install some apt packages] *********************************************
[WARNING]: Updating cache and auto-installing missing dependency: python3-apt
ok: [debian10]
fatal: [debian11]: FAILED! =&amp;gt; changed=false
  msg: &amp;#39;Could not import python modules: apt, apt_pkg. Please install python3-apt package.&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After some troubleshooting I've been able to find the reason for this failure:
Python interpreter was being automatically upgraded to the next minor version
while installing &lt;code&gt;python3-apt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since the &lt;code&gt;apt_pkg&lt;/code&gt; module is distributed as compiled platform-specific binary
(e.g. &lt;code&gt;apt_pkg.cpython-37m-x86_64-linux-gnu.so&lt;/code&gt;), it is only compatible with
Python version it's been built for. In my case the Python interpreter at the
moment of Ansible module invocation was at version 3.8.6, but doing
&lt;code&gt;apt update; apt install python3-apt&lt;/code&gt; had upgraded it to 3.9.1 and installed
&lt;code&gt;apt_pkg&lt;/code&gt; was only compatible with new version of interpreter.&lt;/p&gt;
&lt;p&gt;Ansible apt module was still running under the old version of interpreter and
therefore was unable to import &lt;code&gt;apt_pkg&lt;/code&gt; that it had just installed.&lt;/p&gt;
&lt;p&gt;Such errors are a non-issue on Debian Stable where Python is never upgraded to
the next upstream version, and even in Testing/Sid it's a rare occurence. More
than that, I see no way to add a workaround to the Ansible module that could
allow it to handle this edge case: the whole module is executed with one
instance of Python interpreter and it can not accomodate such change in a
single invocation.&lt;/p&gt;
&lt;p&gt;The solution I see is to explicitly install &lt;code&gt;python3-apt&lt;/code&gt; on Debian
Testing/Sid systems before invoking apt module with Ansible. This can either
be done with &lt;code&gt;raw&lt;/code&gt; module or with the provisioning tools (machine template,
preseed, terraform/packer/etc).&lt;/p&gt;</content><category term="posts"></category><category term="ansible"></category></entry><entry><title>Pegatron Cape 7 nettop (thin client)</title><link href="https://potyarkin.com/posts/2020/pegatron-cape-7/" rel="alternate"></link><published>2020-04-04T00:00:00+03:00</published><updated>2020-04-04T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2020-04-04:/posts/2020/pegatron-cape-7/</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;Below are hardware details of an outdated compact computer that had since
become available for low price on second-hand market. I bought mine for $15
(in March 2020).&lt;/p&gt;
&lt;p&gt;This post is inspired by &lt;a href="https://www.parkytowers.me.uk/thin/"&gt;ParkyTowers Thin Client
Database&lt;/a&gt; - many thanks to David
Parkinson for gathering and sharing all that knowledge!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pegatron …&lt;/strong&gt;&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;Below are hardware details of an outdated compact computer that had since
become available for low price on second-hand market. I bought mine for $15
(in March 2020).&lt;/p&gt;
&lt;p&gt;This post is inspired by &lt;a href="https://www.parkytowers.me.uk/thin/"&gt;ParkyTowers Thin Client
Database&lt;/a&gt; - many thanks to David
Parkinson for gathering and sharing all that knowledge!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pegatron Cape 7&lt;/strong&gt; was announced in early 2009 (&lt;a href="https://potyarkin.com/resources/pegatron/booklet.pdf"&gt;marketing booklet&lt;/a&gt;
is dated by 2009-04-13). It is based on Intel Atom 230 series CPU along with
SiS 968/672 (models A, B, C, D) or nVidia Ion chipset (models E, F). The unit
I have is model D, all further photos and description apply to that model.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://potyarkin.com/gallery/pegatron/" title="More photos of Pegatron Cape 7"&gt;&lt;img alt="Pegatron Cape 7 photos" src="https://potyarkin.com/resources/pegatron/thumb/0-overview.jpg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The hardware is similar to &lt;a href="https://www.parkytowers.me.uk/thin/dell/fx160/"&gt;Dell
FX160&lt;/a&gt; but is packaged into a
significantly more compact case and uses external power supply. Pegatron is an
&lt;a href="https://en.wikipedia.org/wiki/Original_design_manufacturer"&gt;ODM manufacturer&lt;/a&gt; which means the units were sold to end users under a
variety of brand names. Mine was sold in Russia as &lt;strong&gt;Depo Sky 153&lt;/strong&gt;. I've also
seen mentions of it being sold as &lt;a href="https://forums.vrzone.com/singapore-marketplace-garage-sales/487593-wts-pegatron-cape-7-mini-net-pc-brand-new.html"&gt;Pegasus CutePC&lt;/a&gt; in Indonesia, as unknown
model under &lt;a href="https://produto.mercadolivre.com.br/MLB-1283040839-thin-cliente-intel-atom-memoria-ram-2gb-ddr2hd-160gb-c-w-_JM"&gt;iClient&lt;/a&gt; brand in Brazil, and under some local brand in Poland.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cape 7&lt;/strong&gt; is capable of running mainstream operating systems (mine came with
Windows XP preinstalled), general purpose Linux distributions are also
supported.&lt;/p&gt;
&lt;h2 id="specifications"&gt;&lt;a class="toclink" href="#specifications"&gt;Specifications&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Motherboard&lt;/strong&gt;: Pegatron IPP71-CP with SiS 672 northbridge and SiS 968 southbridge&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Processor&lt;/strong&gt;: Intel Atom 230 (1.6GHz, single core, two threads)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RAM&lt;/strong&gt;: DDR2 SO-DIMM, 1GB by default (mine came with 2GB preinstalled by seller)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video&lt;/strong&gt;: SiS Mirage 3&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Storage&lt;/strong&gt;: 2.5" SATA HDD&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ports&lt;/strong&gt;:&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Network&lt;/strong&gt;: Realtek 8111EL 10/100/1000&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;USB&lt;/strong&gt;: 6 USB 2.0 ports (2 front, 4 back)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Video&lt;/strong&gt;: 1 DVI output (other Cape 7 models may use D-Sub or HDMI)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audio&lt;/strong&gt;: 3.5mm audio out, 3.5mm microphone in - Realtek ALC662&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Serial&lt;/strong&gt;: none&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parallel&lt;/strong&gt;: none&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PS/2&lt;/strong&gt;: none&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Other&lt;/strong&gt;: 1 unknown port (next to DCIN), probably for Wi-Fi antenna&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Power&lt;/strong&gt;: External power supply - 19V DC, 2.1A, 5.5mm x 2.5mm connector
  (same as in many ASUS laptops)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cooling&lt;/strong&gt;: passive, completely silent. One removable aluminum heatsink for
  CPU, several thermal pads to transfer heat from northbridge/southbridge to
  metal case frame. My device runs pretty hot even after changing the thermal
  paste - I'll need to monitor how stable it is under workload.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dimensions&lt;/strong&gt;: approx. 173 x 154 x 20mm, the bottom of the case is
  slightly wider (26mm).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="processor"&gt;&lt;a class="toclink" href="#processor"&gt;Processor&lt;/a&gt;&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt;Click to view &lt;strong&gt;/proc/cpuinfo&lt;/strong&gt;&lt;/summary&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 28
model name  : Intel(R) Atom(TM) CPU  230   @ 1.60GHz
stepping    : 2
microcode   : 0x218
cpu MHz     : 1599.527
cache size  : 512 KB
physical id : 0
siblings    : 2
core id     : 0
cpu cores   : 1
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 10
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov
pat clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc
arch_perfmon pebs bts nopl cpuid aperfmperf pni dtes64 monitor ds_cpl tm2
ssse3 cx16 xtpr pdcm movbe lahf_lm dtherm
bugs        :
bogomips    : 3199.05
clflush size    : 64
cache_alignment : 64
address sizes   : 32 bits physical, 48 bits virtual
power management:

processor   : 1
vendor_id   : GenuineIntel
cpu family  : 6
model       : 28
model name  : Intel(R) Atom(TM) CPU  230   @ 1.60GHz
stepping    : 2
microcode   : 0x218
cpu MHz     : 1599.365
cache size  : 512 KB
physical id : 0
siblings    : 2
core id     : 0
cpu cores   : 1
apicid      : 1
initial apicid  : 1
fpu     : yes
fpu_exception   : yes
cpuid level : 10
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov
pat clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx lm constant_tsc
arch_perfmon pebs bts nopl cpuid aperfmperf pni dtes64 monitor ds_cpl tm2
ssse3 cx16 xtpr pdcm movbe lahf_lm dtherm
bugs        :
bogomips    : 3199.05
clflush size    : 64
cache_alignment : 64
address sizes   : 32 bits physical, 48 bits virtual
power management:
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;/details&gt;

&lt;h2 id="pci"&gt;&lt;a class="toclink" href="#pci"&gt;PCI&lt;/a&gt;&lt;/h2&gt;
&lt;details&gt;
&lt;summary&gt;Click to view &lt;strong&gt;lspci -nn&lt;/strong&gt;&lt;/summary&gt;

&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;00:00.0 Host bridge [0600]: Silicon Integrated Systems [SiS] 671MX [1039:0671]
00:01.0 PCI bridge [0604]: Silicon Integrated Systems [SiS] AGP Port (virtual PCI-to-PCI bridge) [1039:0003]
00:02.0 ISA bridge [0601]: Silicon Integrated Systems [SiS] SiS968 [MuTIOL Media IO] [1039:0968] (rev 01)
00:02.5 IDE interface [0101]: Silicon Integrated Systems [SiS] 5513 IDE Controller [1039:5513] (rev 01)
00:03.0 USB controller [0c03]: Silicon Integrated Systems [SiS] USB 1.1 Controller [1039:7001] (rev 0f)
00:03.1 USB controller [0c03]: Silicon Integrated Systems [SiS] USB 1.1 Controller [1039:7001] (rev 0f)
00:03.3 USB controller [0c03]: Silicon Integrated Systems [SiS] USB 2.0 Controller [1039:7002]
00:05.0 IDE interface [0101]: Silicon Integrated Systems [SiS] SATA Controller / IDE mode [1039:1183] (rev 03)
00:06.0 PCI bridge [0604]: Silicon Integrated Systems [SiS] PCI-to-PCI bridge [1039:000a]
00:0f.0 Audio device [0403]: Silicon Integrated Systems [SiS] Azalia Audio Controller [1039:7502]
00:1f.0 PCI bridge [0604]: Silicon Integrated Systems [SiS] PCI-to-PCI bridge [1039:0004]
01:00.0 VGA compatible controller [0300]: Silicon Integrated Systems [SiS] 771/671 PCIE VGA Display Adapter [1039:6351] (rev 10)
02:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 03)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;/details&gt;

&lt;h2 id="disassembly"&gt;&lt;a class="toclink" href="#disassembly"&gt;Disassembly&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The case can be opened without any tools. All visible screws are holding
internal components, not the cover. Bottom corners of the cover are easy to
get a grip of - pull gently on those and expand the opening until plastic
locks click open one by one.&lt;/p&gt;
&lt;h2 id="expansion"&gt;&lt;a class="toclink" href="#expansion"&gt;Expansion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;RAM&lt;/strong&gt;: The unit accepts a single DDR2 SO-DIMM. Default module can be easily
replaced with a larger one. I did not test with 4GB (DDR2 SO-DIMM of that size
is expensive!), but I've seen multiple reports of 2GB working fine. My unit
uses Kingston KVR800D2S6/2G module.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt;: Cape 7 provides conventional SATA slot with enough space to fit a
laptop HDD or SATA SSD. Thick 12.5mm drives fit only when HDD bracket is
removed, but just barely - cover will be slightly bulged. Using SSD might not
provide the expected performance benefit because SATA bus appears to be
limited at 300 Mbps (37.6 MB/s) - numbers are from block diagram (page 15 of
the &lt;a href="https://potyarkin.com/resources/pegatron/booklet.pdf"&gt;booklet&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id="bios"&gt;&lt;a class="toclink" href="#bios"&gt;BIOS&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;BIOS is a standard AMI BIOS. Pressing &lt;strong&gt;Del&lt;/strong&gt; at startup opens setup screen,
&lt;strong&gt;F8&lt;/strong&gt; shows a shorter boot menu to select a device for one-off boot.&lt;/p&gt;
&lt;p&gt;BIOS supports booting from USB devices and network boot. There are also
several configurable options for power management:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Power On by PCI Devices&lt;/li&gt;
&lt;li&gt;Power On by RTC Alarm&lt;/li&gt;
&lt;li&gt;Restore on AC Power Loss&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See also: &lt;a href="https://potyarkin.com/gallery/pegatron-bios/"&gt;screenshots from BIOS setup&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="pictures"&gt;&lt;a class="toclink" href="#pictures"&gt;Pictures&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See &lt;a href="https://potyarkin.com/gallery/pegatron/" title="More photos of Pegatron Cape 7"&gt;the gallery&lt;/a&gt; or download individual images in high resolution:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://potyarkin.com/resources/pegatron/img/1-outlook.jpg"&gt;Nettop overview - standing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://potyarkin.com/resources/pegatron/img/0-overview.jpg"&gt;Nettop overview - lying&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://potyarkin.com/resources/pegatron/img/2-front.jpg"&gt;Front panel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://potyarkin.com/resources/pegatron/img/3-back.jpg"&gt;Back panel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://potyarkin.com/resources/pegatron/img/4-open.jpg"&gt;Open case&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://potyarkin.com/resources/pegatron/img/5-motherboard-front.jpg"&gt;Motherboard (front)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://potyarkin.com/resources/pegatron/img/6-motherboard-back.jpg"&gt;Motherboard (back)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://potyarkin.com/resources/pegatron/img/7-case.jpg"&gt;Empty case (inside)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- internal links --&gt;
&lt;!-- external links --&gt;</content><category term="posts"></category><category term="hardware"></category></entry><entry><title>Running Libvirt (KVM) in Cirrus CI</title><link href="https://potyarkin.com/posts/2020/running-libvirt-kvm-in-cirrus-ci/" rel="alternate"></link><published>2020-02-25T00:00:00+03:00</published><updated>2020-02-25T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2020-02-25:/posts/2020/running-libvirt-kvm-in-cirrus-ci/</id><summary type="html">&lt;p&gt;Up until the middle of 2019 it was very unusual to even expect that any CI
service would allow nested virtualization. Those who required such
functionality had to maintain their own CI runners on their own
infrastructure. Things changed when Google Cloud introduced &lt;a href="https://cloud.google.com/compute/docs/instances/enable-nested-virtualization-vm-instances"&gt;nested
KVM&lt;/a&gt; support.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cirrus-ci.org/"&gt;Cirrus CI&lt;/a&gt; was probably …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Up until the middle of 2019 it was very unusual to even expect that any CI
service would allow nested virtualization. Those who required such
functionality had to maintain their own CI runners on their own
infrastructure. Things changed when Google Cloud introduced &lt;a href="https://cloud.google.com/compute/docs/instances/enable-nested-virtualization-vm-instances"&gt;nested
KVM&lt;/a&gt; support.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://cirrus-ci.org/"&gt;Cirrus CI&lt;/a&gt; was probably the first CI service to &lt;a href="https://cirrus-ci.org/guide/linux/#kvm-enabled-privileged-containers"&gt;officially
support&lt;/a&gt; nested virtualization in free tier. There are reports
that Travis CI currently also provides such feature but no public announcement
has been made yet.&lt;/p&gt;
&lt;p&gt;It turns out I was the first person to try using Libvirt in Cirrus CI (I've
even hit a previously unknown &lt;a href="https://github.com/cirruslabs/cirrus-ci-docs/issues/564"&gt;bug&lt;/a&gt; which was promptly fixed by their staff).
Since the process has some subtle differences to the popular documented use
cases I've decided to describe it here.&lt;/p&gt;
&lt;h2 id="hypervisor-environment"&gt;&lt;a class="toclink" href="#hypervisor-environment"&gt;Hypervisor environment&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cirrus CI uses Docker images as environment for their runners. It
significantly simplifies the setup and enables efficient caching between runs.&lt;/p&gt;
&lt;p&gt;Since popular Docker images do not include any hypervisor packages we need to
build our own image. I've decided to add the required packages to Debian base
image. The whole &lt;a href="https://gitlab.com/sio/server_common/-/blob/master/ansible/tests/Dockerfile.host-kvm"&gt;Dockerfile&lt;/a&gt; is essentially one &lt;code&gt;apt-get&lt;/code&gt; statement.&lt;/p&gt;
&lt;p&gt;Keep in mind that libvirt package in Debian drops root privileges when
launching &lt;code&gt;qemu-kvm&lt;/code&gt;. You'll either need to disable that in
&lt;code&gt;/etc/libvirt/qemu.conf&lt;/code&gt; (as I did) or to change permissions for &lt;code&gt;/dev/kvm&lt;/code&gt; to
allow access by &lt;code&gt;libvirt-qemu&lt;/code&gt; user.&lt;/p&gt;
&lt;h2 id="required-system-services"&gt;&lt;a class="toclink" href="#required-system-services"&gt;Required system services&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Default entry point for CI runner is not customizable in Cirrus CI - it's an
agent process that communicates with CI service and sends progress reports you
see in web interface. Because of that no systemd units are started automatically
as it would have been the case on a normal system. More than that, starting
systemd manually also looks impossible.&lt;/p&gt;
&lt;p&gt;That means all the daemons required by libvirt must be started manually (see
documentation on &lt;a href="https://cirrus-ci.org/guide/writing-tasks/#background-script-instruction"&gt;background_script&lt;/a&gt; syntax):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# .cirrus.yml&lt;/span&gt;
&lt;span class="nt"&gt;dbus_background_script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;mkdir -p /var/run/dbus&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/usr/bin/dbus-daemon --system --nofork --nopidfile&lt;/span&gt;
&lt;span class="nt"&gt;virtlogd_background_script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/usr/sbin/virtlogd&lt;/span&gt;
&lt;span class="nt"&gt;libvirtd_background_script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;sleep 2 &amp;amp;&amp;amp; /usr/sbin/libvirtd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="firewall-configuration"&gt;&lt;a class="toclink" href="#firewall-configuration"&gt;Firewall configuration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hypervisor kernel is provided as is, and it currently runs legacy iptables
firewall. Trying to use iptables-nft (which is the default in current Debian)
produces a misconfigured guest network that is hard to debug.&lt;/p&gt;
&lt;p&gt;That's why we need to tell Debian to use legacy iptables interface across the whole
system:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# .cirrus.yml&lt;/span&gt;
&lt;span class="nt"&gt;iptables_legacy_script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;update-alternatives --set iptables /usr/sbin/iptables-legacy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="_1"&gt;&lt;a class="toclink" href="#_1"&gt;&amp;nbsp;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That's it! Following these steps I was able to execute Libvirt (via
Vagrant-Libvirt, via Molecule) in Cirrus CI environment. &lt;a href="https://gitlab.com/sio/server_common/-/blob/master/.cirrus.yml.j2"&gt;Full configuration&lt;/a&gt;
is available here, it includes some extra caching steps and many debug
statements that helped me to implement this process in the first place.&lt;/p&gt;</content><category term="posts"></category><category term="ci"></category><category term="automation"></category></entry><entry><title>Cirrus CI integration for GitLab projects</title><link href="https://potyarkin.com/posts/2020/cirrus-ci-integration-for-gitlab-projects/" rel="alternate"></link><published>2020-02-18T00:00:00+03:00</published><updated>2020-02-18T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2020-02-18:/posts/2020/cirrus-ci-integration-for-gitlab-projects/</id><summary type="html">&lt;p&gt;&lt;a href="https://cirrus-ci.org/"&gt;Cirrus CI&lt;/a&gt; is a relatively new hosted CI service that offers several unique
features. It's probably the only CI provider to offer full virtualization
(KVM) or FreeBSD runners for free. Currently their business model is centered
around GitHub Marketplace and only the projects hosted at GitHub are
supported.&lt;/p&gt;
&lt;p&gt;Fortunately Cirrus …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;a href="https://cirrus-ci.org/"&gt;Cirrus CI&lt;/a&gt; is a relatively new hosted CI service that offers several unique
features. It's probably the only CI provider to offer full virtualization
(KVM) or FreeBSD runners for free. Currently their business model is centered
around GitHub Marketplace and only the projects hosted at GitHub are
supported.&lt;/p&gt;
&lt;p&gt;Fortunately Cirrus CI provides a very capable GraphQL &lt;a href="https://cirrus-ci.org/api/"&gt;API&lt;/a&gt;. Thanks to that I
was able to write a simple command line tool to trigger CI builds with custom
configuration: &lt;a href="https://github.com/sio/cirrus-run"&gt;cirrus-run&lt;/a&gt;. It reads a local YAML file and executes Cirrus
CI build with that configuration. You can execute the build against any
GitHub repo you're allowed to access.&lt;/p&gt;
&lt;p&gt;Since build configuration is not provided by the repo the job may have no
relation to it. You can execute any number of jobs for any number of projects
against a single dummy holding repo at GitHub - which is the approach I
suggest to use for setting up integration with other source code hosting
platforms.&lt;/p&gt;
&lt;p&gt;Here is how I've set it up with GitLab:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Project repo is hosted at GitLab&lt;/li&gt;
&lt;li&gt;Each push triggers a &lt;a href="https://gitlab.com/sio/server_common/-/jobs/441624574"&gt;new build&lt;/a&gt; in GitLab CI.
    The only purpose of that build is to execute &lt;code&gt;cirrus-run&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/sio/cirrus-run"&gt;cirrus-run&lt;/a&gt; triggers the &lt;a href="https://cirrus-ci.com/task/5420732842770432"&gt;real build&lt;/a&gt; in Cirrus CI, waits for it to
    complete and reports the results.&lt;/p&gt;
&lt;p&gt;Cirrus CI build is owned by a dummy GitHub repo that contains only one
initial commit. Providing &lt;a href="https://cirrus-ci.org/guide/tips-and-tricks/#custom-clone-command"&gt;custom clone script&lt;/a&gt; allows to skip cloning
that dummy repo.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That allows me to continue using GitLab to host my project and GitLab CI to
run the jobs it's good at while delegating the more demanding jobs to Cirrus
CI. All status reports are gathered by GitLab CI and failure notifications
arrive uniformly to my inbox regardless of where the build was executed.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gitlab.com/sio/server_common/pipelines/118955114"&gt;&lt;img alt="Cirrus jobs in GitLab CI" src="https://potyarkin.com/posts/2020/cirrus-ci-integration-for-gitlab-projects/cirrus-gitlab.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Configuration files that enable the workflow described above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/sio/server_common/-/blob/795a204b90ddfd95e36d2753d9c7ea6d3a9f6573/.gitlab-ci.yml#L46-55"&gt;GitLab CI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/sio/server_common/-/blob/795a204b90ddfd95e36d2753d9c7ea6d3a9f6573/.cirrus.yml.j2"&gt;Cirrus CI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="posts"></category><category term="automation"></category><category term="ci"></category></entry><entry><title>Cygwin CI journey</title><link href="https://potyarkin.com/posts/2020/cygwin-ci-journey/" rel="alternate"></link><published>2020-01-28T00:00:00+03:00</published><updated>2020-01-28T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2020-01-28:/posts/2020/cygwin-ci-journey/</id><summary type="html">&lt;p&gt;Setting up Cygwin CI environment for testing one of my projects took more
than fifty trial-and-error attempts - that's why I think it will be useful to
leave some written notes on the issues I've encountered. Here is the end
&lt;a href="https://github.com/sio/Makefile.venv/blob/master/.github/workflows/test.yml"&gt;result&lt;/a&gt; - GitHub CI running some Makefile tests in Cygwin.&lt;/p&gt;
&lt;h2 id="cygwin-gotchas"&gt;&lt;a class="toclink" href="#cygwin-gotchas"&gt;Cygwin gotchas …&lt;/a&gt;&lt;/h2&gt;</summary><content type="html">&lt;p&gt;Setting up Cygwin CI environment for testing one of my projects took more
than fifty trial-and-error attempts - that's why I think it will be useful to
leave some written notes on the issues I've encountered. Here is the end
&lt;a href="https://github.com/sio/Makefile.venv/blob/master/.github/workflows/test.yml"&gt;result&lt;/a&gt; - GitHub CI running some Makefile tests in Cygwin.&lt;/p&gt;
&lt;h2 id="cygwin-gotchas"&gt;&lt;a class="toclink" href="#cygwin-gotchas"&gt;Cygwin gotchas&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Cygwin installer likes to fail silently, providing only cryptic exit codes
    (&lt;code&gt;127&lt;/code&gt; or &lt;code&gt;-1073741571&lt;/code&gt;)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;%CYGWIN_ROOT%\setup.exe --quiet-mode --verbose --no-desktop --download --local-install --no-verify --site %CYGWIN_MIRROR% --local-package-dir %CYGWIN_PACKAGE_CACHE% --root %CYGWIN_ROOT%
Starting cygwin install, version 2.897
User has backup/restore rights
io_stream_cygfile: fopen(/etc/setup/setup.rc) failed 2 No such file or directory
Current Directory: cache\cygwin-packages
Could not open service McShield for query, start and stop. McAfee may not be installed, or we don&amp;#39;t have access.
root: d:\a\Makefile.venv\Makefile.venv\cache\cygwin system
Selected local directory: cache\cygwin-packages
##[error]Process completed with exit code -1073741571.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At least some of those errors can be avoided by providing full absolute
paths to the installer and any argument values instead of relative
ones.&lt;/p&gt;
&lt;p&gt;In the end I've switched to Chocolatey to handle the installation for me.
It's certainly better but still requires calling &lt;code&gt;setup.exe&lt;/code&gt; to install
custom packages.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Since Cygwin installation and configuration are usually pretty slow (~3
    min), CI setup benefits a lot from caching this step between runs.
    Unfortunately, default GitHub actions for caching can't help with Cygwin
    root, because Windows is pretty hostile to an average Joe trying to make
    symlinks.&lt;/p&gt;
&lt;p&gt;Instead, I've added a separate step that calls &lt;code&gt;tar --dereference&lt;/code&gt;
explicitly and leaves only a simple archive for the caching action to
handle. Please note that tar uses exit code &lt;code&gt;1&lt;/code&gt; to indicate that
everything was OK, but some warnings were printed to stderr. Also, order
of command line arguments matters, at least for &lt;code&gt;--exclude&lt;/code&gt; values. That
was pretty unexpected for me.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You need to be careful when invoking apps under Cygwin - some environment
    variables may be inherited from &lt;code&gt;cmd&lt;/code&gt; session. For example, &lt;code&gt;$PATH&lt;/code&gt;
    will almost certainly point to binaries outside Cygwin root. I found that
    explicitly defining &lt;code&gt;$PATH&lt;/code&gt; simplifies matters a lot.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="powershellcmd-peculiarities"&gt;&lt;a class="toclink" href="#powershellcmd-peculiarities"&gt;PowerShell/cmd peculiarities&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Since host OS is still Windows, you get to encounter all the usual
PowerShell/cmd quirks. I tripped over the fact that PowerShell does not like
double-dashed --GNU-style options, and over weird nested quotes required to
properly combine environment variables and special characters in one value in
cmd.&lt;/p&gt;
&lt;h2 id="github-ci"&gt;&lt;a class="toclink" href="#github-ci"&gt;GitHub CI&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some of my attempts were just me trying to work around GitHub CI limitations.
For example, dynamically calculating the value of environment variable based
on values of other variables. This looks quite ugly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Use absolute path for CYGWIN_ROOT&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;echo &amp;quot;::set-env name=CYGWIN_ROOT::${env:GITHUB_WORKSPACE}\${env:CYGWIN_ROOT}&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I've also failed at proper syntax for YAML multiline strings more times than
I'm comfortable admitting :)&lt;/p&gt;</content><category term="posts"></category><category term="windows"></category><category term="cygwin"></category><category term="automation"></category><category term="ci"></category></entry><entry><title>Don't blindly trust Docker for the selfhosted stuff</title><link href="https://potyarkin.com/posts/2020/no-docker-for-selfhosted/" rel="alternate"></link><published>2020-01-27T00:00:00+03:00</published><updated>2020-01-27T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2020-01-27:/posts/2020/no-docker-for-selfhosted/</id><summary type="html">&lt;p&gt;It is my strong belief that you shouldn't go crazy with &lt;em&gt;all-things-docker&lt;/em&gt;
when deploying selfhosted services at home. Online forums, especially
&lt;a href="https://reddit.com/r/selfhosted/"&gt;r/selfhosted&lt;/a&gt;, seem to foster an opinion that providing a &lt;code&gt;Dockerfile&lt;/code&gt; or
better yet a &lt;code&gt;docker-compose.yml&lt;/code&gt; or even prebuilt public images on Docker Hub
is an acceptable way …&lt;/p&gt;</summary><content type="html">&lt;p&gt;It is my strong belief that you shouldn't go crazy with &lt;em&gt;all-things-docker&lt;/em&gt;
when deploying selfhosted services at home. Online forums, especially
&lt;a href="https://reddit.com/r/selfhosted/"&gt;r/selfhosted&lt;/a&gt;, seem to foster an opinion that providing a &lt;code&gt;Dockerfile&lt;/code&gt; or
better yet a &lt;code&gt;docker-compose.yml&lt;/code&gt; or even prebuilt public images on Docker Hub
is an acceptable way to distribute software targeting the selfhosting crowd.&lt;/p&gt;
&lt;p&gt;I agree it is very convenient to deploy complex multipart services via these
tools. But the way many people appear to be doing that is a security
nightmare! This is how we get to encounter &lt;a href="https://www.computerweekly.com/news/252437100/Heartbleed-and-WannaCry-thriving-in-Docker-community"&gt;Heartbleed in the
wild&lt;/a&gt; four years after it should've been extinct.&lt;/p&gt;
&lt;p&gt;There are &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/#protecting-cluster-components-from-compromise"&gt;many&lt;/a&gt; comprehensive &lt;a href="https://www.stackrox.com/post/2019/07/kubernetes-security-101/"&gt;writeups&lt;/a&gt; on
Docker/Kubernetes security, I will highlight only a subset of problems below.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shared libraries&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Running each service in its separate container results in having a
separate set of shared libraries for each one of those services. It is
convenient when you need to provide multiple incompatible dependencies at
once, but that way the burden of tracking the state of all those
dependencies lies on the user. Host OS can not tell you that one of the
containers still ships a vulnerable version of some critical library -
it's up to you to monitor and fix that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Container rebuilding&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Fixing anything related to the container requires rebuilding the image.
When you're using images from public registries you can not initiate image
rebuild even when you know it's needed. Your best option is to contact the
original uploader and to convince them to rebuild. That may take
significant time during which the containers running that image remain
vulnerable.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Images from untrusted sources&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In addition to the points above you put enormous amount of trust into
people who provide the container you're running. In containerless scenario
you're required to trust the vendor who provides the base OS and the
developer who provides the custom applications you run upon that OS. When
containers come into play, you must extend your trust to the maintainer of
the container image, to the vendors who provide the base image that image
is built upon, to all the developers who provide any piece of code
included into that container. It does not even require malicious intent to
introduce a vulnerability into the resulting container, simple
incompetence of any of the parties involved may be just enough.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why containerizing any workload comes with a significant extra cost of
designing and automating security maintenance procedures. It is easy to skip
this step when you're a hobbyist - but that's just burying your head in the
sand and waiting for some script kiddie or botnet to hijack your network.&lt;/p&gt;
&lt;p&gt;Here is a rough overview of the required overhead:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to run only containers that are based on the images you've built
  yourself. This is the only way you can ensure swift rebuilding in case one
  of the base images provides a security update. This step may include running
  your own image registry and build service.&lt;/li&gt;
&lt;li&gt;You need to audit every Dockerfile you intend to build. This can only be
  done manually. And you need to check all the base images in the chain up to
  either a &lt;code&gt;FROM scratch&lt;/code&gt; stanza or to a base image from trusted list.&lt;/li&gt;
&lt;li&gt;You need to maintain a list of trusted base images that come from vendors
  with good reputation in regards to handling security issues.&lt;/li&gt;
&lt;li&gt;You need to blacklist any image that does not come either from a trusted
  list or from a Dockerfile you've audited yourself.&lt;/li&gt;
&lt;li&gt;You need to setup automated image rebuilds and container rollouts:
    a) on schedule
    b) on any update in the base image dependency chain&lt;/li&gt;
&lt;li&gt;You need to setup automated vulnerability monitoring for the images you're
  running. This will require a lot more effort than subscribing to RSS feed of
  your distro security announcements - as it would've been the case with
  containerless deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add that on top of usual container orchestration chores - and bare metal
suddenly becomes attractive. Docker and Kubernetes are great tools that solve
real world problems but using them in a secure manner requires continuous
dedicated effort. For enterprise deployments the benefits of containerization
usually outweigh the extra maintenance cost, but for hobbyist use I'm not so
sure.&lt;/p&gt;</content><category term="posts"></category><category term="docker"></category><category term="kubernetes"></category><category term="automation"></category></entry><entry><title>Manage Python virtual environment from your Makefile</title><link href="https://potyarkin.com/posts/2019/manage-python-virtual-environment-from-your-makefile/" rel="alternate"></link><published>2019-10-01T00:00:00+03:00</published><updated>2019-10-01T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2019-10-01:/posts/2019/manage-python-virtual-environment-from-your-makefile/</id><summary type="html">&lt;p&gt;I often use Makefiles not just as a build tool but as a handy way to execute
sequences of commands. The commands I've found myself executing again and
again lately are the ones to manage Python virtual environments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create new venv&lt;/li&gt;
&lt;li&gt;Update pip to the latest version that enables all …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;I often use Makefiles not just as a build tool but as a handy way to execute
sequences of commands. The commands I've found myself executing again and
again lately are the ones to manage Python virtual environments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create new venv&lt;/li&gt;
&lt;li&gt;Update pip to the latest version that enables all the cool new features&lt;/li&gt;
&lt;li&gt;Install project requirements&lt;/li&gt;
&lt;li&gt;Delete venv and redo it again to see if everything still works from clean slate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The process is tedious and begs to be automated. And Makefile is a good fit
because in addition to basic scripting capabilities it offers proper
dependency handling that simplifies the task quite a bit.&lt;/p&gt;
&lt;p&gt;The outcome of my attempts at such automation is &lt;a href="https://github.com/sio/Makefile.venv"&gt;Makefile.venv&lt;/a&gt; - a Makefile
that seamlessly handles all virtual environment routines without ever needing
to be explicitly invoked. Instead, you write make targets that depend on
&lt;code&gt;venv&lt;/code&gt; and refer to all executables in virtual environment via
&lt;code&gt;$(VENV)/executable&lt;/code&gt;, e.g. &lt;code&gt;$(VENV)/python&lt;/code&gt; or &lt;code&gt;$(VENV)/pip&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;a href="https://github.com/sio/Makefile.venv"&gt;Makefile.venv&lt;/a&gt; is easy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;VENV&lt;span class="k"&gt;)&lt;/span&gt;/python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest

&lt;span class="cp"&gt;include Makefile.venv  # All the magic happens here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Despite its apparent simplicity this Makefile will do very much when invoked
(watch a &lt;a href="https://asciinema.org/a/279646"&gt;screencast&lt;/a&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A virtual environment will be created in current directory&lt;/li&gt;
&lt;li&gt;Pip will be automatically updated to the latest version&lt;/li&gt;
&lt;li&gt;Project requirements will be installed (both &lt;code&gt;requirements.txt&lt;/code&gt; and
  &lt;code&gt;setup.py&lt;/code&gt; are supported)&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;setup.py&lt;/code&gt; is present, the project will be installed in development mode
  into venv (&lt;code&gt;pip install -e&lt;/code&gt;) - all changes to the source code will
  immediately affect the package in virtual environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All these steps will be repeated in case &lt;code&gt;requirements.txt&lt;/code&gt; or &lt;code&gt;setup.py&lt;/code&gt;
is modified. That means you'll never have to worry about syncing venv with its
description. Add new dependency to &lt;code&gt;setup.py&lt;/code&gt; and consider it installed,
because there is no way it'll be forgotten the next time you invoke &lt;code&gt;make&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you'll need to debug something interactively, there are &lt;code&gt;make python&lt;/code&gt; and
&lt;code&gt;make ipython&lt;/code&gt; for REPL and &lt;code&gt;make bash&lt;/code&gt; (or &lt;code&gt;shell&lt;/code&gt; or &lt;code&gt;zsh&lt;/code&gt;) for shell, but I
rarely use those. Most of the time running &lt;code&gt;make&lt;/code&gt; with my targets for
executing the entry point or running unit tests is enough. In fact, I've
noticed that after introducing &lt;a href="https://github.com/sio/Makefile.venv"&gt;Makefile.venv&lt;/a&gt; into my workflow I've
completely stopped activating virtual environments manually.&lt;/p&gt;
&lt;p&gt;I encourage you to try &lt;a href="https://github.com/sio/Makefile.venv"&gt;Makefile.venv&lt;/a&gt; and hope you'll find this approach
useful. If you have some comments or would like to point out the faults of
using Makefiles for venv, please shoot me an e-mail or create an &lt;a href="https://github.com/sio/Makefile.venv/issues"&gt;issue&lt;/a&gt; at
GitHub project's page.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PS: &lt;a href="https://github.com/sio/Makefile.venv"&gt;Makefile.venv&lt;/a&gt; was inspired by &lt;a href="https://stackoverflow.com/questions/24736146"&gt;this StackOverflow
thread&lt;/a&gt; and by &lt;a href="http://blog.bottlepy.org/2012/07/16/virtualenv-and-makefiles.html"&gt;this blog post&lt;/a&gt; from the authors of
Bottle.py&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="posts"></category><category term="python"></category><category term="automation"></category></entry><entry><title>Cygheap base mismatch in Git for Windows</title><link href="https://potyarkin.com/posts/2019/cygheap-base-mismatch-in-git-for-windows/" rel="alternate"></link><published>2019-08-26T00:00:00+03:00</published><updated>2019-08-26T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2019-08-26:/posts/2019/cygheap-base-mismatch-in-git-for-windows/</id><summary type="html">&lt;p&gt;This error has haunted me for several months:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;4 [main] head (6660) C:\...\usr\bin\head.exe:r
*** fatal error - cygheap base mismatch detected - 0x612C7410/0xAF7410.

This problem is probably due to using incompatible versions of the cygwin DLL.
Search for cygwin1.dll using the Windows Start-&amp;gt;Find/Search facility …&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;This error has haunted me for several months:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;4 [main] head (6660) C:\...\usr\bin\head.exe:r
*** fatal error - cygheap base mismatch detected - 0x612C7410/0xAF7410.

This problem is probably due to using incompatible versions of the cygwin DLL.
Search for cygwin1.dll using the Windows Start-&amp;gt;Find/Search facility
and delete all but the most recent version.  The most recent version *should*
reside in x:\cygwin\bin, where &amp;#39;x&amp;#39; is the drive on which you have
installed the cygwin distribution.  Rebooting is also suggested if you
are unable to find another cygwin DLL.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The tricky part is I am not even using Cygwin. I'm running bash with Git for
Windows &lt;a href="https://git-scm.com/download/win"&gt;as published at the official website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've tried suggested solution from the error message, googled around quite a
bit, I've even dabbled with Windows Security settings (ASLR) - nothing helped
and have almost made my peace with the fact that every fifth commandline action
will fail loudly.&lt;/p&gt;
&lt;p&gt;And yet after recent release I've decided to try one more time. This time I've
downloaded the portable build for 64-bit architecture, and it worked! I don't
know if it was the fact that I was previously running a 32-bit Git and bash on
64-bit Windows 7 or if Git maintainers have tweaked their build process for 2.23.&lt;/p&gt;
&lt;p&gt;If you're experiencing segmentation faults while running bash from Git for
Windows package, you should check for &lt;strong&gt;architecture mismatch&lt;/strong&gt; with your OS
and/or for &lt;strong&gt;newer (2.23+) Git version&lt;/strong&gt;.&lt;/p&gt;</content><category term="posts"></category><category term="bash"></category><category term="windows"></category><category term="git"></category></entry><entry><title>On dotfiles management</title><link href="https://potyarkin.com/posts/2019/on-dotfiles-management/" rel="alternate"></link><published>2019-07-30T00:00:00+03:00</published><updated>2019-07-30T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2019-07-30:/posts/2019/on-dotfiles-management/</id><summary type="html">&lt;p&gt;This will be yet another description of dotfiles management by some random
person on the Internet. I will try to explain what my setup is like and why it
is that way.&lt;/p&gt;
&lt;p&gt;If you're not yet using version control software for your configuration files
I strongly encourage you to start …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This will be yet another description of dotfiles management by some random
person on the Internet. I will try to explain what my setup is like and why it
is that way.&lt;/p&gt;
&lt;p&gt;If you're not yet using version control software for your configuration files
I strongly encourage you to start doing so, whichever way you like. These
pages are good places to start:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://dotfiles.github.io/"&gt;An unofficial guide to dotfiles on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/index.php/Dotfiles"&gt;Arch wiki article on dotfiles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="my-chosen-approach"&gt;&lt;a class="toclink" href="#my-chosen-approach"&gt;My chosen approach&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After several attempts that have spanned across many years I've understood
that neither tracking home directory directly with Git nor symlinking all of
the dotfiles from a single directory tree is working for me. Both ways lead to
a mess in the repository and made the whole endeavor of tracking changes
cognitively expensive, so I inevitably started slacking off.&lt;/p&gt;
&lt;p&gt;I've looked at the existing tools that are meant to automate some of the
process and did not find one that would suit all my needs. I've ended up
writing a small shell &lt;a href="https://gitlab.com/sio/server_common/blob/master/dotfiles/bootstrap.sh"&gt;script&lt;/a&gt; that takes care of dotfiles installation but
the main value for me is in the repo layout, not the script itself.&lt;/p&gt;
&lt;p&gt;All configuration files are grouped into directories by topic. These
directories are somewhat similar to packages in GNU &lt;a href="https://www.gnu.org/software/stow/"&gt;stow&lt;/a&gt;. Topic directories
recreate the directory structure for the target location, by default $HOME.
Files that are meant to be installed into target location have to contain an
appropriate suffix at the end of filename (any other files are ignored):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.copy&lt;/code&gt; - for files to be copied over to new location&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.link&lt;/code&gt; - for files to be linked to from new location&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.append&lt;/code&gt; - for files to be appended to the target file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Default behavior may be altered by a &lt;code&gt;dotfiles.meta&lt;/code&gt; file placed into the
topic directory. It is essentially a shell script that is being sourced during
topic installation. Its main purpose is to provide alternative values for
PREFIX and SCOPE variables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PREFIX value determines target directory where the dotfiles will be placed.
  Also if PREFIX is set the dotfiles will not get an extra dot in front of
  their filename (which is the default behavior otherwise).&lt;/li&gt;
&lt;li&gt;SCOPE variable may be used to indicate that a topic requires root privileges
  to be installed (&lt;code&gt;SCOPE=system&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Multiple topics may be installed at once either by providing all of their
names as command line arguments or by listing them all in a text file and
providing path to that file as an argument to the installation &lt;a href="https://gitlab.com/sio/server_common/blob/master/dotfiles/bootstrap.sh"&gt;script&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="examples"&gt;&lt;a class="toclink" href="#examples"&gt;Examples&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;topic-foo/vimrc.link&lt;/code&gt; will be symlinked from &lt;code&gt;~/.vimrc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;topic-bar/bashrc.copy&lt;/code&gt; will be copied over to &lt;code&gt;~/.bashrc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;topic-baz/default/keyboard.copy&lt;/code&gt; with &lt;code&gt;PREFIX=/etc&lt;/code&gt; will be copied to &lt;code&gt;/etc/default/keyboard&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;topic-baz/file/without/valid/suffix&lt;/code&gt; will be ignored&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More examples may be found in my &lt;a href="https://gitlab.com/sio/server_common/tree/master/dotfiles"&gt;dotfiles repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="comparison-with-existing-tools"&gt;&lt;a class="toclink" href="#comparison-with-existing-tools"&gt;Comparison with existing tools&lt;/a&gt;&lt;/h2&gt;
&lt;h4 id="strengths"&gt;&lt;a class="toclink" href="#strengths"&gt;Strengths&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Very small number of dependencies makes this script usable across all my
  Linux and Windows machines. It requires only the core GNU userland: bash,
  coreutils, find and grep.&lt;/li&gt;
&lt;li&gt;Multiple install actions are supported (copy, link, append) unlike &lt;a href="https://www.gnu.org/software/stow/"&gt;stow&lt;/a&gt;
  that only makes symlinks. More than that, my script detects if it's being
  executed on Windows machine and copies over any file that was meant to be
  symlinked - because symlinks on Windows are so tricky they're might as well
  be not supported at all.&lt;/li&gt;
&lt;li&gt;Destination directory may be specified for each topic individually which
  makes it possible to install topics targeting different directories in one
  run.&lt;/li&gt;
&lt;li&gt;Simple partial deployment. If machine requires only a subset of topics
  tracked in the repository it is easy to list them all in a plain text file
  or to provide them as command line arguments to the bootstrap script.
  &lt;a href="https://thelocehiliosan.github.io/yadm/"&gt;yadm&lt;/a&gt;, for example does not provide such ability.&lt;/li&gt;
&lt;li&gt;Dotfiles are not hidden in the repo by default. It makes no sense to have
  &lt;code&gt;~/.bashrc&lt;/code&gt; point to &lt;code&gt;repo/bash/.bashrc&lt;/code&gt; instead of &lt;code&gt;repo/bash/bashrc&lt;/code&gt;, so
  dots are added automatically for topics with default target PREFIX.&lt;/li&gt;
&lt;li&gt;All operations are reversible because all overwritten files are backed up
  beforehand.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="weaknesses"&gt;&lt;a class="toclink" href="#weaknesses"&gt;Weaknesses&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Single pass execution. It means some topics may be left partially configured
  in case of errors. &lt;a href="https://www.gnu.org/software/stow/"&gt;stow&lt;/a&gt; is a good example of cautious approach. This is an
  implementation detail and may be fixed in later versions of bootstrap
  script.&lt;/li&gt;
&lt;li&gt;No support for tree folding/unfolding. I consider that an overkill for
  simple configuration management.&lt;/li&gt;
&lt;li&gt;No automated reverse operation. In case you want to undo the changes made by
  this &lt;a href="https://gitlab.com/sio/server_common/blob/master/dotfiles/bootstrap.sh"&gt;script&lt;/a&gt; you'll have to restore backups manually from &lt;code&gt;$DOTFILES_BACKUP&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="posts"></category><category term="linux"></category><category term="windows"></category><category term="automation"></category><category term="bash"></category></entry><entry><title>Installing One by Wacom in Debian Stretch</title><link href="https://potyarkin.com/posts/2018/installing-one-by-wacom-in-debian-stretch/" rel="alternate"></link><published>2018-08-22T00:00:00+03:00</published><updated>2018-08-22T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-08-22:/posts/2018/installing-one-by-wacom-in-debian-stretch/</id><summary type="html">&lt;p&gt;I believe there are many people who run Debian Stable as their main desktop OS.
This article is a short how-to on enabling newer hardware in Debian Stable
without switching to another version or distribution.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This article was written in 2018, new Debian Stable (Buster) has
been released since …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;I believe there are many people who run Debian Stable as their main desktop OS.
This article is a short how-to on enabling newer hardware in Debian Stable
without switching to another version or distribution.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This article was written in 2018, new Debian Stable (Buster) has
been released since. The instructions below were written for Debian Stretch
and have not been tested with other releases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="one-by-wacom"&gt;&lt;a class="toclink" href="#one-by-wacom"&gt;One by Wacom&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Wacom has released their new graphics tablet, &lt;a href="https://www.wacom.com/en-cn/products/pen-tablets/one-by-wacom"&gt;One by Wacom&lt;/a&gt; in the Fall of 2017
(judging by the dates of reviews in online shops). Linux drivers for this model
(Small: CTL-472, Medium: CTL-672) were added to the &lt;a href="https://github.com/linuxwacom/input-wacom"&gt;git repo&lt;/a&gt; in
December 2017 and were released in March 2018 (&lt;a href="https://github.com/linuxwacom/input-wacom/releases/tag/input-wacom-0.39.0"&gt;input-wacom-0.39.0&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;That is several months after the current Debian Stable (Stretch) was released
(June 2017). So there is exactly zero chance of it containing drivers for that
hardware - "stable" means no new software except security updates gets
introduced during the lifetime of the release (until at least 2020).&lt;/p&gt;
&lt;p&gt;Fortunately, you don't need to switch distribution to use newer hardware. You
don't even need to compile anything from source. Even more so, &lt;strong&gt;PLEASE DON'T
INSTALL INPUT-WACOM FROM SOURCE&lt;/strong&gt;, that will most likely lead to some undesired
side effects.&lt;/p&gt;
&lt;h2 id="debian-backports"&gt;&lt;a class="toclink" href="#debian-backports"&gt;Debian backports&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Backports project was created exactly for cases like this. It allows users to
install newer versions of some packages without breaking anything while keeping
Debian overall at the same (stable) version.&lt;/p&gt;
&lt;p&gt;To enable support for One by Wacom in Debian Stretch you need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://backports.debian.org/Instructions/"&gt;Add stretch-backports&lt;/a&gt; to your sources.list.&lt;br/&gt;
  &lt;em&gt;&lt;strong&gt;Update (2019-11-15):&lt;/strong&gt;
  Now that page contains instructions for Debian Buster. If you wish to enable
  backports in Debian Stretch you still can follow them while replacing every
  occurence of "buster-backports" with "stretch-backports"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Install newer kernel from backports:
  &lt;code&gt;apt-get -t stretch-backports install linux-image-amd64&lt;/code&gt;.
  If you're running Debian on different CPU architecture, replace &lt;code&gt;-amd64&lt;/code&gt; with
  the corresponding suffix, like &lt;code&gt;-686-pae&lt;/code&gt; for older 32-bit computers or
  &lt;code&gt;-arm64&lt;/code&gt; for ARMv8 CPUs).&lt;/li&gt;
&lt;li&gt;Reboot your computer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's it! Newer kernel will have updated drivers for your graphics tablet and
it will be detected automatically. You can start using it right away or tweak
some pressure options in your favorite graphics application (e.g. for Gimp it's
in &lt;em&gt;Edit -&amp;gt; Input Devices&lt;/em&gt;).&lt;/p&gt;
&lt;h2 id="further-reading"&gt;&lt;a class="toclink" href="#further-reading"&gt;Further reading&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wiki.archlinux.org/index.php/wacom_tablet#Configuration"&gt;Arch wiki&lt;/a&gt; - if you want to fine tune your graphics tablet&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wiki.debian.org/WacomTablets"&gt;Debian wiki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="timeline-recap"&gt;&lt;a class="toclink" href="#timeline-recap"&gt;Timeline recap&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.debian.org/News/2017/20170617"&gt;Debian 9&lt;/a&gt; (Stretch) was released in June 2017 and will be supported until
  at least 2020&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wacom.com/en-cn/products/pen-tablets/one-by-wacom"&gt;One by Wacom&lt;/a&gt; (Small: CTL-472, Medium: CTL-672)&lt;ul&gt;
&lt;li&gt;Available in retail: Fall 2017 (judging by the dates of reviews in online
  shops)&lt;/li&gt;
&lt;li&gt;Driver for Linux: &lt;a href="https://github.com/linuxwacom/input-wacom/commit/b12529e589dae810f0b6ef0b22f67b3860f86cde"&gt;patch&lt;/a&gt; added in December 2017, drivers
  &lt;a href="https://github.com/linuxwacom/input-wacom/releases/tag/input-wacom-0.39.0"&gt;released&lt;/a&gt; in March 2018&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="log-messages-for-reference"&gt;&lt;a class="toclink" href="#log-messages-for-reference"&gt;Log messages (for reference)&lt;/a&gt;&lt;/h2&gt;
&lt;h4 id="debian-9-stretch-without-proper-driver"&gt;&lt;a class="toclink" href="#debian-9-stretch-without-proper-driver"&gt;Debian 9 (Stretch) without proper driver&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# dmesg|grep -i wacom
usb 5-1: Manufacturer: Wacom Co.,Ltd.
input: Wacom Co.,Ltd. CTL-472 Pen as /devices/pci0000:00/0000:00:1d.0/usb5/5-1/5-1:1.0/0003:056A:037A.0001/input/input95
wacom 0003:056A:037A.0001: hidraw0: USB HID v1.10 Mouse [Wacom Co.,Ltd. CTL-472] on usb-0000:00:1d.0-1/input0
wacom 0003:056A:037A.0002: Unknown device_type for &amp;#39;Wacom Co.,Ltd. CTL-472&amp;#39;. Ignoring.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id="debian-with-updated-kernel-from-backports"&gt;&lt;a class="toclink" href="#debian-with-updated-kernel-from-backports"&gt;Debian with updated kernel from backports&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;# dmesg|grep -i wacom
usb 6-1: Manufacturer: Wacom Co.,Ltd.
input: Wacom Co.,Ltd. CTL-472 as /devices/pci0000:00/0000:00:1d.0/usb6/6-1/6-1:1.0/0003:056A:037A.0001/input/input23
hid-generic 0003:056A:037A.0001: input,hiddev0,hidraw0: USB HID v1.10 Mouse [Wacom Co.,Ltd. CTL-472] on usb-0000:00:1d.0-1/input0
hid-generic 0003:056A:037A.0002: hiddev1,hidraw1: USB HID v1.10 Device [Wacom Co.,Ltd. CTL-472] on usb-0000:00:1d.0-1/input1
input: Wacom One by Wacom S Pen as /devices/pci0000:00/0000:00:1d.0/usb6/6-1/6-1:1.0/0003:056A:037A.0001/input/input24
wacom 0003:056A:037A.0001: hidraw0: USB HID v1.10 Mouse [Wacom Co.,Ltd. CTL-472] on usb-0000:00:1d.0-1/input0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="posts"></category><category term="linux"></category><category term="hardware"></category><category term="Debian"></category></entry><entry><title>Enhanced file path completion in bash (like in zsh)</title><link href="https://potyarkin.com/posts/2018/enhanced-file-path-completion-in-bash-like-in-zsh/" rel="alternate"></link><published>2018-07-14T00:00:00+03:00</published><updated>2018-07-14T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-07-14:/posts/2018/enhanced-file-path-completion-in-bash-like-in-zsh/</id><summary type="html">&lt;p&gt;Zsh offers a lot of significant improvements over traditional shell experience.
Some of those can also be implemented in bash, but others are not. For a long
time I've thought that advanced file path expansion is something that can't be
done in bash. Today I prove myself wrong.&lt;/p&gt;
&lt;h2 id="background"&gt;&lt;a class="toclink" href="#background"&gt;Background&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Zsh offers a lot of significant improvements over traditional shell experience.
Some of those can also be implemented in bash, but others are not. For a long
time I've thought that advanced file path expansion is something that can't be
done in bash. Today I prove myself wrong.&lt;/p&gt;
&lt;h2 id="background"&gt;&lt;a class="toclink" href="#background"&gt;Background&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When the Tab key is pressed, zsh expands incomplete file path assuming any of
its elements might be incomplete, while bash expands only the last piece. For
example: &lt;code&gt;cd /u/s/app&amp;lt;Tab&amp;gt;&lt;/code&gt; will produce nothing in bash, but will be expanded
to &lt;code&gt;cd /usr/share/applications&lt;/code&gt; in zsh.&lt;/p&gt;
&lt;p&gt;This feature is a huge time saver, but it does not justify switching the shell
completely (for me). So I've been looking for a way to enable the same behavior
in bash.&lt;/p&gt;
&lt;p&gt;The solution had to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Portable between Linux and Windows (msys);&lt;/li&gt;
&lt;li&gt;Implemented in configuration files or short scripts that can be carried
  around easily. No third-party tools like &lt;code&gt;fzf&lt;/code&gt; that would require system-wide
  installation or other tricks to enable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All I've been able to find was
&lt;a href="https://stackoverflow.com/q/25076611"&gt;this StackOverflow thread&lt;/a&gt;.
Accepted answer suggests using a new bash function that is later bound to Tab
keypress. The function provided is a quick hack that was not tested thoroughly
and has problems with spaces in file path. The author recommends to use his
function along with the default completer, not instead of it, so it was not
what I needed.&lt;/p&gt;
&lt;h2 id="workaround"&gt;&lt;a class="toclink" href="#workaround"&gt;Workaround&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Typing &lt;code&gt;cd /u*/s*/app*&amp;lt;Tab&amp;gt;&lt;/code&gt; is somewhat better but not as streamlined an
experience as the one zsh offers.&lt;/p&gt;
&lt;p&gt;This turned out to be an inspiration for the proper completer function though.&lt;/p&gt;
&lt;h2 id="better-solution"&gt;&lt;a class="toclink" href="#better-solution"&gt;Better solution&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have coded a small function that adds wildcards to each element in the path
and executes normal bash completion procedures with modified input. After a bit
of documentation digging I've been able to inject this function into normal
bash completion process. I'm pretty happy with path expansion now.&lt;/p&gt;
&lt;p&gt;To enable the described behavior source &lt;a href="https://github.com/sio/bash-complete-partial-path/blob/master/bash_completion"&gt;&lt;strong&gt;this file&lt;/strong&gt;&lt;/a&gt; from your
~/.bashrc and run &lt;code&gt;_bcpp --defaults&lt;/code&gt;. Supported features are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Special characters in completed path are automatically escaped if present&lt;/li&gt;
&lt;li&gt;Tilde expressions are properly expanded (as per &lt;a href="https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html"&gt;bash documentation&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;If user had started writing the path in quotes, no character escaping is
  applied. Instead the quote is closed with a matching character after expanding
  the path.&lt;/li&gt;
&lt;li&gt;If &lt;a href="https://salsa.debian.org/debian/bash-completion"&gt;bash-completion&lt;/a&gt; package is already in use, this code will safely override
  its &lt;code&gt;_filedir&lt;/code&gt; function. No extra configuration is required.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Watch a demo screencast to see this feature in action:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://asciinema.org/a/0zhzOYbkF22pWLmbx1RHCYyqQ"&gt;&lt;img alt="asciicast" src="https://asciinema.org/a/0zhzOYbkF22pWLmbx1RHCYyqQ.png"&gt;&lt;/a&gt;&lt;/p&gt;</content><category term="posts"></category><category term="bash"></category><category term="gist"></category></entry><entry><title>Liberating effect of Ansible</title><link href="https://potyarkin.com/posts/2018/liberating-effect-of-ansible/" rel="alternate"></link><published>2018-06-26T00:00:00+03:00</published><updated>2018-06-26T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-06-26:/posts/2018/liberating-effect-of-ansible/</id><summary type="html">&lt;p&gt;Maintaining two or three Linux machines is not that hard of a task. For many
years I have thought it was not worth the effort to automate - regular backups
and version-controlled configuration files seemed to be just enough.&lt;/p&gt;
&lt;p&gt;And then Ansible had blown my mind.&lt;/p&gt;
&lt;h2 id="history"&gt;&lt;a class="toclink" href="#history"&gt;History&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It all started with …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Maintaining two or three Linux machines is not that hard of a task. For many
years I have thought it was not worth the effort to automate - regular backups
and version-controlled configuration files seemed to be just enough.&lt;/p&gt;
&lt;p&gt;And then Ansible had blown my mind.&lt;/p&gt;
&lt;h2 id="history"&gt;&lt;a class="toclink" href="#history"&gt;History&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It all started with a web server and a series of subpar hosting providers.
Setting that server up for the first time was an adventure. Repeating the setup
for the first relocation has given me a chance to introduce some improvements
but was otherwise uneventful. I started dreading the process when the need for
the third iteration had arisen. I was willing to put up with sluggishness and
several short downtimes just to delay the move to a new server. That was not OK.&lt;/p&gt;
&lt;p&gt;Automating has clearly become a worthwhile task. I chose Ansible because it
doesn't require any special software on the controlled machines and because it
is mature enough to remain backwards compatible after updates. There are
numerous reviews of pros and cons of different configuration management systems,
but that's outside the scope of this article.&lt;/p&gt;
&lt;h2 id="unexpected-outcome"&gt;&lt;a class="toclink" href="#unexpected-outcome"&gt;Unexpected outcome&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Ansible has definitely succeeded at the task I've thrown it at. That was
expected. What I couldn't foresee is how this experience would affect my
mindset - it was like a breath of fresh air! I have suddenly started to
understand why there exists all the buzz around &lt;em&gt;the cloud&lt;/em&gt; and why cloud
service providers are dominating the market of virtual servers.&lt;/p&gt;
&lt;h3 id="certainty"&gt;&lt;a class="toclink" href="#certainty"&gt;Certainty&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With my Ansible playbook I did not just automate the setup process, I created an
enforceable specification of what my server has to be like. At any time in the
future after executing the same playbook I can be certain that all configurable
parameters will be at the values I've defined.&lt;/p&gt;
&lt;p&gt;Idempotent behavior allows me to run the playbook again and again without fear
of breaking anything. It should be noted though, that not all Ansible modules
are idempotent and one should always check the documentation before
incorporating a new module into the playbook.&lt;/p&gt;
&lt;h3 id="immutability"&gt;&lt;a class="toclink" href="#immutability"&gt;Immutability&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There is even an option to take it one step further to truly &lt;a href="https://www.digitalocean.com/community/tutorials/what-is-immutable-infrastructure"&gt;immutable
infrastructure&lt;/a&gt; where any server can die at any point and no harm will be done.
This approach is for a bigger scale than a humble hobby project, though.&lt;/p&gt;
&lt;p&gt;I have limited the immutability effort to a gentle reminder in /etc/motd and a
policy among administrators (me, myself and I) that no configuration changes are
to be made via shell connection.&lt;/p&gt;
&lt;h3 id="disposability"&gt;&lt;a class="toclink" href="#disposability"&gt;Disposability&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now it is incredibly easy for me to replicate that web server. It requires only
one command and a short coffee break. That means I can switch the hosting
provider at any moment I want. Choosing the hosting company has become a
non-issue: I invest only a minimal payment and no labor at all. If I don't like
what I get I'll be gone in no time.&lt;/p&gt;
&lt;p&gt;Easy replication also means I can fire up another server for testing purposes,
then destroy it in a couple of hours and it will cost me only a few cents
because of hourly billing that most providers offer nowadays.&lt;/p&gt;
&lt;h3 id="emotional-detachment"&gt;&lt;a class="toclink" href="#emotional-detachment"&gt;Emotional detachment&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Setting up a new server used to be somewhat similar to moving into a new
apartment.  First, there was scrupulous comparing of offers, after that a
dramatic moment of signing the lease and then the silent minutes alone in empty
apartment, staring at the walls. Each server was important and loved, breaking
or destroying it on purpose was unthinkable.&lt;/p&gt;
&lt;p&gt;Now it's more like checking into a room at the hotel. And you get to be a
celebrity and send the rider list in advance, so that everything is set up to
your liking when you arrive. You can also check out at any moment without
worrying about the lease. There is no need to lug your favorite vimrc along
because you won't be editing anything there, in fact you'll hardly ever spend
any time logged in at all.&lt;/p&gt;
&lt;p&gt;Randy Bias has summed this attitude in the &lt;a href="http://cloudscaling.com/blog/cloud-computing/the-history-of-pets-vs-cattle/"&gt;pets versus cattle&lt;/a&gt; analogy. I think
it's quite on point even when you're managing a single machine, not the fleet of
servers.&lt;/p&gt;
&lt;h3 id="infrastructure-as-code"&gt;&lt;a class="toclink" href="#infrastructure-as-code"&gt;Infrastructure as code&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;And last, but not least, all you do with Ansible is automatically documented in
the playbook. You can use any version control system to track when any
particular change was introduced and why. That allows to revert the unwanted
changes as easily as was introducing them in the first place.&lt;/p&gt;
&lt;h2 id="afterword"&gt;&lt;a class="toclink" href="#afterword"&gt;Afterword&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When you're outside the professional IT crowd it is not obvious that the cloud
infrastructure and the corresponding tools are within the reach of hobbyist
enthusiasts. But they are and they offer just as much value for personal
projects as they do in production environment.&lt;/p&gt;
&lt;p&gt;If you find yourself tinkering with Linux administration more than once a week
it'll be worthwhile to try out Ansible or some other configuration management
tool.&lt;/p&gt;</content><category term="posts"></category><category term="ansible"></category><category term="web"></category><category term="server"></category><category term="automation"></category></entry><entry><title>Accidental submersion into web development</title><link href="https://potyarkin.com/posts/2018/accidental-submersion-into-web-development/" rel="alternate"></link><published>2018-06-16T00:00:00+03:00</published><updated>2018-06-16T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-06-16:/posts/2018/accidental-submersion-into-web-development/</id><summary type="html">&lt;h2 id="the-library-problem"&gt;&lt;a class="toclink" href="#the-library-problem"&gt;The Library Problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I love reading books. My wife loves reading books. We enjoy shopping for books
and we live a ten minute commute away from a huge used books store. That means
we have a lot of books. Like, really a lot. A little more than one thousand.&lt;/p&gt;
&lt;p&gt;We …&lt;/p&gt;</summary><content type="html">&lt;h2 id="the-library-problem"&gt;&lt;a class="toclink" href="#the-library-problem"&gt;The Library Problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I love reading books. My wife loves reading books. We enjoy shopping for books
and we live a ten minute commute away from a huge used books store. That means
we have a lot of books. Like, really a lot. A little more than one thousand.&lt;/p&gt;
&lt;p&gt;We have lost count how many times we've bought a book that we already owned.
Even more often we had foregone buying a book we liked because we were sure we
have already bought it - only to find out we've been mistaken and have only
considered buying that very book earlier. This has become a problem. The Library
Problem.&lt;/p&gt;
&lt;p&gt;We needed a way to catalog all our books. The catalog had to be accessible from
mobile devices (to look up a book while at the book store) and to be easy to
use. That is to add and edit book information, of which we've needed plenty: in
addition to standard set of author, title and publishing year we wanted to be
able to track book series and keep the list of missing books to look out for the
next time.&lt;/p&gt;
&lt;p&gt;I admit that my research into the subject matter was not scientifically thorough.
I've dug up several comparisons of existing tools and have read several blog
posts of people who have faced the same problem before. I particularly recommend
&lt;a href="http://www.zackgrossbart.com/hackito/the-library-problem/"&gt;this one&lt;/a&gt;. And I have decided that no pre-existing tool will meet our
growing expectations.&lt;/p&gt;
&lt;div class="toc"&gt;&lt;span class="toctitle"&gt;Contents&lt;/span&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-library-problem"&gt;The Library Problem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#naive-foray-into-application-architecture"&gt;Naive foray into application architecture&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#single-spreadsheet-approach"&gt;Single spreadsheet approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#local-database-driven-application-with-web-interface"&gt;Local database driven application with web interface&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#traditional-web-application"&gt;Traditional web application&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-unknown-unknowns"&gt;The unknown unknowns&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#orm"&gt;ORM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#multi-threading"&gt;Multi-threading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#database-migrations"&gt;Database migrations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#javascript-is-a-lot-of-work"&gt;JavaScript is a lot of work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#modular-design"&gt;Modular design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#mvc"&gt;MVC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#it-works"&gt;It works!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;h2 id="naive-foray-into-application-architecture"&gt;&lt;a class="toclink" href="#naive-foray-into-application-architecture"&gt;Naive foray into application architecture&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have never developed an application for any end user other than myself and I
didn't even know I was about to start developing one.&lt;/p&gt;
&lt;h3 id="single-spreadsheet-approach"&gt;&lt;a class="toclink" href="#single-spreadsheet-approach"&gt;Single spreadsheet approach&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The initial idea was that of a "document". It had to contain some essential
information about every book we own and (optionally) a second list of books we
want to acquire. Excel spreadsheet seemed like a natural fit. We are both
spending a better half of each workday juggling spreadsheets in Excel, so
developing and maintaining such "document" appeared doable.&lt;/p&gt;
&lt;p&gt;I have started drafting the column structure, applied some formatting tweaks and
the skeleton of the "document" was ready. We have entered the test batch of
books and were about to begin testing.&lt;/p&gt;
&lt;p&gt;Obstacles arose when we were entering information for the first fifteen books.
Manually typing in all the fields is tiresome and slow. Errors inevitably
happen. What if I miss one letter in the author's name?  That book will be lost
when applying filter on author column. We need autocompletion! What if I
accidentally switch the order of digits in ISBN? We won't be able to do a web
lookup for that book later. We have to write a custom ISBN validator in VBA!&lt;/p&gt;
&lt;p&gt;And the spreadsheet began to amass VBA code, data validation and conditional
formatting rules. Full-blown spaghetti style. Version control has become a
problem. What's the difference between versions 0.0.13 and 0.0.19? I had only a
vague idea.&lt;/p&gt;
&lt;p&gt;I stopped myself when I was about to sketch up a UserForm for data input. Excel
road was leading me nowhere. It was difficult and even if all the difficulties
were to be overcome it imposed some suboptimal compromises on us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single table storage limited the data structures we would be able to enter and
  view. If the book was written by two authors, which one should come first in
  the "author" string?  How would we do column filtering in that case? What
  about sort order?&lt;/li&gt;
&lt;li&gt;The local nature of storage meant there had to be one designated place for
  making changes (home laptop). Any changes made in other locations (smartphone,
  thumb drive) had to be agreed to be declared discardable.&lt;/li&gt;
&lt;li&gt;The spreadsheet had to be exported to HTML to be accessible from smartphones.
  XML and XSLT made this possible but not very pleasant. Although, I am rather
  proud of the VBA code I wrote to save/load XML data automatically upon opening
  the workbook. The data was completely decoupled from the representation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm glad I did not waste more time pursuing this path, but it was still hard to
let go. It took quite some time for me to return to this project afterwards.&lt;/p&gt;
&lt;h3 id="local-database-driven-application-with-web-interface"&gt;&lt;a class="toclink" href="#local-database-driven-application-with-web-interface"&gt;Local database driven application with web interface&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A relational database was the logical solution to spreadsheet limitations.
Store authors separate from books and manage how the former &lt;em&gt;relate&lt;/em&gt; to the
latter! Scientists who pioneered the &lt;a href="https://en.wikipedia.org/wiki/Relational_model#History"&gt;relational model&lt;/a&gt; in 1970s were pure
geniuses and now the whole world relies on their work.&lt;/p&gt;
&lt;p&gt;I drafted the database schema on a piece of paper and have discussed it
extensively with my wife. That's probably the point where book reviews were
added to the requirements list. The database idea made me very enthusiastic and
had swallowed a lot of free time, but that idea alone could not provide a
complete solution for the problem at hand.&lt;/p&gt;
&lt;p&gt;Data input and representation are what the Library project was all about. Yet
relational database management systems provide only the storage solution, not
the full package (if you don't take Microsoft Access into account). So I had to
figure out how to implement the user interface on my own. I chose to write it in
Python because I was somewhat familiar with the language and I enjoy how clean
and readable Python code is. I'm not a programmer, so other options were either
learning a new language or choosing between VBA and Bash, none of which could be
considered enjoyable.&lt;/p&gt;
&lt;p&gt;I have briefly considered building a conventional desktop GUI. The UserForm
experience was still fresh and rather traumatic, so I was reluctant to dive into
another UI toolkit. Even if I were to, which one should I have chosen? Qt seemed
nice, but its Python support reports were contradictory. GTK? Installing it was
rather tricky in Windows. Something non-crossplatform? And what about my Linux
laptop?&lt;/p&gt;
&lt;p&gt;I've had a bunch of HTML templates left from XML/XSLT operations and they could
be trivially transformed to be used in conjunction with the database. While the
catalog pages could be statically generated, the data input required some sort
of server to interact with. And I've had zero experience with that.&lt;/p&gt;
&lt;p&gt;Quick Google search has introduced me to the concept of web frameworks and I
have semi-randomly chosen &lt;a href="https://bottlepy.org/"&gt;Bottle&lt;/a&gt;. At that moment I've had no intention to
expose it to the open network, the app and the database were to be stored on the
USB stick and launched locally when needed. Smartphone interaction was planned
either via LAN or using saved HTML pages for read-only access when not at home.
Bottle uses no dependencies other than standard library and the whole framework
is packaged into a single file. It was perfect for the portable app scenario.&lt;/p&gt;
&lt;p&gt;After the development started and I saw how difficult it is to make a web
application, I've decided there is no point to confine the result of all that
labor to a single computer. Why use static (maybe even outdated) HTML dumps on
the smartphone when we could access full functionality of the application via
web site?&lt;/p&gt;
&lt;h3 id="traditional-web-application"&gt;&lt;a class="toclink" href="#traditional-web-application"&gt;Traditional web application&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The development was already going on, powered by Python and Bottle. What I was
missing was the server to host my application at. Turns out launching a web site
can be very cheap these days! Even as cheap as free.&lt;/p&gt;
&lt;p&gt;I've started with a free domain name from &lt;a href="https://freenom.com/"&gt;Freenom&lt;/a&gt; and a free hosting from Red
Hat's &lt;a href="https://www.openshift.com/"&gt;OpenShift&lt;/a&gt;. Both of them have had upsides and downsides but no downsides
were significant enough to turn down the price of free. Freenom domains are
considered low quality from SEO standpoint, but that was completely irrelevant
in my case. OpenShift server had to receive at least one http request every 24
hours to stay awake, and I've cheated that with a cron job on my router. No
inconveniences whatsoever.&lt;/p&gt;
&lt;p&gt;I have to digress to emphasize: OpenShift was great! It was easy to set up and
very convenient to use. Its documentation was very thorough and up to date.
OpenShift has introduced me to some concepts I would have not encountered
otherwise, like automated deploying after git push and handling proper wsgi
server. And all of that was for free. I'm very thankful to Red Hat for that.&lt;/p&gt;
&lt;p&gt;Unfortunately, as Mr. Heinlein &lt;a href="https://en.wikipedia.org/wiki/The_Moon_Is_a_Harsh_Mistress"&gt;used to reiterate&lt;/a&gt;, there ain't no
such thing as a free lunch. Red Hat could not pay the bills for all the computer
enthusiasts forever and version 3 of OpenShift imposed much tighter restrictions
on the free accounts. I've had to move to a cheap virtual private server and to
learn to set up and maintain an Apache instance on my own. New VPS was sometimes
too slow compared to what OpenShift has offered and I might need to migrate to
another hosting provider later, but everything was fine for now.&lt;/p&gt;
&lt;p&gt;I could concentrate on the development.&lt;/p&gt;
&lt;h2 id="the-unknown-unknowns"&gt;&lt;a class="toclink" href="#the-unknown-unknowns"&gt;The unknown unknowns&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Little did I know that while I was keeping myself busy with seemingly important
problems such as whether to store images as blobs in database or as files on the
file system a number of much more important problems were creeping up behind me.
Donald Rumsfeld has called these things &lt;em&gt;the unknown unknowns&lt;/em&gt; - the things that
we don't know while remaining unaware about the very fact of not knowing.&lt;/p&gt;
&lt;h3 id="orm"&gt;&lt;a class="toclink" href="#orm"&gt;ORM&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;SQLite to store data. Web framework to interact with user. Python to glue them
together. The need in all these parts comes naturally, you could not "forget" to
use any one of them. Yet they are not the only parts that are needed.&lt;/p&gt;
&lt;p&gt;Nowhere in Python's database API documentation it says that composing each query
individually is highly inefficient in a database-driven application. No one
hints that there exist a whole other class of libraries called Object-Relational
Mappers (&lt;a href="https://en.wikipedia.org/wiki/Object-relational_mapping"&gt;ORM&lt;/a&gt;). And I was not clever enough to deduce that on my own.&lt;/p&gt;
&lt;p&gt;But I wasn't too dumb either. I figured that repeating myself every time I
needed to run a simple query was wrong. So I stashed that code away into &lt;a href="https://github.com/sio/HomeLibraryCatalog/blob/1452531ec05f049c6a758530d7f526f05c188ba1/hlc/db.py#L356"&gt;SQL&lt;/a&gt;
class. I figured that Book and Author objects require a lot of common methods to
interact with database. So I separated that code into &lt;a href="https://github.com/sio/HomeLibraryCatalog/blob/1452531ec05f049c6a758530d7f526f05c188ba1/hlc/items.py#L37"&gt;TableEntityWithID&lt;/a&gt; class.
I have essentially implemented an ORM without knowing what ORM is. Of course, it
is far inferior to SQLAlchemy and the likes. Of course, I will never use it in
another project now that I know that there are industry standard solutions in
this area. But I see no point in replacing my (suboptimal) implementation now.
Because it works and nothing is broken and nothing is missing.&lt;/p&gt;
&lt;p&gt;I consider my ORM to be a valuable educational experience even if it somewhat
stalled the overall progress of the project.&lt;/p&gt;
&lt;h3 id="multi-threading"&gt;&lt;a class="toclink" href="#multi-threading"&gt;Multi-threading&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While I was developing the application I was using Bottle's builtin wsgiref web
server. Running that server in production environment is not recommended because
it was not built to withstand all the nastiness of open Internet. It is also a
single threaded server which means it can not accept a request until it's done
serving the previous one. The single-threadiness did not concern me - my app was
intended to be used by two or three users tops and simultaneous requests would
happen very seldom. Yet all the proper web servers (Apache, Nginx etc) are
created to scale up to thousands and millions of users. Of course, they are
multithreaded and multiprocessing. My application was not ready for that. Some
would say SQLite is not ready for that either.&lt;/p&gt;
&lt;p&gt;At first I considered crippling Apache to use only a single thread or switching
to another server that can operate in a single thread mode. OpenShift seduced me
with maintenance-free setup of Apache and I've abandoned that idea.&lt;/p&gt;
&lt;p&gt;The code I've come up with to use a different instance of some objects for each
thread is &lt;a href="https://github.com/sio/HomeLibraryCatalog/blob/1452531ec05f049c6a758530d7f526f05c188ba1/hlc/web.py#L1173"&gt;rather ugly&lt;/a&gt;. And it works only 90% of the time. So I
just restart the Python workers every hour to avoid HTTP 500 errors when the
database gets locked up. Not the best engineering decision of mine.&lt;/p&gt;
&lt;p&gt;There probably exists a proper solution to multithreaded SQLite access, but so
far I have not guessed what keywords to ask Google for it.&lt;/p&gt;
&lt;h3 id="database-migrations"&gt;&lt;a class="toclink" href="#database-migrations"&gt;Database migrations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I was aware that changing database schema after the database was populated is
hard and can lead to data loss. So I have put a lot of thought into designing
it. I've even fired up GraphViz and created a &lt;a href="https://github.com/sio/HomeLibraryCatalog/blob/master/docs/relations.gv.pdf"&gt;nice chart&lt;/a&gt; that I've
printed out and looked at on the commute. I wanted the database schema to be
iron-clad and to require no changes in future.&lt;/p&gt;
&lt;p&gt;I was very naive.&lt;/p&gt;
&lt;p&gt;Of course it would require changes. Everything around us changes, we change, our
expectations towards software change. At that point I understood that updating
database manually was not a solution: there would be no trace left whether the
schema was updated, which changes were introduced and which version does it
correspond now to. So I've coded a simple &lt;a href="https://github.com/sio/HomeLibraryCatalog/blob/master/hlc/db_transition.py"&gt;transition&lt;/a&gt; module that would do that
work for me. And later I've learned that the process I was automating is called
&lt;a href="https://en.wikipedia.org/wiki/Schema_migration"&gt;database migration&lt;/a&gt;, and there are tools written by professionals for it.&lt;/p&gt;
&lt;h3 id="javascript-is-a-lot-of-work"&gt;&lt;a class="toclink" href="#javascript-is-a-lot-of-work"&gt;JavaScript is a lot of work&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I tried to avoid adding dependencies whenever I could. So I've decided to write
what little client-side logic I needed in pure JavaScript. I have no experience
with any JS framework so I thought that avoiding to learn one would compensate
for the inconvenience. I'm not sure it did.&lt;/p&gt;
&lt;p&gt;Most of my JS code is written intuitively, with no awareness of best practises
and is filled with "code smells". Writing from scratch was sometimes quite
educating, e.g with AJAX calls - now I understand and can explain them better than
ever before. Overall conclusion is that JavaScript is hard. And that frameworks
probably exist for a reason. If I'll do another web app project, I'll probably
look into some lightweight framework to save time writing JS, which I find not
very enjoyable.&lt;/p&gt;
&lt;h3 id="modular-design"&gt;&lt;a class="toclink" href="#modular-design"&gt;Modular design&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One might think that splitting code into modules comes naturally. At least I
did. And I was wrong. When I was not specifically thinking about their size some
modules tended to grow large. A thousand lines is pretty hard to do a quick
overview on, and even harder to split after the fact. Some modularity has to be
designed from the beginning.&lt;/p&gt;
&lt;p&gt;I don't know how I could've avoided that. Guess it comes with the experience.&lt;/p&gt;
&lt;h3 id="mvc"&gt;&lt;a class="toclink" href="#mvc"&gt;MVC&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is another example of a sensible principle that's hard to come by without
someone teaching you. &lt;a href="https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller"&gt;Model-View-Controller&lt;/a&gt; is a logical extension of
modularity principle which I was totally unaware about. I have made some
intuitive steps in the right direction, but overall my application is a soup of
interleaved components. That complicates maintenance and further development and
makes the code more difficult to understand for other developers.&lt;/p&gt;
&lt;p&gt;If I would've gone with a bigger framework like Flask or Django, MVC mindset
might have been forced down on me. For a newbie who doesn't know anything some
dictatorship isn't that bad.&lt;/p&gt;
&lt;h2 id="it-works"&gt;&lt;a class="toclink" href="#it-works"&gt;It works!&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After all the difficulties and complications (both expected and unexpected) I
can proudly say that the Library project is a success. The website is up and
running for almost a year. There has been no significant downtimes and no data
loss, most of our books have been catalogued. And most important, me and my wife
do enjoy using it!&lt;/p&gt;
&lt;p&gt;The application supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating and editing book entries.&lt;/li&gt;
&lt;li&gt;Storing and displaying book metadata, cover thumbnail and arbitrary related
  files. Each book can be connected with any number of authors, series and/or
  tags.&lt;/li&gt;
&lt;li&gt;Using ISBN to fetch book information from several third-party sources&lt;/li&gt;
&lt;li&gt;Queuing ISBNs for input. This is helpful when you process a lot of books with
  barcode scanner and don't have the time to clean up automatic metadata on each
  one of them&lt;/li&gt;
&lt;li&gt;Adding 1-to-5 star ratings and text reviews to any book in the library&lt;/li&gt;
&lt;li&gt;Searching for books by exact metadata match and with wildcards&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can access &lt;a href="https://github.com/sio/HomeLibraryCatalog"&gt;the source code&lt;/a&gt; on GitHub and see the site in action at
&lt;a href="https://morebooks.ml"&gt;https://morebooks.ml&lt;/a&gt; (registration is for family members only, sorry).  Of
course, there are plenty of improvements to be made (you can see how long the
TODO list is), but the maintenance itself requires almost zero attention now
and I can happily switch from being a developer to becoming the end user.&lt;/p&gt;</content><category term="posts"></category><category term="python"></category><category term="web"></category><category term="HomeLibraryCatalog"></category></entry><entry><title>Excel as a CSV editor (with VBA)</title><link href="https://potyarkin.com/posts/2018/excel-as-a-csv-editor-with-vba/" rel="alternate"></link><published>2018-06-01T00:00:00+03:00</published><updated>2018-06-01T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-06-01:/posts/2018/excel-as-a-csv-editor-with-vba/</id><summary type="html">&lt;p&gt;One might think that Excel is a decent CSV editor as it is, but it's not. It is
a very capable CSV reader, I do not dispute that. When it comes to writing though,
Excel does not match what you'd expect from a mature application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It might change the delimiter …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;One might think that Excel is a decent CSV editor as it is, but it's not. It is
a very capable CSV reader, I do not dispute that. When it comes to writing though,
Excel does not match what you'd expect from a mature application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It might change the delimiter character arbitrarily;&lt;/li&gt;
&lt;li&gt;It might write numbers in the regional format that does not map to a number
  anywhere outside Excel;&lt;/li&gt;
&lt;li&gt;It might add quotes that are inconsistent with the rest of the file.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you're collaborating on the CSV file with others, their Excel version might
have different defaults and produce incompatible output.  Even if you're the only
one working on that CSV, you can forget about clean diffs and sensible atomic
commits to your version control system.&lt;/p&gt;
&lt;p&gt;The only solution is not to overwrite CSV files you've opened with Excel. Use
another tool designed specifically for dealing with CSV or edit the file
manually in the text editor of your choosing.&lt;/p&gt;
&lt;h2 id="append-to-csv-with-vba"&gt;&lt;a class="toclink" href="#append-to-csv-with-vba"&gt;Append to CSV with VBA&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I wrote a small helper utility to append data rows to the CSV files from Excel that
ensures you won't mess up the existing data. This is a one-day hobby project, and
Excel serves more as the UI toolkit and runtime environment than as the
spreadsheet application, so you should be careful if you decide to rely on that
code. The project is licensed under the &lt;a href="http://www.apache.org/licenses/LICENSE-2.0"&gt;Apache License, Version 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here is the code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/sio/CSV-Append/blob/master/CSVAppend.bas"&gt;Main VBA module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The resulting &lt;a href="https://github.com/sio/CSV-Append/raw/master/CSV-Editor.xlsm"&gt;application&lt;/a&gt;, packaged in a workbook.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The application reads parameters from named ranges, opens the required file,
parses CSV header and displays a submission form for a new data row. Upon
submission it combines new values into a CSV string and appends it to the file.
All data manipulation is done in VBA. This app could have and should have been
written in any modern language - it would probably have cleaner code. Excel is
super easy to draft a simple UI though :)&lt;/p&gt;
&lt;p&gt;The code is pretty straightforward so I'll highlight only the most interesting
parts.&lt;/p&gt;
&lt;h2 id="reading-and-writing-unicode-with-vba"&gt;&lt;a class="toclink" href="#reading-and-writing-unicode-with-vba"&gt;Reading and writing Unicode with VBA&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visual Basic for Applications is a hopelessly outdated environment. Unicode
support can be achieved only with the help of COM interoperability, namely the
&lt;code&gt;ADODB.Stream&lt;/code&gt; object. This object provides a very comfortable interface for
reading and writing text files in a bytestream mode, and also handles character
encoding nicely.&lt;/p&gt;
&lt;p&gt;Appending to a file is done via combination of seeking to the end of stream
and writing the new data.&lt;/p&gt;
&lt;h2 id="csv-packing-and-unpacking"&gt;&lt;a class="toclink" href="#csv-packing-and-unpacking"&gt;CSV packing and unpacking&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm not exactly proud of how CSV string manipulations are implemented in the code.
If VBA would've provided some nicer regex capabilities or a CSV-aware library it
would've been better. I know about &lt;code&gt;VBScript.RegExp&lt;/code&gt;, but it's an overkill for a
small task my app was created to accomplish.&lt;/p&gt;
&lt;p&gt;Current implementation can not handle a quote symbol in the middle of the field
value. This is a known bug.&lt;/p&gt;
&lt;h2 id="demo"&gt;&lt;a class="toclink" href="#demo"&gt;Demo&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This the main and the only UI my utility offers. Inputs and buttons are meant to
be self explaining. No value conversion is done when saving - the value of the
cell is written as is, quotes are added if delimiter character occurs within
the value.&lt;/p&gt;
&lt;p&gt;Screenshot below is produced after loading demo CSV file with the following
header:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ID,Column1,Column2,Column3 with very long header,&amp;quot;Column4, with delimiter in the name&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://potyarkin.com/posts/2018/excel-as-a-csv-editor-with-vba/csv-append.png"&gt;&lt;img alt="CSV Append" src="https://potyarkin.com/posts/2018/excel-as-a-csv-editor-with-vba/csv-append.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The project is published for educational and archival purposes. I'll be glad if
you'll find any use for it.&lt;/p&gt;</content><category term="posts"></category><category term="excel"></category><category term="vba"></category><category term="gist"></category></entry><entry><title>Why software translation is a waste of time</title><link href="https://potyarkin.com/posts/2018/why-software-translation-is-a-waste-of-time/" rel="alternate"></link><published>2018-05-24T00:00:00+03:00</published><updated>2018-05-24T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-05-24:/posts/2018/why-software-translation-is-a-waste-of-time/</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I am not a professional software developer, and my opinion
might not be as authoritative as yours.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My native language is not English and since my first encounter with computers I
have used multiple localized and non-localized computer programs. All these
years of &lt;em&gt;"user experience"&lt;/em&gt; have led me to …&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I am not a professional software developer, and my opinion
might not be as authoritative as yours.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;My native language is not English and since my first encounter with computers I
have used multiple localized and non-localized computer programs. All these
years of &lt;em&gt;"user experience"&lt;/em&gt; have led me to believe that software localization
is more often harmful than not.&lt;/p&gt;
&lt;p&gt;Software translation is a waste of time. Generally.&lt;/p&gt;
&lt;p&gt;I am not against localization as a whole. It has many positive aspects like
supporting foreign date and currency formats, right-to-left writing or
alphabetical sorting. But translating user interface, configuration files,
error and log messages to other languages had destructive consequences most of
the times I've seen it.&lt;/p&gt;
&lt;h2 id="documentation-loss"&gt;&lt;a class="toclink" href="#documentation-loss"&gt;Documentation loss&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The moment software is translated its documentation becomes fragmented and
incomplete. Even if the developer translates 100% of official documentation
they will still lose everything written by others (blog posts, forum questions,
bug reports).&lt;/p&gt;
&lt;p&gt;I was twelve when a friend of mine gave me a book on photo editing. The authors
explained how images are stored on computers, what is the difference between
raster and vector graphics, but the narrative was mostly centered on using Adobe Photoshop -
version 7.0, if I recall correctly. And that was one large useless book.
Because the authors used the English version of that editor and all we've had
was the translated one.&lt;/p&gt;
&lt;p&gt;You might think it was a mistake on the authors' part, but they were smart and
experienced people. They knew it was pointless to reference a translated version
because no professional user would have used Russian interface at that time.
And they knew that the next version might come with totally different
translation for the same UI elements.&lt;/p&gt;
&lt;h2 id="incomplete-or-wrong-translation-is-worse-than-no-translation-at-all"&gt;&lt;a class="toclink" href="#incomplete-or-wrong-translation-is-worse-than-no-translation-at-all"&gt;Incomplete or wrong translation is worse than no translation at all&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you are not sure you can afford a good translation, don't do one. I can not
stress enough how confusing it is to have a piece of software that uses your
native language, and not to be able to understand the meaning of its messages
without translating them back to English first. This happens all the time when
software is translated by people who do not use it daily and do not understand
all the usecases there are.&lt;/p&gt;
&lt;p&gt;I took part in translation of an open source program once. I was a student and
I've had a lot of free time, so I thought I could do some good and contribute
back to the software I thought was worthy.&lt;/p&gt;
&lt;p&gt;It was a social media plugin for a bigger application. We had a team of maybe a
dozen volunteer translators and a coordinator with write access to the source
control system. Usually he would email us a day before the next release with a
file containing strings that needed to be translated. And then the farce
started.&lt;/p&gt;
&lt;p&gt;Those of us who were available at the moment started translating. We didn't
know where in the application we would later see those strings. Even if we
weren't lazy (guilty) and would've launched non-localized development version
of the application, we would not have been able to match 100% of new strings to
all the places they'd be used at. The coordinator was not any less blind than
the rest of us. He knew a lot about the application code base, but he was not a
superhuman - he could not possibly track all the developers and understand all
their intentions. So we shipped some embarrassing translation errors... I'm
glad no lives depended on that software!&lt;/p&gt;
&lt;h2 id="lost-in-translation"&gt;&lt;a class="toclink" href="#lost-in-translation"&gt;Lost in translation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I concede that our team was lacking in terms of organizational skills, after
all we were just part-time volunteers. But the translators hired by big
corporations are merely human too, and they make mistakes. Especially when the
headquarters is pressuring to ship a new product.&lt;/p&gt;
&lt;p&gt;For more than ten years Microsoft Excel, a flagman spreadsheet application used
by millions, has had two duplicate entries in row/column context menu: "Вставить"
and "Вставить". The first one had a nice icon and meant "Paste (copied cells)"
and the second one was iconless and meant "Insert (new row/column)". They've
removed the text from the first one now, converting it to a button. Ambiguity
still remains (the pop-up text for the button is the same) but is less
confusing. Especially since users are already accustomed to it :)&lt;/p&gt;
&lt;h2 id="unsearchable-error-messages"&gt;&lt;a class="toclink" href="#unsearchable-error-messages"&gt;Unsearchable error messages&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Have you ever received a cryptic error message and had no idea what it meant?
I'm sure you have. That message would only become more cryptic if it was
translated. And if the error is not exactly common or the app is not popular in
your country, Google will not be able to help you either.&lt;/p&gt;
&lt;p&gt;So, for the sake of your users' sanity, please do not ever localize error
messages and log files! Help people to help themselves.&lt;/p&gt;
&lt;h2 id="untranslatable-abstractions"&gt;&lt;a class="toclink" href="#untranslatable-abstractions"&gt;Untranslatable abstractions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Some ideas are just so new, or the problem domain is so narrow that there is no
point translating the terms. The concept of &lt;code&gt;File&lt;/code&gt; was foreign to every person
seeing the computer for the first time - but that knowledge is easily acquired.
It would not have been any easier explaining that same concept and labeling it
&lt;code&gt;Файл&lt;/code&gt; (Russian translation), so why bother introducing two terms?&lt;/p&gt;
&lt;p&gt;"File" ship has long sailed, but new abstractions are being introduced every
day. Translating them to multiple languages just slows their adoption and
hinders communication between users.&lt;/p&gt;
&lt;h2 id="afterword"&gt;&lt;a class="toclink" href="#afterword"&gt;Afterword&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'm not hoping we will wake up one day and The Tower of Babel didn't happen.
This rant is mostly useless, but if at any time because of it a software
developer will decide that their users are educated enough to understand
written English or a software user will decide to acquire entry-level English
skills, I'll consider my time well spent.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This has been stewing for quite some time... At least since 2012, after I've
read &lt;a href="https://joeyh.name/blog/entry/on_localization_and_progress/"&gt;this&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="posts"></category><category term="l10n"></category><category term="i18n"></category></entry><entry><title>Unit testing in Power Query M Language</title><link href="https://potyarkin.com/posts/2018/unit-testing-in-power-query-m-language/" rel="alternate"></link><published>2018-04-01T12:00:00+03:00</published><updated>2018-04-01T12:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-04-01:/posts/2018/unit-testing-in-power-query-m-language/</id><summary type="html">&lt;p&gt;As your code base gets bigger,
&lt;a href="https://en.wikipedia.org/wiki/Test_automation"&gt;test automation&lt;/a&gt; becomes more
and more important. This applies to any development platform, including Power
Query / PowerBI. If you reuse your code and improve some low level function
later, test automation allows you to make sure your changes did not break
anything that depends …&lt;/p&gt;</summary><content type="html">&lt;p&gt;As your code base gets bigger,
&lt;a href="https://en.wikipedia.org/wiki/Test_automation"&gt;test automation&lt;/a&gt; becomes more
and more important. This applies to any development platform, including Power
Query / PowerBI. If you reuse your code and improve some low level function
later, test automation allows you to make sure your changes did not break
anything that depends on the part of code you've just modified.&lt;/p&gt;
&lt;p&gt;As far as I know, there are no tools that allow us to perform automated testing
of functions and queries written in Power Query M language. That's why I've
built a simple unit testing framework into &lt;a href="https://libpq.ml"&gt;LibPQ&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="libpq-unittest-framework"&gt;&lt;a class="toclink" href="#libpq-unittest-framework"&gt;LibPQ UnitTest framework&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://libpq.ml/Docs/UnitTesting"&gt;UnitTest&lt;/a&gt; framework is modelled after the only other unit testing tool I
have experience with: Python's
&lt;a href="https://docs.python.org/3/library/unittest.html"&gt;unittest&lt;/a&gt;. It offers the
following features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Test suites to arbitrarily group individual test cases&lt;/li&gt;
&lt;li&gt;Assertion functions to test simple statements&lt;/li&gt;
&lt;li&gt;Subtests to execute the same test on a sequence of sample inputs&lt;/li&gt;
&lt;li&gt;Test runner and test discovery functions to execute your test suites&lt;/li&gt;
&lt;li&gt;Test results table that can be analyzed either manually or with any
  automation tool you create&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Inner workings of the test framework are described in the
&lt;a href="https://libpq.ml/Docs/UnitTesting"&gt;documentation&lt;/a&gt;. This article will demonstrate how it works.&lt;/p&gt;
&lt;h2 id="unittest-demo"&gt;&lt;a class="toclink" href="#unittest-demo"&gt;UnitTest demo&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All modules described here are imported with LibPQ, so a basic familiarity with the library is assumed (&lt;a href="https://libpq.ml"&gt;readme&lt;/a&gt;, &lt;a href="https://potyarkin.com/posts/2018/getting-started-with-libpq/"&gt;getting started&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Let's create a basic test suite and save it in the directory listed in &lt;code&gt;LibPQPath&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* DemoTests.pq - sample test suite */&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Assert&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="nx"&gt;LibPQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;UnitTest.Assert&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;testFirstTest&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="nx"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;42&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;testAlwaysFail&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="nx"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bar&amp;quot;&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="nx"&gt;meta&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;LibPQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TestSuite&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="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The test suite is a record (note the square brackets surrounding the code) that
contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two test cases (values prefixed with "test")&lt;ul&gt;
&lt;li&gt;The first test will pass because 6 times 7 is 42&lt;/li&gt;
&lt;li&gt;The second test will always fail because "foo" and "bar" are different
strings&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;And one related value: &lt;code&gt;Assert&lt;/code&gt; is a helper for building test functions. Its
  use is not required, but makes writing tests much easier.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last line contains metadata that marks the test suite as such and allows
test discovery tools to distinguish it from just another record.&lt;/p&gt;
&lt;p&gt;Here is what &lt;a href="https://github.com/sio/LibPQ/blob/master/Modules/UnitTest.Discover.pq"&gt;UnitTest.Discover&lt;/a&gt; function will do when invoked:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Search all locally available modules for valid test suites (hence the metadata)&lt;/li&gt;
&lt;li&gt;Execute each located test suite with &lt;a href="https://github.com/sio/LibPQ/blob/master/Modules/UnitTest.Run.pq"&gt;UnitTest.Run&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Return the test results as a table, reporting as much data about the failed
  tests as possible&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://potyarkin.com/posts/2018/unit-testing-in-power-query-m-language/libpq-unittest-long.png"&gt;&lt;img alt="Test results" src="https://potyarkin.com/posts/2018/unit-testing-in-power-query-m-language/libpq-unittest-long.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the screenshot above we invoke &lt;a href="https://github.com/sio/LibPQ/blob/master/Modules/UnitTest.Discover.pq"&gt;UnitTest.Discover&lt;/a&gt; with &lt;code&gt;compact_output =
false&lt;/code&gt; but when you'll have dozens of test cases you'll probably prefer default
behavior (group test results by status).&lt;/p&gt;
&lt;h2 id="more-about-unittest-in-libpq"&gt;&lt;a class="toclink" href="#more-about-unittest-in-libpq"&gt;More about UnitTest in LibPQ&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you liked the idea of unit testing M language code, check out the main
&lt;a href="https://libpq.ml/Docs/UnitTesting"&gt;UnitTest&lt;/a&gt; documentation and a more extensive &lt;a href="https://github.com/sio/LibPQ/blob/master/Samples/Tests.Sample.pq"&gt;test sample&lt;/a&gt; that makes use of
subtests.&lt;/p&gt;</content><category term="posts"></category><category term="m"></category><category term="power-query"></category><category term="LibPQ"></category></entry><entry><title>Getting started with LibPQ</title><link href="https://potyarkin.com/posts/2018/getting-started-with-libpq/" rel="alternate"></link><published>2018-04-01T00:00:00+03:00</published><updated>2018-04-01T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-04-01:/posts/2018/getting-started-with-libpq/</id><summary type="html">&lt;p&gt;This is a step by step guide to getting started with &lt;a href="https://potyarkin.com/posts/2018/expanding-power-query-standard-library-introducing-libpq/"&gt;LibPQ&lt;/a&gt;, an illustrated
version of &lt;a href="https://libpq.ml/#installation-and-usage"&gt;"Installation and usage"&lt;/a&gt; section of the official
documentation.&lt;/p&gt;
&lt;h2 id="installation"&gt;&lt;a class="toclink" href="#installation"&gt;Installation&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="libpq-source-code"&gt;&lt;a class="toclink" href="#libpq-source-code"&gt;LibPQ source code&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The source code of the library has to be present in each workbook that uses it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new blank query: &lt;code&gt;Data …&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;This is a step by step guide to getting started with &lt;a href="https://potyarkin.com/posts/2018/expanding-power-query-standard-library-introducing-libpq/"&gt;LibPQ&lt;/a&gt;, an illustrated
version of &lt;a href="https://libpq.ml/#installation-and-usage"&gt;"Installation and usage"&lt;/a&gt; section of the official
documentation.&lt;/p&gt;
&lt;h2 id="installation"&gt;&lt;a class="toclink" href="#installation"&gt;Installation&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="libpq-source-code"&gt;&lt;a class="toclink" href="#libpq-source-code"&gt;LibPQ source code&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The source code of the library has to be present in each workbook that uses it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new blank query: &lt;code&gt;Data &amp;gt; Get &amp;amp; Transform &amp;gt; From Other Sources &amp;gt;
  Blank Query&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Go to "Advanced editor" and replace the query code with the contents of
  &lt;a href="https://potyarkin.com/posts/2018/expanding-power-query-standard-library-introducing-libpq/"&gt;&lt;code&gt;LibPQ.pq&lt;/code&gt;&lt;/a&gt; (switch to "Raw" view to make selecting easier)&lt;/li&gt;
&lt;li&gt;Save new query under the name &lt;code&gt;LibPQ&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://potyarkin.com/posts/2018/getting-started-with-libpq/libpq-main-module.png"&gt;&lt;img alt="Main module of LibPQ" src="https://potyarkin.com/posts/2018/getting-started-with-libpq/libpq-main-module.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="specifying-modules-location"&gt;&lt;a class="toclink" href="#specifying-modules-location"&gt;Specifying modules location&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After the previous step LibPQ doesn't know yet where it should get the modules'
source code from. You can specify an unlimited number of local and web locations
where the modules are saved:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a new blank query and name it &lt;code&gt;LibPQPath&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy the contents of &lt;a href="https://github.com/sio/LibPQ/blob/master/LibPQPath-sample.pq"&gt;LibPQPath-sample.pq&lt;/a&gt; and modify it in Advanced editor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://potyarkin.com/posts/2018/getting-started-with-libpq/libpq-path-editor.png"&gt;&lt;img alt="LibPQ-Path" src="https://potyarkin.com/posts/2018/getting-started-with-libpq/libpq-path-editor.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;LibPQ will search for your modules first in local directories (in order they
are listed), then in web locations. If the module is found, no further
locations are checked.&lt;/p&gt;
&lt;p&gt;It helps with the name collisions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let's say you have a module &lt;code&gt;FavoritePets.pq&lt;/code&gt; stored in your module
  collection at &lt;code&gt;http://yoursite.com/PowerQueryModules/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;At the same time you use some modules from a friend's module collection
  at &lt;code&gt;http://friendname.com/PowerQueryModules/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If your friend adds a module with the same name to their collection, all
  you need to do to ignore it is to make sure that your collection address
  is higher in the &lt;code&gt;LibPQPath&lt;/code&gt; than your friend's.&lt;/li&gt;
&lt;li&gt;That works both ways: you and your friend can continue sharing your
  module collections while using personal modules with colliding names
  without any problems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="reusable-template"&gt;&lt;a class="toclink" href="#reusable-template"&gt;Reusable template&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It is not necessary to repeat the installation steps every time you want to use LibPQ. You can add LibPQ to an empty workbook and save is as a template for future use.&lt;/p&gt;
&lt;h2 id="usage"&gt;&lt;a class="toclink" href="#usage"&gt;Usage&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="importing-existing-module"&gt;&lt;a class="toclink" href="#importing-existing-module"&gt;Importing existing module&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Import any available module with &lt;code&gt;LibPQ("ModuleName")&lt;/code&gt; when writing your
queries in Advanced editor. LibPQ will search for the file named
&lt;code&gt;ModuleName.pq&lt;/code&gt; in all locations that you've listed in LibPQPath. If the module
is found, its source code will be evaluated and the result will be returned.&lt;/p&gt;
&lt;p&gt;For example, let's import &lt;code&gt;Date.Parse&lt;/code&gt; from standard LibPQ collection:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://potyarkin.com/posts/2018/getting-started-with-libpq/libpq-date-parse.png"&gt;&lt;img alt="Date.Parse" src="https://potyarkin.com/posts/2018/getting-started-with-libpq/libpq-date-parse.png"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;That works because &lt;code&gt;LibPQPath&lt;/code&gt; contains reference to
&lt;code&gt;https://raw.githubusercontent.com/sio/LibPQ/master/Modules/&lt;/code&gt;, where the source
code for &lt;code&gt;Date.Parse.pq&lt;/code&gt; is located.&lt;/p&gt;
&lt;h3 id="creating-a-new-module"&gt;&lt;a class="toclink" href="#creating-a-new-module"&gt;Creating a new module&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You can save any reusable Power Query function or query to be imported by LibPQ
later:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copy the code of that module to any text editor (I recommend Notepad++) and
  save it with &lt;code&gt;*.pq&lt;/code&gt; extension&lt;/li&gt;
&lt;li&gt;Place the module into any location listed in &lt;code&gt;LibPQPath&lt;/code&gt; and it will become
  available for importing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have any further questions about LibPQ please create an &lt;a href="https://github.com/sio/LibPQ/issues"&gt;issue&lt;/a&gt; on GitHub
or contact me via &lt;a href="mailto:sio.wtf@gmail.com"&gt;e-mail&lt;/a&gt;.&lt;/p&gt;</content><category term="posts"></category><category term="m"></category><category term="power-query"></category><category term="LibPQ"></category></entry><entry><title>Roads and Bridges - sustaining modern digital infrastructure</title><link href="https://potyarkin.com/posts/2018/roads-and-bridges-sustaining-modern-digital-infrastructure/" rel="alternate"></link><published>2018-02-23T00:00:00+03:00</published><updated>2018-02-23T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-02-23:/posts/2018/roads-and-bridges-sustaining-modern-digital-infrastructure/</id><summary type="html">&lt;p&gt;This week I have stumbled upon a very thorough review of existing problems and
hidden costs of sustaining modern (open source) digital infrastructure. Here it
is: &lt;a href="https://www.fordfoundation.org/library/reports-and-studies/roads-and-bridges-the-unseen-labor-behind-our-digital-infrastructure/"&gt;Roads and Bridges - The Unseen Labor Behind Our Digital
Infrastructure&lt;/a&gt; by &lt;em&gt;Nadia Eghbal&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The essay was created with support from the Ford Foundation and …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This week I have stumbled upon a very thorough review of existing problems and
hidden costs of sustaining modern (open source) digital infrastructure. Here it
is: &lt;a href="https://www.fordfoundation.org/library/reports-and-studies/roads-and-bridges-the-unseen-labor-behind-our-digital-infrastructure/"&gt;Roads and Bridges - The Unseen Labor Behind Our Digital
Infrastructure&lt;/a&gt; by &lt;em&gt;Nadia Eghbal&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The essay was created with support from the Ford Foundation and is published on
their website under a Creative Commons license. Unfortunately, that website
denies access to users from certain countries (like Russia), so here is a
&lt;a href="https://potyarkin.com/posts/2018/roads-and-bridges-sustaining-modern-digital-infrastructure/roads-and-bridges.pdf"&gt;mirror of PDF version&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The author discusses important and often overlooked topics of why open source
software gets built and by whom, of who pays the costs of building and
maintaining that software and of how to ensure that the software we all rely
upon continues to be &lt;em&gt;reliable&lt;/em&gt;. The essay poses more questions than it
answers, but I still consider it the best read on the topic of sustaining open
source development.&lt;/p&gt;
&lt;p&gt;In my case Nadia Eghbal was "preaching to the converted" so this post is me
trying to spread her word. Please read her essay and please do not take open
source software for granted. The costs of building it are just payed by others,
may be you can figure out how to help them?&lt;/p&gt;</content><category term="posts"></category><category term="bookmarks"></category><category term="opensource"></category></entry><entry><title>Expanding Power Query standard library - introducing LibPQ</title><link href="https://potyarkin.com/posts/2018/expanding-power-query-standard-library-introducing-libpq/" rel="alternate"></link><published>2018-01-03T00:00:00+03:00</published><updated>2018-01-03T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2018-01-03:/posts/2018/expanding-power-query-standard-library-introducing-libpq/</id><summary type="html">&lt;p&gt;Power Query formula language (also known as M language) is a very capable yet
not very flexible tool. It lacks some features taken for granted by developers
who are used to other programming languages such as compatibility with version
control systems, extensibility by third-party libraries, etc.&lt;/p&gt;
&lt;p&gt;That is why I …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Power Query formula language (also known as M language) is a very capable yet
not very flexible tool. It lacks some features taken for granted by developers
who are used to other programming languages such as compatibility with version
control systems, extensibility by third-party libraries, etc.&lt;/p&gt;
&lt;p&gt;That is why I have started &lt;strong&gt;&lt;a href="https://libpq.ml"&gt;LibPQ&lt;/a&gt;&lt;/strong&gt; - an open-source M language library
meant to expand the standard library and to make it easier for others to do so.
Its main features are:&lt;/p&gt;
&lt;h3 id="importing-source-code-from-plain-text-files-located-on-disk-or-on-the-web"&gt;&lt;a class="toclink" href="#importing-source-code-from-plain-text-files-located-on-disk-or-on-the-web"&gt;Importing source code from plain text files located on disk or on the web&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;LibPQ stores its modules as plain text files with &lt;code&gt;*.pq&lt;/code&gt; extension.  Detaching
source code from the workbooks that execute it has a lot of advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The source code can be managed by version control system such as git&lt;/li&gt;
&lt;li&gt;Multiple workbooks referring to the same module will always use the same
  (latest) code&lt;/li&gt;
&lt;li&gt;It encourages splitting your code into smaller reusable units&lt;/li&gt;
&lt;li&gt;You can edit the source code with any editor you like (autocompletion and
  syntax highlighting are nice features even though Power Query's Advanced
  Editor does not have them)&lt;/li&gt;
&lt;li&gt;Sharing your code and collaborating becomes much easier&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="supporting-several-import-locations-ordered-by-priority"&gt;&lt;a class="toclink" href="#supporting-several-import-locations-ordered-by-priority"&gt;Supporting several import locations ordered by priority&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;LibPQ does not dictate where you store your source code. Inspired by Python's
&lt;a href="https://docs.python.org/3/library/sys.html#sys.path"&gt;&lt;code&gt;sys.path&lt;/code&gt;&lt;/a&gt; it enables specifying unlimited number of local and/or
remote sources (ordered by priority). When importing a module, LibPQ will check
these sources one by one until the required module is found.&lt;/p&gt;
&lt;h3 id="unit-testing-framework"&gt;&lt;a class="toclink" href="#unit-testing-framework"&gt;Unit testing framework&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Having source code detached from the workbooks encourages you to improve and
refactor existing modules. To make sure you do not introduce regressions you
should cover your code with unit tests.&lt;/p&gt;
&lt;p&gt;There are no unit testing tools in standard library, but LibPQ offers a basic
unit testing framework that supports test discovery, grouping tests into test
suites and comes with a bunch of handy assertion functions. To learn more read
this &lt;a href="https://libpq.ml/Docs/UnitTesting/"&gt;help page&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="a-collection-of-general-purpose-functions-and-queries"&gt;&lt;a class="toclink" href="#a-collection-of-general-purpose-functions-and-queries"&gt;A collection of general purpose functions and queries&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;And last, LibPQ contains some general purpose modules that you might find
useful. If not - go write some new ones, you have the tools now!&lt;/p&gt;
&lt;p&gt;LibPQ is built in such way that you do not need me (or anyone else) to approve
of your work.  Save your code to any convenient location, and LibPQ will help
you to import it into your workbooks. You can even keep your modules private,
no pressure here. Have fun and enjoy your coding!&lt;/p&gt;</content><category term="posts"></category><category term="m"></category><category term="excel"></category><category term="power-query"></category><category term="LibPQ"></category></entry><entry><title>Loops in Power Query M language</title><link href="https://potyarkin.com/posts/2017/loops-in-power-query-m-language/" rel="alternate"></link><published>2017-10-31T00:00:00+03:00</published><updated>2017-10-31T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2017-10-31:/posts/2017/loops-in-power-query-m-language/</id><summary type="html">&lt;p&gt;Power Query Formula Language (also known as M language) is sometimes difficult
to get your head around. This article explains how someone familiar with loops
in other programming languages can approach the same concept in M language.&lt;/p&gt;
&lt;p&gt;First of all let's look at the &lt;a href="https://docs.microsoft.com/en-us/powerquery-m/"&gt;definition&lt;/a&gt; given by Microsoft:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Power …&lt;/p&gt;&lt;/blockquote&gt;</summary><content type="html">&lt;p&gt;Power Query Formula Language (also known as M language) is sometimes difficult
to get your head around. This article explains how someone familiar with loops
in other programming languages can approach the same concept in M language.&lt;/p&gt;
&lt;p&gt;First of all let's look at the &lt;a href="https://docs.microsoft.com/en-us/powerquery-m/"&gt;definition&lt;/a&gt; given by Microsoft:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Power Query M formula language is optimized for building highly flexible
data mashup queries. It's a &lt;strong&gt;functional&lt;/strong&gt;, case sensitive language similar
to F#, which can be used with Power BI Desktop, Power Query in Excel, and Get
&amp;amp; Transform in Excel 2016.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="functional-is-the-key-word"&gt;&lt;a class="toclink" href="#functional-is-the-key-word"&gt;"Functional" is the key word&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Understanding (and accepting) that M is entirely different from most common
programming languages has helped me as much as (maybe even more than) the
exhaustive reference at MSDN. Functional language implies declarative
programming paradigm: you describe &lt;em&gt;what&lt;/em&gt; you want the computer to do instead
of telling &lt;em&gt;how&lt;/em&gt; to do it. If you're familiar with LISP or Erlang or Haskell, M
might not look so foreign to you.&lt;/p&gt;
&lt;p&gt;The code in M is not an explicit sequence of steps that will always be executed
in the same order, it is just a bunch of ground rules that allow the computer
to arrive to the solution. You can check that the order of lines within the
&lt;code&gt;let&lt;/code&gt; statement doesn't matter: as long as all necessary intermediate steps are
described, Power Query will produce the same result even if you rearrange them
randomly.&lt;/p&gt;
&lt;p&gt;And that is the reason you don't get familiar control flow statements. &lt;em&gt;If&lt;/em&gt; is
kinda there, but it has its own quirks too. Loops are out of the question,
unless you somehow manage to implement the function that does the looping for
you. But...&lt;/p&gt;
&lt;p&gt;There already is such a function! It is &lt;code&gt;List.Generate&lt;/code&gt;!&lt;/p&gt;
&lt;h2 id="listgenerate"&gt;&lt;a class="toclink" href="#listgenerate"&gt;List.Generate&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This function takes 3 or 4 parameters, all of them functions.  (You should
always treat the &lt;code&gt;each&lt;/code&gt; statement as a function &lt;a href="https://docs.microsoft.com/en-us/powerquery-m/understanding-power-query-m-functions"&gt;because it is&lt;/a&gt; a shortcut
for function definition.)&lt;/p&gt;
&lt;p&gt;The parameters are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;start&lt;/strong&gt;: a function that takes zero arguments and returns the first loop
  item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;condition&lt;/strong&gt;: a function that takes one argument (loop item) and returns
  boolean value. If this function returns &lt;code&gt;false&lt;/code&gt; the iteration stops,
  otherwise the loop item is added to the list of results. This function will
  be called at the end of each iteration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;next&lt;/strong&gt;: a function that takes one argument (loop item) and returns the next
  loop item. This is the worker body of the loop. Be careful to return the next
  item as the same data type with the same structure, because the returned
  value will be fed to &lt;code&gt;condition()&lt;/code&gt; and &lt;code&gt;next()&lt;/code&gt; functions later. This
  function will be called at the beginning of each iteration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;transform&lt;/strong&gt;: optional argument. A function that takes one argument - the
  item from the list of results and transforms it into something else.  This
  function gets called once per each item in the list of results, and the list
  of values it returns becomes the return value of &lt;code&gt;List.Generate&lt;/code&gt;. If
  &lt;code&gt;transform()&lt;/code&gt; function is not specified, &lt;code&gt;List.Generate&lt;/code&gt; will return the list
  of items at the moment when &lt;code&gt;condition()&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;List.Generate&lt;/code&gt; might be easier to understand with the following pseudocode:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;List&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&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;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;results&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;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;transform&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="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;output&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;transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="a-simple-example"&gt;&lt;a class="toclink" href="#a-simple-example"&gt;A simple example&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We will generate a table of data points for plotting a parabola. Internally we
will be storing each item as the record with &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; fields.  After that we
will transform that data into a Power Query table for output.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;data&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="nx"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Generate&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;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;100&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;x&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="nx"&gt;output&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="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FromRecords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In this example &lt;code&gt;start()&lt;/code&gt; is an anonymous function that always returns the
first data point, &lt;code&gt;condition()&lt;/code&gt; and &lt;code&gt;next()&lt;/code&gt; are also functions even though
they are written using &lt;code&gt;each&lt;/code&gt; shortcut. There is no &lt;code&gt;transform()&lt;/code&gt; function
because it is an optional parameter.&lt;/p&gt;
&lt;h2 id="an-example-from-real-world"&gt;&lt;a class="toclink" href="#an-example-from-real-world"&gt;An example from real world&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the real world you will not need the &lt;code&gt;List.Generate&lt;/code&gt; magic for such simple
tasks, but you will still need it. Here is how I've used it recently.&lt;/p&gt;
&lt;p&gt;Assume you have a list of tables that contain the data in the same format but
for different time periods or for different locations. You have a separate list
of locations (in the correct order), but each individual table does not contain
that information. That's why combining all these tables into one would create a
mess: you have to know which row comes from what table.&lt;/p&gt;
&lt;p&gt;This can be done with &lt;code&gt;List.Generate&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nx"&gt;NamedTables&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="nx"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Generate&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;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;({},{})],&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// initialize loop variables&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Tables&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Tables&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;TableName&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Names&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&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="nx"&gt;each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This code snippet assumes you have the list of tables in the &lt;code&gt;Tables&lt;/code&gt; variable
and the list of their respective names in the &lt;code&gt;Names&lt;/code&gt; variable. The loop starts
with index of -1 and an empty table, and adds a "TableName" column to each of
the tables. After this modification the tables can be safely combined with
&lt;code&gt;Table.Combine(NamedTables)&lt;/code&gt; - no data loss will occur.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;List.Generate&lt;/code&gt; should be considered a last-ditch attempt to looping. M
has dedicated iterative functions for most common looping tasks, so please
check the standard library reference before creating such C-style loops
manually. They are rather hard to read, and readability counts!&lt;/p&gt;
&lt;p&gt;I hope this article will help you to understand the Power Query Formula
Language a little more. It is a powerful tool and even though it is not
perfect, I hope you will find a lot of uses for it in your data crunching
tasks.&lt;/p&gt;
&lt;h2 id="an-afterthought"&gt;&lt;a class="toclink" href="#an-afterthought"&gt;An afterthought&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Also, please keep in mind that the dot symbol in &lt;code&gt;List.Generate&lt;/code&gt; does not have
the same meaning as in other languages either. There are no object methods in
M, and there are no namespaces, so the dot is just another character without
any special meaning.  It could have been a dash or an underscore - it wouldn't
have mattered.&lt;/p&gt;</content><category term="posts"></category><category term="m"></category><category term="excel"></category><category term="power-query"></category></entry><entry><title>Temporary virtual environment for Python</title><link href="https://potyarkin.com/posts/2017/temporary-virtual-environment-for-python/" rel="alternate"></link><published>2017-10-05T16:50:00+03:00</published><updated>2017-10-05T16:50:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2017-10-05:/posts/2017/temporary-virtual-environment-for-python/</id><summary type="html">&lt;p&gt;Using Python on Windows does not come as naturally as on Unix-like systems, so
any help is appreciated.&lt;/p&gt;
&lt;p&gt;I wrote a batch script to automate creation, setup and deletion of Python virtual
environment. This can come in handy when you want to test something in a clean env,
or to …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Using Python on Windows does not come as naturally as on Unix-like systems, so
any help is appreciated.&lt;/p&gt;
&lt;p&gt;I wrote a batch script to automate creation, setup and deletion of Python virtual
environment. This can come in handy when you want to test something in a clean env,
or to play with &lt;code&gt;pip install&lt;/code&gt; and get acquainted with a new package from PyPI.&lt;/p&gt;
&lt;h2 id="venv-tempbat"&gt;&lt;a class="toclink" href="#venv-tempbat"&gt;venv-temp.bat&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can download the script from
&lt;a href="https://gist.github.com/sio/fbc46ae41607b206ce9099dc8485df34"&gt;https://gist.github.com/sio/...&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The code is licensed under a permissive opensource license (Apache License,
Version 2.0) so feel free to use it for your hobby and work projects.&lt;/p&gt;
&lt;p&gt;Report any bugs, ideas, feature requests via GitHub issues/comments -
all feedback is welcome!&lt;/p&gt;
&lt;h2 id="installation"&gt;&lt;a class="toclink" href="#installation"&gt;Installation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Downloaded script does not require any installation.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;python&lt;/code&gt; is not available from your &lt;code&gt;%PATH%&lt;/code&gt;, you have to specify the location
of &lt;code&gt;python.exe&lt;/code&gt; in the script (change the value of &lt;code&gt;PYTHON&lt;/code&gt; variable).&lt;/p&gt;
&lt;h2 id="usage"&gt;&lt;a class="toclink" href="#usage"&gt;Usage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Launch the script from &lt;code&gt;cmd.exe&lt;/code&gt; to read all error output (if any) or by
double-clicking if you're confident it works on your system.&lt;/p&gt;
&lt;p&gt;After you're done experimenting and are ready to discard the venv, just end shell
session with &lt;code&gt;exit&lt;/code&gt; - the script will take care of cleanup.&lt;/p&gt;
&lt;p&gt;If you close the
terminal window without typing &lt;code&gt;exit&lt;/code&gt;, the script will be terminated before it
performs cleanup. This has no harmful consequences except taking 20-50MB of disk
space. Old venv directory will be purged before reusing, so no changes you've
made will affect the environment you'll get next time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; If you have no internet connection, the script remains usable, but &lt;code&gt;pip&lt;/code&gt;
will print a lot of error messages while trying to update itself. Don't worry, that's ok.&lt;/p&gt;</content><category term="posts"></category><category term="windows"></category><category term="script"></category><category term="gist"></category></entry><entry><title>Execute the same git subcommand in all local repositories</title><link href="https://potyarkin.com/posts/2017/execute-the-same-git-subcommand-in-all-local-repositories/" rel="alternate"></link><published>2017-10-05T15:40:00+03:00</published><updated>2017-10-05T15:40:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2017-10-05:/posts/2017/execute-the-same-git-subcommand-in-all-local-repositories/</id><summary type="html">&lt;p&gt;If you work with more than one git project simultaneously, you often need to
do the same maintenance tasks in each cloned repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;check if there are some changes waiting to be pushed,&lt;/li&gt;
&lt;li&gt;check remote URLs for all repos (e.g. when considering to switch from HTTPS
  authentication with GitHub …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;If you work with more than one git project simultaneously, you often need to
do the same maintenance tasks in each cloned repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;check if there are some changes waiting to be pushed,&lt;/li&gt;
&lt;li&gt;check remote URLs for all repos (e.g. when considering to switch from HTTPS
  authentication with GitHub to using SSH keys),&lt;/li&gt;
&lt;li&gt;view last commit messages to refresh your memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Doing so with standard tools would involve a lot of &lt;code&gt;cd&lt;/code&gt;-ing, and the
inconvenience would deter you from checking all repos frequently.&lt;/p&gt;
&lt;p&gt;That's why I wrote a simple bash script that helps to &lt;em&gt;automate the boring
stuff&lt;/em&gt;. The script is well-documented, so I won't discuss implementation
details here.&lt;/p&gt;
&lt;h2 id="git-projectssh"&gt;&lt;a class="toclink" href="#git-projectssh"&gt;git-projects.sh&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can download the script from
&lt;a href="https://gist.github.com/sio/227da259cad7bb549c69909ba428884c"&gt;https://gist.github.com/sio/...&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The code is licensed under a permissive opensource license (Apache License,
Version 2.0) so feel free to use it for your hobby and work projects.&lt;/p&gt;
&lt;p&gt;Report any bugs, ideas, feature requests via GitHub issues/comments -
all feedback is welcome!&lt;/p&gt;
&lt;h2 id="installation"&gt;&lt;a class="toclink" href="#installation"&gt;Installation&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Download the script from GitHub, add execution permissions&lt;/li&gt;
&lt;li&gt;List the paths to the local clones of your git repos in a text
  file (one path per line). If you're using relative paths they must
  be valid relative to the location of the script&lt;/li&gt;
&lt;li&gt;Update the value of &lt;code&gt;PROJECT_LIST&lt;/code&gt; variable with the path of the file
  you've just created&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="usage"&gt;&lt;a class="toclink" href="#usage"&gt;Usage&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All command-line parameters are passed on to the &lt;code&gt;git&lt;/code&gt; command.
When the script is launched without parameters, &lt;code&gt;git-projects.sh&lt;/code&gt; checks the
status of each repo.&lt;/p&gt;
&lt;p&gt;Repositories are processed in alphabetical order sorted by paths
listed in &lt;code&gt;PROJECT_LIST&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="examples"&gt;&lt;a class="toclink" href="#examples"&gt;Examples&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="refreshing-your-memory"&gt;&lt;a class="toclink" href="#refreshing-your-memory"&gt;Refreshing your memory&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./git-projects.sh&lt;span class="w"&gt; &lt;/span&gt;log&lt;span class="w"&gt; &lt;/span&gt;--oneline&lt;span class="w"&gt; &lt;/span&gt;-3&lt;span class="w"&gt; &lt;/span&gt;--no-decorate

HomeLibraryCatalog
b5808f6&lt;span class="w"&gt; &lt;/span&gt;Always&lt;span class="w"&gt; &lt;/span&gt;check&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;db&lt;span class="w"&gt; &lt;/span&gt;before&lt;span class="w"&gt; &lt;/span&gt;showing&lt;span class="w"&gt; &lt;/span&gt;first&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;page
72d2481&lt;span class="w"&gt; &lt;/span&gt;Remove&lt;span class="w"&gt; &lt;/span&gt;/quit&lt;span class="w"&gt; &lt;/span&gt;route
75c707b&lt;span class="w"&gt; &lt;/span&gt;Clean&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;destructors&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;WebUI&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;CatalogueDB

OpenShiftApp
b260276&lt;span class="w"&gt; &lt;/span&gt;Deploy&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;GitHub
05e0206&lt;span class="w"&gt; &lt;/span&gt;Deploy&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;GitHub
54e5cf1&lt;span class="w"&gt; &lt;/span&gt;Deploy&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;GitHub

server_common
bc33836&lt;span class="w"&gt; &lt;/span&gt;Indentation&lt;span class="w"&gt; &lt;/span&gt;rule&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;Makefiles
72fb92a&lt;span class="w"&gt; &lt;/span&gt;Use&lt;span class="w"&gt; &lt;/span&gt;proper&lt;span class="w"&gt; &lt;/span&gt;syntax&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;TODO&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;GitHub&lt;span class="w"&gt; &lt;/span&gt;Flavored&lt;span class="w"&gt; &lt;/span&gt;Markdown
a24e4f2&lt;span class="w"&gt; &lt;/span&gt;More&lt;span class="w"&gt; &lt;/span&gt;familiar&lt;span class="w"&gt; &lt;/span&gt;Home&lt;span class="w"&gt; &lt;/span&gt;and&lt;span class="w"&gt; &lt;/span&gt;Backspace&lt;span class="w"&gt; &lt;/span&gt;behavior
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="view-latest-tag-if-any"&gt;&lt;a class="toclink" href="#view-latest-tag-if-any"&gt;View latest tag (if any)&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./git-projects.sh&lt;span class="w"&gt; &lt;/span&gt;describe&lt;span class="w"&gt; &lt;/span&gt;--tags&lt;span class="w"&gt; &lt;/span&gt;--always

HomeLibraryCatalog
v0.1.0-71-gb5808f6

OpenShiftApp
b260276

server_common
bc33836
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="checking-project-status"&gt;&lt;a class="toclink" href="#checking-project-status"&gt;Checking project status&lt;/a&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;$&lt;span class="w"&gt; &lt;/span&gt;./git-projects.sh

HomeLibraryCatalog
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;master
Your&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;up-to-date&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;origin/master&amp;#39;&lt;/span&gt;.

nothing&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;commit,&lt;span class="w"&gt; &lt;/span&gt;working&lt;span class="w"&gt; &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;clean

OpenShiftApp
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;master
Your&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;up-to-date&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;origin/master&amp;#39;&lt;/span&gt;.

nothing&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;commit,&lt;span class="w"&gt; &lt;/span&gt;working&lt;span class="w"&gt; &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;clean

server_common
On&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;master
Your&lt;span class="w"&gt; &lt;/span&gt;branch&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;up-to-date&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;origin/master&amp;#39;&lt;/span&gt;.

nothing&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;commit,&lt;span class="w"&gt; &lt;/span&gt;working&lt;span class="w"&gt; &lt;/span&gt;tree&lt;span class="w"&gt; &lt;/span&gt;clean
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="posts"></category><category term="bash"></category><category term="script"></category><category term="gist"></category></entry><entry><title>Portable development setup for Python on Windows</title><link href="https://potyarkin.com/posts/2017/portable-development-setup-for-python-on-windows/" rel="alternate"></link><published>2017-09-20T00:00:00+03:00</published><updated>2017-09-20T00:00:00+03:00</updated><author><name>Vitaly Potyarkin</name></author><id>tag:potyarkin.com,2017-09-20:/posts/2017/portable-development-setup-for-python-on-windows/</id><summary type="html">&lt;h2 id="winpython"&gt;&lt;a class="toclink" href="#winpython"&gt;WinPython&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://winpython.github.io/"&gt;https://winpython.github.io/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All-in-one distribution which comes with many difficult-to-build packages
preinstalled. And their ...-Zero version is great for thumb drives!&lt;/p&gt;
&lt;p&gt;Pip works just fine, but installing packages that require C compiler is
always a pain on Windows. May be I should look into conda and see if …&lt;/p&gt;</summary><content type="html">&lt;h2 id="winpython"&gt;&lt;a class="toclink" href="#winpython"&gt;WinPython&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://winpython.github.io/"&gt;https://winpython.github.io/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All-in-one distribution which comes with many difficult-to-build packages
preinstalled. And their ...-Zero version is great for thumb drives!&lt;/p&gt;
&lt;p&gt;Pip works just fine, but installing packages that require C compiler is
always a pain on Windows. May be I should look into conda and see if it
offers a portable variant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; there are unofficial binary wheels for most common Python packages
at &lt;a href="http://www.lfd.uci.edu/~gohlke/pythonlibs/"&gt;http://www.lfd.uci.edu/~gohlke/pythonlibs/&lt;/a&gt; The site's hosting is a little
unreliable, so it might take a few trys to fetch a package.&lt;/p&gt;
&lt;h2 id="git-portable"&gt;&lt;a class="toclink" href="#git-portable"&gt;Git Portable&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://git-scm.com/download/win"&gt;https://git-scm.com/download/win&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Git for Windows is now recommended by official Git website, and there always
is a portable version.&lt;/p&gt;
&lt;p&gt;This package provides not only Git but also bash and a basic MSYS environment
(coreutils, sed, grep, awk, etc) which make life on Windows &lt;em&gt;so much&lt;/em&gt; easier!
Also, it comes with VIM preinstalled, which is a damn good editor and is
preferred by many developers.&lt;/p&gt;
&lt;h2 id="gnu-make"&gt;&lt;a class="toclink" href="#gnu-make"&gt;GNU Make&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="http://www.equation.com/servlet/equation.cmd?fa=make"&gt;http://www.equation.com/servlet/equation.cmd?fa=make&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately Git for Windows does not come with GNU make preinstalled, so
we have to download it manually. Great guys at Equation Solution are regularly
building standalone versions of GNU Make for 32-bit and 64-bit Windows.&lt;/p&gt;
&lt;p&gt;Downloaded file has to be placed somewhere in PATH.&lt;/p&gt;
&lt;h2 id="github-with-ssh-keys"&gt;&lt;a class="toclink" href="#github-with-ssh-keys"&gt;GitHub with SSH keys&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://help.github.com/articles/connecting-to-github-with-ssh/"&gt;https://help.github.com/articles/connecting-to-github-with-ssh/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I don't know if it is even possible to setup HTTPS authentication without
installing GitHub Desktop, and SSH key authentication works with GitHub
same as everywhere.&lt;/p&gt;
&lt;p&gt;I keep the keys on my laptop and the rest of the environment is on a thumb
drive. That way I can develop anywhere I want, Windows comes as a given (sadly),
and I don't have to worry about keys security, because they are not exposed
to random computers.&lt;/p&gt;
&lt;p&gt;Official documentation recommends using HTTPS just because it's easier for
newcomers (&lt;a href="https://stackoverflow.com/questions/11041729"&gt;https://stackoverflow.com/questions/11041729&lt;/a&gt;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It does not require generating public/private keys and uploading the correct
  one to GitHub&lt;/li&gt;
&lt;li&gt;HTTPS is allowed everywhere and SSH might be blocked by a firewall&lt;/li&gt;
&lt;/ul&gt;</content><category term="posts"></category><category term="python"></category><category term="windows"></category><category term="bookmarks"></category></entry></feed>