Knowledge Base

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

My Fedora 39 kickstart process

Clearly I'm getting lazy because I've missed Fedora 38 and 36. I use Devuan Ceres for all desktop systems on my network, and Fedora doesn't get the attention it used to deserve.

files/2023/12/listings/fc39x-ks.cfg (Source)

# File: /mnt/public/Support/Platforms/Fedora/fc39x-ks.cfg
# Locations:
#    /mnt/public/Support/Platforms/Fedora/fc39x-ks.cfg
# Author: bgstack15
# Startdate: 2017-08-16
# Title: Kickstart for Fedora 39 xfce for ipa.internal.com
# Purpose: To provide an easy installation for VMs and other systems in the Internal network
# History:
#    2017-06 I learned how to use kickstart files for the RHCSA EX-200 exam
#    2017-08-08 Added notifyemail to --extra-args
#    2017-11-01 major revision to use local mirror
#    2017-11-04 converted for building directly into an iso file
#    2017-11-15 fedora 27
#    2018-05-05 fedora 28
#    2018-07-08 adjusted to use --network type=bridge,source=br0 instead of type=direct,source=eno1
#    2018-12-01 fedora 29
#    2019-05-05 fedora 30
#    2020-02-20 fedora 31
#    2020-05-05 fedora 32
#    2020-12-02 fedora 33
#    2021-05-04 fedora 34
#    2022-03-28 fedora 35
#    2022-12-08 Fedora 37
#    2023-12-09 Fedora 39
# Usage with virt-install:
#    vm=fc39x-01a ; time sudo virt-install -n "${vm}" --memory 2048 --vcpus=2 --os-variant=fedora32 --accelerate -v --disk path=/var/lib/libvirt/images/"${vm}".qcow2,size=30 -l /mnt/public/Support/SetupsBig/Linux/Fedora-Everything-netinst-x86_64-39-1.5.iso --initrd-inject=/mnt/public/Support/Platforms/Fedora/fc39x-ks.cfg --extra-args "inst.ks=file:/fc39x-ks.cfg SERVERNAME=${vm} NOTIFYEMAIL=bgstack15@gmail.com" --debug --network type=bridge,source=br0 --noautoconsole
#    vm=fc39x-01a; sudo virsh destroy "${vm}"; sudo virsh undefine --remove-all-storage "${vm}";
# Reference:
#    https://sysadmin.compxtreme.ro/automatically-set-the-hostname-during-kickstart-installation/
#    /mnt/public/Support/Platforms/CentOS7/install-vm.txt
#platform=x86, AMD64, or Intel EM64T
#version=DEVEL
# Install OS instead of upgrade
#install
# Keyboard layouts
keyboard --vckeymap=us --xlayouts=''
# Root password
rootpw --plaintext plaintextexamplepw
# my user
user --groups=wheel --name=bgstack15-local --password=$6$.gh9u7vg2HDJPPX/$g3X1l.q75fs7iEXAMPLE1F2EIo1YSGGj/1DGeUzzEXAMPLEh4of6iNYWyxws/EXAMPLEgETqOsYFI5XNrkaUe. --iscrypted --gecos="bgstack15-local"
# System language
lang en_US.UTF-8
# Firewall configuration
firewall --enabled --ssh
# Reboot after installation
reboot
# Network information
#attempting to put it in the included ks file that accepts hostname from the virsh command.
#network  --bootproto=dhcp --device=eth0 --ipv6=auto --activate
%include /tmp/network.ks
# System timezone
timezone America/New_York --utc
# System authorization information
#auth  --useshadow  --passalgo=sha512
# Use network installation instead of CDROM installation media
url --url="https://www.example.com/mirror/fedora/linux/releases/39/Everything/x86_64/os/"
# Use text mode install
text
# SELinux configuration
selinux --enforcing
# Prepare X to run at boot
xconfig --startxonboot
# Use all local repositories
# Online repos
repo --name=internalrpm --baseurl=https://www.example.com/internal/repo/rpm/
repo --name=fedora --baseurl=https://www.example.com/mirror/fedora/linux/releases/$releasever/Everything/$basearch/os/
repo --name=updates --baseurl=https://www.example.com/mirror/fedora/linux/updates/$releasever/Everything/$basearch/
repo --name=rpmfusion-free --baseurl=https://www.example.com/mirror/rpmfusion/free/fedora/releases/$releasever/Everything/$basearch/os/
repo --name=rpmfusion-free-updates --baseurl=https://www.example.com/mirror/rpmfusion/free/fedora/updates/$releasever/$basearch/
repo --name=copr-bgstack15-stackrpms --baseurl=https://www.example.com/mirror/copr-bgstack15-stackrpms/fedora-$releasever-$basearch/
repo --name=copr-bgstack15-aftermozilla --baseurl=https://www.example.com/mirror/copr-bgstack15-aftermozilla/fedora-$releasever-$basearch/
repo --name=fedora-cisco-openh264 --baseurl=https://www.example.com/mirror/fedora-cisco-openh264/$releasever/$basearch/os/
firstboot --disabled
# System bootloader configuration
bootloader --location=mbr
# Partition clearing information
clearpart --all --initlabel
# Disk partitioning information
autopart --type=lvm
%pre
echo "network  --bootproto=dhcp --device=eth0 --ipv6=auto --activate --hostname renameme.ipa.internal.com" > /tmp/network.ks
for x in $( cat /proc/cmdline );
do
   case $x in
      SERVERNAME*)
         eval $x
         echo "network  --bootproto=dhcp --device=eth0 --ipv6=auto --activate --hostname ${SERVERNAME}.ipa.internal.com" > /tmp/network.ks
         ;;
      NOTIFYEMAIL*)
         eval $x
         echo "${NOTIFYEMAIL}" > /mnt/sysroot/root/notifyemail.txt
