Knowledge Base

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

My Radicale improvements

I added to RadicaleInfCloud (the web interface InfCloud ostensibly configured to work with Radicale) a "Download" button which exports the current calendar item as a .ics file.

This patch also adds a stub button for importing. It operates by dropping a .ics file onto it. It doesn't actually load anything from the file yet; I don't have that functionality built yet. I could use guidance on that.

The patch:

diff --git a/radicale_infcloud/web/forms.js b/radicale_infcloud/web/forms.js
index 0e4c816..c733b6d 100644
--- a/radicale_infcloud/web/forms.js
+++ b/radicale_infcloud/web/forms.js
@@ -31,6 +31,35 @@ function updateTodoFormDimensions(setHeight)
    }
 }

+function dragOverHandler(event) {
+   //if (window.console) {
+   //  console.log("dragOverHandler",event);
+   //}
+   event.preventDefault();
+   event.stopPropagation();
+}
+
+/* Incomplete import process. The function logs to console but does not use the vcard contents. */
+function dropHandler(event) {
+   if (event) {
+       event.preventDefault();
+       event.stopPropagation();
+       if (window.console) {
+           console.log("dropHandler",event);
+       }
+       if(event.dataTransfer && event.dataTransfer && event.dataTransfer.files.length > 0) {
+           if(window.console){console.log("files",event.dataTransfer.files);}
+           for (let f of event.dataTransfer.files) {
+               if(window.console){console.log("file:",f)};
+               let reader = new FileReader();
+               reader.readAsText(f);
+               reader.onload = function(){if(window.console){console.log(reader.result)}};
+               reader.onerror = function(){if(window.console){console.log(reader.error)}};
+           }
+       }
+   }
+}
+
 function updateEventFormDimensions(setHeight)
 {
    $('#CAEvent').css('width','');
@@ -51,6 +80,7 @@ function setFormPosition(jsEvent, confirmRepeat)
    dist_y;

    $('#event_details_template').css('max-height','');
+   document.getElementById('uploadButton').addEventListener("drop", dropHandler(event));

    if(jsEvent)
    {
@@ -2316,6 +2346,7 @@ function showEventForm(date, allDay, calEvent, jsEvent, mod, repeatOne, confirmR
    if(mod=='show')
    {
        $('#saveButton').hide();
+       $('#uploadButton').hide();
        $('#resetButton').hide();
        $('#deleteButton').hide();
        if($('#ResourceCalDAVList').find('[data-id="'+calEvent.res_id+'"]').hasClass("resourceCalDAV_item_ro"))
@@ -2622,6 +2653,22 @@ function bindEventForm()
        });
    });

