Skip to main content

the avatar of Ish Sookun

Attila Pinter and Maurizio Galli join the openSUSE Board

We had two candidates for two available seats. As per the board election rules, in such a case, each candidate should receive at least 50% of the total number of votes to be considered as elected.

Both Attila Pinter and Maurizio Galli received good support from the community members. We had 542 eligible voters, out of which 147 voted in the election.

Attila Pinter received 122 votes in his favour which represents 83%, 22 votes against and 3 abstentions.

Maurizio Galli received 120 votes in his favour representing 82%, 21 votes against and 6 abstentions.

Congratulations to both of them! 👏 🎉

a silhouette of a person's head and shoulders, used as a default avatar

27 Years with the Perfect OS

If you are a longtime FreeBSD user, you probably know everything I have to say, and, what’s more, you can probably add a few more points. But hopefully, there will be some Linux or even Windows users among readers who might learn something new!

FreeBSD is not just a kernel but a complete operating system. It has everything to boot and use the system: networking utilities, text editors, development tools and more. Why is that a big deal? Well, because all these components are developed together, they work perfectly together! And a well-polished system is also easier to document. One of my favorite pieces of documentation is the FreeBSD Handbook which covers most of the operating system and is (most of the time) up to date.

Of course, not everything can be integrated into the base operating system, and this is where FreeBSD ports and packages can be useful. The ports system allows a clean separation of the base system and third-party software which allows you to install third-party software on top of a FreeBSD base system. There are tens of thousands ready-to-use software packages to choose from. For example, all the graphical desktop applications are in ports, just as various web servers or more up-to-date development tools.

FreeBSD: the power to serve

You can read the rest of my article in the FreeBSD Journal at https://issue.freebsdfoundation.org/publication/?m=33057&i=733207&p=14&ver=html5

a silhouette of a person's head and shoulders, used as a default avatar

Best Linux Distros for Servers

A lot of people use Linux for their servers. In this article, we’ll guide you through the best Linux distros for servers. With a comparison, recommendations, tips, features, and more. Quick overview of the best Linux distros for servers You can use pretty much any Linux distro as a server, however, some distros are specialized […]

Source

the avatar of Nathan Wolf

Noodlings 35 | Light for Christmas

The holiday season hustle and bustle is through and I am able to concentrate on reorganizing the messes I have made in getting ready for the season. As the fall time projects finished up, the Christmastime projects kicked into full gear between programming my Christmas light display, baking cookies and making Gingerbread houses with the kids. There come a bunch of other miserable cold weather chores that go along with living on a subsistence farm.

a silhouette of a person's head and shoulders, used as a default avatar

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

the avatar of Klaas Freitag

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.

the avatar of Robert Riemann

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.

3rd-class cabin on the RMS Olympic, sister ship of the Titantic.

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)
My cabin with balkony.

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.

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.

Pool area.

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

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

Salon area.

Salon area.

Salon area II.

Salon area II.

Salon area III.

Salon area III.

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

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

Theatre of the ship.

Theatre of the ship.

Sport court.

Sport court.

I have a few more impressions taken at daytime.

View from Deck 12 to the family pool area.

View from Deck 12 to the family pool area.

Spa area.

Spa area.

  1. 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. ↩︎

  2. 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: ↩︎

  3. Not that I have ever done club holidays in Germany–but that’s how I imagine it! ↩︎

the avatar of Zoltán Balogh

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.

the avatar of Nathan Wolf

Home Assistant for a Newbie

Getting started with Home Assistant, you are presented with lots of options and there is a plethora of opinions to steer you in the “right” direction. The problem I had is that I was paralyzed by the options and, as a consequence, set back my adoption of Home Assistant in my everyday life. In an […]

the avatar of Robert Riemann

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

  1. Disqus documents the export and export format at https://docs.disqus.com/developers/export/
  2. 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.

  3. Install the linux tool xq to manipulate XML files and export to JSON and the tool jq. xq is basically a wrapper of jq.

    pip install xq
    

    Download binaries of jq here: https://stedolan.github.io/jq/download/

  4. I convert then the Disqus XML export into a JSON file with the code in export-disqus-xml2json.sh

  5. Then, I pipe the output through import-json-yaml.rb to 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