;;
   esac
done
cp -p /run/install/repo/ca-ipa.internal.com.crt /etc/pki/ca-trust/source/anchors/ 2>/dev/null || :
wget http://www.example.com/internal/certs/ca-ipa.internal.com.crt -O /etc/pki/ca-trust/source/anchors/ca-ipa.internal-wget.com.crt || :
update-ca-trust || :
%end
%post
{
   set -x
   # Set temporary hostname
   #hostnamectl set-hostname renameme.ipa.internal.com;
   # Get local mirror root ca certificate
   wget http://www.example.com/internal/certs/ca-ipa.internal.com.crt -O /etc/pki/ca-trust/source/anchors/ca-ipa.internal.com.crt && update-ca-trust
   # Get local mirror repositories
   wget https://www.example.com/internal/repo/rpm/set-my-repos.sh --output-document /usr/local/sbin/set-my-repos.sh ; chmod +x /usr/local/sbin/set-my-repos.sh ; sh -x /usr/local/sbin/set-my-repos.sh
   #dnf -y remove dnfdragora ;
   #dnf clean all ;
   #dnf update -y ;
   # Remove graphical boot and add serial console
   sed -i -r -e '/^GRUB_CMDLINE_LINUX=/{s/(\s*)(rhgb|quiet)\s*/\1/g;};' -e '/^GRUB_CMDLINE_LINUX=/{s/(\s*)\"$/ console=ttyS0 console=tty1\"/;}' /etc/default/grub
   grub2-mkconfig > /boot/grub2/grub.cfg
   systemctl enable sendmail.service && systemctl start sendmail.service
   # Send IP address to myself
   thisip="$( ifconfig 2>/dev/null | awk '/Bcast|broadcast/{print $2}' | tr -cd '[^0-9\.\n]' | head -n1 )"
   {
      echo "${SERVER} has IP ${thisip}."
      echo "system finished kickstart at $( date "+%Y-%m-%d %T" )";
   } | $( find /usr/share/bgscripts/send.sh /usr/bin/send 2>/dev/null | head -n1 ) -f "root@$( hostname --fqdn )" \
      -h -s "${SERVER} is ${thisip}" $( cat /root/notifyemail.txt 2>/dev/null )
   # Ensure boot to runlevel 5
   systemctl set-default graphical.target
   # fix the mkhomedir problem
   systemctl enable oddjobd.service && systemctl start oddjobd.service
   # Personal customizations
   mkdir -p /mnt/bgstack15 /mnt/public
   #su bgstack15-local -c "sudo /usr/share/bgconf/bgconf.py"
   tf=/etc/cron.d/01_init.cron
   touch "${tf}" ; chown root.root "${tf}" ; chmod 0600 "${tf}"
   cat <<-"EOFCRON" 1>"${tf}"
@reboot         root    su bgstack15-local -c "sudo /usr/bin/bgconf.py" 1>/root/clone.log 2>&1 ; rm -f /etc/cron.d/01_init.cron 1>/dev/null 2>&1 ; systemctl restart lightdm 1>/dev/null 2>&1 ;
EOFCRON
} 2>&1 | tee -a /root/install.log
%end
%packages
@core
@^xfce-desktop-environment
@xfce-apps
@xfce-media
autossh
bc
bgconf
bgscripts
bgscripts-core
bind-utils
cifs-utils
cryptsetup
-dnfdragora
-dnfdragora-updater
dosfstools
expect
-firefox
firewalld
freeipa-client
git
-gstreamer1-plugins-ugly*
-hplip
iotop
librewolf
lightdm-gtk
locale-en_BS
mailx
man
net-tools
newmoon
nfs-utils
numix-icon-theme-circle
p7zip
parted
plocate
python3-policycoreutils
qemu-guest-agent
rpm-build
rsync
scite
screen
sendmail
spice-vdagent
strace
sysstat
tcpdump
telnet
-thunderbird
vim
vlc
wget
xdg-themes-stackrpms
xfce4-whiskermenu-plugin
xrandr
%end

I learned that I absolutely need --vcpus=2 now. I also needed this new fedora-cisco-openh264 repository defined during installation, which I didn't need in the past.

My logic that emails me the IP address doesn't work anymore, so I need to fix that at some point.

Mirror Fedora cisco-openh264 repository

So for some reason Fedora 39 VLC depends on something from the fedora-cisco-openh264 codecs repository for things, for no good reason. Considering the signing key is the same as the rest of Fedora, it sounds awfully official to me. Anyway, so I had to add this repo to my kickstart (coming up soon) for Fedora 39.

And since I like to use all-local resources for installing my systems, I had to mirror this repo. It took 7 seconds to run, and 30 minutes to research and learn the URL, find my utility for doing this, and preparing the config file.

