Knowledge Base

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

Migration plan from Wordpress to nikola

Overview

In August 2021, I started implementing a new website powered by nikola static site generator to replace my wordpress site which I have run for over 5 years. My new site is hosted at https://bgstack15.ddns.net/blog/.

Architecture

Systems involved:

  • server1a: nikola site generator logic
  • doc7-01a: isso comment server and serves the output of nikola

Server1a runs the nikola logic, and then sends the contents up to doc7-01a which is the droplet. I exported the wordpress contents, and then nikola imported its parts, and isso imported its parts.

Building the Blog

Prepare server1

Created user blog and initialize nikola site generator.

sudo useradd blog -s /bin/bash
sudo usermod -a -G nginx blog
sudo su blog
cd
python3 -m venv nikola
cd nikola
source bin/activate
bin/python -m pip install -U pip setuptools wheel
bin/python -m pip install -U "Nikola[extras]"
bin/python -m pip install -U "html2text" # important for wordpress html-to-markdown

Modify ~blog/.bashrc with these contents at the end:

source ~/nikola/bin/activate
cd /mnt/public/Support/Programs/nikola/kb2

Prepare an ssh key for use to the service account blog on the web server.

ssh-keygen

Installation directions taken directly from the Getting started page of Nikola handbook.

Export Wordpress contents

To populate the blog with my existent contents, we need to extract them from Wordpress.

Export everything except media library

In Wordpress wp-admin, visit Tools -> Export: https://bgstack15.wordpress.com/wp-admin/export.php Select "All content" and then "Download Export file". It emails a link, and the output file is /mnt/bgstack15/Backups/Blog/bgstack15.wordpress.com-2021-08-29-18_22_23-yx1jpksw0pvvlunqoxnly4k3zsy7mtly.zip.

Export media library

For some reason it is incredibly difficult in Wordpress to export the media library. The page is https://wordpress.com/export/bgstack15.wordpress.com. Select Export media library -> Download. The file is saved as /mnt/bgstack15/Backups/Blog/media-export-77328224-from-0-to-2775_2021-08-26.tar.gz.

Import wordpress contents to nikola

Make a directory on server1 that will store all the source files for the blog.

mkdir /mnt/public/Support/Programs/nikola/kb2
chmod o=rwX kb2

Extract the xml file for import.

