openSUSE Tumbleweed – Review of the week 2021/52
Dear Tumbleweed users and hackers,
The last week of the year, and the last day of the year, are coming to an end. Tumbleweed had a small dip, as the last two snapshots that moved to openQA had to be stopped from being published. Nevertheless, we still managed to publish 6 snapshots before heading out to the new-year celebrations (1223…1228).
The snapshots contained those relevant changes:
- Qemu 6.2.0
- Boost 1.78.0
- gimp 2.10.30
- gpg 2.3.4
- Pango 1.50.3
- sssd 2.6.3
- Default ruby interpreter switched from 2.7 to 3.0
The staging projects are mostly, but not completely, empty. The few ones in use are testing these items:
- Wayland 1.20 (Seems to cause some bugs, see https://bugzilla.opensuse.org/show_bug.cgi?id=1194190
- Linux kernel 5.15.12
- Ruby 3.1. A direct attempt to switch to Ruby 3.1, and removing Ruby 2.7 and Ruby 3.0 in one go. Most likely it will be phased a little bit (e.g. Vagrant is not Ruby 3.1 compatible yet)
- zstd 1.5.1: breaks build of zchunk and libzypp
- Enabling the build of python310-* modules; the move of the default python3 provider to python310 could follow soon after
PDF Quirk Version Update
The good thing about this quiet holidays is that there is time to work on projects. I had a few nice changes of PDF Quirk laying around, and now finalized them to a new version. Please welcome PDF Quirk version 0.95!
What is PDF Quirk?
PDF Quirk is a little desktop utility to create PDF files from images, targeted to non nerdy desktop users.
Sending PDFs is (still) often a requirement in offices where people are asked to transfer PDF files via email, or better by pushing them through their private ownCloud.
The source images can either be loaded from file, or directly scanned with an hardware scanner. For that, PDF Quirk utilizes the tool scanimage from the SANE Projekt, to avoid reinventing the wheel. Configured once, that works like a charm.
Having scanned or picked the source images, they can be deskewed, turned and rearranged, and finally converted to a good quality PDF file with reasonable file size.
New Version 0.95
The new version brings a few features and some fixes:
- Images can deskewed now
- Basic PDF options like margin, paper size and orientation can be set
- The UI got a little cleanup
- Translations were added, first new language is German
- Dependencies were reduced, new Qt based PDF generator was implemented
- Builds with Qt5 and Qt6
Interested?
Check out PDF Quirks Website for more information.
PDF Quirk is free software. Please contribute through the Github repository.
Orient: Life at Sea in the 21st Century
The Titanic could accommodate about 2500 passengers who booked either first, second or third class. Wikipedia tells that
those travelling in first class, most of them the wealthiest passengers on board, included prominent members of the upper class, businessmen, politicians, high-ranking military personnel, industrialists, bankers, entertainers, socialites, and professional athletes. Second-class passengers were predominantly middle-class travellers and included professors, authors, clergymen, and tourists. Third-class or steerage passengers were primarily immigrants moving to the United States and Canada.
Much has changed since then.
Life at Sea in the 21st Century
Purpose and passengers of such mega ships (rather kilo ships :wink:) have changed dramatically since then. The ship is no longer a mean of transport. Passengers from Central Europe fly 5000 km to the Orient for a cruise of 500 km to a nearby harbour and back to the point of departure. Many passengers are rather age group 50+ and have cruised around quite a lot already1. Then, there are few younger families and couples as well. Other single travellers fall rather into the category of widows2.
My ship features 1267 twin cabins for 2534 passengers, but if need be, can host up to 2700 passengers–the 1030 crew members excluded. The other ship in the habour, Costas Firenze, has 2116 cabins for up to 5078 passengers (two times the Titanic) and provides for a crew of about 1300 members.
Due to Covid-19, the ships are far from fully booked. In my case the occupation rate was about 40%, a bit more than 1000 people.
Life at sea on this German-operated ship is best compared to Club holidays in Germany3:
- room service takes care of cabins twice per day
- all inclusive all you can eat and drink
- about 5 restaurants and several bars
- Döner Kebab at the poolside :sweat_smile:
- SPA area
- fitness club with courses, volleyball court and running area
- entertainment program in the lounge, the two theatres or on the open-air stage with shows that involve the dancers and acrobats belonging to the crew and guest artists (stand-up, magician)
- casino (open when in international waters)
- a few shops for shopping (VAT-free!)
- kids club
- organised day trips (bus, bike and boat tours, etc.)
- mostly German-speaking crew
- 99% German-speaking guests/passengers (I spotted two Dutch)
Consequently, a cruise on this ship is the perfect fit for all those who would like to hang out with Germans, have German bread and bread rolls for every meal, enjoy Sauerkraut, Klöße, Currywurst and Döner Kebab, but at the same time rather prefer a more Mediterranean climate than what Germany can typically offer! Kind of German holidays outside of Germany.
Gallery
With 1000 German passengers on board, it is easy to make pictures of the scenic locations without people: At 8 PM, everyone is at dinner! Let me take you on a tour.

Pool area.

Running path (opening hours: 6 to 8 AM).

Salon area.

Salon area II.

Salon area III.

Midnight snackbar: burgers, Belgium fries, cakes, etc.

Theatre of the ship.

Sport court.
I have a few more impressions taken at daytime.

View from Deck 12 to the family pool area.

Spa area.
-
I know because during some show on the deck, the moderator has asked the question who has been on a cruise before and many hands were raised. ↩︎
-
There was a meetup of single travellers on the ship. However, I didn’t use the occasion to ask them whether they were really widowed. :see_no_evil: ↩︎
-
Not that I have ever done club holidays in Germany–but that’s how I imagine it! ↩︎
Playing with Shelly
For xmass I got few Shelly lamps to play with. Shelly lamps are simple IoT devices. Super easy to install, configure and use. The Youtube is full with instructions on what can be done with these smart lamps. Naturally my main motivation was to figure out how to hack these devices and how ready my openSUSE servers are with tools and services (spoiler: they are ready)
Look daddy no cloud
Needless to say that like most smart home automation devices the Shelly lamps can be operated via the Shelly cloud. I may cover that area in the next post. But now I am interested in what can be done without the cloud. After all, one big selling point of the Shelly devices is that they are fully operable and functional even without Internet connection just on a WiFi LAN. It means that if I am concerned about the security of my home infrastructure I have an option not to expose my smart devices.
Home Assistant for a Newbie
Jekyll: Import Disqus comments for Staticman
For some years already, I try to rely for this website on less external resources and avoid ad-powered services to improve the privacy for my dear readers.
Recently, I removed the comments provided by Disqus from this blog, because Disqus introduced too much data sharing with many third parties. Norway just fined this year Disqus 2,5 Mio Euro for tracking without legal basis.
Please find hereafter some tips on how to export comments from Disqus and display them in a privacy-friendly way in your Jekyll blog.
Export Disqus Comments to JSON and YAML
- Disqus documents the export and export format at https://docs.disqus.com/developers/export/
-
Navigate to http://disqus.com/admin/discussions/export/ to export your comments to XML format.
The XML has principally 3 parts: meta data, a list with webpages and a list with comments that are linked each to a webpage (via a Disqus identifier) and possibly a parent comment in case the comment is a reply.
For use within Jekyll, I need to restructure the data and have a list of comments for each webpage by my own identifier (e.g. post slug) and convert everything to a format that Jekyll can handle, hence YAML, JSON, CSV, or TSV. I choose YAML.
-
Install the linux tool
xqto manipulate XML files and export to JSON and the tooljq.xqis basically a wrapper ofjq.pip install xqDownload binaries of jq here: https://stedolan.github.io/jq/download/
-
I convert then the Disqus XML export into a JSON file with the code in
export-disqus-xml2json.sh - Then, I pipe the output through
import-json-yaml.rbto split the list of comments into individual files for easy consumption by Jekyll.
# file: 'export-disqus-xml2json.sh'
#!/usr/bin/env sh
xq '.disqus | .thread as $threads | .post | map(select(.isDeleted == "false")) | map(.thread."@dsq:id" as $id | ($threads[] | select(."@dsq:id" == $id)) as $thread | {id: ("disqus-"+."@dsq:id"), date: .createdAt, slug: ($thread.id | tostring | gsub("/$";"") | split("/") | last), name: (if .author.name == "Robert" then "Robert Riemann" else .author.name end), avatar: .author | (if has("username") and .username != "rriemann" then "https://disqus.com/api/users/avatars/"+.username+".jpg" else null end), email: .author | (if has("username") and .username == "rriemann" then "my@mail.com" else null end), message, origin: ($thread.link | tostring | gsub("^https://blog.riemann.cc";"")), replying_to: (if has("parent") then ("disqus-"+.parent."@dsq:id") else null end)})' "$@"
Example comment from the JSON list:
{
"id": "disqus-4145062197",
"date": "2018-10-14T22:14:58Z",
"slug": "versioning-of-openoffice-libreoffice-documents-using-git",
"name": "Robert Riemann",
"avatar": null,
"email": "my@mail.com",
"message": "<p>I agree, it is not perfect. I have no solution how to keep the noise out of git.</p>",
"origin": "/2013/04/23/versioning-of-openoffice-libreoffice-documents-using-git/",
"replying_to": "disqus-4136593561"
}
The script import-json-yaml.rb takes each comment and puts it in YAML format with a unique filenname in the folder named after the slug.
# file: 'import-json-yaml.rb'
#!/usr/bin/env ruby
require 'json'
require 'yaml'
require 'fileutils'
require 'date'
data = if ARGV.length > 0 then
JSON.load_file(ARGV[0])
else
JSON.parse(ARGF.read)
end
data.each do |comment|
FileUtils.mkdir_p comment['slug']
File.write "#{comment['slug']}/#{comment['id']}-#{Date.parse(comment['date']).strftime('%s')}.yml", comment.to_yaml
end
The output with tree looks like:
_data
├── comments
│ ├── announcing-kubeplayer
│ │ ├── disqus-113988522-1292630400.yml
│ │ └── disqus-1858985256-1424044800.yml
│ ├── requires-owncloud-serverside-backend
│ │ ├── disqus-41270666-1269302400.yml
│ │ ├── disqus-41273219-1269302400.yml
...
Display Comments in Jekyll
Those comments are accessible in jekyll posts/pages via site.data.comments[page.slug]
Most helpful for the integration of comments to Jekyll was the post https://mademistakes.com/mastering-jekyll/static-comments-improved/.
<!-- file: 'my-comments.html' -->
{% assign comments = site.data.comments[page.slug] | sort %}
{% for comment in comments %}
{% assign index = forloop.index %}
{% assign replying_to = comment[1].replying_to | to_integer %}
{% assign avatar = comment[1].avatar %}
{% assign email = comment[1].email %}
{% assign name = comment[1].name %}
{% assign url = comment[1].url %}
{% assign date = comment[1].date %}
{% assign message = comment[1].message %}
{% include comment index=index replying_to=replying_to avatar=avatar email=email name=name url=url date=date message=message %}
{% endfor %}
<!-- file: 'comment' -->
<article id="comment{% unless include.r %}{{ index | prepend: '-' }}{% else %}{{ include.index | prepend: '-' }}{% endunless %}" class="js-comment comment {% if include.name == site.author.name %}admin{% endif %} {% unless include.replying_to == 0 %}child{% endunless %}">
<div class="comment__avatar">
{% if include.avatar %}
<img src="{{ include.avatar }}" alt="{{ include.name | escape }}">
{% elsif include.email %}
<img src="https://www.gravatar.com/avatar/{{ include.email | md5 }}?d=mm&s=60" srcset="https://www.gravatar.com/avatar/{{ include.email | md5 }}?d=mm&s=120 2x" alt="{{ include.name | escape }}">
{% else %}
<img src="/assets/img/avatar-60.jpg" srcset="/assets/img/avatar-120.jpg 2x" alt="{{ include.name | escape }}">
{% endif %}
</div>
<div class="comment__inner">
<header>
<p>
<span class="comment__author-name">
{% unless include.url == blank %}
<a rel="external nofollow" href="{{ include.url }}">
{{ include.name }}
</a>
{% else %}
{{ include.name }}
{% endunless %}
</span>
wrote on
<span class="comment__timestamp">
{% if include.date %}
{% if include.index %}<a href="#comment{% if r %}{{ index | prepend: '-' }}{% else %}{{ include.index | prepend: '-' }}{% endif %}" title="link to this comment">{% endif %}
<time datetime="{{ include.date | date_to_xmlschema }}">{{ include.date | date: '%B %d, %Y' }}</time>
{% if include.index %}</a>{% endif %}
{% endif %}
</span>
</p>
</header>
<div class="comment__content">
{{ include.message | markdownify }}
</div>
</div>
</article>
Receiving New Comments
Like explained in https://mademistakes.com/mastering-jekyll/static-comments/, the software https://staticman.net/ allows to feed POST HTTP requests to Github and Gitlab pull requests, so that comments can be added automatically. Of course, the website requires after each time a rebuild.
I had much trouble to setup Staticman. Eventually, I decided to use a Ruby CGI program that emails me the new comment as an attachment. I like Ruby very much. :wink: Once I figured out how to use the Gitlab API wrapper, I may also use pull requests instead of email attachments.
# file: 'index.rb'
#!/usr/bin/env ruby
Gem.paths = { 'GEM_PATH' => '/var/www/virtual/rriemann/gem' }
require 'cgi'
require 'yaml'
require 'date'
require 'mail'
cgi = CGI.new
# rudimentary validation
unless ENV['HTTP_ORIGIN'] == 'https://blog.riemann.cc' and
ENV['CONTENT_TYPE'] == 'application/x-www-form-urlencoded' and
ENV['REQUEST_METHOD'] == 'POST' and
cgi.params['email']&.first&.strip =~ URI::MailTo::EMAIL_REGEXP and
cgi.params['age']&.first == '' then # age is a bot honeypot
print cgi.http_header("status" => "FORBIDDEN")
print "<p>Error: 403 Forbidden</p>"
exit
end
output = Hash.new
date = DateTime.now
output['id'] = ENV['UNIQUE_ID']
output['date'] = date.iso8601
output['updated'] = date.iso8601
output['origin'] = cgi.params['origin']&.first
output['slug'] = cgi.params['slug']&.first&.gsub(/[^\w-]/, '') # some sanitizing
output['name'] = cgi.params['name']&.first
output['email'] = cgi.params['email']&.first&.downcase&.strip
output['url'] = cgi.params['url']&.first
output['message'] = cgi.params['message']&.join("\n").encode(universal_newline: true)
output['replying_to'] = cgi.params['replying_to']&.first
#Mail.defaults do
# delivery_method :sendmail
#end
Mail.defaults do
delivery_method :smtp, address: "smtp.domain", port: 587, user_name: "smtp_user", password: "smtp_password", enable_starttls_auto: true
end
mail = Mail.new do
from 'no-reply@domain' # 'rriemann'
to 'comments-recipient@domain' # ENV['SERVER_ADMIN']
reply_to output['email']
header['X-Blog-Comment'] = output['slug']
subject "New Comment from #{output['name']} for #{cgi.params['title']&.first}"
body <<~BODY
Hi blog author,
a new comment from #{output['name']} for https://blog.riemann.cc#{output['origin']}:
#{output['message']}
BODY
add_file(filename: "#{output['id']}-#{date.strftime('%s')}.yml", content: output.to_yaml)
end
mail.deliver
if mail.error_status then
print cgi.http_header("status" => "SERVER_ERROR")
cgi.print <<~RESPONSE
<p><b>Error: </b> #{mail.error_status}</p>
<p>An error occured. Please try again later.</p>
<p><a href="javascript:history.back()">Go back</a></p>
RESPONSE
else
print cgi.http_header
cgi.print <<~RESPONSE
<p><b>Thank you</b> for your fedback! Your comment is published after review.</p>
<p><a href="#{output['origin']}">Back to the previous page</a></p>
RESPONSE
end
To make it work with Apache, you may need to add these lines to the Apache configuration (could be a .htaccess file):
DirectoryIndex index.html index.rb
Options +ExecCGI
SetHandler cgi-script
AddHandler cgi-script .rb
Pi-Hole the Easy Way
Playing with DBus and KDE applications (Part 2)
openSUSE Tumbleweed – Review of the week 2021/50 & 51
Dear Tumbleweed users and hackers,
Unfortunately, I missed writing up the weekly review last week, so I am spanning once again two weeks here. And Tumbleweed has been so stable for the last weeks, even the snapshot count shows this. For example, in the period from snapshot 1116 to 1222, only a total of three snapshots were not released (1204, opneQA issues, and 1216 & 1217 due to a new pango version having an impact on the rendering, which required a lot of needles to be created, which we could not do in time before the next snapshots reached QA). looking only at the time since my last weekly review, we have published 12 snapshots (1209..1215 & 1218..1222). Despite the holiday season, there seem still to be ample changes incoming (but it is getting less, as the look at the Staging dashboard reveals at the moment).
The main changes published in the snapshots of the last two weeks included:
- Linux kernel 5.15.7 & 5.15.8
- Mozilla Firefox 95.0
- Rust 1.57
- Kubernetes 1.23
- KDE Gear 21.12.0
- KDE Frameworks 5.89.0
- GNOME 41.2
- pipewire 0.3.40 & 0.3.42, with wireplumber as the used session-manager
- Pango 1.50.1 & 1.50.2
- Lots of yast modules prepared for Ruby 3.0
The staging projects are mostly empty due to the holiday season being upon us. Only some of the longer-standing changes are still there. New snapshots will be generated as submissions permit, but the snapshots are probably getting a bit smaller over the next few days. Changes being worked on include:
- Qemu 6.2.0 (Snapshot 1223+)
- Boost 1.78.0 (Snapshot 1223+)
- Gimp 2.10.30
- Moving default php version from php7 to php8 (builds basically ok, but our openQA tests are not ready, as they to often explicitly test php7-*)
- Testing the results when moving system ruby from 2.7 to 3.0: the YaST team is moving quickly to make this happen
- Enabling the build of python310-* modules; the move of the devault python3 provider to python310 should follow soon after
- openSSL 3.0
