Knowledge Base

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

Python error in Radicale led to me fixing my httpd config

When I ran OS updates for the month of November, I realized that my Radicale CalDAV server was not working. It was running, but it had an application error. After doing all the requisite research, I opened a bug report.

Nov 01 11:01:13 server3.ipa.internal.com radicale[8675]: [8675/Thread-2] [INFO] Successful login: 'bgstack15@IPA.INTERNAL.COM'
Nov 01 11:01:13 server3.ipa.internal.com radicale[8675]: [8675/Thread-2] [ERROR] An exception occurred during REPORT request on '/bgstack15/': Error in section 'read-domain-principal' of rights file '/etc/radicale/rights': Replacement index 0 out of range for positional args tuple

To simplify out the examples/boilerplate, my rule for calculating per-user permissions when using GSSAPI (kerberos/ldap) auth was throwing a python error.

[principal-domain]
user: (.+)@IPA.EXAMPLE.COM
collection: {0}
permissions: RW

It appears that whatever new logic happened in the 3.3.0 release revised how the regex capture groups work across the attributes of a permission directive.

It worked before. Or at least, it didn't throw errors before. (Technically I'd been patching out the domain name inside radicale already so this rule wasn't even in use).

So, the Radicale team suggested that I strip the domain name at the reverse proxy level, which inspired me to search, and this time I found a solution!

In my httpd config:

RewriteEngine On
RewriteRule ^/radicale$ /radicale/ [R,L]
<Location "/radicale/">
   ProxyPreserveHost On
   Include conf.d/auth-gssapi.cnf
   # which includes these lines:
   #GSS_NAME returns username@IPA.EXAMPLE.COM which merely needs additional rules in /etc/radicale/rights
   #RequestHeader set X_REMOTE_USER "%{GSS_NAME}e"
   Require valid-user
   AuthName "GSSAPI protected"
   ProxyPass        http://localhost:5232/ retry=20 connectiontimeout=300 timeout=300
   ProxyPassReverse http://localhost:5232/
   RequestHeader    set X-Script-Name /radicale
+   RequestHeader    edit X_REMOTE_USER "^(.*)@.*" "$1"
</Location>

So all I needed to do was treat the now-extant request header as a string variable and do a simple regex manipulation to preserve everything before the at sign.

Now I will no longer need to maintain that ridiculous app/__init__.py one-line patch after every python3 or radicale update.

I am always amused that some complex problems can be solved by a one-line change. It's usually the one-line changes that represent hours and hours and hours of work, eh?

Lego Universe use gamepad controller

