Knowledge Base

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

Lua script for mpv to keep xscreensaver from running

As noted in the file itself, this is adapted from MPV script that deactivates XScreensaver when video playback is active, and it was officially listed at User Scripts · mpv-player/mpv Wiki#lua-scripts

files/2024/listings/xscreensaver.lua (Source)

-- Ripped from https://gist.github.com/elenril/f8ff9475a7882b7a16cdd723c7dce150 and configured by bgstack15
-- linked at https://github.com/mpv-player/mpv/wiki/User-Scripts
-- this script periodically deactivates xscreensaver
-- when video playback is active
local function heartbeat()
    if mp.get_property_native("pause") or
       mp.get_property_native("idle")  or
       not mp.get_property_native("vo-configured") then
        return
    end
    mp.command_native_async(
        {
          name           = "subprocess",
          args           = { "xscreensaver-command", "-deactivate" },
          capture_stdout = true,
        },
        function () end)
end
mp.add_periodic_timer(10, heartbeat)
for _, prop in ipairs({"pause", "idle", "vo-configured"}) do
    mp.observe_property(prop, nil, heartbeat)
end

Tells mpv to keep running command xscreensaver-command -deactivate which simulates user activity. Ergo, it is designed actually for this use case.

You can place this file as ~/.config/mpv/scripts/xscreensaver.lua.

Query ldap CDP with ldapsearch

Quick and dirty note for manual inspection of the CRL distribution point stored in LDAP (so primarily for M$ use cases).

ldapsearch -LLL -o ldif-wrap=9000 -H ldap://example.corp -b "CN=CA Name V3,CN=hostname,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=corp" "(objectclass=cRLDistributionPoint)" -x -w "KEEPASS" -D "CN=Account,OU=Accounts,DC=example,DC=corp" certificateRevocationList | awk -F'::' '$1~/certificateRevocationList/{print $NF}' > ~/tmp1
{ printf '%s\n' '-----BEGIN X509 CRL-----' ; <~/tmp1 tr -d '\r\n ' | fold -w64 ; printf '\n%s' '-----END X509 CRL-----' ; } | openssl crl -in /dev/stdin -noout -text

References

  1. getting CRL from Active Directory using ldapsearch
  2. Understanding Certificate Revocation List using OpenSSL | by Arpana Gupta | Medium

Changing license of a project with sed

To change the license of a project I published, at the request of a friend:

sed -i -r -e 's/CC-BY-SA-4.0/LGPL-2.1-only/g;' $( grep -l -riIE 'SPDX-License-Identifier' | xargs grep -l -iE 'Author.?:.*bgstack15' )

If it's not obvious, for each file where SPDX-License-Identifier exists where I am the author, change the license name. Alanis-Morissettely, it was only the one file in the small project, but I only learned that when checking after the fact.

You're welcome, Plasma41.

New package for Devuan: stackrpms-acer-chromebook

I picked up a really old Acer Chromebook, and wanted to make sure my customizations are repeatable. Also, I needed to prove I can control the power button.

So, go download package stackrpms-acer-chromebook now! And of course, the source code is right nearby.

The fluxbox templates react to the keystroke for the power button. Also, I mapped the brightness and volume keys to CTRL+SHIFT, so I can still use the regular F6-F10 functionality.

To disable the default listening behavior of the power button and lid switch, I learned about elogind-inhibit, and I wrote a small program to use it. Just call this from the provided fluxbox/startup file (in /etc/stackrpms-acer-chromebook).

