Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Keepass2 2.60 check update problem

I think I have found a novel bug in keepass2, and submitting a bug report in Debian is tricky when one doesn't use Debian.

The problem went away if I install keepass2=2.57-dfsg-4. I know the rest of the GNU+Linux world moved to keepassxc, but I like keepass2 interface, mostly because the password edit window is a separate window and there for I can move it around and still see the list of passwords.

The short explanation: starting in version 2.60, the application always removes an xml element "CheckForUpdateConfigured" when closing. This makes the application prompt again, after running again and opening a password file.

So my solution is to always enforce this entry, using this script in ~/bin/keepass2.

#!/bin/sh
# Startdate: 2026-03-16-2 15:46
# Purpose: fix keepass2 2.60 problem where it somehow removes this xml config element and therefore asks the user to
always check for updates.
update_xml_element() {
   # usage: parent="/toplevelobject" element="internalid" value="12341234" infile=./foo.xml _insert_xml_element
   _parent="${parent:-${1}}" ; _parent="${_parent%%/}"
   _element="${element:-${2}}"
   _value="${value:-${3}}"
   _infile="${infile:-${4}}"
   # example: xmlstarlet edit --update "/toplevelobject/internalid" --value "if-updated" --subnode "/toplevelobject[not(internalid)]" --type elem -n "internalid" --value "if-inserted"
   xmlstarlet edit --inplace --update "${_parent}/${_element}" --value "${_value}" --subnode "${_parent}[not(${_element})]" --type elem -n "${_element}" --value "${_value}" "${_infile}"
}
infile=~/.config/KeePass/KeePass.config.xml parent="/Configuration/Application/Start" element="CheckForUpdateConfigured" value="true" update_xml_element
infile=~/.config/KeePass/KeePass.config.xml parent="/Configuration/Application/Start" element="CheckForUpdate" value="false" update_xml_element
/usr/bin/keepass2 &
wait %1
infile=~/.config/KeePass/KeePass.config.xml parent="/Configuration/Application/Start" element="CheckForUpdateConfigured" value="true" update_xml_element
infile=~/.config/KeePass/KeePass.config.xml parent="/Configuration/Application/Start" element="CheckForUpdate" value="false" update_xml_element

Luanti mods that sound cool

This is intended to be a living list of mods that sound interesting but don't quite make the cut for essential mods.

Interesting mods for Mineclonia

These sound interesting. Some of these I have investigated or used or even contributed to. These are listed in no particular order, but probably in order than I came across them.

Inventory admin

This lets an admin view and change inventory of other players. Haha, like the stadium guards checking those clear bags! This is from the same guy who wrote inventory_pouches, so it must be good! I haven't actually tried this one; I haven't needed to moderate players' inventories.

ContentDB scm

Extra Woolstuff

In case I want to add stairs and slabs made of wool. I haven't wanted those in my worlds yet, but I might. Same guy as Inventory Admin above! I like his work.

ContentDB scm

Protector

This sounds like the big, popular protection mod. I haven't needed protection on my servers because we have live moderators, but if I needed this, I would check this out first.

ContentDB scm

Magicsquare

I like this one, and use it on a few servers, but it's not essential. It would simplify survival games, for moving animals around, but I can play without it if I had to. It is a rapid replacement of its slightly earlier version known as "mobcrate." When I use this, I use my own fork with patches which have not yet been merged into upstream.

(no picture)

(no contentdb entry) Forum post scm: upstream mine

Invisiblocks

From TenPlus1 again (like Protector), this mod sounds more useful for creative mode, when making a game or puzzle map or something. The mob wall item works with Mobs Redo but not Mineclonia mobs. I wouldn't even want to waste my time asking Mineclonia devs to support it because it's not related to Mineclonia directly so they'd decline immediately. The invisible barrier and invisible light blocks are pretty great though.

I love this idea and implementation, but it's not directly related to survival gameplay, so it's hardly essential. Man, if I could get the MCL animals to obey the mob walls, I probably would put this on my survival servers. But fences work too (minus the mobs lag-jumping over them).

ContentDB scm

Ancient Stuff

A random idea from another supercreator michi16bit: some more armor and tools with a cool design!

ContentDB scm: no upstream mine

Italian Food for VoxeLibre

I helped this author with some mineclonia-related modding questions. I personally don't see the need for a million food mods in my survival worlds, but if I start adding any, I would start here.

ContentDB scm

Automatic Storage & Retrieval System

This adds a nifty way to throw tons of items in a single location and you can sort and search. You wouldn't to track individual boxes anymore. This works in Mineclonia, but it doesn't quite fit with the style of gameplay for it.

