If you work with Python on macOS and are trying to let your kids play with things like turtle
you will encounter errors such as
>>>importturtleTraceback(mostrecentcalllast):File"<stdin>",line1,in<module>File"~/.pyenv/versions/3.7.4/lib/python3.7/turtle.py",line107,in<module>importtkinterasTKFile"~/.pyenv/versions/3.7.4/lib/python3.7/tkinter/__init__.py",line36,in<module>import_tkinter# If this fails your Python may not be configured for TkModuleNotFoundError:Nomodulenamed'_tkinter'
If you use pyenv and brew there’s a simple way to fix it:
brew install tcl-tk
brew install pyenv # skip if you already have pyenv installedexportPATH="/usr/local/opt/tcl-tk/bin:$PATH"exportLDFLAGS="-L/usr/local/opt/tcl-tk/lib"exportCPPFLAGS="-I/usr/local/opt/tcl-tk/include"exportPKG_CONFIG_PATH="/usr/local/opt/tcl-tk/lib/pkgconfig"exportPYTHON_CONFIGURE_OPTS="--with-tcltk-includes='-I$(brew --prefix tcl-tk)/include' \
--with-tcltk-libs='-L$(brew --prefix tcl-tk)/lib -ltcl8.6 -ltk8.6'"pyenv uninstall 3.8.2 # substitute here the version you're using or skip if you were not using pyenvpyenv install $(pyenv install --list | grep -v - | grep -v b | tail -1)
Since having changed my main business title on LinkedIn away from Shoe Designer, I got my good
share of recruiters contacting me whether I’d be interested in the best data scientists and
engineers I’ve ever encountered.
At GoDataDriven we’re always hiring so my standard answer was — initially — “Yes of course, send
the profile over”.
However what happened next was always more or less the following:
I get a bunch of profiles in my inbox;
As everyonewantstobe a data scientist or data engineer, the
profiles are chock full of buzzwords and it is really hard to see if someone if the real deal or
not;
After having lost north of 10 minutes per CV, googling various company names, technologies, and
institutions, I made a list of people I wanted to talk to. Maybe 1 to 2 people every 20;
Talking to these people took at least 45’, probably 1 hour including planning (I didn’t have
somebody to manage my agenda);
During this chat, I often found out glaring flaws that the recruiters should have caught, such as
unwillingness to relocate, very different salary expectations, CV not being 100% honest, etc.
This means that every time a single recruiter would send me 20 CVs, I would lose 4-5 hours,
scattered among multiple days. If I include a generous context-switching time lost of 2-3 hours,
that meant that every recruiter interaction meant a whole day would be wasted.
I should add to this the frustration of not winning: I did not hire a single person in months. And
it wasn’t for lack of clarity on my part. I onboarded each recruiter with a 30’ call explaining in
details what kind of colleagues we were looking for.
Certainly something had to change. I introduced a recruiter “policy”. The policy was very simple
and I would send it as soon as someone contacted me:
You can send a maximum of 4 CVs, all at the same time;
If we don’t hire anyone from this first batch, we terminate our relationship.
My inspiration was something that I believe Atlassian published1. I loved this policy because
it shifted most of the work from my back to the recruiters’ back2.
The full day of work was reduced to 2 hours in total3 — if I didn’t hire anyone: otherwise they
could send me all the CVs they wanted.
Most interestingly, lots of recruiters stopped before sending me a single CV: a strong indicator
that they didn’t want to do their job and rather wanted to continue their volume game with some
other fool.
How many recruiter agency did we end up working with? Just one, comprised of a single person. We
love him, and kept using his service until we hired our first internal recruiter.
So next time you think you’re overwhelmed by the amount of CVs recruiters send your way, try
shifting the work
I cannot find the page anymore, so I am not 100% sure anymore that it was Atlassian. ↩︎
You’d expect this to be obvious, but believe me it’s not. ↩︎
30’ recruiter onboarding, 20’ CV scanning (as they were of higher quality), 45’ to talk to usually a single person, and some 30’ of overhead. ↩︎
On Wednesday July the 10th, the Dutch website NOS broke the news that people employed by or
through Google, listen to thousands of conversations Dutch people are holding in the proximity of
Google Home devices (that’s the reason I keep the microphone of my Sonos turned off).
In our company Slack, I reacted stealing John Gruber’s words about a very different topic:
Today HBR published an article about some causes of burnout1. One struck a cord with me, and,
as a physicist that went more into the managerial path, I’m sure I’m not the only one:
I think they’re all tightly coupled: if you’re good at planning, you must have prioritized
properly by knowing what you can and cannot accomplish with your time, and if you have prioritized
you must say no and you must have delegated tasks. If you’re good at planning, you also can’t be a
perfectionist, because perfection is difficult to plan.
I struggle with three of them mostly: delegating tasks, letting go of perfection, and saying no.
Delegating tasks is hard because I can’t let go of perfection, and because I am usually not good at
communicating the end result. And I am not good at communicating the end result because I delegate
too little: if I were to delegate more, I would learn — from all the times it went wrong — what
things are important to communicate.
Since I know that, I also know that the first times I delegate, the end result will not be what I
want: again, I can’t let go of perfection.
Luckily I’m learning the hard way that I need to let go quickly in these key areas:
Before my last holiday I was real close to losing it, and I felt it and it scared me;
As the line of business I am running grew, I let potential opportunities slide, as I didn’t have
time.
So, right before the summer, I tricked myself into start delegating. Two things helped
me out:
My daughter was going to be born (she’s arrived yesterday), so if I wanted to enjoy time with
her, I had to have my hands free from work;
I said to myself that delegating didn’t mean recognizing that somebody else was better than me at
doing a task, in absolute term2 and that I couldn’t do the job just as well: I said to myself
that other people had either more time, or more focus, or better tools, or more experience in
doing it. In other words, I could do it myself, but it was not efficient.
So here I am now, with time in my hands to write this post :)
Six according to the Areas of Worklife model, but I’m sure there’s more, depending who you ask. ↩︎
Brent Simmons doesn’t mince words when he talks about algorithms to drive engagement, honed and
“abused” by companies such as Facebook and Twitter:
My hypothesis: these algorithms — driven by the all-consuming need for engagement in order to
sell ads — are part of what’s destroying western liberal democracy, and my app will not
contribute to that.
I forgot to link to this very good article from David. Having almost 6 kids, I am usually not
bothered by noise outside my head, but by noise inside my head.
Noise inside my head comes mostly from not having a long or well defined task. These are the kind
of tasks that tends to come in through Slack1.
To fix it I offload most of these tasks — before they reach me — to people who are
better at handling them.
Companies that are business-savvy about the hidden costs of interruptions should know that they
should be penny foolish and pound wise on this one.
Instant messaging in general: I have nothing against the company besides creating a Mac application that loves to eat all my resources and that doesn’t feel Mac-like. ↩︎
A couple of days ago I moved the blog and the website over to Netlify.
The reasons are simple:
The site was previously hosted on S3 + Cloudfront, but I didn’t have https enabled;
I didn’t know how to enable https although I must not be too hard;
The application I was using to deploy to S3 — called Stout — was unmaintained and growing old.
Every time I’ve read comments on Netlify the message was the same: it’s easy to set up, and once
you’ve set it up, you can forget about it.
I gave myself 5’ to try: if I could do it, good, otherwise I would stay on the current setup.
Well, not only I could do it, but the whole project was undistinguishable from magic. They took
care of everything for blog.lanzani.nl and lanzani.nl, including serving the naked domain
(previously it would be forwarded to www.lanzani.nl, something that always bothered me).
As they integrate with GitHub and hugo, I don’t even need to build the website anymore, they do
it for me every time I push the repo!
So the end result is that you can read this blog without fearing that someone has tampered with the
content!
This is another post that is totally a note for my future self.
I don’t write on this blog often. But what I do, a lot, is read what other people write on their
blog. I do that through the wonderful capabilities of RSS.
Doing so in a sane manner involves a few moving parts:
One or multiple feeds you want to read. This is the easy part;
A server that keeps track of them
The server should provide a good interface to read on the web;
Bonus points if the server also provides an API so that I can use apps to read the articles.
Up until a couple of weeks ago I was using a simple pair: Stringer, hosted on a spare GCP
machine, and Unread on iOS. Stringer offers a nice reading experience on the web, so I didn’t
need an app for my Mac.
However, as the spare machine wasn’t spare anymore I started looking for something else as
I did not like the fact that Stringer was an unmaintained Ruby app anymore. I have nothing against
Ruby, but the fact that the app was unmaintained meant running a potentially insecure application.
There are many RSS readers as a service since Google Reader shut down:
Feedbin
Newsblur
Bazqux
The Old Reader
And many more.
The only “problem” is that these services cost from approximately $2 to $5 a month. Can I do
something for free?
At first I thought about running stringer on one of my Raspberry Pis. They are pretty powerful and
I don’t have that many feeds I need to read.
But if I do that, I possibly want to have everything working in a semi-automatic fashion, so that
there’s little to no manual work if the SD is my Raspberry Pi goes south.
The easiest solution — for single machine scenarios and where seconds of downtime are OK — is to
use Docker with docker-compose.
This is where, however, Ruby and the Ruby version stringer uses (2.3.3) are painful:
There were no official Ruby 2.3.3 images for ARM (that I could find);
Updating to the latest 2.3 version (2.3.8 at the time of this writing) would trigger some bug
that I was not able to fix;
Updating to 2.3.4 would have everything working, but
The image for stringer using Ruby 2.3.4 is 857MB: not exactly small;
As I would need to customize the stringer docker image heavily (to make it compatible with 2.3.4,
plus some other small details), that would mean building the images from time to time when new
security updates get pushed (if they ever do);
Doing the above is doable with one of the many CI/CD services out there (for example Azure
Pipelines) but since the build needs to be for ARM on an X86 server, extra care and
configuration is needed (buildx is not generally available yet).
If you’re a bit like me, the above feels like a chore and change of many headaches (that’s probably
why all those RSS as a service services exist in the first place).
So I turned to Reddit to see what others are doing. While searching here
and there, I came across a thread where they mention miniflux.
When I looked at the website, I couldn’t believe it: it has everything I need and then some more:
Easy to get started with;
Provides ARM images out of the box;
Written in Go and hence very small to host (compared to the 857MB of stringer, miniflux docker
image is 17MB, 50x smaller);
Written in Go and hence faster than non-optimized Ruby (and it certainly feels a lot faster than stringer);
Like stringer, it implements the Fever API, meaning I can use it with Unread.
Requirements
Now that I have settled down on the server, what else do I need?
I already said that I want Docker support;
Possibly everything should be scriptable, for 99% of the code (I am OK running a couple of
scripts manually);
I want https: if I’m entering a password it should be secure;
I (ideally) want the latest security patches quickly, without much maintenance;
It should be easy.
The solution
After a bit of googling, I’ve come up with the following folder structure and files to serve my
needs:
ngix is also pretty simple: we add a couple of touches: we mount the configuration folder, we
mount the letsencrypt certificates, and the www folder to put the verification for letsencrypt;
watchtower is a docker image that looks (in my case every 604800 seconds, every week) that
all the images I’m using are up to date. If not, the image will be updated. This is especially
relevant for nginx and miniflux which are the containers facing the users.
For nginx the app.conf file is needed. Its content is
There’s not much to explain here. The last snippet is the init-letsencrypt.sh. The script
“bootstraps” nginx for the first time: since we want https, but we cannot have it without
certificates, but we cannot ask certificates without a running nginx, this script creates fake
certificates, start nginx, removes the certificates, and then request real ones through
letsencrypt. The content is quite long, but here you go:
#!/bin/bash
if ! [ -x "$(command -v docker-compose)"];thenecho'Error: docker-compose is not installed.' >&2exit1fidomains=(<my_domain>)rsa_key_size=4096data_path="./data/certbot"email=""# Adding a valid address is strongly recommendedstaging=0# Set to 1 if you're testing your setup to avoid hitting request limitsif[ -d "$data_path"];thenread -p "Existing data found for $domains. Continue and replace existing certificate? (y/N) " decision
if["$decision" !="Y"]&&["$decision" !="y"];thenexitfifiif[ ! -e "$data_path/conf/options-ssl-nginx.conf"]||[ ! -e "$data_path/conf/ssl-dhparams.pem"];thenecho"### Downloading recommended TLS parameters ..." mkdir -p "$data_path/conf" curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/options-ssl-nginx.conf > "$data_path/conf/options-ssl-nginx.conf" curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/ssl-dhparams.pem > "$data_path/conf/ssl-dhparams.pem"echofiecho"### Creating dummy certificate for $domains ..."path="$data_path/conf/live/$domains"mkdir -p "$data_path/conf/live/$domains"openssl req -x509 -nodes -newkey rsa:1024 -days 1\
-keyout $path/privkey.pem \
-out $path/fullchain.pem \
-subj '/CN=localhost'echoecho"### Starting nginx ..."docker-compose up --force-recreate -d nginx
echoecho"### Deleting dummy certificate for $domains ..."docker-compose run --rm --entrypoint "\
rm -Rf /etc/letsencrypt/live/$domains && \
rm -Rf /etc/letsencrypt/archive/$domains && \
rm -Rf /etc/letsencrypt/renewal/$domains.conf" certbot
echoecho"### Requesting Let's Encrypt certificate for $domains ..."#Join $domains to -d argsdomain_args=""for domain in "${domains[@]}";dodomain_args="$domain_args -d $domain"done# Select appropriate email argcase"$email" in
"")email_arg="--register-unsafely-without-email";; *)email_arg="--email $email";;esac# Enable staging mode if neededif[$staging !="0"];thenstaging_arg="--staging";fidocker-compose run --rm --entrypoint "\
certbot certonly --webroot -w /var/www/certbot \
$staging_arg \
$email_arg \
$domain_args \
--rsa-key-size $rsa_key_size \
--agree-tos \
--force-renewal" certbot
echoecho"### Reloading nginx ..."docker-compose exec nginx nginx -s reload
For this and for app.conf file I took inspiration from the nginx-certbot repository with some
modification: I’m using rpi-certbot instead of certbot and the openssl utility that comes
with the Raspberry (if it doesn’t, use sudo apt-get install openssl to get it).
Outside the Raspberry
The outside world need to know where to find your Raspberry and should be able to get there. Doing
so is outside the scope of this post, but in general
Find your external IP address (and hope it’s static or use a service such as Dyn)
Update the DNS of your (sub)domain (For example reader.lanzani.nl)
Assign a static DNS to your Raspberry from your router
Route port 80 and 443 in your router so that the traffic is handled by the Raspberry (port
80 is necessary for the letsencrypt verification).
Start everything
Once all these files are in place, you are in the right folder, and you have updated the various
variables marked with <> (passwords and domain name) in the files above you can get rolling with
curl -sSL https://get.docker.com | sh
pip install --user docker-compose
bash init-letsencrypt.sh
docker-compose up
Now visit your (sub)domain, use admin as the user and the password you have chosen to log in.
Enjoy the rest!
Sometimes when working with git I find myself having to create a patch, because I had to merge
in my feature branch more than once, but I want to have a single commit when doing a PR.
Assuming I want to merge against master, and my branch is called feature, I can do the following
git checkout feature
git merge master
git diff master..feature > patch.diff
git checkout master # the new branch should stem from master
git checkout -b feature-patch # need a different name
git apply --ignore-space-change --ignore-whitespace patch.diff
git add . # assuming it's a clear working directory, besides the branch
git commit -m "Add reassuring commit message here"
git push -f origin feature-patch:feature # this will push feature-patch on the feature branch
It seems involved, but once you get the hang of it, it’s pretty fast.