One of the few things I know about Minecraft is, some players I know and love could be playing together if someone were to run a server for them. That’s the sort of thing I’d gladly do, provided I could approximately never again pay attention to it.

Here’s what I came up with. Let’s see how it pans out.

Prerequisites

My Virtual Private Server at Panix is NetBSD/amd64 with plenty of RAM, disk, and network headroom. Marginal additional usage is therefore free.

System- and pkgsrc-provided services are controlled by the usual NetBSD-style rc.d scripts.

Site-specific services are supervised, including supervision trees controlled by each user. In the following excerpt from my running system:

/service/sniproxy: up (pid 11133) 81141 seconds

/service/svscan-notqmail: up (pid 325) 846389 seconds
  /home/notqmail/service/renewssl.www.notqmail.org: up (pid 20951) 81141 seconds
  /home/notqmail/service/www.notqmail.org: up (pid 24539) 81141 seconds

/service/svscan-schmonz: up (pid 394) 846389 seconds
  /home/schmonz/service/agilein3minut.es: up (pid 26325) 81141 seconds
  /home/schmonz/service/latentagility.com: up (pid 24554) 81141 seconds
  /home/schmonz/service/renewssl.agilein3minut.es: up (pid 18299) 81141 seconds
  /home/schmonz/service/renewssl.latentagility.com: up (pid 14394) 81141 seconds
  /home/schmonz/service/renewssl.schmonz.com: up (pid 12424) 81140 seconds
  /home/schmonz/service/schmonz.com: up (pid 21449) 81141 seconds

These packages were already installed:

  • djbdns-run
  • s6-portable-utils
  • unzip
  • curl
  • py-html2text
  • jq

I’ve added these:

  • openjdk21
  • execline

I’m installing Minecraft’s server software manually, so it’ll be my job to notice when to update. Gotta automate the noticing, at least.

Oh, and I need a sensible process-supervision run script for the Minecraft server. The script needs to solve two application-specific system-integration problems:

  1. The usual supervision signals cause the server to terminate without saving the state of the world, which seems… rude
  2. The usual way to configure a running server is to get on the console and type commands into it, which implies having started it inside a tmux session (which, I love tmux, but not for this)

We solve for (1) by running the server as a child process and translating supervision signals into Minecraft commands.

We solve for (2) by connecting a named pipe to the server’s standard input. Then commands can be sent to the server using nothing but echo and ordinary output redirection.

Without even really being asked, my brain instantly produced the mechanisms for (1) in Perl. Then someone on #s6 shared a run script that additionally solved (2) while being more than twice as short as mine. How? By writing it in execline, a language expressly designed to be this kind of glue.

Step by step: Supervising Minecraft service

1. Create dedicated Unix user with its own process supervisor

useradd -m minecraft
mkdir -p /etc/service/svscan-minecraft/log
cd /etc/service/svscan-minecraft
cat > log/run <<'EOF'
#!/bin/sh
exec setuidgid minecraft logger -t svscan-minecraft -p daemon.info
EOF
chmod +x log/run
cat > run <<'EOF'
#!/bin/sh
exec 2>&1
exec setuidgid minecraft argv0 svscan svscan-minecraft /home/minecraft/service
EOF
chmod +x run
ln -s /etc/service/svscan-minecraft /var/service/

2. Globally, map hostname to IP

echo '+minecraft.schmonz.com:my.server.ip.here' >> /etc/tinydns/data
service tinydns reload

3. Locally, map port number to service name

echo 'minecraft 25565/tcp # minecraft.schmonz.com' >> /etc/services
service sysdb services

4. Allow incoming connections

$EDITOR /etc/npf.conf # add minecraft to $services_tcp
service npf reload

5. Download software

su minecraft
cd
mkdir -p sites/schmonz.com/minecraft/service
cd sites/schmonz.com/minecraft
mkdir bin
curl -o bin/server.jar https://piston-data.mojang.com/v1/objects/e6ec2f64e6080b9b5d9b471b291c33cc7f509733/server.jar

6. Accept EULA

mkdir data
echo 'eula=true' > data/eula.txt

7. Create control socket

mkfifo -m 0600 data/control

8. Symlink service logs where I usually look

ln -s data/logs logs

9. Create run script

cat > service/run <<'EOF'
#!/opt/pkg/bin/execlineb -P
fdmove -c 2 1
cd /home/minecraft/sites/schmonz.com/minecraft/data
redirfd -w -nb 3 control
trap -x
{
    SIGINT  { fdmove 1 3 s6-echo stop }
    SIGHUP  { fdmove 1 3 s6-echo stop }
    SIGTERM { fdmove 1 3 s6-echo stop }
    SIGPIPE { fdmove 1 3 s6-echo stop }
}
fdclose 3
redirfd -r 0 control
java -Xmx1024M -Xms1024M -jar ../bin/server.jar --nogui
EOF
chmod +x service/run