If you wish to use yummirror.sh (also on gitlab) from my coprmirror project, you just need this config file.

files/2023/12/listings/yummirror-fedora-cisco-openh264-39-x86_64.conf (Source)

# vim: syntax=sh
# startdate: 2023-12-13-4 22:28 for fc39x-ks.cfg kickstart to be able to access this locally
# usage:
#    time sudo YUMMIRROR_CONF=/etc/installed/coprmirror/yummirror-fedora-cisco-openh264-39-x86_64.conf /etc/installed/coprmirror/yummirror.sh
# improve:
#    this can probably be replaced with a shell script that loops through maybe a manual list of fedora release numbers, and then generates the inurl,workdir and then runs yummirror with env vars instead of various .conf files.
logfile=~/log/coprmirror.$( date "+%FT%H%M%S" ).log
# if we leave yum vars in inurl, it will be interpreted by whatever system is running the yummirror task, and not by a pretend version of the desired OS.
#inurl='https://codecs.fedoraproject.org/openh264/$releasever/$basearch/os/'
inurl='https://codecs.fedoraproject.org/openh264/39/x86_64/os/'
workdir=/mnt/mirror/fedora-cisco-openh264/39/x86_64/os/
thisuser=copruser
VERBOSE=1
DEBUG=1
# skip .src.rpms
include_sources=no
# resign is not implemented anyways
resign_repo=no
# because gpgkey is just a file: for a basic fedora signing key that is already here, just leave it undefined so yummirror will skip it.
#gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch

And in case gitlab/cgit goes away, here's the listing for yummirror script itself.

files/2023/12/listings/yummirror.sh (Source)

#!/bin/sh
# File: yummirror.sh
# Location: https://gitlab.com/bgstack15/coprmirror
# Author: bgstack15
# Startdate: 2021-08-16 09:57
# SPDX-License-Identifier: GPL-3.0
# Title: Script that copies a single copr yum repo
# Project: coprmirror
# Purpose: mirror copr locally, given baseurl; part of a larger set
# History:
# Usage:
#    Called by a larger script that catches the entire copr. This file only does a single yum repo for a single architecture and OS release. See coprmirror.sh
# References:
#    https://unix.stackexchange.com/questions/19701/yum-how-can-i-view-variables-like-releasever-basearch-yum0
#    https://gitlab.com/bgstack15/former-gists/-/blob/master/obsmirror.sh/obsmirror.sh
# Improve:
#    resign_repo is not implemented yet.
# Dependencies:
#    jq, yum-utils, awk, sed, grep
umask 0002
test -n "${YUMMIRROR_CONF}" && . "${YUMMIRROR_CONF}"
test -z "${logfile}" && logfile=./yummirror.$( date "+%FT%H%M%S" ).log
test -z "${inurl}" && inurl='https://copr-be.cloud.fedoraproject.org/results/bgstack15/stackrpms/epel-7-$basearch/'
test -z "${workdir}" && workdir=/tmp/copr
test -z "${thisuser}" && thisuser=${USER}
# also use include_sources resign_repo DEBUG VERBOSE
exec 3>&1
show() {
    printf "%s" "${*}" 1>&3
}
reset_show() {
    printf "\r%s" "${*}" 1>&3
}
## Functions
get_file() {
   # call: get_file "${tu}" "${md5sum}" "absolute"
   ___tu="${1}"
   ___sum="${2}"
   ___abs="${3}"
   if test "${___abs}" = "absolute" ; then
      tn="$( basename "${___tu}" )"
   else
      tn="${___tu##${inurl}}"
   fi
   tf="${workdir}/${tn}" ; tf="$( readlink -m "${tf}" )"
   td="$( dirname "${tf}" )"
   test -d "${td}" || mkdir -p "${td}"
   gotten="skipped   "
   #printf '\n%s\n' "inside get_file ${@}, DRYRUN=${DRYRUN}"
   if test -z "${DRYRUN}" || test "${DRYRUN}" = "metadata";
   then
      if test -z "${___sum}" || test "$( sha256sum "${tf}" 2>/dev/null | awk '{print $1}' )" != "${___sum}" ;
      then
         test -n "${VERBOSE}" && show "retrieving ${___tu}" 2>/dev/null || :
         wget --content-disposition --no-verbose --quiet -O "${tf}" "${___tu}" && gotten=DOWNLOADED
      fi
   fi
   test -n "${VERBOSE}" && reset_show 2>/dev/null || :
   echo "${gotten} ${___tu} -> ${tf}"
}
## MAIN
# Interpret any yum vars in the inurl
if echo "${inurl}" | grep -qE '\$' ;
then
   echo "" | jq 1>/dev/null 2>&1 || { echo "Need jq to interpret yum vars in baseurl ${inurl}. Aborted." ; exit 1 ; }
   raw="$( python -c 'import yum, json; yb = yum.YumBase(); print json.dumps(yb.conf.yumvar, indent=2)' )"
   # validated on centos 7
   basearch="$( echo "${raw}" | sed -n '1!p' | jq '.basearch' | tr -d '"' )"
   releasever="$( echo "${raw}" | sed -n '1!p' | jq '.releasever' | tr -d '"' )"
   #echo "${inurl}" | awk -v "basearch=${basearch}" -v "releasever=${releasever}" '{gsub("\$basearch",basearch,$0);gsub("\$releasever",releasever,$0);print}'
   newurl="$( echo "${inurl}" | sed -r -e "s/\\\$basearch/${basearch}/g;" -e "s/\\\$releasever/${releasever}/g;" )"
   test -n "${DEBUG}" && {
      echo "Interpreting ${inurl} -> ${newurl}"
   } 1>&2
   inurl="${newurl}"
