Knowledge Base

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

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
Image

Screenshot of some waterfalls
Image

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
Image

Crafting recipe for a blotter
Image

Screenshot of some highlight strokes in the world
Image

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.

Gdb backtrace notes

tl; dr

export DEBUGINFOD_URLS="https://debuginfod.debian.net"
gdb -batch -n -ex 'set debuginfod enabled on' -ex 'set pagination off' -ex run -ex bt -ex 'bt full' -ex 'thread apply all bt full' --args pidgin --debug 2>&1 | tee -a ~/debug8.log

Longer explanation

Debuginfod is a newer mechanism where a debugger can be told where to download symbols, so you don't necessarily have to have the <package>-dbgsym installed, in a Devuan instance. But for any situation where that is not suitable, you can use a -dbgsym package.

So when troubleshooting an application crash, the last few instructions to gdb dump all the stack info (backtrace, or bt). And then you can distribute a scrubbed backtrace to the developers.

References

  1. HowToGetABacktrace - Debian Wiki
  2. Debugging :: Pidgin, the universal chat client
  3. Debuginfod Settings (Debugging with GDB)

Script to stay synced to upstreams for Luanti mods

I added a script to my repo for initializing a Mineclonia installation that uses a separate location to synchronize my local repositories to the upstream paths, if they still exist.

files/2026/listings/mod-upstreams.sh (Source)

#!/bin/sh
# Startdate: 2026-01-30-6 13:35
# Purpose: List and track git remotes for Luanti mods I care about
# Config
host="http://server3/git/luanti"
WORKDIR="${WORKDIR:-${HOME}/mod-upstreams}"
# Functions
ginit() {
    err() {
        printf '%s\n' "${@}" 1>&2
    }
    err "# ginit ${*}"
    if test "${#}" -lt 1 ;
    then
        err "Usage: ginit <repository_url> [branch_name] [dirname]"
        return 1
    fi
    REPO_URL="${1}"
    REPO_DIR="$( basename "${REPO_URL}" .git )"
    BRANCH_NAME="${2}"
    DIRNAME="${3}"
    if test -n "${DIRNAME}" ; then REPO_DIR="${DIRNAME}" ; fi
    if test -d "${REPO_DIR}" ;
    then
        err "Directory ${REPO_DIR} already exists. Changing directory to it."
        cd "${REPO_DIR}" || return
        if test -n "${BRANCH_NAME}" ;
        then
            err "Switching to branch ${BRANCH_NAME}"
            git fetch --all
            # my old redirect trick to filter stderr
            {
                {
                    # Use 'git switch' (modern) or 'git checkout' (older versions)
                    # 'git switch -c' creates and switches to a new branch if it doesn't exist locally
                    git switch -c "${BRANCH_NAME}" "origin/${BRANCH_NAME}" || \
                        git checkout -b "${BRANCH_NAME}" "origin/${BRANCH_NAME}" || \
                        git checkout "${BRANCH_NAME}"
                } 2>&1 1>&3 | sed -r -e '/fatal: a branch named.*already exists/d' 1>&2
            } 3>&1
        fi
    else
        if test -n "${BRANCH_NAME}" ;
        then
            err "Cloning branch ${BRANCH_NAME} into ${REPO_DIR}"
            git clone -b "${BRANCH_NAME}" "${REPO_URL}" "${REPO_DIR}"
        else
            err "Cloning default branch into ${REPO_DIR}"
            git clone "${REPO_URL}" "${REPO_DIR}"
        fi
        if test -d "${REPO_DIR}" ;
        then
            cd "${REPO_DIR}" || return
            git pull
            err "Changed directory to ${REPO_DIR}"
        else
            err "Failed to clone repository"
            return 1
        fi
    fi
}
sync_repos() {
    _origin="${1}"
    _dest="${2}"
    _branches="${3}"
    cd "${WORKDIR}"
    if ginit "${_origin}" ;
    then
        { {
            git remote add dest "${_dest}"
        } 2>&1 1>&3 | sed -r -e '/error: remote.*already exists/d' 1>&2 ; } 3>&1
        git pull --all
        echo "${_branches},main,master" | tr ',' '\n' | awk '!x[$0]++' | while read branch ;
        do
            { {
                git switch -c "${branch}" "origin/${branch}" || \
                    git checkout -b "${branch}" "origin/${branch}" || \
                    git checkout "${branch}"
                git push dest "${branch}"
            } 2>&1 1>&3 | sed -r -e '/fatal: a branch named.*already exists/d' 1>&2 ; } 3>&1
        done
    fi
}
# sync_repos origin dest "branches,comma-separated"
sync_repos https://github.com/JamesClarke7283/enchantments_extractor "${host}/enchantments_extractor" "main"
# enchantments_workbench is mine
sync_repos https://codeberg.org/camelia/farmtools "${host}/farmtools" "main"
sync_repos https://codeberg.org/Wuzzy/minetest_inventory_icon "${host}/minetest_inventory_icon"
sync_repos https://github.com/JamesClarke7283/inventory_pouches "${host}/inventory_pouches"
sync_repos https://github.com/ketwaroo/minetest-k-ambient-light "${host}-readonly/minetest-k-ambient-light" "main"
sync_repos https://github.com/ketwaroo/minetest-k-recycler "${host}-readonly/minetest-k-recycler" "main"
# lava_furnace is mine
sync_repos https://codeberg.org/SilverSandstone/leads "${host}-readonly/leads" "main"
# list_to_file is mine
sync_repos https://github.com/minetest-mapserver/mapserver_mod "${host}-readonly/mapserver_mod" "master"
# mcl_chiseled_bookshelf is mine
# mcl_colored_chests is mine
sync_repos https://codeberg.org/TomCon/mcl_copper_stuff "${host}-readonly/mcl_copper_stuff" "master"
sync_repos https://codeberg.org/rudzik8/mcl_cozy "${host}/mcl_cozy" "master"
sync_repos https://codeberg.org/rudzik8/mcl_decor "${host}-readonly/mcl_decor" "master"
sync_repos https://codeberg.org/rudzik8/mcl_emerald_stuff "${host}/mcl_emerald_stuff" "main"
# mcl_lapis_stuff is mine
sync_repos https://github.com/ketwaroo/minetest-mcl-misk-recipes "${host}/mcl_misk_recipes" "main"
sync_repos https://codeberg.org/rudzik8/mcl_morefood "${host}/mcl_morefood" "main"
# milk_potion is mine
# molten_sailor_mcl is mine
sync_repos https://git.0x7be.net/dirk/mtimer "${host}/mtimer" "main"
sync_repos https://github.com/minetest-go/mtui_mod "${host}-readonly/mtui_mod" "master"
# obsidian_extra is mine
sync_repos https://github.com/LizzyFleckenstein03/playerlist "${host}/playerlist" "main"
sync_repos https://github.com/acmgit/minetest_poi "${host}/minetest_poi"
# privs_per_world is mine
sync_repos https://gitlab.com/cronvel/mt-respawn "${host}/mt-respawn"
# safe_chest upstream is gone, so I guess I am upstream now.
# sync_repos https://github.com/smnoe01/safe_chest "${host}/safe_chest"
sync_repos https://github.com/mt-historical/snippets "${host}-readonly/snippets"
sync_repos https://github.com/DonBatman/myairwand "${host}-readonly/myairwand"
sync_repos https://github.com/Twinsonian/magicsquare "${host}/magicsquare" "main"