10. Enable service

ln -s /home/minecraft/sites/schmonz.com/minecraft/service ~/service/minecraft.schmonz.com

11. Send initial configuration

cat > data/control <<'EOF'
whitelist on
whitelist add so-and-so
save-on
EOF

Be notified of updates

As the minecraft user:

1. Create service and run script

cd ~/sites/schmonz.com/minecraft
mkdir -p update/service/log
cd update/service

cat > log/run <<'EOF'
#!/bin/sh

exec multilog t ./main
EOF
chmod +x log/run

cat > run <<'EOF'
#!/bin/sh

set -e
set -x

exec 2>&1

MINECRAFT_SERVER_DOWNLOAD_PAGE='https://www.minecraft.net/en-us/download/server'

running_version() {
   unzip -p ../bin/server.jar version.json \
       | jq -r .name
}

available_version() {
   curl \
       --silent \
       --user-agent "Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0" "${MINECRAFT_SERVER_DOWNLOAD_PAGE}" \
       | html2text \
       | grep -i 'minecraft_server.* nogui' \
       | sed -e 's|.*minecraft_server\.||' -e 's|\.jar.*||'
}

main() {
   cd /home/minecraft/sites/schmonz.com/minecraft/data

   while true; do
       running="$(running_version)"
       available="$(available_version)"
       if [ "${running}" != "${available}" ]; then
           echo "MINECRAFT SERVER UPDATE NEEDED: ${running} -> ${available}"
           echo "" | mail -s "Please update Minecraft server: foo -> bar" your@email.address.here
       fi
       sleep 250000
   done
}

main "$@"
exit $?
EOF
chmod +x run

2. Enable service

ln -s /home/minecraft/sites/schmonz.com/minecraft/update/service ~/service/update.minecraft.schmonz.com

Further improvements

If it’s desirable to have the server periodically auto-save, I’ll add one more service that writes save-all to data/control.

If pkgsrc had a minecraft-server package (FreeBSD Ports does), I could delete my ad hoc update-notification service. Maybe I’ll make it so. I do like packaging things as part of my area-under-the-curve strategy.

My host-specific supervision trees and pkgsrc’s djbware rc.d scripts (qmailsmtpd, for instance) are still using daemontools. I’ve had the intention to switch to s6 for a long time. When it happens, I’ll be happy about it.

I haven’t tried to understand why there are “Java” and “Bedrock” editions of Minecraft, but it seems like they don’t interoperate. I’m sure there are reasons for this product segmentation and that I’ll come to better understand them in the fullness of time. Meanwhile, if and when I need to add Bedrock client interop, Geyser looks like an easy option — once I’m running something other than the vanilla upstream Minecraft Java server. I’ll need to try to understand that whole situation, too.

Are you an experienced Minecraft server administrator? Please share tips and advice!

Posted April 15, 2025 at 06:00:34 PM EDT Tags:

We have a 2017 13” MacBook Air that’s periodically of interest to the kids, but Apple doesn’t offer macOS updates for it beyond Monterey (12.x).

I just installed OpenCore Legacy Patcher on it, clicked “Build and Install OpenCore”, rebooted, ran Software Update again, and was offered Sonoma (14.x), the latest available macOS at the time of writing.

Upgraded, rebooted, ran OpenCore Legacy Patcher, clicked “Post-Install Root Patch”, rebooted when prompted, and that was that.

Posted June 21, 2024 at 01:57:00 PM EDT Tags:

On my home network, some important jobs are performed by little ARM computers.

AirPlay to sound system

The house came with a decent sound system wired in. The receiver can take 1/8” stereo input — from AirPlay, with help from a decade-old Raspberry Pi 1 Model B Rev 2.

1. Prepare disk

With a 4GB SD card, from macOS:

$ diskutil list    # inspect output
$ SDCARD=disk6
$ diskutil unmountDisk ${SDCARD}
$ links https://raspi.debian.net/tested-images/
$ DISKIMAGE=20231109_raspi_1_bookworm.img.xz
$ fetch https://raspi.debian.net/tested/${DISKIMAGE}
$ xzcat ${DISKIMAGE} \
  | sudo dd of=/dev/r${SDCARD} bs=64k oflag=sync status=progress
$ diskutil eject ${SDCARD}

2. First boot

Place the RPi somewhere convenient. Connect SD card, keyboard, HDMI, Ethernet, and power. Log in as root, no password:

# apt update
# apt -y install etckeeper
# cd /etc
# git branch -M main
# apt -y install sudo
# visudo    # for the sudo group, insert NOPASSWD: before the final ALL
# useradd -m -G sudo -s /bin/bash schmonz
# passwd schmonz
# exit