files/2024/listings/disable-powerbutton-daemon (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
#!/bin/sh
# File: /usr/bin/disable-powerbutton-daemon
# Location: stackrpms-acer-chromebook package
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0
# Startdate: 2024-04-29-2 20:22
# Title: Power Button Suppressor Daemon
# Project: disable-powerbutton
# Purpose: Runs elogind-inhibit so we can use the power button to run logout-manager
# History:
# Usage:
#   In ~/.fluxbox/startup:   disable-powerbutton-daemon &
#   Which enables this behavior to work:
#      In ~/.fluxbox/keys:   124 :Exec logout-manager-gtk
# Reference:
#    Enlightenment desktop, man elogind-inhibit
# Related:
#    /usr/libexec/stackrpms-acer-chromebook/disable-powerbutton-loop
#    /etc/stackrpms-acer-chromebook/fluxbox.keys
#    /etc/stackrpms-acer-chromebook/fluxbox.startup
#    /etc/default/disable-powerbutton
# Improve:
# Dependencies:
#    dep-devuan: logout-manager, elogind
# Documentation:
#    To run this manually:
#    elogind-inhibit --what shutdown:handle-suspend-key:handle-hibernate-key:handle-lid-switch:idle:sleep --who "stackrpms5" --why "i hate the power button" --mode=block '/bin/forever.sh'
DAEMON_NAME="disable-powerbutton"
test -f "/etc/default/${DAEMON_NAME}" && . "/etc/default/${DAEMON_NAME}"
test -f "/etc/sysconfig/${DAEMON_NAME}" && . "/etc/sysconfig/${DAEMON_NAME}"
test -z "${DP_WHAT}" && DP_WHAT="shutdown:sleep:idle:handle-power-key:handle-suspend-key:handle-hibernate-key:handle-lid-switch"
test -z "${DP_COMMAND}" && DP_COMMAND="/usr/libexec/stackrpms-acer-chromebook/disable-powerbutton-loop"
export DP_SAFETY_FILE
elogind-inhibit --what "${DP_WHAT}"--who "disable-powerbutton-daemon" --why "Let power button call logout-manager" --mode=block "${DP_COMMAND}"

That can use /etc/default/disable-powerbutton or not, because the included defaults are good enough.

It runs an infinite loop script:

files/2024/listings/disable-powerbutton-loop (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh
# File: /usr/libexec/stackrpms-acer-chromebook/disable-powerbutton-loop
# Location: stackrpms-acer-chromebook package
# Author: bgstack15
# Startdate: 2024-04-29-2 19:56
# Title: Sleep forever
# Project: disable-powerbutton
# Purpose: Sleep forever until a safety file exists
# History:
# Usage:
#    called by disable-powerbutton-daemon
# Reference:
# Related:
#    /usr/bin/disable-powerbutton-daemon
# Improve:
#    Probably should be a very small inotify loop
# Dependencies:
# Documentation:
test -z "${DP_SAFETY_FILE}" && DP_SAFETY_FILE=/tmp/stop-disable-powerbutton
while test ! -f "${DP_SAFETY_FILE}" ;
do
   sleep 900
done

convert old/ directory to git history

So one of my old ways, that I still use, of simple configuration management/history of a file is to bup (short for backup) it to an old/ directory right where the file is. That is, /etc/httpd/httpd.conf goes to /etc/httpd/old/httpd.conf.2024-05-09.01 for example.

You can find my bup script in the source of my bgscripts package.

Anyway, so now that I have years and years worth of changes to compare, of say my Devuan preseed file, but only stored in this old/ directory method, I wanted to be able to visualize some of the changes.

So I whipped up this script, and then threw the results into my cgit for myself.

files/2024/listings/convert-bup-to-git.sh (Source)

#/bin/sh
# File: convert-bup-to-git.sh
# Location: blog exclusive
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0-only
# Startdate: 2024-04-15-2 10:03
# Title: Convert Bup directory to git history
# Purpose: turn an old/ directory full of FILE.YYYY-MM-DD.[0-]{1,2} files into a git history so I can visualize with cgit
# History:
# Usage:
#    INDIR=/path/to/scripts/old
#    Optional:
#       WORKDIR if not set, will use ${INDIR}/to-git/
#       VERBOSE
#       INFILE if not set, will find most-used filename prefix in INDIR.
# Reference: https://bgstack15.ddns.net/blog/posts/2024/05/09/convert-old-directory-to-git-history
# Improve:
# Documentation:
#    This only works against one file exactly.
# Dependencies: git, find
test -z "${INDIR}" && test -n "${1}" && INDIR="${1}"
test -z "${INDIR}" && { echo "Fatal! Need INDIR or \$1 provided. Aborted." ; exit 1 ; }
# find the intended file if not provided
if test -z "${INFILE}" ;
then
   if test -n "${2}" ;
   then
      INFILE="${2}"
   else
      # find the intended file by counting the most common filename prefix
      # this YYYY-MM-DD format in the sed expression is from bup and thankfully exactly what I want to search for and I do not need a more general solution.
      # use an awk trick to count unique occurrences, and then sort, and then print the filename of the first one. If there is a comma in the filename that could be a problem but thankfully I avoid those in general, and particuarly for source code files such as what I would want to convert to an scm history.
      INFILE="$( find "${INDIR}" -maxdepth 1 -mindepth 1 ! -type d -printf '%f\n' | sed -r -e "s/\.[0-9]{4}(-[0-9]{2}){2}\.[0-9]{2}//" | awk 'BEGIN{OFS=","} !x[$0]++{} END{for(i in x)print x[i],i}' | sort -r -n -k1 | awk -F',' 'NR==1{print $NF}' )"
   fi
fi
# prepare the workdir
if test -z "${WORKDIR}" ;
then
   if test -n "${3}" ;
   then
      WORKDIR="${3}"
   else
      WORKDIR="${INDIR}/to-git"
   fi
fi
export WORKDIR
test -n "${VERBOSE}" && {
   echo "Using INDIR=${INDIR}, INFILE=${INFILE}, WORKDIR=${WORKDIR}" 1>&2
}
# main
# loop through all files that match
mkdir -p "${WORKDIR}" ; cd "${WORKDIR}" ; git init .
find "${INDIR}" -maxdepth 1 -mindepth 1 ! -type d -iname "${INFILE}*" -printf "%T@ %f\n" | sort -k1 -n | while read a b ;
do
   cp -pf "${INDIR}/${b}" "${INFILE}"
   git add "${INFILE}"
   GIT_AUTHOR_DATE="@${a}" GIT_COMMITTER_DATE="@${a}" git commit -m "changes for day"
done

It's not perfect, and I should bother to find a way to easily rewrite the whole history after adding useful commit comments, but today is not that day.

Devuan preseed, May 2024 edition

Overview

I wrote this post before the latest netinstall iso (which I usually get from https://mirror.leaseweb.com/devuan/devuan_daedalus/installer-iso/), so maybe the process is a little improved. But here is my current preseed file which allows the oneliner command:

vm=d2-05a ; time sudo virt-install -n "${vm}" --memory 2048 --vcpus=1 --os-variant=debiantesting -v --disk path=/var/lib/libvirt/images/"${vm}".qcow2,size=20 -l /mnt/public/Support/SetupsBig/Linux/devuan_daedalus_5.0.preview-20230116_amd64_netinstall.iso --initrd-inject=/mnt/public/Support/Platforms/devuan/preseed/preseed.cfg --extra-args "NOTIFYEMAIL=bgstack15@gmail.com interface=auto netcfg/get_hostname=${vm}" --debug --network type=bridge,source=br0

I really should increase that disk space because the large number of medium projects that I tinker with really interfers with my limited disk space.

Read more…

Virt-manager fails with packet bytes received from server too large

Recently, I had some problems connecting to one of my libvirt qemu hosts. When I ran virt-manager from my desktop which uses a qemu+ssh://root@vm3 connection with ssh key, I got this error:

Unable to connect to libvirt qemu+ssh://root@vm3/system.

packet 167772156 bytes received from server too large, want 33554432

Verify that the 'libvirtd' daemon is running on the remote host.

Libvirt URI is: qemu+ssh://root@vm3/system

Traceback (most recent call last): File "/usr/share/virt-manager/virtManager/connection.py", line 923, in _do_open self._backend.open(cb, data) File "/usr/share/virt-manager/virtinst/connection.py", line 171, in open conn = libvirt.openAuth(self._open_uri, ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/libvirt.py", line 147, in openAuth raise libvirtError('virConnectOpenAuth() failed') libvirt.libvirtError: packet 167772156 bytes received from server too large, want 33554432

After a lot of searching the Interwebs, I have learned the problem. It was hilarious and pointless.

I confirmed my ssh key still lets me ssh to root@vm3. (So sue me)

I confirmed libvirt is indeed running on the host (systemctl status -l libvirt) and noticed I had a bad image path, to a BSD/ directory I must have deleted on my network share. So I fixed that (already forgot the command but was probably virt edit-pool or something), but that didn't fix my issue today.

I logged to my good host (vm1) and bad host (vm3) just to tty and saw there's an additional newline when I log in to vm3. So I traced that down. In my .bashrc there was a command to run my "bash profile" /usr/bin/bp which I install via OS package on every system of mine. There's no reason it should be printing a newline. I had to set verbosity very high.

I eventually found that because file /var/spool/mail/root exists on vm3, it was printing the undefined variable $MAILMSG, and echo always throws in a newline.

Now, my root mail spool hasn't been updated since 2018, so I've had the one error message about something lame sitting there for 6 years, but it was only just now that virt-manager on Devuan Ceres fails to connect.

I'm guessing it has something to do with a newer version of virt-manager and older (unsupported now, apparently) version of libvirt on CentOS 7.

Make optionMenu with images in python tkinter

Here is another fantastic trick in tkinter: get images on an optionmenu (drop down, or combobox).

files/2024/listings/demo-optionmenu-with-images.py (Source)

#!/usr/bin/env python3
# Startdate: 2024-04-08-2 13:44
# Purpose: demo of the cool features of tkstackrpms
# Reference:
#    [python - Can I possibly put an image/label into an option box in Tkinter? - Stack Overflow](https://stackoverflow.com/questions/67334913/can-i-possibly-put-an-image-label-into-an-option-box-in-tkinter/67337760#67337760)
# demo 1: optionmenu with images and text
import tkinter as tk
import tkstackrpms as stk
class App(tk.Frame):
   def __init__(self, master):
      super().__init__(master)
      self.master.title("Demo of tkstackrpms features")
      imgicon = stk.get_scaled_icon("battery",24,"default", "","apps")
      self.master.tk.call("wm","iconphoto",self.master._w,imgicon)
      self.grid()
      self.options = ["Blue","Green","Yellow","Purple","Red"]
      # prepare set of images that corresponse to the options
      self.img_colors = []
      for i in self.options:
         t_image = tk.PhotoImage(width=16)
         t_image.put(i,to=(0,0,15,15))
         self.img_colors.append(t_image)
      self.opt_choice = tk.StringVar()
      self.opt_menu = tk.OptionMenu(self.master,self.opt_choice,*self.options)
      # indicatoron=False turns off the weird second-button item within the button.
      # minimize the padding so the box is not huge
      # takefocus allows navigation to this widget with Tab
      # compound means when an image is present, put it to the left of the text.
      self.opt_menu.config(indicatoron=False,padx=1,pady=1,takefocus=1,compound="left")
      self.opt_choice.set(self.options[0])
      self.opt_menu.grid(row=0,column=1)
      ow = self.opt_menu.nametowidget(self.opt_menu.menuname)
      x = 0
      for i in self.options:
         ow.entryconfigure(i,image=self.img_colors[x],compound="left")
         x += 1
      self.opt_choice.trace("w",self.set_selected_optmenu_entry)
      self.set_selected_optmenu_entry()
   def set_selected_optmenu_entry(self, arg1 = None, arg2 = None, arg3 = None):
      this_color = None
      x = 0
      for i in self.options:
         if self.opt_choice.get() == i:
            self.opt_menu.config(image=self.img_colors[x])
            break
         x += 1
# main
root = tk.Tk()
app_tk = App(root)
app_tk.mainloop()

So you have an optionMenu widget, where each option has an image associated with it. We're using very basic samples here; any real-world use of this trick would have a more complex list of images and options.

You can even set the front label of it to have an image, so you can view the currently selected image without having to open the drop-down.

Make flashing text box in python tkinter

One of the useful features of my recently-announced tkstackrpms python library is a function that flashes an entry widget (text box).

files/2024/listings/demo-flashing-entry.py (Source)

#!/usr/bin/env python3
# Startdate: 2024-04-08-2 13:44
# Purpose: demo of the cool features of tkstackrpms
# Reference:
#    [python - How to make a flashing text box in tkinter? - Stack Overflow](https://stackoverflow.com/questions/27533244/how-to-make-a-flashing-text-box-in-tkinter/27533595#27533595)
# demo: flashing entry widget
import tkinter as tk
import tkstackrpms as stk
class App(tk.Frame):
   def __init__(self, master):
      super().__init__(master)
      self.master.title("Demo of tkstackrpms features")
      imgicon = stk.get_scaled_icon("battery",24,"default", "","apps")
      self.master.tk.call("wm","iconphoto",self.master._w,imgicon)
      self.grid()
      # Variables
      self.name1 = tk.StringVar()
      self.name2 = tk.StringVar()
      self.name3 = tk.StringVar()
      # Widgets
      tk.Label(self.master,text="If an A is in a text box when you tab out or press Enter,\nit will flash to indicate there is a problem.").grid(row=0,column=0,columnspan=3)
      self.ent_name1 = stk.Entry(self.master,textvariable=self.name1,func=self.validate_inputs)
      self.ent_name1.grid(row=1,column=0)
      self.ent_name2 = stk.Entry(self.master,textvariable=self.name2,func=self.validate_inputs)
      self.ent_name2.grid(row=1,column=1)
      self.ent_name3 = stk.Entry(self.master,textvariable=self.name3,func=self.validate_inputs)
      self.ent_name3.grid(row=1,column=2)
   # Functions
   def validate_inputs(self, arg1 = None, arg2 = None, arg3 = None):
      if "a" in self.name1.get():
         stk.flash_entry(self.ent_name1,["red","white","blue","white","red","white","red","white"],333)
      if "a" in self.name2.get():
         stk.flash_entry(self.ent_name2,["red","white","blue","white","red","white","red","white"],333)
      if "a" in self.name3.get():
         stk.flash_entry(self.ent_name3,["red","white","blue","white","red","white","red","white"],333)
# main
root = tk.Tk()
app_tk = App(root)
app_tk.mainloop()

I haven't bothered to learn how to make an animated GIF showcasing this, sorry. I bet this logic could be hooked up to the function called by the invalidcommand parameter of the entry widget, but I've used this where I had complex logic that evaluated a whole form so I didn't try the per-widget stuff.