Generate EPC QR codes

In the past weeks I recognized that I got more invoices with a EPC QR code to automatically fill out the fields for a wire transfer. The system is very convenient and feels better than typing everything. Obviously you still have to look if the inserted values are correct.

The EPC (European Payments Council) QR code is only a specified string with a few newlines. And of course there is a Python package (python-qrcode) that has everything needed.

The example from the EPC QR code Wikipedia page using Python and writing a png version of the qr code:

import qrcode

text = """BCD
001
1
SCT
BPOTBEB1
Red Cross of Belgium
BE72000000001616
EUR1
CHAR

Urgency fund
Sample EPC QR code"""

img = qrcode.make(text)
img.save("qrcode-redcross.png")

Save the code into run.py and run with uv: uvx --with "qrcode[pil]" run.py. I tested the resulting QR Code and it worked as expected with the Android app from my bank.

There are multiple pages with more detailed specs linked in the Wikipedia article. From the ones I looked at this Austian one (there is a German and an English version) seems complete enough to understand the possible values for each line.

Generating EPC QR codes feels solved. So why are there (European) invoices without such a QR code?

Revisit OpenStreetMap login to Datasette

About 2 years ago I experimented with OAuth2 login for Openstreetmap and wrote an incomplete blog post about it. It is time to revisit the now public repo and fix the demo deployment.

First the demo. I deleted all my Google Cloud deployments a while ago, so the demo is not running anymore. Having a demo feels essential, so I moved the demo to Fly.io and added a private database example to it.

Two things I learned while doing this:

For Fly.io it is actually important to add --setting force_https_urls 1 to the Datasette call. OpenStreetmap OAuth (and probably others too) is very picky about using the same url that you configured in the registration of your app. I used Tailscale Funnel to experiment before deployment and there everything worked without forcing https. The proxy from Fly.io seems to loose the https information -- which is a bit annoying.

The second thing I learned is, that the id: "*" in the Datasette documentation only works when you named your actor field like that. I named the actor field id_osm, so it needs to be used that way for permissions.

The repository for datasette-auth-osm was private for 2 years, because I never finished it up. There are still things missing: automatic publishing to pypi and maybe one integration test. But this is still better than nothing.

The demo is now at https://datasette-auth-osm-demo.fly.dev/ and should show a private database when logged in with OpenStreetmap.

Links to internal services

I added to my Forgejo instance a Tailscale sidecar to get a hostname and described it in a previous blog post. Doing the same for all services running on my Raspberry PIs seems a bit excessive and not needed. There is an alternative I wanted to try: golink from Tailscale. Golink adds a local redirect service that can redirect to the correct url including the port the service is running. Parameters can be used too, as described in the announcement post from Tailscale. You can see them in the help page after you deployed yours: http://go/.help.

The compose.yml to run golink:

services:
  golink:
    container_name: golink
    restart: unless-stopped
    image: ghcr.io/tailscale/golink:main
    volumes:
      - './data:/home/nonroot'
    environment:
      - TS_AUTHKEY=tskey-auth-xxxxx

The data folder needs to be owned by userid 65532. So I created the data folder like this:

mkdir data
sudo chown 65532 data

The TS_AUTHKEY in the compose.yml is only needed for the first start and can be removed after. The secrets are stored in the data folder together with an SQLite database containing the links and the click counts. For me the first start had a hickup generating the https certificate, but on the second start everything worked.

Entering http://go/ redirects to the fully qualified domain with https. So http://go/immich redirects for me to http://pi4c:2283/

I backup the data by creating an export and store the jsonl in a folder that is part of my automatic backup. The download happens like this:

curl -s https://go.tail07efb.ts.net/.export > backup.me/golink.jsonl

I need to train a bit to use golink more. But overall this is a very cool link shortener for stuff I that I don't want to remember, i.e. port numbers. For me golink has even a few features too many, i.e. click counts. I don't want them tracked so I added to my backup cron this call to remove the clicks from the database:

sudo sqlite3 ~/golink/data/golink.db "DELETE FROM Stats;"

The clicks are tracked in memory when the container is running, so this only is updated when restarting the container. We need sudo here because the database belongs to user 65532 as described above. Good enough for me for now.