fi
# clean up trailing slashes
inurl="${inurl%%/}"
# sync to workdir
wget_verbose=--quiet
test -n "${VERBOSE}" && unset wget_verbose
{
   test "${DEBUG:-NONE}" = "FULL" && set -x
   echo "logfile=${logfile}"
   mkdir -p "${workdir}" ; cd "${workdir}"
   # This file lists the files that define a yum repo
   for word in repodata/repomd.xml ;
   do
      # coprmirror.sh will pass DRYRUN=metadata if it has anything set in dryrun, and we interpret "metadata" here, because we absolutely need the repomd.xml and associated files no matter what, even for a dry run.
      DRYRUN="${DRYRUN:+metadata}" get_file "${inurl%%/}/${word}"
   done
   # loop through the important files listed in that file.
   # read sha256sum, so we don't have to download these if we already have them.
   metadata_list="$( grep -iE '<checksum |href' repodata/repomd.xml | awk -F'"' '/checksum/{print $3} /href/{print $2}' | awk "/^>/{gsub(\"^>\",\"\",\$0);gsub(\"<.*$\",\"\",\$0);a=\$0;} /repodata/{print a,\$0}" )"
   echo "${metadata_list}" | while read sum word ;
   do
      DRYRUN="${DRYRUN:+metadata}" get_file "${inurl%%/}/${word}" "${sum}" 3>/dev/null
   done
   # go ahead and fetch the gpgkey if resign_repo=no
   if echo "${resign_repo}" | grep -qiE 'yes|\<y\>|1|true' ;
   then
      # do something
      echo "Resigning functionality not built yet. Skipping..."
   else
      if test -n "${gpgkey}" ;
      then
         # fetch the gpgkey
         # note: yum variable parsing not yet implemented here. I would need to turn the variable parsing into a function and call it here.
         # cannot use get_file because it uses a relative path evaluation
         #wget --output-file="${workdir}/pubkey.gpg" "${gpgkey}"
         get_file "${gpgkey}" "" "absolute"
      fi
   fi
   # COPR always provides a primary.xml.gz file, which lists the assets to download
   full_list="$( zgrep -iE 'checksum|href' $( grep -oiE 'href=.*primary.xml.gz.*$' repodata/repomd.xml 2>/dev/null | awk -F'"' '{print $2}' ; echo "none.XXXXXXX" ) 2>/dev/null | awk "/checksum/{gsub(\".*\\\">\",\"\",\$0);gsub(\"</.*\",\"\",\$0);a=\$0;} /href/{gsub(\".*=\\\"\",\"\",\$0);gsub(\"\\\"/.*\",\"\",\$0);print a,\$0;}" )"
   #echo "FULL_LIST=${full_list}"
   # protect against the "metadata only" run
   test "${DRYRUN}" = "metadata" && DRYRUN=1
   echo "${full_list}" | while read sum word ;
   do
      if echo "${word}" | grep -qiE "\.src\.rpm" ;
      then
         # if a srpm, only get it if user has set include_sources.
         if echo "${include_sources}" | grep -qiE 'yes|\<y\>|1|true' ;
         then
            get_file "${inurl}/${word}" "${sum}"
         fi
      else
         # always get all regular rpms and other non-.src.rpm files if any
         get_file "${inurl}/${word}" "${sum}"
      fi
   done
   chown -R "${thisuser}:$( id -G "${thisuser}" | awk '{print $1}' )" "${workdir}"
} 2>&1 | tee -a "${logfile}"

And now you have the entire (3-package) yum repository for local use.

script for fixing gray-on-gray icons december 2023 edition

I previously wrote about fixing the offending icons for audio volume notifications, and here are a few more files that need to be fixed in the Numix-Circle theme.

files/2023/12/listings/fix-icon-colors.sh (Source)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/bin/sh
# Startdate: 2023-12-11-2 18:46
# Purpose: fix gray-on-gray for icons that I use
# Reference:
#    1. https://bgstack15.ddns.net/blog/posts/2023/10/26/fixed-the-offending-icons-for-audio-volume-notifications/
# Improve:
# Documentation:
#    powerkit has a different problem. see fix-powerkit-icons.sh

# icons in notifications for changing audio volume
sudo sed -i.bup -r -e "s/fill:#ececec;/fill:#000000;/g;" $( readlink -f $( find /usr/share/icons \( -iname 'audio-volume-medium.*' -o -iname 'audio-volume-low.*' -o -iname 'audio-volume-high.*' -o -iname 'audio-volume-*mute*' \) ! -iname '*block*' -ipath '*Numix/*' ) | sort -u )

# tray icons for krb5-auth-dialog
sudo sed -i.bup -r -e "s/fill:#ececec;/fill:#000000;/g;" $( find /usr/share/icons/Numix -iname 'krb-no-valid-ticket*' -o -iname 'gtk-dialog-authentication-panel.*' | sort -u )