ContentDB scm

Whiter List (whitelist)

This uses a file in the world directory to manage a whitelist of users who can join the world. If I need this feature, this is the one I would use. There are a host of alternatives but this one uses a local file which appeals to me, instead of mod storage which is more obtuse to manage.

(no picture)

ContentDB scm

Bulldozer

Obvious. It has no recipe, because it's so powerful, so it is for creative mode only.

ContentDB scm

Updates

Crafty fonts (crafty_fonts)

added 2026-04-16

This adds familiar pixel fonts for any Luanti game. This helps the immersion in Mineclonia! The fonts get used automatically by the game when the mod is enabled for your world.

ContentDB scm

New Luanti mod: mcl_ruby_stuff

I wrote another clone of mcl_emerald_stuff, which I named mcl_ruby_stuff. I also previously wrote mcl_lapis_stuff but I failed to have a separate post for that.

This mod today is only for Mineclonia and VoxeLibre. It is published in official ContentDB and in the internal ContentDB.

Description

You have to fashion rubies before you can make the gear.

Not only is this gear scaled for very late-game play, it also looks fashionable because it is red.

Using

Craft rubies based on the recipe chosen by the server admin. Craft with rubies to fashion all the ordinary gear like tools, armors, and horse armor. Equip the tools and armor!

Screenshots

Screenshot of armor and tools made with rubies

Screenshot of horse armor on a horse

Optional support

Mod farmtools has been requested to support this mod.

Alternatives

For a Minetest Game ruby experience, use mod Ruby Tools+. That mod is entirely unrelated to this one.

Settings

Once at installation time, configure the build recipe in minetest.conf, entry mcl_ruby_stuff.recipe. The options are the following. The recipe affects the strength of the equipment.

  • emerald_and_redstone_dust: 1 emerald + 1 redstone dust.
emerald redstone dust
  • eight_redstone_dust: 8 redstone dust.
redstone dust redstone dust redstone dust
redstone dust redstone dust redstone dust
redstone dust redstone dust
  • nine_redstone_blocks: 9 redstone blocks. This will make the strength of the equipment 2x that of emerald equipment.
redstone block redstone block redstone block
redstone block redstone block redstone block
redstone block redstone block redstone block

Useful vim snippet for converting links

tl;dr

:%s/](http/](\/outbound\/http/g

When I write posts for this blog, I like to include links either inline or as a reference near the bottom.

Whenever a link points outbound, I want to track that users have used it! Sounds sketchy, doesn't it? What could I possibly want click data for? Is that even legal? If you don't like it, just copy the link and remove the leading parts including the /outbound/ part. I've noticed query parameters might get swallowed in my little app, sorry. Or just search for the link's name, etc.

I saw all those discourse (forum software) sites show a small number of how many times a link has been clicked. I wondered how they're doing that. I did zero research on the topic, and decided to write my own (See reference 2). I'm sure discourse is very nice on the server side. I love using phpBB as a user, but I'm sure the php part makes server admin kinda gross. Anyway, discourse has a weird layout in every instance I can think of. While technically some discourse instances let you use the site without javascript, it'll be partially broken. I don't like it either way.

Anyway, I don't plan on showing how many people have clicked a link; that would involve using the data and figuring out how to show it. Someday (read: never), I'll look through my server logs for /outbound/ and try to determine which links were clicked. Maybe everybody uses my site to bounce to the Luanti site, or something. Or maybe everybody loves my links to codeberg. But I don't even use my own analytics thing anymore. My blog is just a symlink to our favorite circular file, I guess. (/dev/null for the aware.)

And this vim command at the top changes out any link that starts with http protocol to go through my little outbound app. Now I can run that once at the end of writing a post, instead of visually scanning everything multiple times or as I go.

References

  1. Front page of my site
  2. previous post

comaps

README for CoMaps

CoMaps is a mobile app for Android, available in F-Droid app store, that lets you view and navigate maps.

I store some CoMaps maps in my http mirror so I do not need to depend on the public CDN.

Operations

Download the latest maps

To determine which URLs are needed, configure the app to use the local mirror, and try ot download maps. View the 404 errors on server3.

# grep -E 'mirror\/comaps' /var/log/httpd/access_log | grep 404

Then adjust the urls and visit them at the official url, seen below. A minimal script is below.

td=/mnt/mirror/comaps/maps/260207
mkdir -p "${td}" ; cd "${td}"
wget --continue https://cdn-fi-1.comaps.app/maps/260207/<region_territory>.mwm \
    https://cdn-fi-1.comaps.app/maps/260207/<region_territory>.mwm \
    https://cdn-fi-1.comaps.app/maps/260207/World.mwm
    https://cdn-fi-1.comaps.app/maps/260207/WorldCoasts.mwm

It appears that the date component is derived by the latest release of the app in F-Droid:

New in version 2026.02.09-4-FDroid • OpenStreetMap data as of February 7

But that might change based on a comment

Ye, when i did a build i just set the metaserver to "" and just put my proxy as the default cdn server, sine the logic for multiple servers is not needed

Also, the very first thing that users will run into with this feature is that /YYMMDD/{country names}.mwm is hardcoded into each app's compilation via countries.txt

If the user just hosts the same files or just proxy's from upstream that will not be a problem, but in future might be more complicated if the app and map-version become separate, #110 , comaps/meta-php#26

On the World download view imo the advanced.. should be more sutle of a setting since most users wont need it and if you are a advanced user you know you have to press the little menu in the top right for example

Configure app to use my local mirror.

Adjust the settings in the app to use http://server3.ipa.internal.com/mirror/comaps/.

Restore configuration of app to use default server.

Delete the setting in the app, or use https://cdn-fi-1.comaps.app/maps/

Export my data from CoMaps android app

This also works for OrganicMaps. On the main interface when looking at the map, select the Star icon for opening the Favorites. Select menu entry "Export all Places and Tracks."

Alternatives

CoMaps forked from OrganicMaps, which is still a fine alternative.

References

  1. Custom map server / CDN url option · Issue #4232 · organicmaps/organicmaps
  2. #41 - Make maps download url configurable - comaps/comaps - Codeberg.org
  3. #2565 - [android] Make map download server configurable - comaps/comaps - Codeberg.org
  4. comaps/private.h at main - comaps/comaps - Codeberg.org
    #define DEFAULT_URLS_JSON R"([ "https://cdn-us-2.comaps.tech/", "https://comaps.firewall-gateway.de/", "https://cdn-fi-1.comaps.app/", "https://comaps-cdn.s3-website.cloud.ru/", "https://mapgen-fi-1.comaps.app/" ])"
    

Luanti containers, part 3: Comparing podman-compose rootless and systemd containers (quadlet?)

This is part 3 of a short series on comparing various container tech to run my Luanti game server and related services.

I believe quadlet is the term to describe using systemd to manage containers. It's now "bundled into podman," so I think that's the only dependency you need to install.

This is a major change in syntax and file layout, but it makes sense to me: Use systemd components in a systemd world, and work around systemd problems.

Podman compose rootless

files/2026/listings/luanti-rootless-podman-compose.yml (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File: /home/luanti/game-onyx/docker-compose.yml
# Author: bgstack15
# Startdate: 2026-02-20-6 08:42
# History:
#    2025
# this app in the container will not read the ~/.minetest/minetest.conf, particularly for mcl_keepInventory=true, so I had to modify ~/.minetest/games/mineclonia/minetest.conf
# Reference:
#    https://github.com/linuxserver/docker-luanti
#    https://github.com/minetest-mapserver/mapserver/blob/master/docker-compose.yml
#    https://github.com/minetest-mapserver/mapserver/blob/master/doc/install.md more useful
#    https://github.com/pandorabox-io/pandorabox.io/blob/master/docker-compose.yml#L45-L57
#    uid 30000 from https://github.com/luanti-org/luanti/blob/master/Dockerfile#L71
# Alternatives:
#    image: lscr.io/linuxserver/luanti:latest
---
version: "3.5"
services:
  luanti:
    image: ghcr.io/luanti-org/luanti:latest
    userns_mode: keep-id:uid=30000,gid=30000
    networks:
      - luanti
    environment:
      - MINETEST_USER_PATH=/var/lib/minetest/.minetest
      - TZ=Etc/UTC
    command: "--gameid mineclonia --worldname world"
    volumes:
      # ghcr.io image needs ./minetest.conf
      - /home/luanti/game-onyx:/var/lib/minetest/.minetest:z
    ports:
      - 30070:30000/udp
      - 30070:30000/tcp
    restart: unless-stopped
  mapserver:
    image: ghcr.io/minetest-mapserver/mapserver:latest
    userns_mode: keep-id:uid=30000,gid=30000
    restart: always
    networks:
      - luanti
    volumes:
      - /home/luanti/game-onyx/worlds/world:/minetest:z
    working_dir: "/minetest"
    ports:
      - 30071:8080/tcp
  ui:
    image: ghcr.io/minetest-go/mtui:latest
    restart: always
    environment:
      WORLD_DIR: "/world"
      LOGLEVEL: debug
      #COOKIE_DOMAIN: "www.example.com"
      #COOKIE_SECURE: "false"
      #COOKIE_PATH: "/games/luanti/namibia/ui"
      SERVER_NAME: "Onyx"
      #WASM_MINETEST_HOST: "minetest"
      API_KEY: "ExampleKeyGoesHere"
      #all features: "api,shell,luashell,minetest_config,docker,modmanagement,signup,chat,minetest_web"
      # enable features does not work. I had to be an admin from minetest.conf `name=<myname>` and then visit webpage and enable shell, luashell, chat
      ENABLE_FEATURES: "shell,luashell,chat"
    volumes:
      - "/home/luanti/game-onyx/worlds/world:/world:z"
      #- "postgres_socket:/var/run/postgresql"
    networks:
      - luanti
    ports:
      - 30072:8080/tcp
x-podman:
  in_pod: false
networks:
  luanti:
    name: luanti07
...

You saw this file in the previous post, and I needed to make very small changes so the converter tool could use it. They were: delete the network custom alias/name, also the x-podman attribute, which I found ironic.

files/2026/listings/luanti-rootless-podman-compose-for-podlet.diff (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
--- luanti-rootless-podman-compose.yml	2026-03-08 21:42:05.272649939 -0400
+++ luanti-rootless-podman-compose-for-podlet.yml	2026-03-08 21:53:50.416488977 -0400
@@ -64,9 +64,6 @@
       - luanti
     ports:
       - 30072:8080/tcp
-x-podman:
-  in_pod: false
 networks:
   luanti:
-    name: luanti07
 ...

Podman/systemd rootless

I used a converter tool named podlet, which I found in a copr. To install, I ran this sequence of commands.

sudo dnf copr enable cyqsimon/el-rust-pkgs
sudo dnf repo-pkgs copr:copr.fedorainfracloud.org:cyqsimon:el-rust-pkgs list --available
sudo dnf install podlet

In order for the user to use systemctl --user, I had to fix my dbus environment variable.

echo 'export XDG_RUNTIME_DIR=/run/user/$(id -u)' >> ~/.bashrc

And then log out and back in.

And now, time for the big conversion process, after the diff from above.

mkdir -p ~/.config/containers/systemd
podlet compose docker-compose.yml > ~/.config/containers/systemd/onyx-luanti.container

I then split this file up, and tweaked them by hand to get what I needed. Here are the final files.

files/2026/listings/onyx-luanti.container (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# luanti.container
[Container]
Environment=MINETEST_USER_PATH=/var/lib/minetest/.minetest TZ=Etc/UTC
Exec=--gameid mineclonia --worldname world
Image=ghcr.io/luanti-org/luanti:latest
Network=onyx.network
PublishPort=30070:30000/udp
PublishPort=30070:30000/tcp
UserNS=keep-id:uid=30000,gid=30000
Volume=/home/luanti/game-onyx:/var/lib/minetest/.minetest:z
ContainerName=luanti

[Service]
Restart=always

[Install]
WantedBy=default.target
files/2026/listings/onyx-mapserver.container (Source)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# mapserver.container
[Container]
Image=ghcr.io/minetest-mapserver/mapserver:latest
Network=onyx.network
PublishPort=30071:8080/tcp
UserNS=keep-id:uid=30000,gid=30000
Volume=/home/luanti/game-onyx/worlds/world:/minetest:z
WorkingDir=/minetest
ContainerName=mapserver

[Service]
Restart=always

[Install]
WantedBy=default.target
files/2026/listings/onyx-ui.container (Source)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# ui.container
[Container]
Environment=WORLD_DIR=/world LOGLEVEL=debug SERVER_NAME=Onyx API_KEY=ExampleKeyGoesHere ENABLE_FEATURES=shell,luashell,chat
Image=ghcr.io/minetest-go/mtui:latest
Network=onyx.network
PublishPort=30072:8080/tcp
Volume=/home/luanti/game-onyx/worlds/world:/world:z
ContainerName=ui

[Service]
Restart=always

[Install]
WantedBy=default.target
files/2026/listings/onyx.network (Source)
1
2
# onyx.network
[Network]

Steps not included by the conversion process

  • ContainerName: Without that, the container names would resemble systemd-onyx-luanti, which just felt redundant and also slightly insulting to see that name show up everywhere.
  • [Install] WantedBy=default.target. This is what tells the system to restart these containers at boot time. I had a little trouble with default.target or multi-user.target, but settled on default.target.
  • File /home/luanti/.config/user-tmpfiles.d/onyx.conf which contains these contents.

files/2026/listings/user-tmpfiles.d-onyx.conf (Source)

1
2
R!    /tmp/storage-run-1009/containers
R!    /tmp/storage-run-1009/libpod/tmp

This file contributes to starting the containers at boot time, by cleaning up the previous boot id-connected caches. It's a very systemd problem, solved in a systemd manner.

Final thoughts

I like using the native systemd components which feels more future-proof than Docker on Enterprise Linux derivatives. I didn't like that it was split across multiple files, in multiple directories. Docker-compose just feels much simpler and cleaner. One file, in one directory, which I store alongside the application files.

I also was able to use podman build . in the source directory of the mapserver project, when I was developing a code change. It worked very well, and I then tagged the package with 'mapserver1', so it ended up with name localhost:mapserver1, which I then used in the container defintion and a systemct --user daemon-reload and restart of the container.

References

In case I didn't list these elsewhere.

  1. Podman: Method to convert docker-compose files to systemd unit files - Server Fault
  2. containers/podlet: Generate Podman Quadlet files from a Podman command, compose file, or existing object
  3. podman-systemd.unit — Podman documentation
  4. Getting podman quadlets talking to each other · Major Hayden
  5. arch linux - Start a systemd user service at boot - Super User
  6. Unable to autostart podman container systemd service | Red Hat Customer Portal

Luanti containers, part 2: Comparing podman-compose rootful and podman-compose rootless

This is part 2 of a short series on comparing various container tech to run my Luanti game server and related services.

Podman compose rootful

files/2026/listings/luanti-rootful-podman-compose.yml (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File: /home/luanti/game-onyx/docker-compose.yml
# Author: bgstack15
# Startdate: 2026-02-20-6 08:42
# History:
#    2025
# this app in the container will not read the ~/.minetest/minetest.conf, particularly for mcl_keepInventory=true, so I had to modify ~/.minetest/games/mineclonia/minetest.conf
# Reference:
#    https://github.com/linuxserver/docker-luanti
#    https://github.com/minetest-mapserver/mapserver/blob/master/docker-compose.yml
#    https://github.com/minetest-mapserver/mapserver/blob/master/doc/install.md more useful
#    https://github.com/pandorabox-io/pandorabox.io/blob/master/docker-compose.yml#L45-L57
---
version: "3.5"
services:
  luanti:
    image: lscr.io/linuxserver/luanti:latest
    networks:
      - luanti
    environment:
      - PUID=1009
      - PGID=1009
      - TZ=Etc/UTC
      - "CLI_ARGS=--gameid mineclonia --worldname world"
    volumes:
      # lscr.io image needs ./main-config/minetest.conf
      - /home/luanti/game-onyx/:/config/.minetest/:z
    ports:
      - 30070:30000/udp
      - 30070:30000/tcp
    restart: unless-stopped
  mapserver:
    image: ghcr.io/minetest-mapserver/mapserver:latest
    restart: always
    networks:
      - luanti
    volumes:
      - /home/luanti/game-onyx/worlds/world:/minetest:z
    working_dir: "/minetest"
    ports:
      - 30071:8080/tcp
  ui:
    image: ghcr.io/minetest-go/mtui:latest
    restart: always
    environment:
      WORLD_DIR: "/world"
      LOGLEVEL: debug
      #COOKIE_DOMAIN: "www.example.com"
      #COOKIE_SECURE: "false"
      #COOKIE_PATH: "/games/luanti/namibia/ui"
      SERVER_NAME: "Onyx"
      #WASM_MINETEST_HOST: "minetest"
      API_KEY: "ExampleKeyGoesHere"
      #all features: "api,shell,luashell,minetest_config,docker,modmanagement,signup,chat,minetest_web"
      # enable features does not work. I had to be an admin from minetest.conf `name=<myname>` and then visit webpage and enable shell, luashell, chat
      ENABLE_FEATURES: "shell,luashell,chat"
    volumes:
      - "/home/luanti/game-onyx/worlds/world:/world:z"
      #- "postgres_socket:/var/run/postgresql"
    networks:
      - luanti
    ports:
      - 30072:8080/tcp
networks:
  luanti:
    name: luanti07
...

We've seen this before; see the previous post.

Podman compose rootless

files/2026/listings/luanti-rootless-podman-compose.yml (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File: /home/luanti/game-onyx/docker-compose.yml
# Author: bgstack15
# Startdate: 2026-02-20-6 08:42
# History:
#    2025
# this app in the container will not read the ~/.minetest/minetest.conf, particularly for mcl_keepInventory=true, so I had to modify ~/.minetest/games/mineclonia/minetest.conf
# Reference:
#    https://github.com/linuxserver/docker-luanti
#    https://github.com/minetest-mapserver/mapserver/blob/master/docker-compose.yml
#    https://github.com/minetest-mapserver/mapserver/blob/master/doc/install.md more useful
#    https://github.com/pandorabox-io/pandorabox.io/blob/master/docker-compose.yml#L45-L57
#    uid 30000 from https://github.com/luanti-org/luanti/blob/master/Dockerfile#L71
# Alternatives:
#    image: lscr.io/linuxserver/luanti:latest
---
version: "3.5"
services:
  luanti:
    image: ghcr.io/luanti-org/luanti:latest
    userns_mode: keep-id:uid=30000,gid=30000
    networks:
      - luanti
    environment:
      - MINETEST_USER_PATH=/var/lib/minetest/.minetest
      - TZ=Etc/UTC
    command: "--gameid mineclonia --worldname world"
    volumes:
      # ghcr.io image needs ./minetest.conf
      - /home/luanti/game-onyx:/var/lib/minetest/.minetest:z
    ports:
      - 30070:30000/udp
      - 30070:30000/tcp
    restart: unless-stopped
  mapserver:
    image: ghcr.io/minetest-mapserver/mapserver:latest
    userns_mode: keep-id:uid=30000,gid=30000
    restart: always
    networks:
      - luanti
    volumes:
      - /home/luanti/game-onyx/worlds/world:/minetest:z
    working_dir: "/minetest"
    ports:
      - 30071:8080/tcp
  ui:
    image: ghcr.io/minetest-go/mtui:latest
    restart: always
    environment:
      WORLD_DIR: "/world"
      LOGLEVEL: debug
      #COOKIE_DOMAIN: "www.example.com"
      #COOKIE_SECURE: "false"
      #COOKIE_PATH: "/games/luanti/namibia/ui"
      SERVER_NAME: "Onyx"
      #WASM_MINETEST_HOST: "minetest"
      API_KEY: "ExampleKeyGoesHere"
      #all features: "api,shell,luashell,minetest_config,docker,modmanagement,signup,chat,minetest_web"
      # enable features does not work. I had to be an admin from minetest.conf `name=<myname>` and then visit webpage and enable shell, luashell, chat
      ENABLE_FEATURES: "shell,luashell,chat"
    volumes:
      - "/home/luanti/game-onyx/worlds/world:/world:z"
      #- "postgres_socket:/var/run/postgresql"
    networks:
      - luanti
    ports:
      - 30072:8080/tcp
x-podman:
  in_pod: false
networks:
  luanti:
    name: luanti07
...

First of all, I had to switch to the official upstream Luanti image, because the linuxserver.io image just didn't work in rootless mode.

The official image uses uid 30000 internally, and podman has a great uid redirection.

The x-podman: in_pod: false lets podman use the userns userid redirection. Systemd things breaking in systemd ways, what can I say.

Environment variables and commandline parameters are passed slightly differently than before, due to the different image. I also learned the application, with MINETEST_USER_PATH manually set, did not want to use /etc/minetest/minetest.conf like the docs describe; the app wants to use $MINETEST_USER_PATH/minetest.conf, which was surprising but not a dealbreaker.

I really like being able to use a non-privileged user to run podman up -d and all my services start.

Only minimal changes were needed: some for different image configuration, and one for podman quirks.

Luanti containers, part 1: Comparing docker-compose and podman-compose rootful

I currently use Docker to run my Luanti game server and related services. But because I use RHEL derivatives (Rocky Linux, primarily), I wanted to see how the podman tech would work. Here's first post in a short series, explaining the differences in running Luanti in docker versus podman.

Docker-compose

I use docker-compose, so I define all the services in one file. It is very convenient for small scale deployments!

files/2026/listings/luanti-docker-compose.yml (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File: /home/luanti/game-onyx/docker-compose.yml
# Author: bgstack15
# Startdate: 2026-02-20-6 08:42
# History:
#    2025
# this app in the container will not read the ~/.minetest/minetest.conf, particularly for mcl_keepInventory=true, so I had to modify ~/.minetest/games/mineclonia/minetest.conf
# Reference:
#    https://github.com/linuxserver/docker-luanti
#    https://github.com/minetest-mapserver/mapserver/blob/master/docker-compose.yml
#    https://github.com/minetest-mapserver/mapserver/blob/master/doc/install.md more useful
#    https://github.com/pandorabox-io/pandorabox.io/blob/master/docker-compose.yml#L45-L57
---
version: "3.5"
services:
  luanti:
    image: lscr.io/linuxserver/luanti:latest
    networks:
      - luanti
    environment:
      - PUID=1009
      - PGID=1009
      - TZ=Etc/UTC
      - "CLI_ARGS=--gameid mineclonia --worldname world"
    volumes:
      - /home/luanti/game-onyx/:/config/.minetest
    ports:
      - 30070:30000/udp
    restart: unless-stopped
  mapserver:
    image: ghcr.io/minetest-mapserver/mapserver:latest
    restart: always
    networks:
      - luanti
    volumes:
      - /home/luanti/game-onyx/worlds/world:/minetest
    working_dir: "/minetest"
    ports:
      - 30071:8080/tcp
  ui:
    image: ghcr.io/minetest-go/mtui:latest
    restart: always
    environment:
      WORLD_DIR: "/world"
      LOGLEVEL: debug
      #COOKIE_DOMAIN: "www.example.com"
      #COOKIE_SECURE: "false"
      #COOKIE_PATH: "/games/luanti/namibia/ui"
      SERVER_NAME: "Onyx"
      #WASM_MINETEST_HOST: "minetest"
      API_KEY: "ExampleKeyGoesHere"
      #all features: "api,shell,luashell,minetest_config,docker,modmanagement,signup,chat,minetest_web"
      # enable features does not work. I had to be an admin from minetest.conf `name=<myname>` and then visit webpage and enable shell, luashell, chat
      ENABLE_FEATURES: "shell,luashell,chat"
    volumes:
      - "/home/luanti/game-onyx/worlds/world:/world"
      #- "postgres_socket:/var/run/postgresql"
    networks:
      - luanti
    ports:
      - 30072:8080/tcp
networks:
  luanti:
    name: luanti07
...

I use a third-party image, and not the official Luanti image. The only reason is I found it first on the world wide web. The Mineclonia client side mod does not need anything special in server-side, so any image will suffice.

The environment variables PUID and GUID are specific to this image of Luanti, based on the build team's consistency between all their images.

Even with SELinux enforcing on the docker host, the container doesn't seem to need special selinux tags on the volume mounts :z.

The restart: unless-stopped makes the docker daemon restart these even at reboot.

And, it's a little out of scope here, but the minetest.conf needs to set mapserver.url and mtui.url values to the container names of those other services.

Podman compose rootful

This is the experimental stage for me, and it took me a little while to get it working.

files/2026/listings/luanti-rootful-podman-compose.yml (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File: /home/luanti/game-onyx/docker-compose.yml
# Author: bgstack15
# Startdate: 2026-02-20-6 08:42
# History:
#    2025
# this app in the container will not read the ~/.minetest/minetest.conf, particularly for mcl_keepInventory=true, so I had to modify ~/.minetest/games/mineclonia/minetest.conf
# Reference:
#    https://github.com/linuxserver/docker-luanti
#    https://github.com/minetest-mapserver/mapserver/blob/master/docker-compose.yml
#    https://github.com/minetest-mapserver/mapserver/blob/master/doc/install.md more useful
#    https://github.com/pandorabox-io/pandorabox.io/blob/master/docker-compose.yml#L45-L57
---
version: "3.5"
services:
  luanti:
    image: lscr.io/linuxserver/luanti:latest
    networks:
      - luanti
    environment:
      - PUID=1009
      - PGID=1009
      - TZ=Etc/UTC
      - "CLI_ARGS=--gameid mineclonia --worldname world"
    volumes:
      # lscr.io image needs ./main-config/minetest.conf
      - /home/luanti/game-onyx/:/config/.minetest/:z
    ports:
      - 30070:30000/udp
      - 30070:30000/tcp
    restart: unless-stopped
  mapserver:
    image: ghcr.io/minetest-mapserver/mapserver:latest
    restart: always
    networks:
      - luanti
    volumes:
      - /home/luanti/game-onyx/worlds/world:/minetest:z
    working_dir: "/minetest"
    ports:
      - 30071:8080/tcp
  ui:
    image: ghcr.io/minetest-go/mtui:latest
    restart: always
    environment:
      WORLD_DIR: "/world"
      LOGLEVEL: debug
      #COOKIE_DOMAIN: "www.example.com"
      #COOKIE_SECURE: "false"
      #COOKIE_PATH: "/games/luanti/namibia/ui"
      SERVER_NAME: "Onyx"
      #WASM_MINETEST_HOST: "minetest"
      API_KEY: "ExampleKeyGoesHere"
      #all features: "api,shell,luashell,minetest_config,docker,modmanagement,signup,chat,minetest_web"
      # enable features does not work. I had to be an admin from minetest.conf `name=<myname>` and then visit webpage and enable shell, luashell, chat
      ENABLE_FEATURES: "shell,luashell,chat"
    volumes:
      - "/home/luanti/game-onyx/worlds/world:/world:z"
      #- "postgres_socket:/var/run/postgresql"
    networks:
      - luanti
    ports:
      - 30072:8080/tcp
networks:
  luanti:
    name: luanti07
...

Of course, install podman and podman-compose.

sudo dnf install -y podman podman-compose
sudo mkdir -p /etc/containers/containers.conf.d
echo "compose_warning_logs=false" | sudo tee /etc/containers/containers.conf.d/60-warnings.conf
sudo groupadd --gid 1009 luanti
sudo useradd --gid 1009 --uid 1009 luanti # I have not set a password
sudo loginctl enable-linger 1009

For the user to work with podman, I had to enable linger. I'm not going to go into the plagued history of this, today. This is necessary for any future podman use by this user, including the upcoming blog posts.

I had to use rootful podman (sudo podman-compose up) because the lscr.io image does some chown operations at initialization, and do not work in rootless mode.

I ran into some selinux problems, or at least I think I did. It certainly didn't hurt me to use the :z selinux option. Lowercase Z means podman should relabel the file objects for multiple containers, but uppercase Z would mean a private, unshared label for just this one container.

Common

The reverse proxy and port forwarding is out of scope for this document.

Troubleshooting Mineclonia server lag, and podman

Random Troubleshooting narrative

My new multiplayer Mineclonia survival world was suffering from severe lag. I wondered if it was related to the old CentOS 7-era hardware I run it on. Investigating showed some log entries about redstone events.

2026-03-05 16:00:12: ERROR[Server]: [mcl_redstone]: Maximum number of queued redstone events (65535) exceeded, deleting all of them.

This was a new world, with under 10 hours of gameplay, and we didn't have any redstone contraptions at all. I tried adjusting the incorrectly-labeled mcl_redstone_max_events (it was listed as mcl_redstone_event_max), which then let it actually count out all its redstone events, but then the errors occurred about max lag. So the server felt like it performed in the same bad way.

2026-03-05 16:00:10: WARNING[Server]: Server: Maximum lag peaked at 2.109 (steplen=0.05)

I found my old desktop computer, which was already running Rocky Linux 9, and set up podman, to conduct my first experiment with rootless podman. I've heard it supports compose compatibility, and I don't want to rewrite my docker-compose file into systemd units yet. I like the single file to control it all, rather than rewriting it in a different paradigm across 3 different service (unit) files.

I experimented with adjusting userns_mode and /run mounted from empty volumes. The game container fails because of s6 errors: It cannot chown various things. So I gave up on using rootless podman, and gave user luanti access to run sudo podman-compose. Then, the application would operate just fine.

So, with the game running, I connected and observed the same lagginess as before, even on the beefier host system. I investigated the game settings, and found an experimental flag I had turned on. By setting mcl_liquids_enable = false again, the game server operated normally.

Follow-up

Deep under the main play area, I found some large waterfalls. I could see how some performance-inhibiting waterfall calculations operated by the Mineclonia lua code could cause problems with that. Screenshot of gameplay

Screenshot of some waterfalls

Mod for Luanti: Highlighter Pen

I wrote a novel Luanti mod, that works in any game. I have tested it specifically in the following games.

Description

This mod for Luanti adds highlighter pen tools. A player can highlight nodes, and erase highlights. The highlights persist after a safe server restart.

The canonical scm repo for this mod is at https://bgstack15.ddns.net/cgit/luanti/highlighter_pen, and an additional repo suitable for collaboration is at https://gitlab.com/bgsack15/highlighter_pen.

Using

Craft a highlighter pen. Right-click a node with the tool to set a highlight. Left-click to remove a highlight on a node.

Craft and use a blotter to remove highlights in a small radius (default radius is 3).

Dependencies

  • Vendors node_highlighter because that is not in ContentDB. You can also use that mod, and it will load that instead of using the vendored components.

Optional dependencies

Screenshots

Crafting recipe for a highlighter pen

Crafting recipe for a blotter

Screenshot of some highlight strokes in the world

Settings

  • How many nodes can a highlighter pen be used in survival mode on before the pen runs out (default 200)
  • Highlighter pen can erase all highlight-colors from a node (default true)
  • Using a highlighter in the air (an empty node) erases this color entirely, on all nodes (default false)
  • Blotter radius, where 1 is the single node that was right-clicked (default 3)

Current weaknesses

  • The blotter is the only component that respects core.is_protected, and only on the center of the usage.
  • Unifieddyes does not produce highlighter pens for each color variant for creative mode inventory; you have to craft them.