If you want to use a usb gamepad controller (no affiliation/kickbacks here: I'm just a happy user) to emulate keypresses for the game of Lego Universe (DarkflameServer), here is how to repeat what I set up.

Install qjoypad. Run, and load this configuration. This is for the default keyboard configuration in-game, which I never tried to change. It might not even be possible.

files/2024/listings/lego-universe-qjoypad.lyt (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# QJoyPad 4.3 Layout File
# Startdate: 2024-10-23-4 15:25
# Author: bgstack15

Joystick 1 {
	Axis 1: gradient, +key 40, -key 38
	Axis 2: gradient, +key 39, -key 25
	Axis 3: +key 11, -key 0
	Axis 4: gradient, maxSpeed 1, tCurve 0, mouse+h
	Axis 5: gradient, maxSpeed 1, mouse+v
	Axis 6: +key 14, -key 0
	Axis 7: gradient, +key 40, -key 38
	Axis 8: gradient, +key 39, -key 25
	Button 1: key 65
	Button 2: key 64
	Button 3: key 50
	Button 4: key 37
	Button 5: key 12
	Button 6: key 13
	Button 7: key 56
	Button 8: mouse 1
	Button 11: rapidfire, mouse 3
}

Screenshot of qjoypad configuration window. This is not enough information to configure your controller for yourself.

The gamepad will have this layout.

Key mapping of XBOX 360-like controller, that is useful for Lego Universe default keybinds

Lego Universe cancel mission

If you are using Darkflame Server to emulate the long-gone Lego Universe MMO, and you accidentally accept a mission you cannot finish, you can use an admin command to remove the mission from your list.

You have to be an admin (aka GM or Mythran) on the server, which is not covered here, to be able to run the macro. It appears DarkFlame added a command for resetting an individual mission.

Screenshot of mission that I will never get to complete.

To determine which mission you need to scrub, search the text in client file locale/locale.xml. You want to grab the id number from the MissionText_[id]_in_progress.

    <phrase id="MissionText_1194_in_progress">
        <translation locale="en_US">Visit 5 Block Yard or Avant Grove Properties today.</translation>
        <translation locale="de_DE">Besuche heute 5 Steinhof- oder Avanthain-Grundstücke.</translation>
        <translation locale="en_GB">Visit 5 Block Yard or Avant Grove Properties today.</translation>
    </phrase>

You can confirm the mission id is correct on explorer.lu (lu:explorer). So with the mission id, you can go chat this in-game:

/gmlevel 8
/resetmission 1194
/gmlevel 0

Screenshot of entering admin chat command to hide the mission.

References

  1. LU Development Network | LU Development Network
  2. Game master levels in DarkflameServer/docs/Commands.md at main · DarkflameUniverse/DarkflameServer
  3. move command resetmission to gmlevel 0 by bgstack15 · Pull Request #1638 · DarkflameUniverse/DarkflameServer

git commit custom timestamp

tl;dr

GIT_AUTHOR_DATE="2024-07-01T15:30:00Z" GIT_COMMITTER_DATE="2024-07-01T15:30:00Z" git commit -m 'in the past'

Explanation

It's not enough to set the commit date, you have to set the author date as well. I also find that it's more believable to use non-zero seconds values, such as:

GIT_AUTHOR_DATE="$( date -d "now - 2 hours" )" GIT_COMMITTER_DATE="$( date -d "now - 2 hours" )" git commit -m 'before the deadline, see the commit timestamp?'

References

This page has way more info about how to do this to multiple commits, such as modifying the entire history of a branch.

  1. How to tweak Git commit timestamps (author and committer dates) - Sling Academy

Lego Universe autologin

I got tired of my Lego Universe game crashing, and having to type my username/password in again. This is not a shared machine, so I wrote a small utility to handle logging in for me.

The first time you use it, it will prompt for username and password. You can trigger this prompting again with parameter --login.

To simulate the clicks and key presses, use parameter --apply.

files/2024/listings/autologin-lego-universe.sh (Source)

#!/bin/sh
# File: autologin-lego-universe.sh
# Location: /mnt/public/Games/lego-universe/
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0-only
# Startdate: 2024-10-07-2 15:26
# Title Autologin for Lego Universe client
# Purpose: Given a username and password, auto log in to the Lego Universe/Darkflame client
# History:
# Usage:
#    Env vars:
#       APPLY
#    Parameters:
#       --login Change username and password in conf file
#       --apply Actually run automation
# Reference:
#    https://stackoverflow.com/questions/2683279/how-to-detect-if-a-script-is-being-sourced/28776166#28776166
#    man xdotool, yad
# Improve:
#    This is slightly buggy. The longer the delays in typing and between tasks, the better it runs.
#    This is only tested against 1920x1080
# Dependencies:
#    dep-devuan: xdotool, yad
#    Lego Universe already running! This script does not know how long it takes to load the username prompt in the game.
# Documentation:
# variables
CONFFILE=~/.config/autologin-lego-universe.conf
autologin_lego_universe() {
   # pixel offset, only tested against 1920x1080
   _type_delay_ms=60
   _step_delay_s=0.3
   _y_offset_username_field=50
   _loop_max_safety=10
   _tmpfile1="$( mktemp )" ; rm "${_tmpfile1}"
   # needs env vars: USERNAME, PASSWORD
   # step 0: find window that matters
   _x=0
   while test ! -f "${_tmpfile1}" ;
   do
      printf '%s ' "Loop ${_x}" 1>&2
      WINDOWID="$( xdotool search "Lego Universe - " )"
      test -n "${WINDOWID}" && touch "${_tmpfile1}"
      sleep 0.5 ;
      _x=$(( _x + 1 ))
      test ${_x} -ge ${_loop_max_safety} && { echo "Fatal! Safety valve: could not find game window after 20 loops. Aborting." 1>&2 ; rm -f "${_tmpfile1:-NOTHINGTODEL}" 1>/dev/null 2>&1 ; return 1 ; }
   done
   echo "Found WINDOWID=${WINDOWID}"
   rm -f "${_tmpfile1:-NOTHINGTODEL}"
   # step 0: bring game window to top. This does not work for me, but maybe it works for somebody else.
   xdotool windowactivate --window "${WINDOWID}" windowfocus --window "${WINDOWID}"
   # step 1: move mouse to username field
   xdotool mousemove --window "${WINDOWID}" $(( 1920 / 2 )) $(( 1080 / 2 - ${_y_offset_username_field} ))
   # step 2: enter username
   xdotool click 1 sleep "${_step_delay_s}" click 1 key ctrl+a type --delay "${_type_delay_ms}" "${USERNAME}"
   # step 3: move to password field
   xdotool sleep "${_step_delay_s}" key Tab
   # step 4: enter password
   xdotool sleep "${_step_delay_s}" sleep "${_step_delay_s}" type --delay "${_type_delay_ms}" "${PASSWORD}"
   # step 5: press enter
   xdotool sleep "${_step_delay_s}" key Return
}
save_field() {
   # usage: save_field USERNAME
   _field="${1}"
   # no error handling. Do not mess up save_field!
   case "${_field}" in
      USERNAME)
         _yad_opts='--text Username'
         _default="${USERNAME}"
         ;;
      PASSWORD)
         _yad_opts='--hide-text --text Password'
         set -x
         _default="${PASSWORD}"
         ;;
      *)
         echo "Error! save_field ${@} is invalid. Needs USERNAME or PASSWORD. Continuing..." 1>&2
         return 1
         ;;
   esac
   # do not quote _yad_opts on next line.
   result="$( yad ${_yad_opts} --entry --entry-text "${_default}" --title "Autologin for Lego Universe" )"
   _response_code=$?
   # write to file
   if test 0 -eq ${_response_code} ;
   then
      sed -i -r -e "/^${_field}=/d" "${CONFFILE}"
      echo "${_field}=\"${result}\"" >> "${CONFFILE}"
   else
      yad --text "Action cancelled."
   fi
   set +x
   unset _default _response_code
}
# if dot-sourced, run main
# BEGIN IF-DOT-SOURCED, so 2683279
sourced=0
if [ -n "$ZSH_VERSION" ]; then
  case $ZSH_EVAL_CONTEXT in *:file) sourced=1;; esac