# tray icons for connman-gtk network manager
sudo sed -i.bup -r -e "s/fill:#ececec;/fill:#000000;/g;" /usr/share/icons/Numix/16/status/network-offline.svg /usr/share/icons/Numix/16/status/network-transmit-receive.svg

Found! Oneliner for nvidia display settings

tl;dr

I finally found a oneliner method for controlling which monitors are actively used for my NVIDIA graphics card after using it for years. Xrandr doesn't cut it when using nvidia.

nvidia-settings --assign "CurrentMetaMode=DPY-4: nvidia-auto-select @1920x1080 +1920+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}, DPY-1: nvidia-auto-select @1920x1080 +0+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}"

Of course the exact settings depend on your setup. You need all the contents after the colons, ::, from this command:

$ nvidia-settings -q CurrentMetaMode

  Attribute 'CurrentMetaMode' (pcb-009:0.0): id=50, switchable=no, source=nv-control :: DPY-4: nvidia-auto-select @1920x1080 +1920+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}, DPY-1: nvidia-auto-select @1920x1080 +0+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}

That's it. I've pondered this ease of use for years, and had actually started an xdotool wrapper but one final Internet search led to this great discovery.

File listings

I of course wrote a script that takes a limited input, "both" or "right" to use those monitors.

files/2023/12/listings/switch-monitors.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
#!/bin/sh
# File: switch-monitors.sh
# Location: /usr/local/bin
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0
# Startdate: 2023-12-05-3 16:15
# Title: Switch monitor config for NVIDIA graphics card
# Purpose: easily switch between 1 monitor and 2 monitors on pcb-009.
# History: attempt 2. The first was attemping to read nvidia-settings -q dpys and parsing enabled/connected, but I didn't know how to use the derived values
# Usage: Called by tray icon
# Reference:
#    https://wiki.archlinux.org/title/NVIDIA#Using_nvidia-settings
# Improve:
# Documentation:
#    Configure `nvidia-settings` as desired. Print metamode value with following command.
#    nvidia-settings -q CurrentMetaMode
#    Use everything after the `::`

test -z "${DESIRED}" && test -n "${1}" && DESIRED="${1}"
desired="$( echo "${DESIRED}" | tr 'A-Z' 'a-z' )"
case "${desired}" in
   "both"|"left+right"|"left,right","double")
      echo "Using both monitors."
      nvidia-settings --assign "CurrentMetaMode=DPY-4: nvidia-auto-select @1920x1080 +1920+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}, DPY-1: nvidia-auto-select @1920x1080 +0+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}"
      ;;
   "one"|"right"|"single")
      nvidia-settings --assign "CurrentMetaMode=DPY-4: nvidia-auto-select @1920x1080 +0+0 {ViewPortIn=1920x1080, ViewPortOut=1920x1080+0+0}"
      ;;
   *) echo "Unknown option: ${desired}. Aborted." ;
      exit 1 ;
      ;;
esac

And then I wrote a tray icon so it's easy to get to these.

files/2023/12/listings/switch-monitors-trayicon.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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/usr/bin/env sh
# File: switch-monitors-trayicon.sh
# Location: /usr/local/bin
# Author: bgstack15
# SPDX-License-Identifier: GPL-3.0
# Startdate: 2023-12-05-3 16:29
# Title: Trayicon for switching monitor config
# Purpose: Provide easy drop-down for pcb-009 for monitor control
# History:
# Usage:
# Reference:
#    Heavily ripped keyboard-leds-trayicons
# Improve:
#    There is no loop, so manually touching the kill file does not stop the process like it should.
# Dependencies:
#    raw: mktrayicon, awk, switch-monitors.sh
#    devuan: mktrayicon, mawk | gawk, x11-xserver-utils

# CONFIG FILES
test -z "${SWITCH_MONITORS_CONF}" && SWITCH_MONITORS_CONF="${HOME}/.config/switch-monitors.conf"

# FUNCTIONS

get_conf() {
   # Ripped from framework.sh
   # call: get_conf "${conffile}"
   local _infile="$1"
   local _tmpfile1="$( mktemp )"
   sed -e 's/^\s*//;s/\s*$//;/^[#$]/d;s/\s*[^\]#.*$//;' "${_infile}" | grep -viE "^$" | while read _line ;
   do
      local _left="$( echo "${_line}" | cut -d'=' -f1 )"
      eval "_thisval=\"\${${_left}}\""
      test -z "${_thisval}" && echo "${_line}" >> "${_tmpfile1}"
   done
   test -f "${_tmpfile1}" && { . "${_tmpfile1}" 1>/dev/null 2>&1 ; }
   /bin/rm -rf "${_tmpfile1}" 1>/dev/null 2>&1
}

clean_switch_monitors_trayicon() {
   { test -e "${switch_monitors_icon}" && echo "q" > "${switch_monitors_icon}" ; } 1>/dev/null 2>&1 &
   sleep 1 && rm -f "${switch_monitors_icon}" "${SMT_KILLFILE}"
}

# LOAD CONFIGS
# order is important! The last one called gets precedence.
# instead of simply dot-sourcing the conf file, pass it to get_conf which only applies new values, so this process's environment is preserved
for thisconf in "${SMT_CONF}" "${SMT_USER_CONF}" "${SMT_GLOBAL_CONF}" ;
do
   test -r "${thisconf}" && get_conf "${thisconf}"