Log in as schmonz:

$ sudo passwd root
$ sudo sh -c 'echo 127.0.1.1 schleierplay >> /etc/hosts'
$ sudo hostnamectl hostname schleierplay
$ sudo ln -sf /usr/share/zoneinfo/US/Eastern /etc/localtime
$ sudo etckeeper commit -m 'Set root password, hostname, and timezone.'
$ sudo apt -y install shairport-sync
$ sudo vi /etc/shairport-sync.conf
$ sudo etckeeper commit -m 'Set AirPlay name.'
$ sudo shutdown -h now

3. Deployment

Raspberry Pi (with green case) _in situ_

Place the RPi where it’ll live. Connect audio cable, Ethernet, and power.

$ ssh-copy-id schleierplay.local

4. Usage

Make sure receiver is set to AUX input. Use AirPlay.

5. Maintenance

As with any Debian:

$ ssh schleierplay.local -t 'sudo apt update && sudo apt -y upgrade && sudo apt -y autoremove'

To back up /etc, git push it someplace trustworthy and private.

6. Wishlist

I’d rather run NetBSD, but on 10.0 with shairport-sync, I saw a lot of AirPlay Speaker Not Available: 'House' is being used by someone else (even when it wasn’t).


AirPrint to old printer

My ancient USB-only HP LaserJet P1006 remains reliable for our basic needs and we’ve still got a pile of toner cartridges. A friend recently sent me a comparatively beefy Pine A64 board.

1. Prepare disk

With a 4GB SD card, from macOS:

$ diskutil list    # inspect output
$ SDCARD=disk6
$ diskutil unmountDisk ${SDCARD}
$ links https://www.armbian.com/pine64/
$ DISKIMAGE=Armbian_24.5.1_Pine64_bookworm_current_6.6.31_minimal.img.xz
$ fetch https://dl.armbian.com/pine64/archive/${DISKIMAGE}
$ xzcat ${DISKIMAGE} \
  | sudo dd of=/dev/r${SDCARD} bs=64k oflag=sync status=progress
$ diskutil eject ${SDCARD}

2. First boot

Place the A64 somewhere convenient. Connect SD card, keyboard, HDMI, Ethernet, and power. Follow the prompts to set the root password, create a user account, and select a locale. Then continue:

# apt update
# apt -y install etckeeper
# cd /etc
# git branch -M main
# visudo    # for the sudo group, insert NOPASSWD: before the final ALL
# exit

Log in as schmonz:

$ sudo sh -c 'echo 127.0.1.1 schleierprint >> /etc/hosts'
$ sudo hostnamectl hostname schleierprint
$ sudo ln -sf /usr/share/zoneinfo/US/Eastern /etc/localtime
$ sudo etckeeper commit -m 'Set root password, hostname, and timezone.'
$ sudo apt -y install hplip avahi-daemon
$ sudo usermod -a -G lpadmin schmonz
$ sudo etckeeper commit -m 'Make myself a printer admin.'
$ sudo shutdown -h now

3. Deployment

Pine A64 Pi (with black and white case) _in situ_

Place the A64 where it’ll live. Connect printer, Ethernet, and power.

$ ssh-copy-id schleierprint.local
$ ssh schleierprint.local
$ sudo hp-setup -i    # follow prompts, mostly defaults; name the queue 'hpljp1006'
$ sudo etckeeper commit -m 'Add initial hplip config for P1006.'
$ sudo sed -i \
  -e '/^\*ColorDevice: True$/s|True|False|' \
  -e '/^\*OpenUI \*Duplex\/Double-Sided Printing: PickOne$/,/^\*CloseUI: \*Duplex$/s|^|*% |' \
  -e '/^\*OpenUI \*ColorModel\/Output Mode: PickOne$/,/^\*CloseUI: \*ColorModel$/s|^|*% |' \
  /etc/cups/ppd/hpljp1006.ppd
$ sudo etckeeper commit -m 'Correct advertised printer capabilities.'
$ sudo sed -i \
  -e 's|^Info $|Info HP LaserJet P1006|' \
  /etc/cups/printers.conf
$ sudo lpadmin -d hpljp1006
$ sudo etckeeper commit -m 'Name printer and set it as default.'
$ sudo cupsctl --remote-any
$ sudo etckeeper commit -m 'Let local network talk to CUPS.'
$ sudo sed -i \
  -e '/^WebInterface /a PreserveJobFiles No' \
  /etc/cups/cupsd.conf
$ sudo etckeeper commit -m 'Maybe avoid some disk writes.'
$ sudo systemctl restart cups

