Trying something new: a blog post for every day between now and
Christmas. They will necessarily be a bit shorter and even less
polished than the usual ones.
Day 1: internet music streaming for people who'd rather own their music
than rent it.
I have ~ 200GB of files in /srv/media/music on my desktop PC, most
of it from a massive CD ripping exercise about ten years ago, and I'd
like to be able to listen to it from anywhere in the house (at
minimum) or on the internet (ideally). I accomplish this using
Airsonic - a "free, web-based media
streamer, providing ubiquitous access to your music", coupled with
Audinaut -
an Android app to "stream music from Subsonic-compatible servers."
Airsonic is in Nixpkgs and there's a Nixos module for it. Audinaut is
in the F-droid repo and apparently also in the Play Store
but you should note that it relies on its own stateful database of
some kind ( don't know what exactly, haven't dug into it) for
configuration, so you have to set that stuff up by hand outside of
Nix.
One caveat with Airsonic is, per the documentation:
Also note that Airsonic will organize your music according to how they are organized on your disk. Unlike many other music applications, Airsonic does not organize the music according to the tag information embedded in the files. (It does, however, also read the tags for presentation and search purposes.)
Consequently, it’s recommended that the music folders you add to Airsonic are organized in an “artist/album/song” manner. There are music managers, like MediaMonkey, that can help you achieve this.
My music collection didn't quite conform to this convention. The bulk
of it was in flac/artistname-albumname, a bunch of other albums were
in mp3/artist/album, and then the "long tail" included files in
Songbird, annabelle, very_old_mp3_player and from_usb_stick.
I straightened it all out using
beets which describes
itself as "the media library management system for
obsessive-compulsive music geeks." but also works very nicely as a
media library management system for entirely neurotypical (as far as I
know) computer geeks who have a significant collection of music just
because they've been buying CDs for a long time. I configured it
approximately thus:
and then cd /srv/media/music-old && beet import .. Do this inside of
tmux, because it takes ages and asks a lot of questions.
Tomorrow I'll talk about sniproxy which is how, with one static
IPv4 address but a bazillion IPv6 addresses but a mobile phone
network that doesn't support IPv6 (this is the UK. None of them do) I
can make my music server visible outside of the house.
Advent of Blog, day 2: publishing IPv6 services to the IPv4 net
One of the nice things about IPv6 is that with a reasonably competent
ISP (I would recommend Andrews & Arnold
who have so far proven to be far more than just reasonably competent)
for your home internet you can have squillions of internet
addresses and run public services from any box in your home
network. Servers under desks, or VMs or laptops or Raspberry Pis or
... - unlike the bad old days of IPv4 you don't have to decide on one
single machine to forward port 443 to and have everything running on
nginx "virtual hosts" on that one box.
As long as you're accessing them from the IPv6 internet, anyway. If
you're stuck with legacy internet (in my case, my mobile provider, and
when in some post-covid world they finally let me back into the
office, $WORK's wifi) this doesn't help and you have to go back to
forwarding ports. Here's a reasonably hassle-free way to do it though:
sniproxy.
[SNI Proxy] proxies incoming HTTP and TLS connections based
on the hostname contained in the initial request of the TCP
session. This enables HTTPS name-based virtual hosting to separate
backend servers without installing the private key on the proxy
machine.
How we do it:
pick one machine to be the proxy. Add a port forward in the router
so that $YOUREXTERNALIPV4ADDRESS:443 is forwarded to $THISMACHINE:8443
(that 8443 could be anything really)
set up DNS for each of your services such that they have an AAAA
record with their actual global IPv6 address, and an A record of
$YOUREXTERNALIPV4ADDRESS
configure sniproxy something like this (in configuration.nix or some
other file it imports):
What this says is: "listen on ipv4 port 8443, when you get a request
for a hostname ending in `telent.net`, forward to the same hostname,
port 443, but resolved using IPv6".
Job's a good'un and that wraps it up for today. If I don't think of a better idea in the next 23 hours, tomorrow's topic has no Nix in it at all and is all about MQTT-controlled Wifi Christmas lights.
a breadboard and some jumper wires to connect it all together
Method
Wiring a Neopixel strip to an Arduino is well-trodden
territory
. The capacitor smooths out the power, the resistor is in series with
the first LED in the strip, and some digital output pin on the Arduino
connects to the resistor.
However: this works only for 5v devices. The Wemos D1, although
behaving for many purposes like an Arduino, is a 3.3v device. So, we
need something to switch it up. The Adafruit page says "you must use
a logic level shifter such as a 74AHCT125 or 74HCT245." but Bitsbox didn't have any, so improvisation was needed.
After some web searching to actually understand what I was sticking
together, this is what I found: the 74 series of ICs are logic gates
(the AND/OR/NAND/NOT etc that we learned about in secondary school
electronics lessons). 74LS are TTL chips (so, power-hungry old
technology), 74HC are CMOS, and 74HCT is a special version of 74HC
with 74LS TTL-compatible inputs. Why is this interesting? TTL says
that logic 0 is 0-0.8V, and logic 1 is 2-5.5V, so the 3.3V emitted by
the D1 is nicely inside that range. CMOS says that the output voltage
for a logic 1 is the supply voltage. So, all we have to do is implement
the identity function using some 74HCT-family chip, and we will get a
3.3v to 5v convertor "for free". The simplest possible way to do this
with components that were in stock seemed to be to get a 74HCT04 -
also known as "six NOT gates in one IC" and wire two of them in
series. So, the MCU's digital output pin connects to a NOT gate, which connects
to another NOT gate, which connects to the resistor, which connects to the strip.
That's the hardware, pretty much.
For the software I wanted to use MQTT, mostly because I have used it
before and it's low-effort in the Arduino environment. One thing to be
aware of here is that the Arduino MQTT library may have a payload
size limit of 128 bytes (some sources say 256), which is clearly not
enough to send individual 8 bit RGB values for each of 150 LEDs in the
strip, so some kind of encoding/trade-off is needed. I settled on a
format that allows a small number of pixels to be defined precisely in
HSV format, and has the MCU interpolate the values of all the
intervening pixels. A cheap and cheesy Ruby script to send the packets
looks like this:
def send_strip(speed, positions)
IO.popen("mosquitto_pub -h hostname.lan -d --stdin-file --username username --pw password -t sensors/neoxmas", "w") do |io|
array = [speed] + positions
io.print array.pack("C*")
end
end
and the Arduino sketch to receive them and make the pixels light up is
in this gist here
I know I said there would probably be no Nix in this entry, but I
can't help myself. Nixpkgs support for Arduino includes only the stuff for actual Arduinos, not for other architectures that also use the IDE. To get something that will also work for ESP8266 and the like, you can do
nix-shell https://github.com/nix-community/nix-environments/archive/master.tar.gz -A arduino
It's not very declarative, but nothing I build on a breadboard is
reproducible anyway so ...
In unrelated news, I am deeply indebted to leo homestuck who kindly pointed out after reading yesterday's entry that I should have been more careful writing my regex in the sniproxy config. I should have been and now I have been and I will update the blog entry to reflect this, when it's not two minutes until tomorrow.
Day 4 of Advent of Blog and already I've run out of things to talk about.
Today at work I pushed a change that caused a production bug. I'd set
out (and succeeded) to fix a different production bug, but when I
pushed it into CI I got a complaint from Rubocop that I'd exceeded the
line count limit for module size and the limit for method size, so I
added another couple of changes to see if I could make it
shorter.
Which I did, but ... if you don't know Rails this explanation - indeed, most
of this blog post - will not make sense, and I apologise in advance. Sorry.
It was a controller. The original code had a before_action that
checked if the request method was HEAD and handled it specially by
sending a head response. I moved this to the beginning of the
handler. Two hours later, we're getting DoubleRenderError reports
in production. Reverting the commit fixed the error. I don't know the
root cause yet (this was on Friday afternoon) but I speculate that
there is a subclass of this controller class that overrides handle
to call super but not return immediately. Or something. When the
HEAD case was in a before_action this wasn't a problem because
Rails knows when a before_action has rendered, and skips the rest of
the response processing after that point.
So anyway. I screwed up by making an obviously-correct change that
wasn't, and because it is in my nature as a programmer to find some
reason it wasn't my fault (narrator: it was his fault) I'm directing
my ire at Rubocop - or at least, at my willingness to pay too much
attention to it.
I don’t dislike RuboCop. It’s well made, and a useful tool.
The problem starts when it is viewed not as a tool, but as a set of divine commandments. Thou shalt not make implicit nils explicit. Thou shalt not write long-form conditionals. Thus saith the RuboCop.
This is not necessarily a problem with RuboCop itself — it’s a problem with how people sometimes use RuboCop. One could argue that the defaults aren’t great, but they are not a problem until someone hooks them up to CI.
I feel link linters pull you towards a certain level of readability. Whether they pull you up or pull you down depends on where you started at.
While I'm here and thinking about Rubocop, and because I don't often talk Ruby in this blog, if you are a Ruby programmer and your team uses Rubocop, do yourselves a favour and change the default setting for Style/BlockDelimiters from line_count_based to semantic. Here is Avdi Grimm on the topic.
Short one, today. After all that writing about airsonic and sniproxy
the other day, I went to put some music on yesterday and found that it
didn't work any more.
A series of suppositions
I broke my network significantly last week by switching from GNOME to KDE
... Let's just take a moment to reflect on how utterly bonkers it is
that switching the desktop environment should break networking, but
GNOME apparently has NetworkManager as a dependency, or possibly vice versa ...
and in the course of fixing that, I seem to have enabled
systemd-networkd, which had changed the way that the machine was
getting an IPv6 address. Where I previously had
2001:nnn:nnn:nnn::ed9, now I have 2001:nnn:nnn:nnn::148 and
2001:nnn:nnn:nnn:xxxx:xxxx:xxxx:xxxx.
IPv6 address allocation is simultaneously much simpler and much more
complicated than IPv4 was, but the short form is: you can have a
global address via SLAAC or via DHCPv6 or both. Addresses from SLAAC
are 64 bits of prefix (here, 2001:nnn:nnn:nnn) plus 64 bits derived
from your 48 bit MAC address. Addresses from DHCP are the prefix plus
whatever the DHCP server gives you when you present it with a DUID, so
I'm going to assume that these ::148 and ::ed9 are from DHCP - my MAC
address isn't full of zeroes - and that networkd is presenting a
different DUID than NetworkManager was.
There are, I guess, two possible routes to win here: find out how to
make networkd present the same DUID as NetworkManager did, or
reconfigure OpenWrt to accept the new one and give me the old
address. I'm going for the latter approach, so, let's login to the
router and use uci show dhcp to find the per-host setting
There are two things I now know to be wrong with this:
the duid in this configuration bears witness to my previous attempt
to fix this problem, but it doesn't actually match what is
presented. Although networkctl status vbridge0 on the client says
From DUID-EN/Vendor:0000ab11406665c3f2e752320000, the duid that
OpenWrt sees is 00020000ab11406665c3f2e75232. I don't know of
another way to check this than by looking in the Luci web interface,
in the "Active DHCPv6 Leases" section
the hostid is in the wrong format - I think OpenWrt doesn't like the colons
So we can fix them both:
uci set 'dhcp.@host[0].duid=00020000ab11406665c3f2e75232'
uci set 'dhcp.@host[0].hostid=0e9d'
/etc/init.d/odhcpd stop; /etc/init.d/odhcpd start
And down/up the interface on the client to make a new request (don't
use renew, it doesn't help here)
sudo networkctl down vbridge0 ;sleep 1; sudo networkctl up vbridge0
and now I have my local services accessible again. Yay me.
You may be wondering why I have vbridge0 as my primary interface on
that machine and not enp1 or en0s31f6 or en4s31f6z1q3zz9zzzα,
and the answer is: it's a bridge between the ethernet interface and
one or more tap devices for virtual hosts. I don't think anything I've
described here would be different for a hardware ethernet, but don't
quote me, I haven't tried it.
I haven't touched a computer all day today except to buy wood screws,
so this post is neither new now original.
I can't easily find any record of when it was I permanently switched
from X11 to Wayland: I know I've tried it a number of times without
success before eventually saying "screw it why don't I give in and use
GNOME", and mostly because my laptop has a HiDPI screen and
GNOME/Wayland seems (or at any rate at the time, seemed) to be the
only platform with an option for 1.5x scaling.
(Rant: this "scaling" thing is a nonsense. A not-stupid system would
define everything including window decorations and icons and the
like using a resolution-independent measure - like "points" - and have
no need for all this "actually this pixel is twice as big as a real
pixel" rubbish which has always been and will always be a
kludge. Bitmapped graphics in any sensible format already carry "x
resolution" and "y resolution" metadata: scale them instead of
scaling the entire universe they inhabit. End rant)
Anyway, I spent a subjectively long time with a nice crisp Wayland
Firefox window, a nice crisp Wayland terminal window, and an ugly
fuzzy scaled XWayland Emacs window, because Emacs - the released
version, at least - uses X11 for some stuff even when you tell it to
build with Gtk so it has to run in XWayland.
No more! https://github.com/masm11/emacs/ is a fork of Emacs to "pure
Gtk", which means it works as a native Wayland app. Build it with the
--with-pgtk configure flag. I've been using it for about six weeks,
and in my experience it has been about stable enough for daily use -
there have been a couple of times it's frozen with an eventual "not
responding" dialog, but both times when I waited for a few tens of
seconds it came back to life without my intervention.
One wrinkle you may want to be aware of if you use Magit (if you use
git and you don't use magit, I would heartily endorse it) is that
Emacs 28 changes the signature of server-switch-buffer meaning that
"the with-editor advice server-switch-buffer--with-editor-server-window-alist has too few arguments", meaning that you can't commit anything. Check the Github
issue for more
details: if like me you don't want to go around upgrading a bunch of
stuff to versions newer than those in NixOS Stable, the workaround is
to redefine server-switch-buffer--with-editor-server-window-alist in
your .emacs or somewhere
Today I have mostly been playing with AWS Lightsail containers, which
look like the result of saying to some AWS employees "hey, why don't
you build something user-friendly?". If you're familiar with AWS,
you'll know why this would be a bad suggestion. Sure, the web ui is pretty, but
oh my lord, the feedback cycles are stupid long ("no you can't abort
this failing deploy, you have to wait for it to timeout") and the
diagostics are awful. "Here's a window with some 'logs' in it. They
might be from this deploy or from a previous one, there is no way to
tell and no timestamps on the log lines".
Also I found out that there is some weird circumstance where the rails
server will fail to start and instead print out the rather obscure
message Switch to inspect mode, but I don't know what that
circumstance actually is. It went away when I added -b 0.0.0.0 to
the rails server invocation, but I don't see offhand how that could
have been the problem. Hey ho.
I'm not quite old enough to remember the days of punching your program
into a deck of cards and sending it to the operators to be read in,
but I imagine it must have been a bit like this.
No blog of substance today, sorry. The title is because I have also
been fighting with (and losing to) Cmake on Darwin
I was going to write about how I patched openssl on all my machines
with a five line fix and the minimum of fuss, but it turns out that I
spent all evening fighting letsencrypt instead. I think it is mostly
correct now.
I have to add: I had not fully realised when i set out on this that my simple overlay
This week is Hackathon
week at work, and we're
solving latency on the Internet so that people in different places can
play music together.
Well, not really. The speed of light is the speed of light whatever
you do, the speed of light in optical fibres is slightly lower, and
the speed of your packets through all the switches/routers/etc in the
path to your band members - that's not something we can solve in two
days. So what we're doing instead is adding latency : the idea
being that if the delay between you and your bandmembers is a multiple
of the time to play a measure, at least you can play in sync with what
they were playing a bar (or two bars or 8 bars) ago. There is software
that does this already: the most well-known is apparently
Ninjam , but we're using a fork of it
called Wahjam - mostly because
Wahjam has simple free-as-in-freedom clients whereas Ninjam steers you
quite firmly towards something complicated-looking (and probably $$$)
called Reaper.
Anyway, yes, basically it's been two days of compiling code qith Qt on
macOS and cargo-culting fixes by copy-paste from Stackoverflow, and I
won't deny it's kind of fun. My contribution to the intellectual
commons is a patch to allow an unsigned Wahjam binary to open the
microphone although I
freely admit I have very little idea if I've done it right.
It turns out to be due to this magic morsel in the nginx systemd unit
ProtectHome=true
Now I'm sure (actually, I'm not) that this is a reasonable default
behaviour for a daemon that perhaps should not be able to read users'
home files, but - I am struggling to avoid swearing in print here -
would it kill you to print an error message that has at least some
vague hint somewhere of what the error might be?
Pardon typos. It's too late for this shit, am going to bed now. Maybe
tomorrow I'll be able to do my upgrades and get back to hacking my
thermometer.
Maybe tomorrow I'll be able to do my upgrades and get back to hacking my thermometer.
Score 1/2. I got the upgrades done, I successfully rebuild the
Arduino program that sends temperature data to MQTT, now I need to
revive the thing on the server that listens to MQTT and logs to
Influx. The first step towards this end is run nix-shell and watch
it for some length of time (longer than it will take to post this blog
entry) as it rebuilds apparently the known Haskell universe.
In other news, bought some wifi-enabled
sockets so
we can control the Christmas lights without having to grope around
behind the TV. This information more relevant to UK readers than
anyone in the rest of the world - they are branded "TCPSmart", they're
available in Screwfix, and I am 95% sure they have
Tuya internals. At least, I successfully added
them to the Smart Life app on my phone. Relevant links I haven't tried yet:
Last winter I embarked on a project to replace my annoying wireless
thermostat with something less annoying: started out sticking together
MQTT and ESP8266
devices and
then found myself trying to decode the thermostat's radio
transmissions using
SDR and then
suddenly it was springtime and there was no need to heat the house
anyway.
I spent a little while today trying to put it back together, because
it's assuredly not springtime any longer. So far I have succeeded in
reflashing one of the ESP8266 devices with a slightly newer version of
the mqtt-sending software, and rebuilding the Haskell glue that
subscribes to all the sensor messages and writes them into InfluxDB.
Whinge of the day: you picked MQTT because it's a simple fast
low-overhead protocol ideally suited to MCUs, why are you[1] encoding the
payload as JSON objects? It's only one notch less verbose than XML,
for goodness sake.
[1] I wasn't quite doing this, but I was formatting floats to
strings to send them over the wire. The newer version multiplies the
temperature by 100 and sends a little-endian integer instead: I'm
sure in the grand scheme of things it makes sod-all difference to
anything but it makes me feel happier.
A very short entry today as it's already bedtime, but I would like to
note that I have followed all the Tuyapi instructions
and can now turn my Christmas tree lights on and off from the command
line.
First impressions: seems to work. There are occasional "Timeout
waiting for status response from device id" errors, which seem to
happen mostly if I try doing two `set` operations without an
intervening `get`. I don't yet know the technical details of how it
works, but I do find it interesting that (according to strace) it
seems to be talking directly to the device instead of to a cloud
server:
When I last stopped doing NixWRT it was after I had decided (perhaps
inadvisedly) to write a process/service monitor in Lua , and was getting
slightly fed up of debugging it on a (admittedly virtual) read-only
system that required a reboot to test anything.
I am not sure that rewriting the Lua in Fennel is going to make this
better, but I have converted the files using
Antifennel and will note
so far that
it has found a couple of bugs where I had inadvertantly introduced a global
fennel code is marginally less verbose (the line count is lower)
I haven't found a nice way to iterate through an array and return
the first element that matches a predicate (as far as I can tell, an
early return from an each is not possible) but I'm sure there must
be one
My hope is that the rather nicer syntax in Fennel will make it a bit
easier for a swarm service to "publish" the expected shape (schema,
loosely speaking) of its value, and this will make it easier to
validate that the services that depend on it are reading properties
that are actually likely to exist. Whether this is how it actually
works out remains to be seen.
The opposite of progress is regress, but I don't know if there's a
name for the neutral position, as it were. Nogress. This is a nogress
report, or at least a notmuchgress report.
Been through the autotranslated-to-Fennel Swarm library replacing many
occurrences of (set-forcibly! foo (fn [] ... with (lambda foo []
and - where I can see easy ways to do it - rewriting places it said
(lua "return v") with constructs that don't need an early return.
Right now it passes all its tests, which is both good news (it passes
tests!) and bad news (this demonstrates some truly humungous gaps in
the test coverage)
I'm going to attempt to explain Lua iterators, in the hope that by the
time I've finished writing I'll understand them. So.
There's a construct in Lua called for which is used for iterating
over a collection or other sequencey-type thing. You might write e.g.
> for k, v in pairs({a=2, b=5, c=9}) do print(k,v) end
a 2
b 5
c 9
or
> b=io.open("/etc/hosts", "r"); for l in b:lines() do print(l) end
127.0.0.1 localhost
::1 localhost
127.0.0.2 noetbook
::1 noetbook
Per the Lua manual, the syntax for the for is as follows:
for <var-list> in <exp-list> do
<body>
end
where <exp-list> is (between one and) three values - or something
that produces three values when evaluated. The first value is an
"iterator function" f, the second is "invariant state" state
and the third is the "control variable" a.
The interpreter then runs the loop: repeatedly, it
calls f(state, a)
binds the return values to <exp-list>
runs the loop body
updates the control variable a to be the first of the values returned by the previous call to f
until a becomes nil which signals the end of the iteration.
The builtin ipairs iterator, used for traversing "array" tables -
tables in which the indices are consecutive integers - provides an
iterator function which when provided with a table and an index
returns the next indes, and the value at that index
> gen,_,_ = pairs({5,6,7,8})
> =gen
function: 0x41c600
> gen({5,6,7,8}, nil) -- what's the first element?
1 5
> gen({5,6,7,8}, 3) -- what's after the third element?
4 8
The standard pairs iterator, used for traversing tables with
arbitrary keys, uses the builtin next function as an iterator
function. next works similarly to the iterator function in the
previous example: given a key and a table, it returns the next key
(for some value of "next" we aren't interested in the details of) and
the value at that key
> next({a=2, b=4}, "a")
b 4
These both depend on the for construct's behaviour of using the
first return value from each call to the iterator function as the
second argument to it on the subsequent call. Which is fine if we
want that value, but quite often we don't, so we end up doing this:
for _, v in ipairs(an_array) do
print(v)
end
If we want to write an iterator values_of that returns only the
value not the index, so could be used something more like this:
for v in values_of(an_array) do
print(v)
end
then the iterator function returned by values_of would be called each
time only with the value v, which would not be sufficient for it to work
out how far through the array it had got last time. Instead we have to
make our iterator close over the state it needs:
function valuesof(anarray)
local index = 0
return function()
index = index+1
return an_array[index]
end
end
> for v in values_of({7,3,5}) do print(v) end
7
3
5
I note that the io.lines function has similar behaviour to
values_of in that it returns only the next data value and not also
the index of that value. Assuming it uses the standard C library
functions for file input, I am guessing that it does this by reading
from the current file position. So it also has internal state, but
that state is implicit in the depths of stdio
Yes, but why?
Why did I start looking at this? Reasonable question. Because I wanted
to write an idiomatic find function in Fennel and thought it would
be a lot neater to have it work on any iterator, not just on
tables. So I I could say something like
but it does look messy having to accept and ignore that first
parameter, especially if I want to pass bog standard predicate
functions. I want to write something more like
>> (find even? (vals [ 1 2 3 4 5]))
and indeed this is possible, but only if I write the vals iterator
to close over its state instead of being able to use the parameters
that Lua passes into it when it's called.
Nothing, really, to say today, except that I have spent an hour or two
trying (and mostly, failing) to find the Fennel equivalent of
{table.unpack(tbl)}. Failing mostly because I accidentally had the
LuaJIT version of Lua on my path ahead of the Lua 5.3 that I thought I
was using, and as a result becoming more than mildly confused that
table.unpack appeared to be missing anyway and oh this makes no
sense what the hell is going on.
Having now tried it in a Lua 5.3 I discover that table.packis a
thing, wich is good, but that after all that the whole approach is
worthless because they only work on array-like tables in the first
place.
Some days are better than others, and today wasn't one of them.
But, to paraphrase Terry Pratchett, "don't arsk about Mrs Cake delivery to Google"
I have run my own mail servers for something around a decade (and run
mailservers for other people previous to that) and the longer I do it
the less convinced I am that it is worth the faff, considering I
receive about three pieces of non-spam non-list mail a week.
Anyway, some (but not all) of my email to Google domains is apparently
disappearing into a black hole; other mails are being greylisted and
yet others are turning up perfectly OK.
I still don't know why - it's google, there are no rules - but I did
find out that my DMARC record was wrong, which can't have been
helping. When I moved the service from Bytemark to Mythic Beasts it
looks like I forgot to transfer the private key, and NixOS generated a
on the new machine a new key pair that I have only now put the public
half of in DNS
:embarrassed_smiley:
One of the things that made yesterday's "why does google hate me" (I
suppose it is fair to say that the feeling is mutual) introspection so
frustrating was the sheer volume of crap that cascades through my
syslog systemd journal making it quite hard to see what's going
on. Most of it seems to be bots trying not-very-hard to look for open
SMTP relays, and a particularly tedious strain is the ones that try to
authenticate as
I really do literally mean UGFzc3dvcmQ6 there. A web search will confirm that I'm
not the only one to see this, and it's not the string
of random alphanumerics you might think it is on first glance:
$ echo -n Password: | base64
UGFzc3dvcmQ6
So I thought I might see about applying the banhammer, and as fail2ban
is included in NixOS, let's set ourselves up a rule. This required a
lot of futzing around in the "obvious in retrospect" space, so here is
what I did that eventually worked.
/etc/fail2ban/filter.d is full of filter rules (essentially, regexes plus boilerplate) - none of them are applicable to this quite specific use case, but we can add our own file to that directory.
the journalmatch entry is copied from /etc/fail2ban/filter.d/postfix.conf
you need enabled = true because otherwise it seems to default false.
Observationally, most of the hosts trying to login to my server with the password Password: seem to be the same ones trying to send mail in six other ways, so this rule cuts out a lot of all the kinds of postfix lognoise.
As a concept, this blog dates back to November 2001 - though I note now reviewing the first few entries, I was back then quite emphatic in my claim that it was not a blog. How times change.
In that time it has been implemented with
flat HTML files and a couple of elisp functions, from 2001
a Ruby script based on Soks, an early Ruby Wiki system, from ~ 2005? I think?
my-way - a different Ruby script that I wrote myself
yablog - a Clojure program which set out to be "a two-hour Tuesday morning hack" and apparently took a week
What's notable about Yablog is that it took a week in 2015 and has scarely been touched since, until yesterday, when I decided to add Markdown support. For the last 15 years (ever since I switched to Soks, bascially) I've been writing blog entries using Textile, and for the most recent ~ 10 of them, the blog entries are pretty much the only thing I've been writing in Textile, so this change is long overdue. I was getting a bit fed up of writing backticks and then publishing entries before remembering that Textile uses @ signs for that instead.
A year or so ago I suddenly realised that for the first time in a long time I no longer have a favourite programming language, but it's been apparent to me for a while before that that Clojure isn't it any longer anyway. That said, I must admit I'm quite happy that I can pick up a ~ 6 year old project, update the dependency versions and hack this in, all in the space of a couple of hours. I have trouble imagining that the same would be true in Ruby.
(Ruby isn't it either. But Ruby has never been it)
Shoutout to @yogthos@mastodon.social for markdown-clj which did all the hard work already.
2021 will be, I assert confidently, the year I get NixWRT running on my internet gateway at home. A short list of the yaks I need to shave to get there, which you will note is a lot more concrete at the front end than the back:
swarm: test harness for event system
swarm: a well-tested event system :-)
swarm: anything else that needs doing
see if swarm can implement the existing service abstraction (can it plugin to replace monit?), do whatever necessary if it can't
dnsmasq
routing and firewalling (and NAT for IPv4)
ntp
bonus points: a better story for firmware upgrades
bonus points: external "secrets" config so passwords aren't baked in