done

# DEFAULTS in case configs did not have these values
test -z "${SMT_ICON}" && SMT_ICON=display
test -z "${SMT_KILLFILE}" && SMT_KILLFILE="/tmp/kill-all-switch-monitors-trayicons"

# INITIALIZATION

switch_monitors_icon="/var/run/user/$( id -u )/${$}.switch_monitors.icon"

mkfifo "${switch_monitors_icon}"
mktrayicon "${switch_monitors_icon}" &
{
   echo "i ${SMT_ICON}"
   echo "t pcb-009 easy display settings"
   echo "m right,switch-monitors.sh right|both,switch-monitors.sh both|-----|quit,echo 'q' > ${switch_monitors_icon} ; touch "${SMT_KILLFILE}""
} > "${switch_monitors_icon}"

rm -f "${SMT_KILLFILE}"

trap 'trap "" 2 ; touch "${SMT_KILLFILE}" '  2 # CTRL-C
wait %1

# safety shutoff
clean_switch_monitors_trayicon

References

Weblinks

  1. NVIDIA - ArchWiki#Using_nvidia-settings

Firefox proxy notes

Run SOCKS proxy

I normally use Firefox with a SOCKS proxy, which I run with:

ssh -D 8880 ${USER}@proxyserver -n -f -N

Configure firefox proxy

Visit about:preferences, search proxy (tab General -> heading Network Settings -> button Settings...), and choose Manual proxy configuration.

  • SOCKS host: localhost, port 8880

My No proxy for box includes:

192.168.1.0/24, 10.35.0.0/16, server1, server4, server1, server2, server3, server3, *.ipa.example.com

Firefox extension to make it easier

If I want to use an extension that adds the setting to the toolbar, I use Simple Proxy Toggle – Get this Extension for Firefox (en-US) by Strega. It doesn't manage its own settings; it merely lets you switch easily between the high-level Firefox settings.

I had to temporarily modify my chrome/userChrome.css file to the following:

#unified-extensions-button, #unified-extensions-button > .toolbarbutton-icon{
width: 60px !important;
padding: 5px !important;
}

So that that annoying puzzle piece button would appear, so I could then right-click the extension icon and make it apepar on the toolbar.

References

Weblinks

  1. Simple Proxy Toggle – Get this Extension for Firefox (en-US)

My blog

  1. socks-proxy-command
  2. firefox-111.0-fix-puzzle-piece-in-toolbar

Internal

  1. file:///mnt/public/Support/Programs/Browsers/firefox-proxy-notes.md

OS updates notes, December 2023

This month in Devuan ceres, usrmerge bit me in the rear, in multiple ways!

Mount.nfs

The main was that mount.nfs couldn't operate due to something not found in /bin. So I, using my extremely acute sense of logical deduction, had to go apt-get install usrmerge and delete a few random udev rules that are duplicated between /lib and /usr/lib or some baloney so it would install (well, dpkg-configure) correctly.

I saved this script as the filename indicated below, which of course is on my nfs server which is inaccessible until you solve the problem. So I had to copy-paste it into each ssh session to the affected (all) systems.

sudo rm -f /lib/udev/rules.d/99-libsane1.rules /lib/udev/rules.d/60-libsane1.rules /lib/udev/hwdb.d/20-sane.hwdb
sudo apt-get install -y usrmerge

And then mount.nfs works again.

/run is gone

One one particularly remote system (where I had to involve others to access a shell), the /etc/resolv.conf file which is symlinked to ../run/resolvconf/resolv.conf pointed to nowhere. There was no /run. The system was on its local network, but due to name resolution failures, it was unable to establish the vpn connection back to the Internal network. So we set up a hardcoded resolv.conf:

search ipa.example.com #useless with the following, though
nameserver 8.8.8.8
nameserver 8.8.4.4

I benefit from google's generosity.

So, then we got wireguard back up, wg-quick up wg0. And then I took over the rest of the efforts, which included the same copy-pasted instructions. And now I've left that hardcoded resolv.conf on server2, so that it will not fail in this manner again. Although I suppose it will never have to undergo that particular lobotomy again. Until I decide to risk changing it back, it'll never get the name resolution across the vpn like before. Ah, well.

References

Internal files

  1. file:///mnt/public/Support/Platforms/devuan/scripts/merge-usr.sh

resolv.conf when turning off wireguard vpn

This probably isn't the best way to handle things, but because I use both connman and wireguard on a laptop that I take on the road, I have this snippet in my /etc/wireguard/wg0.conf:

[Interface]
DNS = 192.168.1.1,192.168.1.2, ipa.internal.com, vm.internal.com, remote.internal.com, internal.com
PostUp = ln -sf /run/resolvconf/resolv.conf /etc/resolv.conf
PostDown = ln -sf /run/connman/resolv.conf /etc/resolv.conf

This forces my resolv.conf to use whatever resolvconf generates, which wireguard uses. And wireguard passes those non-IP address names as the search domains to resolvconf, and of course those nameserver entries.

I should probably bother to learn how to get connman to use resolvconf, or get it to use wireguard.

Further reading