+   $('#downloadButton').click(function(e){
+       if($('#uid').val()!='') {
+           var dlname = $('#name').val();
+           var dlhref = $('#uid').val().replace(/(:\/\/)\w*@/,"$1");
+           function downloadIt(){
+               if (dlname && dlname != '' && dlhref && dlhref != '') {
+                   var link = document.createElement('a');
+                   link.download = dlname;
+                   link.href = dlhref;
+                   link.dispatchEvent(new MouseEvent("click"));
+               }
+           }
+           downloadIt();
+       }
+   });
+
    $('#resetButton').click(function(){
        $('#event_details_template').find('img[data-type=invalidSmall]').css('display','none');
        var uid=$('#uid').val();
@@ -2828,6 +2875,7 @@ function startEditModeEvent()
    $('#saveButton').show();
    $('#resetButton').show();
    $('#deleteButton').show();
+   $('#uploadButton').show();
    $('#show').val('');
    $('#eventDetailsTable :input[disabled]').prop('disabled', false);
    $('#eventDetailsTable :input[type="text"]').prop('readonly', false);
diff --git a/radicale_infcloud/web/index.html b/radicale_infcloud/web/index.html
index a62ccb8..6a2deb5 100644
--- a/radicale_infcloud/web/index.html
+++ b/radicale_infcloud/web/index.html
@@ -631,6 +631,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
                                <input id="editOptionsButton" type="button" value="edit repeat" data-type="editOptions" />
                                <input id="resetButton" type="button" value="Reset" data-type="reset" />
                                <input id="closeButton" type="button" value="Cancel" data-type="cancel" />
+                               <input id="downloadButton" type="button" value="Download" data-type="download"/>
+                               <input id="uploadButton" type="button" value="Import" data-type="upload" ondrop="dropHandler(event)" ondragover="dragOverHandler(event);"/>
                                <input id="deleteButton" type="button" value="Delete" data-type="delete" onclick="updateEventFormDimensions(true);$('#CAEvent .saveLoader').show();deleteEvent();" />
                            </td>
                        </tr>

You can see my work at my cgit or gitlab.

Radicale thoughts

In a wild and random activity that might help me leave Google someday, I decided to reinvestigate radicale. I had never set it up before; just read a lot about it. Now it was time to stand it up on a pre-production server.

Compiling radicale3 for CentOS 7

Radicale3 can be compiled for centos 7 just fine! You need to compile some of the python3 dependencies for it, which you can adapt with minimal changes from the fedora rpm sources.

Use https://src.fedoraproject.org/rpms/radicale for git clone sources. radicale needs custom python36 packages for:

  • python36-passlib
  • python36-vobject >= 0.9.6

And radicale.spec run 's/policycoreutils-python-utils/policycoreutils-python/g'

python36-passlib needs:

  • python3-nose
  • needs python36-bcrypt installed; it's already available but not in the dep list?

python3-nose needs, and then built just fine with no changes

  • python3-coverage

python3-coverage built just fine, no changes.

python36-vobject needs:

  • to be adjusted to use python36-dateutil package name

radicale application needs python36-dateutil>=2.7.3, which therefore needs to be built.

python-dateutil needs:

  • to be adjusted to use python36 buildreq names, and customized to disable building docs. Fairly simple spec modifications.

Radicale can then be installed from the rpms that you built:

$ sudo yum install ~/rpmbuild/RPMS/noarch/radicale3-*.rpm ~/rpmbuild/RPMS/noarch/python36-radicale3-3.1.7-30.el7.noarch.rpm
Installed:
  python36-radicale3.noarch 0:3.1.7-30.el7   radicale3.noarch 0:3.1.7-30.el7   radicale3-httpd.noarch 0:3.1.7-30.el7  
  radicale3-selinux.noarch 0:3.1.7-30.el7

Running radicale

I have not tested any authentication on my radicale server yet. I went out and exported my entire google contacts into the .vcf format (labeled "vCard for iOS Contacts"). I used the web interface of radicale to sign in with a blank or any password (no auth yet, remember?). I then tried to upload my 80KB .vcf file. It fails, and the server logs indicate that FN cannot be blank. FN is first name, and I had to manually poke around my .vcf file to find where I had a blank first name.

$ awk 'BEGIN{c=0} /FN:/{a=a+1} /END:VCARD/{b=b+1;if(b>(a+c)){c=c+1;print NR}} END{print a,b}' < contacts.vcf 
2506
2557
390 392

So these were the line numbers where END:VCARD occurred without a preceding FN: field. So I had two contacts (which I discovered after I had found and finally fixed the first one). I would actually consider this a bug in radicale, but after investigating the github issues for the app repo, it's related to that Radicale conforms to spec and the spec says a Vcard must have a FN field. So at least now I've fixed those two contacts!

I then set up RadicaleInfCloud which provides a web interface to the calendars and contact book. It looks really slick, but I haven't tested all my use cases yet.

Next steps

Investigate these use cases

  1. Can I import an invitation, say from my email, e.g., click the .ics invitation to an event, and add it to my calendar?
  2. Can I export an invitation, probably in .ics format, to send to others so they can add my event to their calendar?

If I go to production with this solution: 1. build copr repo with these customized packages! 2. install radicale with (ldap) authentication 3. install radicale_infcloud with auth

Experiencing Xubuntu 20.04.3 live

A friend of mine has an old, Windows Vista-era consumer-level computer that does somehow have 6 GB of RAM. The system is running Windows 10, extremely slowly. My friend asked me for advice about what sort of computer to replace it, or what could possibly be done to the existing computer.

I brought over a Xubuntu 20.04.3 live image flashed to a 2GB usb drive. The first thing we noticed is that Xfce is way more responsive than the Windows 10 environment on that exact hardware.

The first thing my friend wanted to test was the software from TeachingTextbooks.com. I've never heard of it before; it's a elementary school-focused site and software. The thick client is of course a thin wrapper that downloads the lesson assets, usually images and some videos. It gets installed from a .msi, which wine handles like a champ. The software itself thankfully runs just fine, too! (As always, I needed to add the i386 architecture to dpkg.)

Disney+ and Netflix both worked in official Mozilla Firefox on Ubuntu, which shocked me! I didn't dig into if this was already the snap version of Firefox; it doesn't matter to the user, of course.

We ran out of disk space in the live environment for the Minecraft installer. I'm quite confident that Minecraft java edition runs in Ubuntu. Although I confess, back when I played (probably pre-1.0) Minecraft last, it was running on my Windows Server 2008 server.

My friend is ordering an ssd and even more RAM, and when he gets those in the mail, we'll install the software for real and set up dual-boot. I suspect the hard drive itself is the limiting factor for the Windows installation. I had trouble reading parts of it even from my live Ubuntu environment. As much as I hate the CPUs from that era, that hard drive scared me more.

Dpkg recipe for NotepadNext

I saw an article on Hacker News about NotepadNext, a project that is a QT-based, cross-platform reimplementation of my beloved Notepad++. I couldn't find any dpkg implementations of NotepadNext, so I decided to take that mantle up for myself. Upstream wasn't interested in housing the debian directory because they want to use Flatpak or AppImage or some other short-term trendy package format.

Here is my debian/ for you.

The only noteworthy parts are in d/rules:

# Omit -Werror=format-security with -format here, because of LuaExtension.cpp weirdness, 2022-04-14
export DEB_BUILD_MAINT_OPTIONS = hardening=+all,-format

%:
    dh $@ --buildsystem=qmake --sourcedirectory=src/NotepadNext

override_dh_auto_build:
    mkdir build || :
    cd build ; pwd ; qmake6 ../src/NotepadNext.pro; make

override_dh_auto_clean:
    dh_auto_clean
    rm -rf build/ || :

Thanks to the AUR PKGBUILD for the guidance on the build steps!

Vimrc: adding timestamp functions

Just like my last post about an F5 timestamp function in scite, I wanted to apply the same functionality to Vim.

A friend of mine recently asked me, "What do you program in?" Turns out he meant IDE, and I said vim. I hardly ever work with compiled languages, so vim works great for me.

When first started in the Unix world, I swore to myself that I wouldn't work on a .vimrc because then I'd have to copy it everywhere, and sometimes I just don't have time or access to fling my ~/.vimrc file everywhere. But like an experienced Linux user, now I find myself building a larger and larger vimrc file.

Here's my timestamp functions for F5.

function! DateStamp()
   let a = strftime("%Y-%m-%d-") | let b = strftime("%w")+1 | let c = strftime(" %H:%M")
   execute "normal! i" . a . b . c
endfun
function! DateStampIsoUtc()
   let a = system("date -u +%FT%TZ")
   execute "normal! i" . a
endfun
" This is superfluous, but demonstrates command!
command! InsertDateStamp call DateStamp()
nmap <F5> i<C-O>:call DateStamp()<CR><Esc>:normal ll<CR><Esc>:<Esc>
imap <F5> <C-O>:call DateStamp()<CR><Esc>:normal l<CR>a
nmap <S-F5> i<C-O>:call DateStampIsoUtc()<CR><Esc>:normal ll<CR><Esc>:<Esc>
imap <S-F5> <C-O>:call DateStampIsoUtc()<CR><Esc>:normal l<CR>a
" Iso8601 but without timezone
nmap <C-F5> i<C-R>=strftime("%Y-%m-%dT%T")<CR><Esc>
imap <C-F5> <C-R>=strftime("%Y-%m-%dT%T")<CR>

Again, because I use a non-glibc-standard day of week numeral, my task is slightly longer than normal. But now I can press F5 in command or insert mode, and get a timestamp at the cursor!

Scite: new packages and useful lua scripts

In my olden days, I would use Notepad.exe on a non-free OS, and it had a nice feature where you could press F5 to insert the datestamp. I used to even implement that hotkey in Notepad++.

I finally bothered to research if I could implement this in scite. And, when you compile scite with lua enabled, you can use lua user scripts to accomplish this! I have built a new rpm (sources and made a new fork of the Debian dpkg (sources).

I fixed up a few of the function calls in the lua bits so the package can compile against lua 5.4.4! I also enabled lua in the rpm release of scite, because it was previously disabled.

So back to the topic of the user scripts, I included my lua script in my package directly, and added a few hotkeys in the SciTEGlobal.properties file.

ext.lua.startup.script=$(SciteDefaultHome)/stackrpms.lua
command.name.8.*=Emit &UTF8 Unicode
command.subsystem.8.*=3
command.8.*=emitUnicode
command.mode.8.*=savebefore:no
command.shortcut.8.*=Ctrl+U
command.name.9.*=Insert &Date
command.subsystem.9.*=3
command.9.*=InsertDate
command.mode.9.*=savebefore:no
command.shortcut.9.*=F5
command.name.10.*=Insert Date iso8601
command.subsystem.10.*=3
command.10.*=InsertDateIso
command.mode.10.*=savebefore:no
command.shortcut.10.*=Shift+F5

The logic in the lua script for the emit unicode was ripped 100% from http://lua-users.org/wiki/SciteUnicodeInput.

I spent some time tweaking my timestamp functions, and this is the result.

-- Reference: http://lua-users.org/wiki/SciteInsertDate
-- Tags used by os.date:
--   %a abbreviated weekday name (e.g., Wed)
--   %A full weekday name (e.g., Wednesday)
--   %b abbreviated month name (e.g., Sep)
--   %B full month name (e.g., September)
--   %c date and time (e.g., 09/16/98 23:48:10)
--   %d day of the month (16) [01-31]
--   %H hour, using a 24-hour clock (23) [00-23]
--   %I hour, using a 12-hour clock (11) [01-12]
--   %M minute (48) [00-59]
--   %m month (09) [01-12]
--   %p either "am" or "pm" (pm)
--   %S second (10) [00-61]
--   %w weekday (3) [0-6 = Sunday-Saturday]
--   %x date (e.g., 09/16/98)
--   %X time (e.g., 23:48:10)
--   %Y full year (1998)
--   %y two-digit year (98) [00-99]
--   %% the character '%'
function InsertDate()
  -- I use 1-7 for dow, 1=Sunday. How unfortunate that it is one off from glibc %w value.
  local dow = tostring(os.date("%w") + 1)
  local d1 = os.date("%Y-%m-%d-")
  local d2 = os.date(" %H:%M")
  editor:AddText(d1..dow..d2)
end
function InsertDateIso()
  local date_string = os.date("%Y-%m-%dT%T")
  editor:AddText(date_string)
end

And now, these functions are available on those hotkeys in any of my installations of scite!

Improvements to obsmirror

Previous posts on the topic of mirroring the OBS apt repos locally:

  1. Mirror an OBS deb repository locally
  2. Mirror an OBS repository locally -- update 1

I have improved the shell script that is the main obsmirror logic. I also split out the rebuild-apt-repo logic into its own script so I could call it separately.

#!/bin/sh
# File: /etc/installed/obsmirror.sh
# Location: https://gitlab.com/bgstack15/former-gists/tree/master/obsmirror.sh
# Author: bgstack15
# Startdate: 2020-03-03 08:43
# SPDX-License-Identifier: CC-BY-SA-4.0
# Title: Script that scrapes down OBS site to serve a copy to intranet
# Purpose: save down my OBS site so I can serve it locally
# History:
#    2020-01-05 v1: begin which used httrack
#    2020-02-28 v2: complete rewrite to exclude httrack
#    2020-03-03 v3: complete rewrite to get explicit files and loop through their contents, and rebuild apt repo
#    2020-03-13 add on-prompt notifications
#    2022-04-01 22:33
# Usage:
#    in a cron job: /etc/cron.d/mirror.cron
#       50  12  *   *   *   root    /etc/installed/obsmirror.sh 1>/dev/null 2>&1
# Reference:
#    https://software.opensuse.org//download.html?project=home%3Abgstack15&package=freefilesync
#    /mnt/public/www/internal/repo/devuan-deb/update-devuan-deb.sh
#    https://medium.com/sqooba/create-your-own-custom-and-authenticated-apt-repository-1e4a4cf0b864
#    https://unix.stackexchange.com/questions/113898/how-to-merge-two-files-based-on-the-matching-of-two-columns/113903#113903
#    sed|sed to get line numbers printed https://stackoverflow.com/questions/52882594/insert-line-numbers-into-file-with-sed/52884598#52884598
# Improve:
# Documentation:
#    Download the release key and trust it.
#       curl -s http://repo.example.com/mirror/obs/Release.key | apt-key add -
#    Use a sources.list.d/ file with contents:
#       deb https://repo.example.com/mirror/obs/ /
# Dependencies:
#    binaries: wget sed awk
#    user: obsmirror
umask 0002
exec 8>&0
test -n "${OBSMIRROR_CONF}" && . "${OBSMIRROR_CONF}"
test -z "${logfile}" && logfile="/tmp/var/log/obsmirror/obsmirror.$( date "+%FT%H%M%S" ).log"
test -z "${inurl}" && inurl="http://download.opensuse.org/repositories/home:/bgstack15/Debian_Unstable"
test -z "${workdir}" && workdir=/tmp/obs
test -z "${thisuser}" && thisuser=obsmirror
test -z "${tempdir}" && tempdir="$( TMPDIR="${TMPDIR:-/tmp}" mktemp -d )"
# also use include_sources resign_repo gpg_passfile gpg_keyfile DEBUG
exec 3>&1
show() {
    printf "%s" "${*}" 1>&3
}
reset_show() {
    printf "\r%s" "${*}" 1>&3
}
get_file() {
   # call: get_file "${tu}" "${md5sum}"
   ___tu="${1}"
   ___sum1="${2}" # nominally from the locally-generated Packages from previous run
   ___sum2="${3}" # from current set
   tn="${___tu##${inurl}}"
   tf="${workdir}/${tn}" ; tf="$( readlink -m "${tf}" )"
   td="$( dirname "${tf}" )"
   test -d "${td}" || mkdir -p "${td}"
   gotten="skipped   "
   if test -z "${DRYRUN}" ;
   then
      # determine if file is good enough
      ___matches_either=0
      ___md5sum_file="$( md5sum "${tf}" 2>/dev/null | awk '{print $1}' )"
      # sum1 check is disabled. If upstream obs rebuilds a package, we would never download the new package of the exact same name+version! So all my fancy sum1 logic is useless.
      #test "${___md5sum_file}" = "${___sum1}" && ___matches_either=1
      test "${___md5sum_file}" = "${___sum2}" && ___matches_either=1
      #test -n "${VERBOSE}" && printf "%s matches:%s\n" "${tn}" "${___matches_either}"
      if test -z "${___sum2}" || test "${___matches_either}" = "0" ;
      then
         # so the checksum is empty, or the given checksum does not match the existing downloaded file
         # use the Link headers because provo-mirror sucks and presents a lot of 404s.
         # if Links header does not exist then this list will be short.
         ___links="$( printf '%s\n%s\n' "${___tu}" "$( curl --head "${___tu}" --silent | awk '/Link:/ && !/type=/{print $2}' | tr -d '<>;' )" | sed -e '/^\s*$/d' )"
         test -n "${VERBOSE}" && show "retrieving ${___tu}" 2>/dev/null || :
         ___valid=0
         ___x=0
         while test ${___valid} -eq 0 && test ${___x} -lt $( echo "${___links}" | wc -l ) ;
         do
            ___x=$((___x+1))
            ___tl="$( echo "${___links}" | sed -n "${___x}p" 2>/dev/null )"
            test -n "${___tl}" && wget --content-disposition --no-verbose --quiet --output-document "${tf}" "${___tl}" && ___valid=1
            grep -qiE '404 Not Found' "${tf}" 2>/dev/null && ___valid=0
            ! test -s "${tf}" && ___valid=0
            ___matches_either=0
            ___md5sum_file="$( mdtsum "${tf}" 2>/dev/null | awk '{print $1}' )"
            #test "${___md5sum_file}" = "${___sum1}" && ___matches_either=1
            test "${___md5sum_file}" = "${___sum2}" && ___matches_either=1
            if test "${___matches_either}" = "1" ;
            then
               ___valid=1
            fi
         done
         test ${___valid:-0} -eq 1 && gotten="DOWNLOADED"
         test ${___valid:-0} -eq 0 && gotten='x FAILED   '
      fi
   fi
   test -n "${VERBOSE}" && reset_show 2>/dev/null || :
   test -n "${VERBOSE}" && echo "${gotten} ${___tu} -> ${tf}"
   #echo "PAUSED: " ; read -u8 foo
}
wget_verbose=--quiet
test -n "${VERBOSE}" && unset wget_verbose
{
   test "${DEBUG:-NONE}" = "FULL" && set -x
   echo "logfile=${logfile}"
   # These files define an apt repo
   # archive the Packages file, which might be generated locally by rebuild-apt-repo.sh the previous time, and might have more useful md5sums of the packages
   /bin/cp -pf "${workdir}/Packages" "${tempdir}/Packages.$$"
   for word in InRelease Packages Packages.gz Release Release.gpg Release.key Sources Sources.gz ;
   do
      get_file "${inurl}/${word}"
   done
   # loop through named packages and download them
   # extract these 2 fields, from both old and new Packages files. Combine them into one line. Sed|sed adds the line numbers so we can put the lines back in original order just because, and then sort.
   awk '/Filename:|MD5/{print $2}' "${tempdir}/Packages.$$" | xargs -n2 | sed '=' | sed 'N; s/\n/ /' | sort -k2 > "${tempdir}/old_list"
   awk '/Filename:|MD5/{print $2}' "${workdir}/Packages"    | xargs -n2 | sed '=' | sed 'N; s/\n/ /' | sort -k2 > "${tempdir}/new_list"
   #awk '/Filename:|MD5/{print $2}' "${workdir}/Packages" | xargs -n2 | while read word sum
   # The sort above was so join will work. Print these columns, sort by original line number, and then remove the lineno column with awk. Then split into these var names and process.
   join -j 2 -a 2 -o 2.1,2.2,1.3,2.3 "${tempdir}/old_list" "${tempdir}/new_list" | sort -k1 | awk '{$1="";print}' | while read word sum_old sum_new
   do
      get_file "$( echo "${word}" | sed -r -e "s@^\.@${inurl}@;" )" "${sum_old}" "${sum_new}"
      #echo "a=${a}   b=${b}"
   done
   # loop through dsc, orig.tar.gz, and debian.tar.xz files
   test -n "${include_sources}" && {
      for word in $( sed -n -r -e '/Files:/,/^\s*$/{/^ /p;}' ${workdir}/Sources | awk '{print $NF}' ) ;
      do
         get_file "${inurl}/${word}"
      done
   }
   test -n "${resign_repo}" && . /etc/installed/rebuild-apt-repo.sh
   chown -R "${thisuser}:$( id -G "${thisuser}" | awk '{print $1}' )" "${workdir}"
} 2>&1 | tee -a "${logfile}"
test -z "${NO_CLEAN}" && rm -rf "${tempdir:-NOTHINGTODEL}"

Observe the ___sum1 lines that are commented out. I wrote a lot of logic to support reading the old (previous run's locally-generated) Packages file and accepting the .deb checksums from it. I realized that due to how I rebuild the exact same version of a package (yes, I'm a terrible person and I know it), accepting the old Packages's checksum for a file means that it would never download the newer build of the exact same version-release number. So I disabled allowing it from the old Packages file, which entirely defeats the purpose of my byzantine logic to join the two Packages entries' together and comparing against both checksums for a given file.

Here is the rebuild-apt-repo.sh script.

#!/bin/sh
# Startdate: 2022-04-02 19:51
# Purpose: To rebuild apt repo, primarily for obsmirror operations
# Usage: Can be called by itself, with appropriate env vars, or from obsmirror.sh
#    ( . /etc/installed/obsmirror-cdemu.conf ; . /etc/installed/rebuild-apt-repo.sh ; )
# Dependencies:
#    environment vars: workdir, gpg_passfile, gpg_keyfile
#    On CentOS7, gnupg2 package that supports --pinentry-mode loopback, 2.2.18-2.el7 from @copr:bgstack15:el7-gnupg2-debmirror
#    /usr/bin/dpkg-scanpackages
# References:
#    obsmirror.sh
#    https://medium.com/sqooba/create-your-own-custom-and-authenticated-apt-repository-1e4a4cf0b864
# rebuild release files
echo "Rebuild apt repo in ${workdir}"
repodir="${workdir}"
cd "${repodir}"
dpkg-scanpackages -m . > Packages
gzip -9c < Packages > Packages.gz
# create the Release file
PKGS="$(wc -c Packages)"
PKGS_GZ="$(wc -c Packages.gz)"
old_headers1="$( grep -E '^(Archive|Codename|Origin|Label|Architectures):' Release )"
old_headers2="$( grep -E '^(Description):' Release )"
cat <<EOF > Release
${old_headers1}
Date: $(date -u '+%a, %d %b %Y %T %Z')
${old_headers2}
MD5Sum:
$(md5sum Packages  | cut -d" " -f1) $PKGS
$(md5sum Packages.gz  | cut -d" " -f1) $PKGS_GZ
SHA1:
$(sha1sum Packages  | cut -d" " -f1) $PKGS
$(sha1sum Packages.gz  | cut -d" " -f1) $PKGS_GZ
SHA256:
$(sha256sum Packages | cut -d" " -f1) $PKGS
$(sha256sum Packages.gz | cut -d" " -f1) $PKGS_GZ
EOF
test -e "${gpg_passfile}" && gpg --batch --yes --passphrase-file "${gpg_passfile}" --pinentry-mode loopback -abs -o Release.gpg Release
test -e "${gpg_passfile}" && gpg --batch --yes --passphrase-file "${gpg_passfile}" --pinentry-mode loopback --clearsign -o InRelease Release
# and because we are resigning it, replace Release.key with the one we used
test -e "${gpg_keyfile}" && /bin/cp -pf "${gpg_keyfile}" Release.key

To run this by itself, I recommend you run it in a sub shell, because you need to dot-source the conf file.

( . /etc/installed/obsmirror-cdemu.conf ; . /etc/installed/rebuild-apt-repo.sh ; )

And a reminder, the conf file looks like this:

# vim: syntax=sh
logfile="/var/server1/shares/public/Support/Systems/server1/var/log/obsmirror/obsmirror.$( date "+%FT%H%M%S" ).log"
inurl="http://download.opensuse.org/repositories/home:/bgstack15/Debian_Unstable"
workdir=/var/server1/shares/public/www/mirror/obs
include_sources=
resign_repo=yes
gpg_passfile=/root/.gnupg/linuxadmin
gpg_keyfile=/var/server1/shares/public/www/internal/repo/deb/internaldeb.gpg
thisuser=obsmirror
VERBOSE=1

My kickstart for Fedora 35 VMs

When I started this week's upgrades to my VM deployment logic, I had started with Fedora 36, but come to find out it's still in Beta. I had realized that the last time I worked on my Fedora vm installation task, it was last May and for version 34.

This is almost identical to my deployment of AlmaLinux 8, just for Fedora for which I include an Xfce desktop.

The kickstart file

# File: /mnt/public/Support/Platforms/Fedora/fc35x-ks.cfg
# Locations:
#    /mnt/public/Support/Platforms/Fedora/fc35x-ks.cfg
# Author: bgstack15
# Startdate: 2017-08-16
# Title: Kickstart for Fedora 35 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
# Usage with virt-install:
#    vm=fc35x-01a ; time sudo virt-install -n "${vm}" --memory 2048 --vcpus=1 --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-35-1.2.iso --initrd-inject=/mnt/public/Support/Platforms/Fedora/fc35x-ks.cfg --extra-args "inst.ks=file:/fc35x-ks.cfg SERVERNAME=${vm} NOTIFYEMAIL=bgstack15@gmail.com" --debug --network type=bridge,source=br0 --noautoconsole
#    vm=fc35x-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/scrubbedpasswdentrygoeshere --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/35/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://download.copr.fedorainfracloud.org/results/bgstack15/AfterMozilla/fedora-$releasever-$basearch/

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-scripts.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
-hplip
iotop
lightdm-gtk
librewolf
locale-en_BS
mailx
man
mlocate
newmoon
net-tools
nfs-utils
numix-icon-theme-circle
p7zip
parted
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
-gstreamer1-plugins-ugly*
%end

The narrative

This time, I had to troubleshoot my %post section many times until I learned that the yum commands were causing the disk to "remain online" which somehow causes a dbus error and prevents the OS installation from continuing (installing the bootloader, at least). I don't understand how a %post can happen before other things, but I haven't read the specs in a while.

One thing I am very appreciative of is that if my package list contains an unresolvable package, the installer will ask if I want to continue with that entry removed. I still needed to fix it, which in this case was the LibreWolf package available from my AfterMozilla copr and not my main one. I suppose I will need to mirror that copr locally too.

I now of course, use LibreWolf instead of Firefox. I also am now using my own custom locale-en_US glibc locale.

set-my-repos.sh

Set my repos has been improved to work out of the box on Fedora, CentOS 7, and AlmaLinux 8.

 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
75
#!/bin/sh
# File: /mnt/public/www/internal/repo/rpm/set-my-repos.sh
# Location:
# Author: bgstack15
# Startdate: 2019-08-10 16:02
# Title: Script that Establishes the repos needed for GNU/Linux
# Purpose: Set up the 3 repos I always need on devuan clients
# History:
#     2019-09-24 forked from devuan set-my-repos.
#     2022-03-18 changed from C8 to A8, changed if-then line #44 to not use ! and instead use : else
#     2022-03-28 adapted to work with Fedora, C7, or A8
# Usage:
#    sudo set-my-repos.sh
# Reference:
#    /mnt/public/Support/Platforms/devuan/devuan.txt
# Improve:
# Documentation:

ALLREPOSGLOB="/etc/yum.repos.d/*.repo"
REPOSBASE="/etc/yum.repos.d"

# confirm key
confirm_key() {
   # call: confirm_key "${SEARCHPHRASE}" "${URL_OF_KEY}"
   ___ck_repo="${1}"
   ___ck_sp="${2}"
   ___ck_url="${3}"
   if rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n' 2>/dev/null | grep -qe "${___ck_sp}" ;
   then
      :
   else
      # not found so please add it
      echo "Adding key for ${___ck_repo}" 1>&2
      wget -O- "${___ck_url}" | sudo rpm --import -
   fi
}

# confirm repo
confirm_repo_byurl() {
   # call: confirm_repo "${REPO_FILENAME}" "${REPO_FILE_URL}" "${SEARCHGLOB}"
   ___cr_repo="${1}"
   ___cr_url="${2}"
   ___cr_sf="${3}"
   # if we cannot find a file matching the requested name in the glob
   if ! grep -q -F -e "${___cr_repo}" ${ALLREPOSGLOB} 2>/dev/null ;
   then
   #   :
   #else
      # not found so please download it
      echo "Adding repo ${___cr_repo}" 1>&2
      wget -O- "${___cr_url}" --quiet >> "${REPOSBASE}/${___cr_sf}"
   fi
}

# MAIN
distro="$( . /etc/os-release ; echo "${ID}${VERSION_ID%%.*}" )"
# calculate copr repo filename
copr_d=fedora
grep -qiE 'ID=.*(almalinux|centos|redhat)' /etc/os-release && copr_d=epel
copr_v="$( . /etc/os-release ; echo "${VERSION_ID%%.*}" )"

# REPO 1: internal bundle
bundle_name="[baseos-internal]"
grep -qiE 'ID=.*(fedora)' /etc/os-release && bundle_name="[fedora-internal]"
confirm_repo_byurl "${bundle_name}" "https://www.example.com/internal/repo/mirror/internal-bundle-${distro}.repo" "internal-bundle-${distro}.repo"
# It is a good idea to run this too.
grep -oP "(?<=^\[).*(?=-internal])" /etc/yum.repos.d/internal-bundle-${distro}.repo | while read thisrepo; do yum-config-manager --disable "${thisrepo}"; dnf config-manager --disable "${thisrepo}" ; done

# REPO 2: local internalrpm
confirm_repo_byurl "[internalrpm]" "https://www.example.com/internal/repo/rpm/internalrpm.repo" "internalrpm.repo"
wget --continue "https://www.example.com/internal/repo/rpm/internalrpm.mirrorlist" --output-document "${REPOSBASE}/internalrpm.mirrorlist" --quiet

# REPO 3: copr
# yum will download key and ask for confirmation during first use.
confirm_repo_byurl "[copr:copr.fedorainfracloud.org:bgstack15:stackrpms]" "https://www.example.com/internal/repo/mirror/bgstack15-stackrpms-${copr_d}-${copr_v}.repo" "bgstack15-stackrpms-${copr_d}-${copr_v}.repo"

And the internal-bundle-fedora35.repo file:

# internal-bundle-fedora35.repo
# Install with:
# distro=fedora35 ; sudo wget https://www.example.com/internal/repo/mirror/internal-bundle-${distro}.repo -O /etc/yum.repos.d/internal-bundle-${distro}.repo && grep -oP "(?<=^\[).*(?=-internal])" /etc/yum.repos.d/internal-bundle-${distro}.repo | while read thisrepo; do sudo dnf config-manager --set-disabled "${thisrepo}"; done ; sudo wget https://www.example.com/internal/repo/mirror/RPM-GPG-KEY-rpmfusion-free-fedora-35 -O /etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-fedora-35

[fedora-internal]
name=Fedora $releasever - $basearch internal
#metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$basearch
#baseurl=http://download.fedoraproject.org/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/
baseurl=http://www.example.com/mirror/fedora/linux/releases/$releasever/Everything/$basearch/os/
enabled=1
metadata_expire=7d
repo_gpgcheck=0
type=rpm
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False

[updates-internal]
name=Fedora $releasever - $basearch - Updates internal
failovermethod=priority
#metalink=https://mirrors.fedoraproject.org/metalink?repo=updates-released-f$releasever&arch=$basearch
#baseurl=http://download.fedoraproject.org/pub/fedora/linux/updates/$releasever/Everything/$basearch/
baseurl=http://www.example.com/mirror/fedora/linux/updates/$releasever/Everything/$basearch/
enabled=1
repo_gpgcheck=0
type=rpm
gpgcheck=1
metadata_expire=6h
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False

[rpmfusion-free-internal]
name=RPM Fusion for Fedora $releasever - Free internal
#metalink=https://mirrors.rpmfusion.org/metalink?repo=free-fedora-$releasever&arch=$basearch
#baseurl=http://download1.rpmfusion.org/free/fedora/releases/$releasever/Everything/$basearch/os/
baseurl=http://www.example.com/mirror/rpmfusion/free/fedora/releases/$releasever/Everything/$basearch/os/
enabled=1
metadata_expire=14d
type=rpm-md
gpgcheck=1
repo_gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-fedora-$releasever

[rpmfusion-free-updates-internal]
name=RPM Fusion for Fedora $releasever - Free - Updates internal
#metalink=https://mirrors.rpmfusion.org/metalink?repo=free-fedora-updates-released-$releasever&arch=$basearch
#baseurl=http://download1.rpmfusion.org/free/fedora/updates/$releasever/$basearch/
baseurl=http://www.example.com/mirror/rpmfusion/free/fedora/updates/$releasever/$basearch/
enabled=1
type=rpm-md
gpgcheck=1
repo_gpgcheck=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rpmfusion-free-fedora-$releasever

LibreWolf 98.0 packages: problems resolved!

Last time I had a problem with building the rpm for LibreWolf 98.0.

The research about the PGO was the hint I needed. When I disabled PGO in the build, the application compiles in half the time, and works way better! I haven't even experienced a dead tab at all yet.

I don't understand how Profile Guided Optimization is supposed to help, when disabling it makes everything work better and compile faster. But what do I know?

Status of LibreWolf 98.0 packages

Now that Debian finally got around to getting Firefox 98.0, I had to get off my lazy rear end and start compiling LibreWolf 98.0 in my dpkg and rpm formats.

The dpkg is done! That one seems to behave well.

The rpm is not ready yet. I've run into this problem before, and it is plaguing me again. I don't recall how I dealt with this last time. Perhaps I didn't, because I'm still running LibreWolf 95.0 for myself on Fedora.

339:37.56 gmake: Leaving directory '/builddir/build/BUILD/firefox-98.0/objdir/instrumented'
console.warn: SearchSettings: "get: No settings file exists, new profile?" (new NotFoundError("Could not open the file at /tmp/tmp10udhqzo/search.json.mozlz4", (void 0)))
JavaScript error: data:text/html,<script>Quitter.quit()</script>, line 1: ReferenceError: Quitter is not defined
JavaScript error: resource://gre/modules/XULStore.jsm, line 66: Error: Can't find profile directory.
JavaScript error: resource://gre/modules/XULStore.jsm, line 66: Error: Can't find profile directory.
1648175948935   addons.xpi  ERROR   System addon update list error Error: got node name: parsererror, expected: updates
 !! Copr timeout => sending INT
WARNING: Machine d4f311a8ee7e47d693631f8e3ca38c59 still running. Killing...
ERROR: Build root is locked by another process.
Copr build error: Build failed

Possible research avenues

  1. Segfault when compiling Firefox with PGO : Gentoo
  2. 826954 – www-client/firefox-91.3.0 emerge fails with multiple errors, starting with: "NotFoundError("Could not open the file at /var/tmp/portage/www-client/firefox-91 .3.0/temp/tmph42x4w2v/search.json.mozlz4", (void 0)))"