7za x bgstack15.wordpress.com-2021-08-29-18_22_23-yx1jpksw0pvvlunqoxnly4k3zsy7mtly.zip
mv bgstack15.wordpress.com-2021-08-29-18_22_19/*xml .
rmdir bgstack15.wordpress.com-2021-08-29-18_22_19/

Switch to user blog and then import the file.

sudo su blog
# the user profile should already run `source ~/nikola/bin/activate`
# the user profile should already cd /mnt/public/Support/Programs/nikola/
time nikola import_wordpress -o kb2 --squash-newlines --html2text --export-comments --one-file knowledgebase.wordpress.2021-08-29.001.xml

Manually modify conf.py, which is stored in /mnt/public/Support/Programs/nikola/initial/conf.py.

One of the changes in the file is the theme. I have customized the bootblog-jinja theme and made it my own, knowledgebase. Copy in /mnt/public/Support/Programs/nikola/initial/themes to the kb2/ path.

cp -pr /mnt/public/Support/Programs/nikola/initial/themes /mnt/public/Support/Programs/nikola/kb2/

Prepare the output directory.

OUTDIR=/mnt/public/Support/Programs/nikola/blog/
mkdir -p "${OUTDIR}" ; sudo chown blog.admins "${OUTDIR}" ; chmod 0775 "${OUTDIR}"

Tweak the markdown contents to fix some broken links (due to newline characters in long links).

Update local links within post contents. Note how /blog was not needed here! Nikola interprets the relative links as relative to the top-level dir in conf.py.

time sed -i -r -e 's@https://bgstack15.wordpress.com/@/posts/@g;' $( grep --include '*md' -l -riIE 'https://bgstack15.wordpress.com/' posts )

Fix references to a custom wordpress domain for the files.

sed -i -r -e 's@https://bgstack15.files.wordpress.com/@/@g;' $( grep -l --exclude-dir 'cache' -riIE 'https://bgstack15\.files\.wordpress\.com' posts )

Fix links that were broken across multiple lines by html2md process of nikola wordpress migration action

time sed -i -r -e ':a;/\]\([^\)]+-$/{N;s/\n//;:ba;}' $( grep --include '*md' -l -riIE '\]\([^\)]+-$' posts )

Fix more broken links.

sed -i -r -e ':a;/]\(/{/-$/{N;s/\n//;:ba}}' $( grep -l --exclude-dir 'cache' -riIE ']\(.*-$' posts )

Run the initial buildout.

sudo su blog # which should already source the venv, and cd to kb2/
nikola build

The contents are ready to be deployed, but the destination web server needs configuration.

Prepare doc7-01a

On the web server doc7-01a, establish a user, blog.

Set up the account and isso. We have to use a python virtual environment for isso because that is the only way to make isso work on CentOS 7; the native python seems to not operate correctly with isso.

sudo useradd -s /bin/bash blog
sudo su blog
# as user blog:
python3 -m venv isso
cd isso
source bin/activate
bin/python -m pip install -U isso

Load in the public ssh key for service account blog from server1.

Fix some localization within isso files:

# still as user blog:
cd ~/isso
sed -i -r -e 's/One Comment/1 Comment/g;' $(  grep -l -riE 'One Comment' )`

Establish the isso config file /home/blog/iso.kb2.conf:

[general]
dbpath = /home/blog/isso.kb2.db
host = https://bgstack15.ddns.net/blog/
[server]
listen = http://127.0.0.1:8080/
public-endpoint = https://bgstack15.ddns.net/isso/

Establish file /etc/systemd/system/isso.service from [reference 7][7].

[Unit]
Description=Isso Comment Server
After=network.service
Before=network.target
# Ref: /mnt/public/Support/Platforms/vps/vps.action

[Service]
Type=simple
User=blog
WorkingDirectory=/home/blog
ExecStart=/home/blog/isso/bin/isso -c /home/blog/isso.kb2.conf
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
Import wordpress comments to isso

Copy the wordpress .xml file up to the web server (not shown here). Import the wordpress comments to the isso database.

sudo su blog -s /bin/bash
~/isso/bin/isso -c /home/blog/isso.kb2.conf import /tmp/knowledgebase.wordpress.2021-08-29.001.xml

My intial import had 137 threads and 281 comments.

Modify the threads' URLs to work within the new blog address. A custom python script was developed for this operation, scripts/fix-isso-comments.py. Run these following commands as user blog.

Fix comments for migration from wordpress.com to nikola site at /blog/

/usr/local/bin/fix-isso-comments.py -a -v --dbfile isso.kb2.db -m "" -N "/blog/posts"

Fix pingback website entries.

/usr/local/bin/fix-isso-comments.py -a -v --dbfile isso.kb2.db -m "https://bgstack15.wordpress.com/" -N "/blog/posts/" --action pingbacks

Isso is ready to run, with corrected content, so test it to ensure correctness.

sudo systemctl daemon-reload
sudo systemctl start isso

Ensure that port 8080 is listening, and that you can pull isso contents. Isso only responds to hostname requests that match what is configured, so you have to use the --resolve parameter so curl sends that hostname.

sudo netstat -tplnu | grep 8080
curl -L --resolve bgstack15.ddns.net:8080:127.0.0.1 http://bgstack15.ddns.net/isso/js/embed.min.js
Modify nginx

Nginx is used on doc7-01a primarily for this blog, but it does have other uses. This post might not contain all the nginx settings in use. Nginx was modified with the certbot logic to use https and is not described in detail here. The CentOS 7 default nginx.conf includes all files from glob default.d/*.conf.

Establish file /etc/nginx/default.d/isso.conf:

location / {
   root /var/www;
}

location /isso {
   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
   proxy_set_header X-Script-Name /isso;
   proxy_set_header Host $host;
   proxy_set_header X-Forwarded-Proto $scheme;
   proxy_pass http://127.0.0.1:8080;
}

The path /var/www/blog will contain the contents of the blog directory from server1.

Additional assets deployed at initialization time.

Files on server1a:

Files on doc7-01a:

The generate-tag-cloud.sh script produces the tagcloud.html and .css files used in the sidebar of every page. The deploy-part2 script calls this one.

Operations

These tasks are expected to be run in the future and/or on a recurring basis.

Writing a new post

Nikola has its own command for making a new post, where it builds a mostly empty file with a few metadata fields.

nikola new_post

Or you can manually make a new markdown file in the kb2/posts/YYYY/MM/DD directory. Just inspect any extant markdown file for the metadata fields to use.

Adding media

Place files underneath directory kb2/blog/files/, normally in a YYYY/MM/DD directory that matches the post that first uses the image.

Refer to these images with this markdown snippet, but html can also be used directly.

[![](/2021/08/30/bgstack15_logo.png)](/2021/08/30/bgstack15_logo.png)

Deploying site

On any system on the internal network, run this script as your regular login user. It will connect to server1 and run any necessary commands.

/mnt/public/Support/Programs/nikola/scripts/build.sh

And now deploy to the web server.

/mnt/public/Support/Programs/nikola/scripts/deploy.sh

References

  1. [internal file]
  2. [internal file]
  3. original blog
  4. [internal file]
  5. Isso comment server
  6. Nikola static site generator
  7. systemd service file for isso

Some other netizens migrated to nikola from Wordpress, or use nikola:

  1. https://anteru.net/blog/2016/moving-from-wordpress-to-nikola/index.html
  2. https://baptiste-wicht.com/posts/2014/03/migrated-from-wordpress-to-nikola.html
  3. Martin Wimpress of Ubuntu fame
  4. unit193 Xubuntu dev who has interacted with me personally on irc. He uses nikola and that's how I discovered it.

Alternatives

  1. My original site, which is the wordpress one.
  2. List of comment solutions for static sites

Additional posts in this series

  1. /posts/2021/09/07/fix-isso-comments-from-old-url-to-new-url
  2. /posts/2021/09/11/automatic-build-script-for-my-static-site
  3. /posts/2021/09/15/deploy-my-nikola-site
  4. /posts/2021/09/19/scheduling-my-blog-updates
  5. /posts/2021/09/23/deploy-my-nikola-site-part-2
  6. /posts/2021/09/27/generate-tag-cloud-for-static-site

First post in nikola

Hello world!

This is the first new content added to my site after migrating to nikola static site generator.

While I appreciate Wordpress, it just sounds cooler to control everything about my site. I'm in good company, as an Internet search shows that many people have performed the same wordpress-to-nikola migration.

I found nikola because I was visiting my friend Unit193's website which is powered by nikola.

Nikola supports markdown files, which is a format I tend to use for internal documentation already, and who doesn't like the idea of presenting static files with the web server? Easy indexing by the Internet Archive is a plus, too, because my content is so important that it will make it there!

And since this is my first post, I need to test adding an image, so enjoy this image.

ps show start time in ISO 8601 format

From unix.stackexchange.com:

ps -e --no-headers -o lstart,pid,command --sort=start_time | awk '{ cmd="date -d\""$1 FS $2 FS $3 FS $4 FS $5"\" +\047%Y-%m-%dT%H:%M:%S\047"; cmd | getline d; close(cmd); $1=$2=$3=$4=$5=""; printf "%s\n",d$0 }'


2021-08-24T07:42:42     25863 [kworker/0:3-events]
2021-08-24T07:42:42     25927 [kworker/2:3-events]
2021-08-24T07:43:08     27437 [kworker/u9:0-xprtiod]
2021-08-24T07:44:00     30436 /usr/lib/firefox/firefox -contentproc -childID 114 -isForBrowser -prefsLen 10798 -prefMapSize 258189 -parentBuildID 20210504152106 -appdir /usr/lib/firefox/browser 5803 true tab
2021-08-24T07:49:28     16224 [kworker/u8:0-kcryptd/254:0]
2021-08-24T07:49:34     16656 sleep 2
2021-08-24T07:49:36     16688 sleep 0.75

You of course can adapt this to whichever additional parameters you need to send to ps, or grep for.

Import passwords to Chrome browser with csv file

If your Chome web browser is locked down and unable to offer to save passwords, you can probably still load in passwords. You can use csv import and export features to examine how Chrome exports them, and then write your csv entry manually and import it. You might need to enable password import: Visit chrome://flags#PasswordImport and enable that flag. Export any current passwords you have to a csv, so you can examine the columns. For me, the columns were:

name,url,username,password
site.internal.example.com,https://site.internal.example.com/full/url/to/login/page,daUserName,Th3p@s5w*rd

The passwords are saved in file "Login Data" which in Windows is stored as %localappdata%\Google\Chrome\User Data\Default\Login Data. If Chrome is wiping your passwords when you close the browser, then you can mark the "Login Data" file as read-only before running Chrome. But to import a new password from csv, you need to run the program with the file as writeable, and then back up the file after importing the password. Then, close the browser and it will wipe the passwords in the main file. Then restore your backup, and mark it as read-only, and then you can run Chrome and close it at will, with the new passwords.

Mirror a copr, all architectures and releases and versions

coprmirror is my yum mirror solution, with a wrapper to mirror specifically a named COPR repository.

Overview

COPR is a distro-run community offering that lets users build yum/dnf repositories with their own software (primarily if not exclusively in rpm format). COPR performs the builds, and then hosts the binary and source rpms for client machines. My project today downloads using native GNU/Linux tools the yum repositories that collectively make up a COPR, so each release-releasever-basearch triplet. The end goal is to have a local copy of the entire current yum repos. This does not copy the build assets or log files; just what a yum repository defines and also the gpg public key. Notably this utility needs the yum python packages present only for evaluating yum variables in the inurl (baseurl from a .repo file), so if you have a string literal as the inurl value, you can run this on a system that does not have yum installed.

Using

Configure coprmirror.conf from the provided .example file, and then run:

COPRMIRROR_CONF=coprmirror.conf VERBOSE=1 DEBUG=1 ./coprmirror.sh

Upstream

Original content The get_file function was improved after being imported from my Reference 1 script.

Alternatives

I felt like ansible and system are overkill, but if you like those, this is perfect for you: https://github.com/ganto/ansible-copr_reposync

Dependencies

wget, grep, awk, sed, jq

References

obsmirror Mirror an OBS repository locally — update 1 [this blog]

Chicago95 icon set for LibreOffice now upstreamed

The upstream folks at Chicago95 total conversion mod for Xfce desktop environment on GNU/Linux have graciously upstreamed my Chicago95 icon theme for LibreOffice! Check it out in the official repository: https://github.com/grassmunk/Chicago95/tree/master/Extras/libreoffice- chicago95-iconset. Now if only we could get them to make a new release, so I could go update the dpkgs in the apt repo in the Open Build Service... LibreOffice Writer with the Chicago95
iconset

My DVD-ripping solution

This is a work in progress, but its current status is as follows.

Ripping the DVDs

To rip my DVD collection, use my handbrake wrapper script. My ripping machine uses two disc drives. Run the script once per drive, and save all output to a temporary file.

{ INPUT=/dev/sr0 handbrake.sh ; INPUT=/dev/sr1 handbrake.sh ; } > ~/handbrake1

Inspect the script for correctness. Mine looks like:

HandBrakeCLI --markers --format mkv --min-duration 125 --input /dev/sr0 --output /mnt/public/Video/temp/TNGS2D3_V1.mkv --encoder x264 --rate 30 --native-language eng --title 1 --audio 1,2, --subtitle 1,2,scan --native-language eng --mixdown 6ch --aencoder ffaac
HandBrakeCLI --markers --format mkv --min-duration 125 --input /dev/sr0 --output /mnt/public/Video/temp/TNGS2D3_V2.mkv --encoder x264 --rate 30 --native-language eng --title 2 --audio 1,2, --subtitle 1,2,scan --native-language eng --mixdown 6ch --aencoder ffaac

The handbrake.sh script can be found at the bottom of this post and is explained further there. Run the temp file's commands. I like to do this in a GNU screen session.

time sh -x ~/handbrake1

Adding TV show metadata

Prepare a csv with s, sep, ep, airdate, title, and filename columns.

have,s,ep,sep,title,airdate,filename
1,1,1,01-e02,Encounter at Farpoint,1987-09-28,s01e01-e02 - Encounter at Farpoint
1,1,3,3,The Naked Now,1987-10-05,s01e03 - The Naked Now
1,1,4,4,Code of Honor,1987-10-12,s01e04 - Code of Honor
1,1,5,5,The Last Outpost,1987-10-19,s01e05 - The Last Outpost
1,1,6,6,Where No One Has Gone Before,1987-10-26,s01e06 - Where No One Has Gone Before

This filename output is supposed to be what Jellyfin wants for TV shows. I used the information from Wikipedia and some LibreOffice Calc functions to make this csv file, and some hand-munging for the double-long episodes.

="s" & TEXT(A2,"\00") & "e" & TEXT(C2,"00") & " - " & REGEX($D2,"[,!']","")

I'm a bit obsessive about removing commas, exclamation marks, question marks, and quote marks from my filenames. Inspect the saved files and rename each one to the filename string from the csv that corresponds to the correct episode. To add the mkv metadata including airdate, and next and previous episodes, run the next script on the directory. This does not currently recurse into subdirectories.

time tv-mkv-helper.py --inputcsv "/mnt/public/Video/TV/Star Trek The Next Generation (1987)/STTNG.csv" -d /mnt/public/Video/temp/

Alternatives

Automatic Ripping Machine is just too fully-featured for me.

References

  1. Jellyfin TV show naming scheme

Scripts

handbrake.sh

My handbrake.sh wrapper reads the disc contents to learn the disc title and the number of video tracks longer than 125 seconds.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/sh
# startdate: 2021-03-10 11:36
# Alternatives:
#   handbrake preset: /mnt/public/Support/Programs/DVDs/1920x1080.json

test -z "${INPUT}" && INPUT=/dev/sr0 ; export INPUT
test -z "${OUTPUT}" && OUTPUT=/mnt/public/Video/temp ; export OUTPUT

raw="$( HandBrakeCLI --markers --format mkv --min-duration 125 --scan --input "${INPUT}" --output "${OUTPUT}"  --encoder x264 --rate 30 --native-language eng --title 0 2>&1 )"
disctitle="$( echo "${raw}" | sed -n -r -e '/DVD Title:/{s/.*DVD Title: //;p}' )"
array="$( echo "${raw}" | grep -E '^\s*\+' | awk 'BEGIN{x=0} /title [0-9]/{gsub(":","",$NF);x++;title[x]=$NF} /kbps/{audio[x]=audio[x]""$2} !/subtitle tracks:/ && !/\+ [0-9]+,/ {us=0} /subtitle tracks:/{us=1} us == 1 && $2 ~ /[0-9]+,/{subtitle[x]=subtitle[x]""$2} END {for(i in title) {{print title[i],audio[i],subtitle[i]}}}' )"
commands="$( echo "${array}" | awk -v "disctitle=${disctitle}" -v "input=${INPUT}" -v "output=${OUTPUT}" '{a=$1;b=$2;c=$3;print "HandBrakeCLI --markers --format mkv --min-duration 125 --input "input" --output "output"/"disctitle"_V"a".mkv --encoder x264 --rate 30 --native-language eng --title "a" --audio "b" --subtitle "c"scan --native-language eng --mixdown 6ch --aencoder ffaac"}' )" 
echo "${commands}"

My hard-coded values include outputting to mkv, and x264 video encoding, and 30 bps, with English language. I also tell it to use the specific audio encoder ffaac which was the first one I tried when trying to get my 6-channel mixdown (that's 5.1 surround sound) option to work. I'm not a media bitrate snob; just trying to preserve some semblance of fancier file value than I can even consume, should I ever upgrade my viewing equipment in the next 40 years.

tv-mkv-helper.py

  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
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/usr/bin/env python3
# Startdate: 2021-08-07
# Purpose: given INCSV="/mnt/public/Video/TV/Star Trek The Next Generation 1987)/STTNG.csv"
#    with columns s, ep, sep, title, airdate, filename,
#    update files in provided directory, whose name matches the filename column, with the mkv properties
# Usage:
#    time tv-mkv-helper.py --inputcsv "/mnt/public/Video/TV/Star Trek The Next Generation (1987)/STTNG.csv" -d /mnt/public/Video/temp/
# References:
#    /mnt/public/Support/Programs/DVDs/handbrake-internal.sh
# Dependencies:
#    STTNG.csv sorted ascending, with columns s, sep, title, airdate, filename
#    mkvpropedit

import csv, os, subprocess

###############################################################
def load_file_to_array(inputcsv):
   episode_array = []
   with open(inputcsv,"r") as o:
      csv_reader = csv.reader(o)
      headers = next(csv_reader)
      error_string = ""

      # sanity check for the columns we want
      if "s" not in headers:
         error_string = "Inputcsv needs column named 's' for season number."
      #elif "ep" not in headers:
      #   error_string = "Inputcsv needs column named 'ep' for episode number."
      elif "sep" not in headers:
         error_string = "Inputcsv needs column named 'sep' for season episode number."
      elif "airdate" not in headers:
         error_string = "Inputcsv needs column named 'airdate'."
      elif "filename" not in headers:
         error_string = "Inputcsv needs column named 'filename'."
      if error_string != "":
         print(error_string)
         return -1

      x = 0
      for row in csv_reader:
         x = x + 1
         ep = {key: value for key, value in zip(headers,row)}
         episode_array.append(ep)
   return episode_array

############################################################
def fix_directory_contents(directory, inputcsv):
   os.chdir(directory)
   episodes = load_file_to_array(inputcsv)
   if episodes == -1:
      print("Aborting.")
      return -1
   # simple one-level down method. This might be insufficient someday.
   for filename in os.listdir(directory):
      if filename.endswith(".mkv"):
         print("----------------------")
         print(f"Checking file {filename}")
         filename_sans_ext, _ = os.path.splitext(filename)
         # loop through episodes to find this file
         # need enumerate, per https://stackoverflow.com/questions/1011938/loop-that-also-accesses-previous-and-next-values/1011962#1011962
         l = len(episodes)
         for index, ep in enumerate(episodes):
            previous = next_ = None
            if ep['filename'] == filename_sans_ext:
               if index > 0:
                  previous = episodes[index-1]['filename']
               if index < ( l - 1 ):
                  next_ = episodes[index+1]['filename']
               fix_file(
                  filename=filename,
                  title=ep['filename'],
                  season=ep['s'],
                  seasonep=ep['sep'],
                  next_name=next_,
                  prev_name=previous,
                  airdate=ep['airdate']
               )

############################################
def fix_file(filename,title,season,seasonep,airdate,next_name, prev_name):
   print(f"Please fix {filename} to have")
   print(f"title: {title}")
   print(f"season: {season}")
   print(f"seasonep: {seasonep}")
   print(f"airdate: {airdate}")
   print(f"next_name: {next_name}")
   print(f"prev_name: {prev_name}")
   if next_name is not None and not next_name.endswith(".mkv"):
      next_name = next_name + ".mkv"
   if prev_name is not None and not prev_name.endswith(".mkv"):
      prev_name = prev_name + ".mkv"
   date_string = str(airdate) + "T12:00:00Z" # just set it to noon UTC, so the EST/EDT time will still be the same day.
   #cmd_string = f"mkvpropedit --set 'title={title}' --set 'date={date_string}' --set 'segment-filename={filename}.mkv' --set 'prev-filename={prev_name}' --set 'next-filename={next_name}'"
   cmd_array = ["mkvpropedit","--set",f'title={title}',"--set",f'date={date_string}',"--set",f'segment-filename={filename}',"--set",f'prev-filename={prev_name}',"--set",f'next-filename={next_name}',filename]
   result = subprocess.run(cmd_array)
   print(result)
   aux_cmd_array=["mkvpropedit",filename,"--delete"]
   if next_name is None or next_name == "":
      aux_cmd_array.append("next-filename")
   if prev_name is None or prev_name == "":
      aux_cmd_array.append("prev-filename")
   # if we added anything to the auxiliary command array, then run it
   if aux_cmd_array[-1] != "--delete":
      result = subprocess.run(aux_cmd_array)
      print(result)

# argparse stuff
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-i","--inputcsv",required=True,help="Episode CSV, with columns s, sep, title, filename, airdate")
parser.add_argument("-d","--directory",required=True,help="Path to directory where files should be given metadata")
args = parser.parse_args()

inputcsv = args.inputcsv
directory = args.directory

if __name__ == "__main__":
   print(f"running with inputcsv=\"{inputcsv}\", directory={directory}")
   fix_directory_contents(directory,inputcsv)

In a departure from my normal style where I put all the functions in a library and call it, I wanted a self-contained script with the argument parsing. It was quick and dirty. And all I'm doing is adding the 3 or 4 mkv attributes to the mkv files whose filename matches an entry in the csv. I do all this using a custom csv, because I'm too lazy to learn how to use some fancy TheTVDB api or whatever. I'm obsessive enough to be fine with manually scraping metadata from Wikipedia tv show episode lists.

Use gtk3-classic on Devuan Ceres for type-to-navigate in file dialog

Gtk3-classic-build-deb is builder utility for gtk3-classic for Devuan Ceres. Check out the final assets at my OBS sub- project.

Overview

Notable improvements over stock gtk3 include

  • Regular type-to-navigate-to-filename in the file dialog instead of the "typeahead" behavior
  • CSDs are removed, which actually could deprecate gtk3-nocsd (official upstream Debian package)

Notable weaknesses include:

Using

The gtk3-classic-build-deb.sh script and Makefile can be used to generate the build assets that can be used to build the binary dpkgs of gtk3 with the gtk3-classic patches. The shell script finds the available gtk3 versions in debian, and gtk3-classic releases, and then finds the highest version that matches between the two. This highest version then gets downloaded, given the patches in debian/patches/series, and then the .dsc and .debian.tar.xz file are generated!

Upstreams

Build script

The build script is loosely inspired by luigifab/deb.sh (gist.github.com), but is maintained separately.

gtk3-classic

The gtk3-classic patch set is one of the main inputs to this process.

gtk3

The debian orig tarball is used for the build process.

Alternatives

gtk3-stackrpms suite which suits my needs almost exactly, minus the file dialog type-to-find functionality.

Dependencies

To run the build script you need:

  • rmadison
  • git
  • tar

To build gtk3, you need the standard set which is available in the debian/control file.

References

Read RDP certificate in use

One minor problem I have come across a few times is, "How do I confirm that a Remote Desktop server is actually using the correct TLS certificate?" I deal with end results, and not policies or settings, so how I check tls servers is make a connection with openssl s_client. Well, RDP doesn't start with the certificate, so s_client cannot extract the certificate information directly. A solution I devised is to capture the packets of an initial RDP connection, and then extract the certificate from the TLSv1 Certificates protocol. My project can read a packet capture, really any pcap that contains the TLSv1 Certificate protocol, and save from the TLSv1 Certificates packets any pem-format certificates to disk. Of course this project is open-source, so you can adapt it to do whatever you want. You need to generate a packet capture file, which can be done with wireshark or tcpdump. I used filters:

sudo tcpdump -w ~/packets.in -n -v -A "port 3389 and (tcp[((tcp[12] & 0xf0) >> 2)] = 0x16)"

In wireshark, you can even use a display of tls.handshake.type == 11 as well. So with this pcap file, run read_rdp_cert.py.

./read_rdp_cert.py --pcapfile ~/packets.in

The utility will extract all certificates that it can find from the tls handshake packets, into the current directory. I was unable to find any other projects on the World Wide Web that solve the problem I am solving here. I depended heavily on an example from cuckoolinux -> network.py to narrow down inside a network packet. Unfortunately the dpkt library has very weak documentation so I had to implement my own partial protocol analyzer to get to the good parts inside the TLS handshake. Maybe a future version of this tool will even make the RDP requests, so it can be a self-contained utility.

Notes for jellyfin

Jellyfin is the Free Software Media System (from their website). I had installed a demo at some point in 2019 but that effort failed for undocumented reasons (the worst kind). I tried again this month, and am incredibly pleased! I also installed it on a beefy machine this time and not a flimsy 1-vCPU virtual machine with <2 GB of RAM. My media already conforms to the Plex media organization methodology, mostly. One hangup I still had was getting Chromecast support from the Android mobile app. While Jellyfin is available on f-droid, I learned, it does not contain the necessary bits to talk to Chromecast because it is the libre release. The Chromecast support is in the Play store version. However, to get Chromecast to operate successfully, I was told, you need to have https on the jellyfin connection. I use apache httpd for my reverse proxy, so it was thankfully easy to get my Let's Encrypt tls certificate on that. However, the jellyfin documentation that demonstrates using apache as a reverse proxy makes jellyfin take up the top-level virtual path, i.e., https://example.com/. I use my httpd instance for many things, so I could not afford to lose my entire site. The main networking page states that you can change the baseurl (which I believe is more accurately called base path, but now I'm quibbling) to use /jellyfin/ for example, but this breaks certain client softwares including Chromecast. So I experimented with just shoving it under a different port on apache httpd, and thankfully Chromecast handles it just fine! So as long as my users remember to add port 500 in the url, everything will work including the much-used Chromecast! My apache configs can be boiled down to the following.

Listen 192.168.300.52:500
SSLSessionCache   shmcb:/run/httpd/sslcache(512000)
SSLSessionCacheTimeout 300
<VirtualHost 192.168.300.52:500>
   ServerName  www.example.com:500
   ServerAlias www.example.com www server1 server1.ipa.internal.com internal.ignorelist.com

   SSLEngine on
   SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
   SSLHonorCipherOrder on
   SSLCipherSuite          "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384"

   SSLCertificateFile /etc/letsencrypt/live/www.example.com/fullchain.pem
   SSLCertificateKeyFile /etc/letsencrypt/live/www.example.com/privkey.pem

   # This is important to allow the httpsonly part to work
   DocumentRoot   /var/www/external

   SSLProxyEngine On
   RewriteEngine On

   ProxyPreserveHost On
   ProxyPass "/.well-known/" "!"
   ProxyPass "/socket" "ws://vm4.ipa.internal.com:8096/socket"
   ProxyPassReverse "/socket" "ws://vm4.ipa.internal.com:8096/socket"
   ProxyPass "/" "http://vm4.ipa.internal.com:8096/"
   ProxyPassReverse "/" "http://vm4.ipa.internal.com:8096/"
</VirtualHost>

And for good measure, I added file /var/www/jellyfin/index.html with the following line, to act as a redirect for when people visit https://www.example.com/jellyfin/"

<meta http-equiv="Refresh" content="0; url=https://www.example.com:500/">