elif [ -n "$KSH_VERSION" ]; then
  [ "$(cd -- "$(dirname -- "$0")" && pwd -P)/$(basename -- "$0")" != "$(cd -- "$(dirname -- "${.sh.file}")" && pwd -P)/$(basename -- "${.sh.file}")" ] && sourced=1
elif [ -n "$BASH_VERSION" ]; then
  (return 0 2>/dev/null) && sourced=1
else # All other shells: examine $0 for known shell binary filenames.
  # Detects `sh` and `dash`; add additional shell filenames as needed.
  case ${0##*/} in sh|-sh|dash|-dash) sourced=1;; esac
fi
# END IF-DOT-SOURCED
if test "${sourced}" = "0" ;
then
   test -f "${CONFFILE}" && . "${CONFFILE}"
   unset _reload
   if test -z "${USERNAME}" || echo " ${@} " | grep -qE " --login " ; then save_field USERNAME ; _reload=1 ; fi
   if test -z "${PASSWORD}" || echo " ${@} " | grep -qE " --login " ; then save_field PASSWORD ; _reload=1 ; fi
   echo " ${@} " | grep -qE " --apply " && APPLY=1
   test -n "${_reload}" && test -f "${CONFFILE}" && . "${CONFFILE}"
   # If you want to be more basic:
   #test -z "${USERNAME}" && { echo "Fatal! Need env var USERNAME. Aborted." 1>&2 ; exit 1 ; }
   #test -z "${PASSWORD}" && { echo "Fatal! Need env var PASSWORD. Aborted." 1>&2 ; exit 1 ; }
   unset _reload
   test -n "${APPLY}" && autologin_lego_universe ;
fi

And you can set your desktop shortcut to run this script then:

files/2024/listings/run-lego-universe-and-autologin.sh (Source)

#!/bin/sh
# Startdate: 2024-10-08-3 14:30
# Purpose: run Lego Universe, and then wait a few seconds, and then auto login.
legouniverse_script="$( find ~/.wine* -iname lego-universe.sh -print -quit )"
"${legouniverse_script}" &
# Hopefully this is long enough
sleep 10
# This if-dot-sourcing does not work so just go ahead and call it dot-sourced and then call the relevant function.
. /mnt/public/Games/lego-universe/autologin-lego-universe.sh
autologin_lego_universe

Nexus Dashboard: vendor fonts

The Nexus Dashboard admin control panel for the Lego Universe server emulator DarkflameServer works really well. It just uses some bog-standard Internet stuff where it pulls in Google fonts. It makes the site look really nice, but for people who don't want to leak the referer and/or are not on the world wide web, you can vendor the font yourself.

In the docker-compose.yml, add to the container darkflamserverweb a volume:

- ${HOME}/web/css:/app/static/css

Then make that directory, and pull down the included url yourself, once.

/* stackrpms,8 This pulls down https://fonts.gstatic.com/s/nunito/v26/XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmdTQ3ig.ttf so I put that here. */
/* @import url(https://fonts.googleapis.com/css?family=Nunito:700); */
@font-face {
  font-family: 'Nunito';
  font-style: normal;
  font-weight: 700;
  src: url(XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmdTQ3ig.ttf) format('truetype');
}

So my directory contents are as follows.

$ ls -al web/css
total 220
drwxr-xr-x. 2 bgstack15 admins        72 Oct 11 08:54 ./
drwxr-xr-x. 6 bgstack15 admins        54 Oct 11 08:29 ../
-rw-r--r--. 1 bgstack15 bgstack15 181989 Oct 11 08:28 site.css
-rw-rw-r--. 1 bgstack15 bgstack15  39108 Sep 13  2023 XRXI3I6Li01BKofiOc5wtlZ2di8HDFwmdTQ3ig.ttf

Cruddy, and silly, but it works. Now I get the nice fonts without telling Google every time I visit the admin console.

Nexus dashboard with nice fonts

gksu: Turn warning back on

Problem

Gksu (yes, the long-gone program throws a notice message if you have sudo NOPASSWD access, indicating you were able to do this operation without a password/authentication. And if you check the box to hide this notice in the future, you won't ever see again.

Solution

Use gconftool-2 to control it. Apparently the gksu uses the slightly-less-deprecated gconf idea (binary registry, sounds familiar? And now replaced with dconf which is different somehow?).

Show the current status.

gconftool-2 --get /apps/gksu/display-no-pass-info

Turn the warnings back on.

 gconftool-2 --set /apps/gksu/display-no-pass-info --type boolean true

Backstory

I was checking on one of my favorite utilities, xdgmenumaker, and it is authored by a guy named gapan. Well, one of his other pinned repositories is salixtools-gtk, which had a nifty user management program. I was setting up a system for an offsite user who will have to manage local users, and I found salixtools-gtk's gtkusersetup program really nice.

I learned that it takes a user with access to /etc/shadow so either a member of Debian group shadow, or some setgid business, or sudo. Or, as the .desktop file for that project suggets, gksu. Which I had forgotten about; I don't normally use root functions from a graphical environment so hadn't needed it. But of course all good things must have ended before I came around, because now "they" want you to use policykit or other loennart-level dumb things.

I got really scared when I couldn't find a ~/.config file for gksu anywhere. Come to find out it's buried in some binary thingy (worse than the xfce4 xml files that only update upon a normal exiting of xfce4) that you can't just use Unixy tools to control. I forget exactly how I learned it was gconf. I think I used strace to read what it was doing or maybe got lucky examining other possible places on my $HOME filesystem. I shouldn't have to search so hard.

Crop screenshots to same window area

I had problems getting exact screenshots of some work, and was merely able to get full desktop screenshots. So I needed to crop to just the relevant portion. In my case, I wanted an 800x600 picture, from my second monitor (first being the left monitor, and second being the right monitor), starting at 1920+560 pixels by 240 pixels.

for word in *png ; do convert "${word}" -crop 800x600+2480+240 foo/${word} ; done

Really small, and dumb, but I'll need this again someday.

References

  1. Cropping Images using Command Line Tools Only - Ask Ubuntu

CEP/CES enrollment with UsernamePassword auth from Linux to ADCS

If you have GNU/Linux clients that need to use CEP/CES with UsernamePassword auth (because kerberos is unavailable for bad reasons), you can use my new project cepceslib.

It's not better than using proper kerberos auth, but it serves a niche my users came across.

Read the readme, but the basic usage is:

CESURL="https://ces.example.com/Example%20CA%20Name_CES_UsernamePassword/service.svc/CES" KEYFILE=example.key CSRFILE=example.csr CESPASSWORDFILE=~/.config/user1 CESUSER=sa839 CERTFILE=example.pem TEMPLATE="WebServer" CN="example1.example.com" SANS="san1.example.com,san2.example.com" ./cepceslib.sh use_ces

Which will generate the private key and public certificate to the files indicated.

This newer tech was much easier to use, even with the hidden invisible magic values of xml needed, than my crusty certreq. I'm very proud of certreq, and it served my needs then, but with the modern way of disposing of old (and presumably insecure) tech, I got to write a new client, and it was way smaller and easier!