Further research indicates that while Connman has wireguard support, it is incomplete/buggy and I will stick to my current methods of using it.

  1. Intel's ConnMan Is Ready With WireGuard Support - Phoronix sa.
  2. connman/connman.git - Connection Manager
  3. connman/connman.git - Connection Manager
  4. connman/connman.git - Connection Manager
  5. WireGuard - LibreELEC.wiki#known-issues

Convert PKCS7 to PEM in shell, python, powershell, and go

I wrote these functions to aid myself when working with some know PKCS7 blocks from which I wanted to extract the certificates.

Powershell

Function Convert-Pkcs7-To-Pem {
   <#
      Convert Pkcs7 format to list of base64 PEM blocks like `openssl pkcs7 -print_certs`. If importing from Get-Contents -Path file, be sure to do a -join "`n" as well.
      Reference: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/verify-pkcs7.html
   #>
   [CmdletBinding(SupportsShouldProcess)]
   Param(
      [Parameter(Mandatory=$True)]$inPkcs7
   )
   Process {
      Try {
         $inPkcs7 = Convert-FileContents-To-String -Verbose:$false -inItem $inPkcs7 -joiningElement "`n"
         Add-Type -AssemblyName System.Security
         $newPkcs7b64 = [Convert]::FromBase64String($inPkcs7.Replace("-----BEGIN CERTIFICATE-----","").Replace("-----END CERTIFICATE-----",""))
         $newCerts = [Security.Cryptography.Pkcs.SignedCms]::new()
         $newCerts.decode($newPkcs7b64)
         # output resembles the `openssl pkcs7 -print_certs`
         $newCerts.Certificates | ForEach { "subject=$($_.Subject)`nissuer=$($_.Issuer)`n$($_.ExportCertificatePem())" }
      }
      Catch {
          $_
      }
   }
}
Function Convert-FileContents-To-String {
   <#
      This safely converts a Get-Contents -Path (of type Object[] basetype System.Array) to string, and passes through a string.
   #>
   [CmdletBinding(SupportsShouldProcess)]
   Param(
      [Parameter(Mandatory=$True)]$inItem,
      [Parameter(Mandatory=$False)][string]$itemName = "input",
      [Parameter(Mandatory=$False)][string]$joiningElement = "\n"
   )
   Process {
      $outItem = $inItem
      $inItemType = $inItem.GetType().Name
      Switch ($inItemType) {
         "Object[]" {
             Write-Verbose "Fixing line endings of $($itemName) with -Join `"$($joiningElement)`"."
             $outItem = $inItem -join $joiningElement
         }
         "String" {
             Write-Verbose "$($itemName) is already a string."
         }
         default {
             Write-Warning "This `$inItem.GetType().Name is not configured: $inItemType"
         }
      }
      $outItem
   }
}

Shell

This is the canonical output and the basis for the generated output of the other functions here.

printf '%s' "${pkcs7}" | openssl pkcs7 -print_certs

Python

import cryptography
from cryptography.hazmat.primitives.serialization import pkcs7
from cryptography.hazmat.primitives import serialization

def ConvertPkcs7ToPem(newPkcs7):
   newPem = ""
   newCerts = pkcs7.load_pem_pkcs7_certificates(str.encode(newPkcs7))
   for eachCert in newCerts:
      newPem += eachCert.subject.rfc4514_string() + "\n"
      newPem += eachCert.issuer.rfc4514_string() + "\n"
      newPem += eachCert.public_bytes(serialization.Encoding.PEM).decode()
   return newPem

Go

The golang one took me the longest because I barely know what I'm doing with Go and the nuances of x509 versus pem. I know this code heavily depends on knowing that my input is only ever going to contain CERTIFICATE blocks. I don't know how one would prove which type of PEM block the bytes are, if you are dealing with unspecified input.

import (
   "encoding/pem"
   "fmt"
   "go.mozilla.org/pkcs7" // run `go get go.mozilla.org/pkcs7`
)

func ConvertPkcs7ToPem(inPkcs7 string) (string, error) {
   // Assume the string already has newlines handled correctly, for now
   // We know the entire possible contents of our pkcs7 is just certificates (and not private keys or a mix of various things)
   // Reference: make PEM block myself https://stackoverflow.com/questions/56074289/how-to-get-a-string-out-of-x509-certificate-public-key-in-go
   var outString string = "";
   pemAll, _ := pem.Decode([]byte(inPkcs7))
   p7new, err := pkcs7.Parse(pemAll.Bytes)
   if err != nil {
      return "FATAL during pkcs7.Parse", err
   }
   for _, x := range p7new.Certificates {
      allBlock := pem.Block{
         Type: "CERTIFICATE",
         Bytes: x.Raw,
      }
      allBlockPem := string(pem.EncodeToMemory(&allBlock))
      outString += fmt.Sprintf("subject=%s\n",x.Subject)
      outString += fmt.Sprintf("issuer=%s\n",x.Issuer)
      outString += fmt.Sprintf("%s\n",allBlockPem)
   }
   return outString, nil
}

aoe2de protocol handler for Linux

Overview

The url using a custom protocol aoe2de://1/12394872348 can be used by Age of Empires 2: Definitive Edition to directly join or spectate a game. This behavior can be established in GNU/Linux with some easy customization.