The purpose of this script is to make synchronizing to upstream easy. I tend to have very small changes to upstream. On occasion my changes get merged into upstream, and I don't need any feature branches! For some of these, I have private patches that have been declined by upstream or never sent to them in the first place.

This script also documents the mods I care about. Any of the "readonly" ones are ones for which I have no changes at all. I have changed my mind and have had to move some around, when I realized I wanted to patch them after all.

Barcode 128 font on devuan

I wanted to print a barcode for a serial number, and I learned it was barcode 128. And apparently it's not as simple as just typing in that font; you have to encode a message. Thankfully, the smart folks on the Internet have provided a solution.

That's pretty much it. In the end, I couldn't get my use case completed, because I couldn't get the encoding (which did scan to the same value) to look identical as a barcode as what I was matching. I can't prove the destination environment (a second party) will read the correct value.

References

  1. Libre Barcode Font Project | Libre Barcode Project
  2. Code 128 | Libre Barcode Project
  3. graphicore/librebarcode: Libre Barcode: barcode fonts for various barcode standards.
  4. Fonts - Debian Wiki
  5. Libre Barcode 128 - Google Fonts
  6. How to Identify a Barcode | BarcodeFAQ.com
  7. Code 128 Barcode Fonts

New Luanti csm: node_inv_read

I wrote a new client side mod for Luanti that is an inventory cheat. Technically it doesn't work in Minetest Game, so it only works in Mineclonia. I couldn't figure that out.

This mod lets you punch any node, and if it has inventory, it will show all the inventories to you. I have tested it with up to 6 inventories. And if a node does not use the allow_metadata_inventory_put functions, you can even move items around!

Get the code.

Image

Git clone and/or pull

I heavily adapted a function generated by a Copyright Expunging Engine. Here is my function for easily cloning/pulling a git remote.

files/2026/listings/ginit.sh (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
ginit() {
    err() {
        printf '%s\n' "${@}" 1>&2
    }
    err "# ginit ${*}"
    ( # parenthesis to suppress the cd
        if test "${#}" -lt 1 ;
        then
            err "Usage: ginit <repository_url> [branch_name] [dirname]"
            return 1
        fi
        REPO_URL="${1}"
        REPO_DIR="$( basename "${REPO_URL}" .git )"
        BRANCH_NAME="${2}"
        DIRNAME="${3}"
        if test -n "${DIRNAME}" ; then REPO_DIR="${DIRNAME}" ; fi
        if test -d "${REPO_DIR}" ;
        then
            err "Directory ${REPO_DIR} already exists. Changing directory to it."
            cd "${REPO_DIR}" || return
            if test -n "${BRANCH_NAME}" ;
            then
                err "Switching to branch ${BRANCH_NAME}"
                git fetch --all
                # my old redirect trick to filter stderr
                {
                    {
                        # Use 'git switch' (modern) or 'git checkout' (older versions)
                        # 'git switch -c' creates and switches to a new branch if it doesn't exist locally
                        git switch -c "${BRANCH_NAME}" "origin/${BRANCH_NAME}" || \
                            git checkout -b "${BRANCH_NAME}" "origin/${BRANCH_NAME}" || \
                            git checkout "${BRANCH_NAME}"
                    } 2>&1 1>&3 | sed -r -e '/fatal: a branch named.*already exists/d' 1>&2
                } 3>&1
            fi
        else
            if test -n "${BRANCH_NAME}" ;
            then
                err "Cloning branch ${BRANCH_NAME} into ${REPO_DIR}"
                git clone -b "${BRANCH_NAME}" "${REPO_URL}" "${REPO_DIR}"
            else
                err "Cloning default branch into ${REPO_DIR}"
                git clone "${REPO_URL}" "${REPO_DIR}"
            fi
            if test -d "${REPO_DIR}" ;
            then
                cd "${REPO_DIR}" || return
                git pull
                err "Changed directory to ${REPO_DIR}"
            else
                err "Failed to clone repository"
                return 1
            fi
        fi
    )
}