On macOS, do not override the generic driver with “HP LaserJet P1006”. You won’t be able to print (with filter failed in the server logs), except that every “Supply Levels” check — including the ones that happen as part of every print job — will produce a piece of paper containing the single line @PJL INFO SUPPLIES.

As I understand it, some versions of CUPS have a server bug where it can’t discern whether incoming data has already been filtered for the target queue: filters converted the data (via application/vnd.cups-raster) to the printer’s native command set (whatever that might be)… but when the job got sent to the CUPS server it was tagged as application/vnd.cups-raster rather than, say, application/octet-stream.

While that discussion is over a decade old, its advice — leave the filtering to the server, and make sure clients don’t do any — has me printing from macOS, iOS, and Windows.

4. Usage

On macOS, add the printer. When it autoselects “Generic PostScript Printer”, leave it (details in sidebar). Print.

On iOS, print.

On Windows, add the printer. Print.

5. Maintenance

As with any Debian:

$ ssh schleierprint.local -t 'sudo apt update && sudo apt -y upgrade && sudo apt -y autoremove'

To back up /etc, git push it someplace trustworthy and private.

6. Wishlist

I’d rather run NetBSD, but neither 10.0 nor -current brought up HDMI. I could try writing NetBSD to an SD card, mounting it from another NetBSD system, setting hostname in rc.conf, adding a non-root user, and then booting the A64 from it in order to do the rest over ssh. (Other systems that also didn’t bring up HDMI, wherefore I landed by trial and error on Armbian: FreeBSD 14, OpenBSD 7.5, Debian 12.)


AirPlay to old Sonos

Since one of my old Sonos speakers can’t be upgraded to AirPlay-compatible firmware, I’m not eager to upgrade the other. Instead, I’ve added AirConnect on the Pine A64 as an AirPlay relay.

Contents of /etc/systemd/system/airupnp.service:

[Unit]
Description=AirUPnP bridge
After=network-online.target
Wants=network-online.target

[Service]
ExecStart=/home/schmonz/bin/airupnp-linux-aarch64-static -l 1000:2000 -N '%%s' -x /home/schmonz/etc/airupnp.xml -Z
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target

Contents of /home/schmonz/etc/airupnp.xml (to omit my UPnP router from the AirPlay list):

<?xml version="1.0"?>
<airupnp>
    <device>
        <udn>uuid:1e38fc78-51f5-5f5d-9268-50c6b1dc59f8</udn>
        <name>Verizon FiOS-G1100 ManageableDevice+</name>
        <mac>bb:bb:bb:bb:bb:bb</mac>
        <enabled>0</enabled>
    </device>
</airupnp>

I’d rather install AirConnect from a system-provided package, but there isn’t one for Debian. Maybe I can puzzle out the AirConnect build system and add it to pkgsrc.

Posted June 7, 2024 at 12:39:37 PM EDT Tags:

On Friday, May 10, I presented “Not So Extreme Programming” for Large Scale Scrum (LeSS) in NYC & Global.

The abstract:

Where “Agile” sounds pleasant and inclusive, “Extreme Programming”… doesn’t. But it’s a differentiator: teams practicing XP are seen to move with uncommon agility. The name has other problems, too. For one, XP is about much more than programming. For another, when compared with other ways software still gets developed, XP is much less extreme. This talk — for anyone involved with Agile in any role, at any scale — will take you through where Extreme Programming came from, where it’s going, what it requires, why it remains as relevant as ever, and how to take advantage.

Video:

Posted May 10, 2024 at 12:00:00 PM EDT Tags:

notqmail logo

My early imaginings of a collaborative Open Source successor to qmail, let me assure you, did not include going nearly four years between releases. Well, at least it hasn’t been more than four. notqmail 1.09 is here:

For decades, due to each administrator needing to patch in their particular missing bits of functionality, the qmail source code itself has effectively been a public API. Some future release of notqmail will include everything most everyone needs. On that day, we’ll freely make desirable code changes without worrying about breaking people’s patches. On that day, notqmail will have become a relatively normal software project operating under relatively normal constraints.

This is not that day. notqmail remains a uniquely challenging legacy-code rehabilitation project, and 1.09 is merely a solid, long-overdue release that includes the work of a couple dozen new contributors.

Since this release took too long, our next development cycle will be

  1. Time-bounded
  2. Focused on process improvements

In legacy code, every time we can turn a vicious cycle virtuous, it’s a big win. By making the code easier and safer to change, we’ll have more fun; by having more fun, we’ll make more progress; by making more progress, we’ll get more feedback; by getting more feedback, we’ll have more fun; and so on.

Have fun with notqmail 1.09! Let us know how the upgrade goes for you. (I’ll be updating the pkgsrc package soon.) And if getting involved is your kind of thing, please feel welcome to join us.

Posted May 7, 2024 at 10:44:01 AM EDT Tags: