Jonathan McDowell: Christmas Movies

podman
command line is nearly identical to the docker
command line, excepts that podman
expects the fully qualified domain name associated with the container image, and I chose to run the freshrss container on the localhost interface only. I also use a defined version tag, because using the latest
tag makes it complicated to track which exact ersion I have installed.# podman pull docker.io/freshrss/freshrss:1.20.1
# podman run --detach --restart unless-stopped --log-opt max-size=10m \
--publish 127.0.0.1:8081:80 \
--env TZ=Europe/Paris \
--env 'CRON_MIN=1,31' \
--volume freshrss_data:/var/www/FreshRSS/data \
--volume freshrss_extensions:/var/www/FreshRSS/extensions \
--name freshrss \
docker.io/freshrss/freshrss:1.20.1
# podman volume ls
# podman volume inspect freshrss_data
# ls -l /var/lib/containers/storage/volumes/freshrss_data/_data/users/
echo '.tables' sqlite3 /var/lib/containers/storage/volumes/freshrss_data/_data/users/<your freshrss user>/db.sqlite
category entry entrytag entrytmp feed tag
Going with FreshRSS in Production
Podman has this very nice feature that it can generate a systemd unit from a running container, and use systemd to start a container on boot. This is in contrary to docker where the docker daemon does the stop/start of containers on boot.
I prefer the systemd approach as it treats containers the same way as other system services.
Once the freshrss container is running we can generate a systemd unit of it with:
# podman generate systemd --new --name freshrss tee /etc/systemd/system/container-freshrss.service
Let s stop the container we started previously, and use systemd to manage it:
# podman stop freshrss
# systemctl enable --now container-freshrss.service
We can verify that we have a listening socket on the localhost interface, on the source port 8081
# systemctl status container-freshrss.service
...
# ss --listening --numeric --process '( sport = 8081 )'
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 4096 127.0.0.1:8081 0.0.0.0:* users:(("conmon",pid=4464,fd=5))
Nota Bene: conmon (8)
is the process managing the network namespace in which fresh-rss is running, hence it is displayed as the process owning the listening socket
Exposing FreshRSS to the external world
We have now a running service, but we need to make it reachable from the internet.
The simplest, classical way, is to create a subdomain and a VirtualHost configured as a reverse proxy to access the service at 127.0.0.1:8081. Fortunately the FreshRSS authors have documented this setup in https://github.com/FreshRSS/FreshRSS/tree/edge/Docker#alternative-reverse-proxy-using-apache
and those steps are no different from a standard application behind a web reverse proxy.
Upgrading freshrss container to a newer version
A documentation showing how to install a piece of software is nothing when
it does not show how to upgrade that said software. Installing is easy, upgrading is where the challenge is. Fortunately to the good stateless design of freshrss (everything is in the sqlite database, which is backed by a non-epheremal volume in our setup), switchting versions is a peace of cake.
# podman pull docker.io/freshrss/freshrss:1.20.2
# systemctl stop container-freshrss.service
# sed -i 's,docker.io/freshrss/freshrss:1.20.1,docker.io/freshrss/freshrss:1.20.2,' /etc/systemd/system/container-freshrss.service
# systemctl daemon-reload
# systemctl start container-freshrss.service
If you need to rollback, you just need to revert version numbers in the instruction above.
Enjoy your own reader feed !
I will add the following feeds of blogs I like, let us see if I follow them better with a feed reader !
error
type, but Rust seems to have much more tightly typed errors. I was pointed at anyhow as the right way to do this in Rust. I still find this surprising; I ended up using unwrap()
a lot when I think with more generic error handling I could have used ?
.
The other thing I discovered is that by default rustc
is heavy on the debug output. I got significantly better results on some of the solutions with rustc -O -C target-cpu=native source.rs
. I probably shouldn t be surprised by this, but worth noting.
Rust, to me, has a syntax only a C++ programmer could love. I am not a C++ programmer. Coming from C I found Go to be a nice, simple syntax to learn. Rust has not been the same. There s a lot more punctuation, and it s not always clear to me what it s doing. This applies more when reading other people s code than when writing it myself, obviously, but I see a lot of Rust code that could give Perl a run for its money in terms of looking like line noise.
The borrow checker didn t bug me too much, but did add overhead to my thinking. The Rust compiler is generally very good at outputting helpful error messages when the programmer is an idiot. I ended up having to use a RefCell for one solution, and using .iter()
for loops rather than explicit iterators (why, why is this different?). I also kept forgetting to explicitly mark variables as mutable when declaring them.
Things I liked? There s a rich set of first class data types. Look, I m a C programmer, I m easily pleased. You give me some sort of hash array and I ll be happy. Rust manages that, tuples, strings, all the standard bits any modern language can provide. The whole impl
thing for adding methods to structures I like as a way of providing some abstraction, though I think Go has a nicer syntax for it. The compiler, as mentioned, is great at spitting out useful errors for the most part. Also although I wasn t using external crates for AoC I do appreciate there s a decent ecosystem there now (though that brings up another gripe: rust seems to still be a fairly fast moving target, to the extent I can no longer rely on the compiler in Debian stable to be able to compile random projects I find).
sevenone
at the end of a line ended up as 7 or 1 really tripped me up). I don t recall any other problems that bit me as hard on the specification as this one, but it happening up front was unfortunate.
The short example input doesn t always help with this either; either it s not enough to be able to extrapolate patterns, or it doesn t show all the variations you need to account for (that aren t fully specified in the text), or in a few cases it turned out I needed to understand the shape of the actual data to produce a solution that could actually complete in a reasonable time.
Which brings me to another matter, sometimes brute force doesn t actually work. This is fine, but the second part of the day s problem can change the approach you d take. So sometimes I got lucky in the way I handled the first half, and doing the second half was a simple 5 minute tweak, and sometimes I had to entirely change the way I was storing data.
You might claim that if I was a better programmer I d have always produced a first half solution that was amenable to extension for the second half. First, I dispute that; I think there are always situations where the problem domain can change in enough directions that you can t handle all of them without a lot of effort. Secondly, I didn t find AoC an environment that encouraged me to optimise for generic solutions. Maybe some of the puzzles in isolation would allow for that, but a month of daily problems to solve while still engaging in regular life meant I hacked things up, took short cuts based on the knowledge I had of the input data, etc, etc.
Overall I can see the appeal, but the sheer quantity and the fact I write code as part of my day job just made it feel too much like a chore, rather than a fun mental exercise. I did wonder how they d look as a set of interview puzzles (obviously a subset, rather than all of them), but I m not sure how you d actually use them for that - I wouldn t want anyone to have to solve them in a live interview.
So, in case it s not obvious, I m not planning to engage in AoC again this yet. But I m continuing to persevere with Rust (though most of my work stuff is thankfully still Go).
qcow2
image and use fstrim to keep the actual on disk size small, without constraining my potential for file system expansion should I need itTPMEventLog
through to the guest kernel properly. The events get measured and the PCRs updated just fine, but /sys/kernel/security/tpm0/binary_bios_measurements
doesn t even get created. Using either grub 2.06 from FC40, or the 2.12 backport in bookworm-backports
, makes this work just fine.
Anyway, for reference, the following is the script I use to start the swtpm, and then qemu. The debugcon
line can be dropped if you re not interested in OVMF debug logging. This needs the guest OS to be configured up for a serial console, but avoids the overhead of graphics emulation.
As I said at the start, I m open to any hints about other options I should be passing; as long as I get acceptable performance in the guest I care more about reducing host load than optimising for the guest.
#!/bin/sh
BASEDIR=/home/noodles/debian-qemu
if [ ! -S $ BASEDIR /swtpm/swtpm-sock ]; then
echo Starting swtpm:
swtpm socket --tpmstate dir=$ BASEDIR /swtpm \
--tpm2 \
--ctrl type=unixio,path=$ BASEDIR /swtpm/swtpm-sock &
fi
echo Starting QEMU:
qemu-system-x86_64 -enable-kvm -m 2048 \
-machine type=q35 \
-smbios type=1,serial=N00DL35,uuid=fd225315-f72a-4d66-9b16-55363c6c938b \
-drive if=pflash,format=qcow2,readonly=on,file=/usr/share/edk2/ovmf/OVMF_CODE_4M.qcow2 \
-drive if=pflash,format=raw,file=$ BASEDIR /OVMF_VARS.fd \
-global isa-debugcon.iobase=0x402 -debugcon file:$ BASEDIR /debian.ovmf.log \
-device virtio-blk-pci,drive=drive0,id=virblk0 \
-drive file=$ BASEDIR /debian-12-efi.qcow2,if=none,id=drive0,discard=on \
-net nic,model=virtio -net user \
-chardev socket,id=chrtpm,path=$ BASEDIR /swtpm/swtpm-sock \
-tpmdev emulator,id=tpm0,chardev=chrtpm \
-device tpm-tis,tpmdev=tpm0 \
-display none \
-nographic \
-boot menu=on
/etc/dhcp/dhclient-exit-hooks.d/modem-interface-route
where we are dealing with an interface IP change:
# Update IPv6 tunnel with Hurricane Electric
curl --interface $interface 'https://username:password@ipv4.tunnelbroker.net/nic/update?hostname=1234'
/etc/network/interfaces
got the following, configuring the 6in4 tunnel as well as the low preference default route, and source routing via the 5g table, similar to IPv4:
pre-up ip tunnel add he-ipv6 mode sit remote 216.66.80.26
pre-up ip link set he-ipv6 up
pre-up ip addr add 2001:db8:1234::2/64 dev he-ipv6
pre-up ip -6 rule add from 2001:db8:1234::/64 lookup 5g
pre-up ip -6 route add default dev he-ipv6 table 5g
pre-up ip -6 route add default dev he-ipv6 metric 1000
post-down ip tunnel del he-ipv6
modem-interface-route
also got:
ip tunnel change he-ipv6 local $new_ip_address
/etc/nftables.conf
had to be taught to accept the 6in4 packets from the tunnel in the input chain:
# Allow HE tunnel
iifname "sfp.31" ip protocol 41 ip saddr 216.66.80.26 accept
table ip6 nat
chain postrouting
type nat hook postrouting priority 0
oifname "he-ipv6" snat ip6 prefix to ip6 saddr map 2001:db8:f00d::/56 : 2001:db8:666::/56
/etc/ppp/peers/aquiss
I have:
lcp-echo-interval 1
lcp-echo-failure 5
lcp-echo-adaptive
192.168.254.x
IP to talk to its web interface. As the 5G modem is the last resort path I choose not to do anything special with this, but the information is at least there if I need it.
To allow both interfaces to be up and the FTTP to be preferred I m simply using route metrics. For the PPP configuration that s:
defaultroute-metric 100
iface sfp.31 inet dhcp
metric 1000
vlan-raw-device sfp
pppd
will not replace an existing default route, so I ve created /etc/ppp/ip-up.d/default-route
to ensure it s added:
#!/bin/bash
[ "$PPP_IFACE" = "pppoe-wan" ] exit 0
# Ensure we add a default route; pppd will not do so if we have
# a lower pref route out the 5G modem
ip route add default dev pppoe-wan metric 100 true
/etc/dhcp/dhclient.conf
I ve disabled asking for any server details (DNS, NTP, etc) - I have internal setups for the servers I want, and don t want to be trying to select things over the 5G link by default.
However, what I do want is to be able to access the 5G modem web interface and explicitly route some traffic out that link (e.g. so I can add it to my smokeping tests). For that I need some source based routing.
First step, add a 5g table to /etc/iproute2/rt_tables
:
16 5g
/etc/dhcp/dhclient-exit-hooks.d/modem-interface-route
, which is more complex than I d like but seems to do what I want:
#!/bin/sh
case "$reason" in
BOUND RENEW REBIND REBOOT)
# Check if we've actually changed IP address
if [ -z "$old_ip_address" ]
[ "$old_ip_address" != "$new_ip_address" ]
[ "$reason" = "BOUND" ] [ "$reason" = "REBOOT" ]; then
if [ ! -z "$old_ip_address" ]; then
ip rule del from $old_ip_address lookup 5g
fi
ip rule add from $new_ip_address lookup 5g
ip route add default dev sfp.31 table 5g true
ip route add 192.168.254.1 dev sfp.31 2>/dev/null true
fi
;;
EXPIRE)
if [ ! -z "$old_ip_address" ]; then
ip rule del from $old_ip_address lookup 5g
fi
;;
*)
;;
esac
5g
routing table, and a default route in that table which uses the 5G link. There s no configuration for the FTTP connection in that table, so if the 5G link is down the traffic gets dropped, which is what we want. We also configure 192.168.254.1
to go out the link to the modem, as that s where the web interface lives.
I also have a curl callout (curl --interface sfp.31
to ensure it goes out the 5G link) after the routes are configured to set dynamic DNS with Mythic Beasts, which helps with knowing where to connect back to. I seem to see IP address changes on the 5G link every couple of days at least.
Additionally, I have an entry in the interfaces
configuration carving out the top set of the netblock my smokeping server is in:
up ip rule add from 192.0.2.224/27 lookup 5g
/etc/smokeping/config.d/Probes
file then looks like:
*** Probes ***
+ FPing
binary = /usr/bin/fping
++ FPingNormal
++ FPing5G
sourceaddress = 192.0.2.225
+ FPing6
binary = /usr/bin/fping
probe = FPing5G
for targets to test them over the 5G link.
That mostly covers the functionality I want for a backup link. There s one piece that isn t quite solved, however, IPv6, which can wait for another post.
resolv.conf
when the primary network went down, but then I would have to get into moving files around based on networking status and that felt a bit clunky.
So I decided to finally setup a proper local recursive DNS server, which is something I ve kinda meant to do for a while but never had sufficient reason to look into. Last time I did this I did it with BIND 9 but there are more options these days, and I decided to go with unbound, which is primarily focused on recursive DNS.
One extra wrinkle, pointed out by Lars, is that having dynamic name information from DHCP hosts is exceptionally convenient. I ve kept dnsmasq as the local DHCP server, so I wanted to be able to forward local queries there.
I m doing all of this on my RB5009, running Debian. Installing unbound was a simple matter of apt install unbound
. I needed 2 pieces of configuration over the default, one to enable recursive serving for the house networks, and one to enable forwarding of queries for the local domain to dnsmasq. I originally had specified the wildcard address for listening, but this caused problems with the fact my router has many interfaces and would sometimes respond from a different address than the request had come in on.
server:
interface: 192.0.2.1
interface: 2001::db8:f00d::1
access-control: 192.0.2.0/24 allow
access-control: 2001::db8:f00d::/56 allow
server:
domain-insecure: "example.org"
do-not-query-localhost: no
forward-zone:
name: "example.org"
forward-addr: 127.0.0.1@5353
interface=lo
port=5353
dhcp-option=option6:dns-server,[2001::db8:f00d::1]
dhcp-option=option:dns-server,192.0.2.1
noodles@buildhost:~$ mkdir ~/BPI
noodles@buildhost:~$ cd ~/BPI
noodles@buildhost:~/BPI$ ls
noodles@buildhost:~/BPI$ git clone https://source.denx.de/u-boot/u-boot.git
Cloning into 'u-boot'...
remote: Enumerating objects: 935825, done.
remote: Counting objects: 100% (5777/5777), done.
remote: Compressing objects: 100% (1967/1967), done.
remote: Total 935825 (delta 3799), reused 5716 (delta 3769), pack-reused 930048
Receiving objects: 100% (935825/935825), 186.15 MiB 2.21 MiB/s, done.
Resolving deltas: 100% (785671/785671), done.
noodles@buildhost:~/BPI$ mkdir u-boot-build
noodles@buildhost:~/BPI$ cd u-boot
noodles@buildhost:~/BPI/u-boot$ git checkout v2023.07.02
...
HEAD is now at 83cdab8b2c Prepare v2023.07.02
noodles@buildhost:~/BPI/u-boot$ make O=../u-boot-build bananapi_m2_zero_defconfig
HOSTCC scripts/basic/fixdep
GEN Makefile
HOSTCC scripts/kconfig/conf.o
YACC scripts/kconfig/zconf.tab.c
LEX scripts/kconfig/zconf.lex.c
HOSTCC scripts/kconfig/zconf.tab.o
HOSTLD scripts/kconfig/conf
#
# configuration written to .config
#
make[1]: Leaving directory '/home/noodles/BPI/u-boot-build'
noodles@buildhost:~/BPI/u-boot$ cd ../u-boot-build/
noodles@buildhost:~/BPI/u-boot-build$ make CROSS_COMPILE=arm-linux-gnueabihf-
GEN Makefile
scripts/kconfig/conf --syncconfig Kconfig
...
LD spl/u-boot-spl
OBJCOPY spl/u-boot-spl-nodtb.bin
COPY spl/u-boot-spl.bin
SYM spl/u-boot-spl.sym
MKIMAGE spl/sunxi-spl.bin
MKIMAGE u-boot.img
COPY u-boot.dtb
MKIMAGE u-boot-dtb.img
BINMAN .binman_stamp
OFCHK .config
noodles@buildhost:~/BPI/u-boot-build$ ls -l u-boot-sunxi-with-spl.bin
-rw-r--r-- 1 noodles noodles 494900 Aug 8 08:06 u-boot-sunxi-with-spl.bin
noodles@buildhost:~/BPI$ wget https://deb.debian.org/debian/dists/bookworm/main/installer-armhf/20230607%2Bdeb12u1/images/netboot/netboot.tar.gz
...
2023-08-08 10:15:03 (34.5 MB/s) - netboot.tar.gz saved [37851404/37851404]
noodles@buildhost:~/BPI$ tar -axf netboot.tar.gz
noodles@buildhost:~/BPI$ sudo fdisk /dev/sdb
Welcome to fdisk (util-linux 2.38.1).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): o
Created a new DOS (MBR) disklabel with disk identifier 0x793729b3.
Command (m for help): n
Partition type
p primary (0 primary, 0 extended, 4 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (1-4, default 1):
First sector (2048-60440575, default 2048):
Last sector, +/-sectors or +/-size K,M,G,T,P (2048-60440575, default 60440575): +500M
Created a new partition 1 of type 'Linux' and of size 500 MiB.
Command (m for help): t
Selected partition 1
Hex code or alias (type L to list all): c
Changed type of partition 'Linux' to 'W95 FAT32 (LBA)'.
Command (m for help): n
Partition type
p primary (1 primary, 0 extended, 3 free)
e extended (container for logical partitions)
Select (default p):
Using default response p.
Partition number (2-4, default 2):
First sector (1026048-60440575, default 1026048):
Last sector, +/-sectors or +/-size K,M,G,T,P (534528-60440575, default 60440575):
Created a new partition 2 of type 'Linux' and of size 28.3 GiB.
Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
$ sudo mkfs -t vfat -n BPI-UBOOT /dev/sdb1
mkfs.fat 4.2 (2021-01-31)
noodles@buildhost:~/BPI$ sudo dd if=u-boot-build/u-boot-sunxi-with-spl.bin of=/dev/sdb bs=1024 seek=8
483+1 records in
483+1 records out
494900 bytes (495 kB, 483 KiB) copied, 0.0282234 s, 17.5 MB/s
noodles@buildhost:~/BPI$ cp -r debian-installer/ /media/noodles/BPI-UBOOT/
U-Boot SPL 2023.07.02 (Aug 08 2023 - 09:05:44 +0100)
DRAM: 512 MiB
Trying to boot from MMC1
U-Boot 2023.07.02 (Aug 08 2023 - 09:05:44 +0100) Allwinner Technology
CPU: Allwinner H3 (SUN8I 1680)
Model: Banana Pi BPI-M2-Zero
DRAM: 512 MiB
Core: 60 devices, 17 uclasses, devicetree: separate
WDT: Not starting watchdog@1c20ca0
MMC: mmc@1c0f000: 0, mmc@1c10000: 1
Loading Environment from FAT... Unable to read "uboot.env" from mmc0:1...
In: serial
Out: serial
Err: serial
Net: No ethernet found.
Hit any key to stop autoboot: 0
=> setenv dibase /debian-installer/armhf
=> fatload mmc 0:1 $ kernel_addr_r $ dibase /vmlinuz
5333504 bytes read in 225 ms (22.6 MiB/s)
=> setenv bootargs "console=ttyS0,115200n8"
=> fatload mmc 0:1 $ fdt_addr_r $ dibase /dtbs/sun8i-h2-plus-bananapi-m2-zero.dtb
25254 bytes read in 7 ms (3.4 MiB/s)
=> fdt addr $ fdt_addr_r 0x40000
Working FDT set to 43000000
=> fatload mmc 0:1 $ ramdisk_addr_r $ dibase /initrd.gz
31693887 bytes read in 1312 ms (23 MiB/s)
=> bootz $ kernel_addr_r $ ramdisk_addr_r :$ filesize $ fdt_addr_r
Kernel image @ 0x42000000 [ 0x000000 - 0x516200 ]
## Flattened Device Tree blob at 43000000
Booting using the fdt blob at 0x43000000
Working FDT set to 43000000
Loading Ramdisk to 481c6000, end 49fffc3f ... OK
Loading Device Tree to 48183000, end 481c5fff ... OK
Working FDT set to 48183000
Starting kernel ...
firmware-brcm80211
once installation completed allowed the built-in wifi to work fine.
After install you need to configure u-boot to boot without intervention. At the u-boot prompt (i.e. after hitting a key to stop autoboot):
=> setenv bootargs "console=ttyS0,115200n8 root=LABEL=BPI-ROOT ro"
=> setenv bootcmd 'ext4load mmc 0:2 $ fdt_addr_r /boot/sun8i-h2-plus-bananapi-m2-zero.dtb ; fdt addr $ fdt_addr_r 0x40000 ; ext4load mmc 0:2 $ kernel_addr_r /boot/vmlinuz ; ext4load mmc 0:2 $ ramdisk_addr_r /boot/initrd.img ; bootz $ kernel_addr_r $ ramdisk_addr_r :$ filesize $ fdt_addr_r '
=> saveenv
Saving Environment to FAT... OK
=> reset
e2label /dev/sdb2 BPI-ROOT
to label /
appropriately; otherwise I occasionally saw the SD card appear as mmc1 for Linux (I m guessing due to asynchronous boot order with the wifi). You should now find the device boots without intervention.
0.6.3 - 26th September 2023
- Fix systemd detection + socket activation
- Add CMake checking for Berkeley DB
- Minor improvements to keyd logging
- Fix decoding of signature creation time
- Relax version check on parsing signature + key packets
- Improve HTML escaping
- Handle failed database initialisation more gracefully
- Fix bug with EDDSA signatures with top 8+ bits unset
/etc/network/interfaces
is a fairly basic (if powerful) mechanism for configuring network interfaces. NetworkManager is a better bet for dynamic hosts (i.e. clients), and systemd-network
seems to be a good choice for servers (I m gradually moving machines over to it). Netplan tries to provide a unified mechanism for configuring both with a single configuration language. A noble aim, but I don t see a lot of benefit for anything I use - my NetworkManager hosts are highly dynamic (so no need to push shared config) and systemd-network
(or /etc/network/interfaces
) works just fine on the other hosts. I m told Netplan has more use with more complicated setups, e.g. when OpenVSwitch is involved.
.deb
and chisel it into smaller components, which then helps separate out dependencies rather than pulling in as much as the original .deb
would. This was touted as being useful, in particular, for building targeted containers. Definitely appealing over custom built userspaces for containers, but in an ideal world I think we d want the information in the main packaging and it becomes a lot of work.
pub ed25519 2023-08-19 [C] [expires: 2025-08-18]
419F B4B6 567E 6EF7 DEAF 80A0 9026 108F B942 BEA4
uid [ultimate] Jonathan McDowell <noodles@earth.li>
$ listadmin3
fetching data for partypeople@example.org ... 200 messages
(1/200) 5303: omgitsspam@example.org / March 31, 2023, 6:39 a.m.:
The message is not from a list member: TOP PICK
(a)ccept, (d)iscard, (b)ody, (h)eaders, (s)kip, (q)uit? q
Moving on...
fetching data for admins@example.org ... 1 subscription requests
(1/1) "The New Admin" <newadmin@example.org>
(a)ccept, (d)iscard, (r)eject, (s)kip, (q)uit? a
1 messages
(1/1) 6560: anastyspamer@example.org / Aug. 13, 2023, 3:15 p.m.:
The message is not from a list member: Buy my stuff!
(a)ccept, (d)iscard, (b)ody, (h)eaders, (s)kip, (q)uit? d
0 to accept, 1 to discard, proceed? (y/n) y
fetching data for announce@example.org ... nothing in queue
$
dpkg-buildpackage -uc -us -b
will spit you out a .deb
) but I m holding off on filing an ITP + actually uploading until I know if it s useful enough for others before doing so. You only really need the listadmin3
file and to ensure you have Python3 + MechanicalSoup installed.
(Yes, I still run mailing lists. Hell, I still run a Usenet server.)
Card | Exchange rate | Charge % |
---|---|---|
Co-Op | 1.15 | 2.65% |
HSBC | 1.15 | 0.00% |
Monzo | 1.15 | 0.00% |
Nationwide | 1.15 | 2.99% |
Next.