Making the protocol links work

  1. Modify xdg-open.

    By default, xdg-open does not recognize protocol aoe2de:// as a URL because of the digit. It treats the protocol as a file instead of a url, so I had to modify /usr/bin/xdg-open:

    sudo sed -i.bup -r -e '/grep -q.\*:alpha:.\*then/s/:alpha:/:alnum:/g;' /usr/bin/xdg-open
    

    Alternatively, you just need to modify the regular expression inside function to use alnum:

    # Returns true if argument is a file:// URL or path
    is_file_url_or_path()
    {
        if echo "$1" | grep -q '^file://' \
                || ! echo "$1" | egrep -q '^[[:alnum:]+\.\-]+:'; then
            return 0
        else
            return 1
        fi
    }
    
  2. Establish script to run AOEURLHelper.exe.

    I named it ~/bin/aoe2de-protocol.

    #!/bin/sh
    # Startdate: 2023-11-14-3 19:51
    # Author: bgstack15
    # Documentation:
    #    Because I use Proton Experimental, I use that path.
    #    813780 = steamid for Age of Empires 2 Definitive Edition
    STEAM_COMPAT_CLIENT_INSTALL_PATH=~/.steampath STEAM_COMPAT_DATA_PATH="${HOME}/.local/share/Steam/steamapps/compatdata/813780" ${HOME}/.local/share/Steam/steamapps/common/Proton\ -\ Experimental/proton runinprefix "${HOME}/.local/share/Steam/steamapps/compatdata/813780/pfx/drive_c/Program Files (x86)/AOE URL Helper/AOEURLHelper.exe" "${@}"
  3. Establish desktop file with mimetype handler.

    I named mine ~/.local/share/applications/aoe2de-protocol.desktop.

    [Desktop Entry]
    Name=AOEURLHelper
    Comment=Load url for Age of Empires 2: Definitive Edition
    Exec=aoe2de-protocol %u
    Icon=steam_icon_813780
    Terminal=false
    Type=Application
    Categories=Game;
    MimeType=x-scheme-handler/aoe2de;
    
  4. Ensure this desktop file is used with xdg-open.

    Run these commands.

    $ xdg-mime default aoe2de-protocol.desktop x-scheme-handler/aoe2de
    $ xdg-mime query default x-scheme-handler/aoe2de
    aoe2de-protocol.desktop
    
  5. Configure Firefox-based web browser to use this application for links of this protocol.

    In about:config, set a new boolean network.protocol-handler.expose.aoe2de = false. Then visit one of the lobby browsers (below) and find a join link. Select the link, and choose your new .desktop file.

    The first time, it failed. When I tried again, the web browser showed the second menu item named "AOEURLHelper" which is what I put in that "Name" field and it did invoke my shell script and then try to join the game.

Additional thoughts

The xdg-open aoe2de://0/123412341234 invocation works when the game is not already running, and when the game is already running. Of course, due to how long it takes the game to load, loading a aoe2de link loads faster if the game is already running.

Protocol description

This is not a specification because I don't know of any that has been published.

aoe2de://0/126811196

This link tries to join game id 126811196. You can learn your game id when you are the host, from the button in the upper-right of the lobby screen.

aoe2de://1/123498272

The top virtual directory /1/ indicates that the program should try to spectate the match.

Lobby browsers

If these are working at any given moment, here are a few that I found:

  1. Lobby Browser - Age of Empires 2 - AoE2 Insights
  2. AoE2.net

References

Weblinks

  1. xdg - Create a custom URL Protocol Handler - Unix & Linux Stack Exchange
  2. linux - Ubuntu custom URL protocol handler - Stack Overflow
  3. [RESOLVED] AoE2.net Spectate button no longer working. Someone please help me!!! | AoEZone - The International Age of Empires Community
  4. Running a second program inside a proton prefix. : linux_gaming
  5. Set custom protocol handler in Firefox? - Stack Overflow
  6. Register protocol - MozillaZine Knowledge Base

Web search

  1. steam run command in proton context
  2. firefox handle protocol

My blog

  1. Download voobly mods from browser

notification-daemon start from dbus

I use Fluxbox as my graphical desktop environment, and a hodgepodge of various applications to accomplish all the small pieces of using a desktop computer. I use notification-daemon, a small, generic utility for displaying notifications that one might send with:

notify-send "Hello world" --hint "STRING:action-icons:" acction "numlock-on=Yes" --action "numlock-off=No" --icon "battery-low" --expire-time 0

Yes, that is nonsensical. But it shows a few of the features. But if you run that when notification-daemon is not running, you get this error:

(notify-send:19364): libnotify-WARNING **: 17:33:59.890: Failed to connect to proxy
Actions are not supported by this notifications server. Displaying non-interactively.
GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.Notifications was not provided by any .service files

You can always just run /usr/lib/notification-daemon/notification-daemon & in ~/.fluxbox/startup like most people. I learned though, after examining the xfce4-notifyd package, that you really just need this file installed:

# File /usr/share/dbus-1/services/com.example.Notifications.service
[D-BUS Service]
Name=org.freedesktop.Notifications
Exec=/usr/lib/notification-daemon/notification-daemon

And then dbus, which is always running because I've had to embrace the darkness, always keeps notification-daemon running. I'm surprised this file wasn't included in the notification-daemon package, but I'm guessing it's a systemd/GNOME thing to always have that running and us edge case users are not considered a valid use case.