Linux-Software installieren mit Ansible

Ich habe deutlich mehr als nur ein oder zwei Linux-Rechner, die ich alle gern auf dem aktuellen Software-Stand halte. Da manuelle apt Kommandos auf Dauer lästig werden, habe ich mir jetzt Ansible-Skripte geschrieben, die sehr bald nach dem Aufsetzen eines Linux-Rechners die restliche Arbeit übernehmen. „Sehr bald“ heißt in dem Zusammenhang, nach der grundlegenden Installation und dem Installieren von Ansible selbst mittels apt install ansible. Ansible ist zwar eigentlich dafür gemacht, dutzende, hunderte oder gar tausende Server zu administrieren, aber für die Arbeiten am localhost bietet es auch Vorteile.

Ich nutze dabei zwei Skripte, sogenannte „Playbooks“, eines für die Kombination aus apt update/upgrade und ein Skript für die grundlegende Paketinstallation.

Hier das erste (Quelle: https://www.cyberciti.biz/faq/ansible-apt-update-all-packages-on-ubuntu-debian-linux/):


- hosts: all
become: true
become_user: root
tasks:
- name: Update apt repo and cache on all Debian/Ubuntu boxes
apt: update_cache=yes force_apt_get=yes cache_valid_time=3600

- name: Upgrade all packages on servers
apt: upgrade=dist force_apt_get=yes

Und hier das zweite Playbook für die Installation der grundlegenden Software:


- hosts: all
become: true
become_user: root
tasks:
- name: Install git
apt:
name: git
state: present
update_cache: yes

- name: Install gparted
apt:
name: gparted
state: present
update_cache: yes

- name: Install subversion
apt:
name: subversion
state: present
update_cache: yes

- name: Install emacs
apt:
name: emacs
state: present
update_cache: yes

- name: Install geany
apt:
name: geany
state: present
update_cache: yes

- name: Install vlc
apt:
name: vlc
state: present
update_cache: yes

- name: Install ssh
apt:
name: ssh
state: present
update_cache: yes

- name: Install VSC via snap
snap:
name: code
classic: yes

Ausgeführt werden die Skripte dann mittels


ansible-playbook --connection=local --inventory 127.0.0.1, playbookname.yml

Ansible kann noch unendlich viel mehr, dazu vielleicht in weiteren Beiträgen.

Fehlerhafte SQLite Updates abfangen

Bei einem kleinen Projekt, an dem ich arbeite, fiel mir auf, dass fehlerhafte — d.h. syntaktisch richtige, aber inhaltlich falsche — SQLite Updates im Nirvana verschwinden. Danke Google und Stackexchange war die Lösung wie folgt: fehlerhafte SQL-Updates erzeugen keine Exception, hier muss man nach dem Update den rowcount prüfen, um die Anzahl der aktualisierten Zeilen zu checken. Das folgende Programm illustriert das ganze:

import sqlite3
 
conn = sqlite3.connect(':memory:')
conn.execute('create table if not exists mytable(id INTEGER PRIMARY KEY \
             AUTOINCREMENT, status TEXT, prio TEXT)')
 
today = '2020-08-06'
cursor = conn.cursor()
task = "Foobar"
 
cursor.execute('insert into mytable(status, prio) values (?, ?)',
               [task, today])
conn.commit()
 
my_id = 1 # For my_id = 1 it works, for any other number rowcount is 0
prio = 'A'
cursor = conn.cursor()
cursor.execute('UPDATE mytable SET prio = ? WHERE id = ?', [prio, my_id])
conn.commit()
print(cursor.rowcount)
 
try:
    cursor = conn.cursor()
    cursor.execute('select * from mytable')
    results = cursor.fetchall()
    for entry in results:
        print(entry)
except Exception as err:
    print('Listing failed: %s\nError: %s' % str(err))
 
finally:
    cursor.close()

Lose-Blatt-Sammlungen mit LaTeX

Für ein spezielles Projekt habe ich eine Möglichkeit gesucht, eine Lose-Blatt-Sammlung mit LaTeX zu gestalten. Lose-Blatt-Sammlung heißt in dem Zusammenhang:

1) Für jede (logische) Seite existiert ein separates Unterdokument.

2) Im Dateinamen jedes Unterdokuments ist die Seitennummer enthalten.

3) Einzelne Seiten können dadurch problemlos ausgetauscht werden, ohne dass sich die Seitenzahlen der anderen Seiten ändern.

4) Jedes Unterdokument besteht grundsätzlich aus nur einer physischen Textseite, kann jedoch auch länger sein. Daher hat jedes Unterdokument eine Seitennummerierung der Form xx-yy. xx steht dabei für die logische Seitennummer im Hauptdokument, die aus dem Dateinamen gezogen wird, yy für die fortlaufende (physische) Seitennummer im Unterdokument selbst. Die fortlaufende Seitennummer für das Gesamtdokument werte ich nicht aus.

5) Alle Dokumente stehen natürlich unter Versionverwaltung, hier nutze ich Subversion.

Wie habe ich es umgesetzt?

Das Hauptdokument sieht wie folgt aus. Neben dem svn-multi Paket, das die Subversion-Daten bereitstellt, nutze ich varsfromjobname, um die einzelnen Komponenten der Dateinamen (xx-yy.tex) auswerten zu können. Die einzelnen Teile von Kopf- und Fußzeile werden durch scrlayer-scrpage modifiziert.

%!TEX TS-program = Arara
% arara: pdflatex: {shell: yes}
% arara: pdflatex: {shell: yes}
 
\documentclass[12pt,ngerman,headsepline,footsepline]{scrartcl}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{babel}
\usepackage{csquotes}
\usepackage{xcolor}
\usepackage{svn-multi}
\usepackage{currfile}
\usepackage{varsfromjobname}
 
\usepackage{blindtext}
\addtokomafont{pagenumber}{\textit}
\addtokomafont{sectionentrypagenumber}{\color{white}}
\usepackage[left=3cm,right=1.5cm,top=2cm,bottom=3cm]{geometry}
 
\usepackage[headsepline=0.25pt,footsepline=0.25pt]{scrlayer-scrpage}
\pagestyle{scrheadings}
 
\svnidlong{$HeadURL: svn://192.168.0.80/Dokumente/Uwe/Main.tex $}
{$LastChangedDate: 2020-07-16 10:42:03 +0200 (Do, 16 Jul 2020) $}
{$LastChangedRevision: 62 $}
{$LastChangedBy: uwe $}
 
\title{Main Document}
\author{Max Mustermann}
 
\begin{document}
 
\tableofcontents
 
\include{sub-01}
\include{sub-02}
 
\end{document}

Die Subdokumente sehen jeweils wie folgt aus. Über die *foot und *head Befehle werden die Seitenköpfe und -füße mit den Komponenten aus dem Dateinamen befüllt, \addsec fügt die Dokumente zum Inhaltsverzeichnis hinzu.

%!TeX root=main.tex
 
\svnidlong
{$HeadURL: svn://192.168.0.42/Dokumente/Uwe/Sub-01.tex $}
{$LastChangedDate: 2020-07-16 10:10:19 +0200 (Do, 16 Jul 2020) $}
{$LastChangedRevision: 60 $}
{$LastChangedBy: uwe $}
 
\setcounter{page}{1}
 
\ohead{V\svnfilerev\ vom \svnfileday.\svnfilemonth.\svnfileyear}
\ofoot[\gettwofromcurrfilename-\pagemark]{\gettwofromcurrfilename-\pagemark}
\ifoot[\currfilename]{\currfilename} % inner foot
\ihead[]{} % inner head
\cfoot[]{} % center foot
\chead[]{} % center head
 
 
\addsec{\gettwofromcurrfilename~\getonefromcurrfilename}
 
 
\blindtext[6]

Beispieldokument: Main.pdf

Remark: An English version of this article has been published on latex.net.

Stellen wir uns vor, dass wir in einer Präsentation eine Abschlussarbeit verteidigen müssen. Es kann sein, dass weitergehende Fragen kommen, die dann tiefergehend beantwortet werden müssen.

Wir könnten das entsprechende Material direkt in die Präsentation setzen, müssen dann beim Vortrag aber eventuell Folien überspringen, was auch kein guter Präsentationsstil ist. Die zusätzlichen Inhalte einfach nur ans Ende setzen ist auch schlecht, im schlimmsten Fall muss man dann während der Präsentation hin- und herspringen.

Eine elegante Lösung gibt es mit dem beamerappendixnote Paket. Es nutzt zwei Befehle, \appxnote und \printappxnotes, um Material für den Anhang zu integrieren.

Der erste Befehl erstellt den Link zum Anhang und den anzuzeigenden Link-Text, der zweite Befehl setzt alle Appendix-Notizen.

\documentclass[12pt,ngerman]{beamer}
\usepackage[T1]{fontenc}
\usepackage{babel}
\usepackage{beamerappendixnote}
 
\begin{document}
 
\begin{frame}
\frametitle{Some Beamer Slide}
 
\begin{itemize}
	\item Some stuff
	\item that requires
	\item more background 
\end{itemize}
 
\appxnote{Proof}{We can easily prove this.}
 
\end{frame}
 
\printappxnotes
 
\end{document}

Fortnite zeitweise blockieren

Ich wurde von Freunden gebeten, eine Lösung für das temporäre Blockieren von Fortnite zu geben, um ihren Kindern a) die Möglichkeit zum Spielen zu geben, aber b) auch eine zeitliche Vorgabe wirksam umsetzen zu können.

Hier möchte ich kurz beschreiben, wie ich das umgesetzt habe. Die Lösung ist noch nicht perfekt, erfüllt aber aktuell den Zweck ganz gut.

Schritt 1: Ein Pi-Hole auf einem Raspberry Pi 4 wird als DNS Server im Netzwerk eingerichtet. Der Raspi 4 war bereits vorhanden, alternativ hätte es auch ein älteres Modell getan.

Zur Installation von Pi-Hole siehe beispielsweise https://www.youtube.com/watch?v=ubzd2H1wZxE oder https://www.heise.de/tipps-tricks/Pi-Hole-auf-dem-Raspberry-Pi-einrichten-so-geht-s-4358553.html.

Schritt 2: Eine Web-Anwendung mit Flask bauen. Flask ist ein Python-Framework für Web-Applikationen, mit dem man recht schnell zu guten Ergebnissen kommt. Wir legen die folgende Datei server.py an:

import os
from flask import Flask
 
app = Flask(__name__)
 
@app.route('/')
def index():
    return('<h1>Use /on and /off to enable/disable blocking</h1>')
 
@app.route('/<status>')
def setter(status):
    if status=='off':
        os.system("/usr/local/bin/pihole regex '.*\.epicgames.com' > /home/pi/epic.log")
        return '<h1>Turning off Fortnite</h1>'
    elif status=='on':
        os.system("/usr/local/bin/pihole regex -d '.*\.epicgames.com' >/home/pi/epic.log")
        return '<h1>Turning on Fortnite</h1>'

Je nachdem, ob diese server.py mit dem Parameter /on oder /off aufgerufen wird, wird epicgames geblockt oder nicht. Den entsprechenden Aufruf von pihole habe ich unter https://www.reddit.com/r/pihole/comments/a51wjr/blocking_fortnite_monday_to_friday/ gefunden.

Schritt 3: Setzen der Umgebungsvariablen für die Flask-App mittels export FLASK_APP=server.py. Optional kann man noch export FLASK_DEBUG=1 für das Debugging nutzen.

Schritt 4: Starten der Server.py mittels nohup flask run --host=192.168.0.91 & Dieser Aufruf sorgt dafür, dass auch nach dem Beenden der Shell das Programm weiterläuft. Als host muss man natürlich die IP-Adresse des Raspi nutzen.

Unter 192.168.0.91/on bzw. 192.168.0.91/off kann man jetzt die Blockierung an- bzw. ausschalten, diese URLs lassen sich auch gut auf dem Mobiltelefon wie eine App ablegen.

Verbesserungspotential besteht noch:

  • Nach dem Neustart des Raspi muss auch das Skript neu gestartet werden, das sollte man automatisieren. (Hinweis: Ich habe versucht, https://blog.miguelgrinberg.com/post/running-a-flask-application-as-a-service-with-systemd zu folgen, klappt aber noch nicht.)
  • Es kann sein, dass Pi-Hole auch ein Caching vornimmt und das Blockieren nicht sofort greift.
  • Flask läuft hier in einem DEV-Modus, für „Produktionsumgebungen“ empfiehlt sich ein WSGI-Server
  • Ein Aktivieren für einen bestimmten Zeitraum mit folgender automatischer Blockierung ist noch nicht implementiert, mit atd und cron sollte das aber möglich sein.

Mehrsprachige Beamerfolien erstellen – Teil 2

Basierend auf dem letzten Blog-Artikel dazu und einer Frage, die ich 2014 auf TSX gestellt habe, habe ich den Code für mehrsprachige Beamer-Folien angepasst.

\documentclass[ngerman]{beamer}
 
\usepackage{comment}
\makeatletter
\newif\if@ngerman
\newif\if@option@ngerman
\DeclareOption{ngerman}{%
	\@ngermantrue
	\@option@ngermantrue
}
\ProcessOptions*\relax
\newcommand*{\ifngerman}{%
	\if@ngerman
	\expandafter\@firstoftwo
	\else
	\expandafter\@secondoftwo
	\fi
}
\makeatother
 
\ifngerman{
\usepackage[main=ngerman]{babel}
\includecomment{DE}
\excludecomment{EN}
}{
\usepackage[main=english]{babel}
\includecomment{EN}
\excludecomment{DE}
}
 
\begin{document}
	\begin{DE}
		\begin{frame}[fragile]{Hallo Welt}
		Hallo Welt
	\end{frame}
\end{DE}
 
\begin{EN}
	\begin{frame}[fragile]{Hello World}
	Hello World
\end{frame}
\end{EN}
 
\end{document}

Normalisierung von Datenbanktabellen – Die 3. Normalform

This entry is part 3 of 3 in the series Data Warehouse, 3NF und Dimensional Modelling

In diesem Artikel geht es um die 3. Normalform. 3. Normalform bedeutet, dass die Daten

  • in der 2. Normalform sind und
  • kein Nichtschlüsselattribut transitiv von einem Kandidatenschlüssel abhängt.

Diese transitive Abhängigkeit erklärt man am besten an einem Beispiel:

CD

CD-ID Interpret Album Erscheinungsjahr Geburtsjahr
1234 Gabi Mustermann Gabi singt 2001 1963
2345 Max Mustermann Debütalbum 2001 1960
  • Aus der CD-ID folgt der Interpret oder die Interpretin
  • Aus dem Interpreten können wir nicht auf die CD schließen, denn jeder Interpret oder jede Interpretin kann mehr als eine CD veröffentlichen.
  • Aus dem Interpreten folgt aber das Geburtsjahr

Damit hängt das Geburtsjahr (ein Nichtschlüsselattribut) transitiv von der CD-ID (dem Schlüssel/Schlüsselkandidaten) ab. Transitiv bedeutet formell ausgedrückt: „Eine zweistellige Relation R heißt transitiv, wenn aus a R b und b R c stets a R c folgt.“

Wie löst man die transitive Abhängigkeit auf? Indem man weitere Tabellen erstellt. In unserem Beispiel erstellen wir eine Interpreten-Tabelle und verweisen in der CD-Tabelle nur noch auf den entsprechenden Schlüssel.

Interpret

Interpret-ID Name Geburtsjahr
1 Gabi Mustermann 1963
2 Max Muster 1960

CD

CD-ID Interpret-ID Album Erscheinungsjahr
1234 1 Gabi singt 2001
2345 3 Debütalbum 2001

Titel

CD-ID Tracknummer Titel
1234 1 Gabi singt laut
1234 2 Gabi singt leise
1234 3 Gabi singt weiter
2345 1 Von der Liebe
2345 2 Vom Leben
2345 3 Vom Ableben
2345 4 Duett mit Gabi

Normalisierung von Datenbanktabellen – Die 2. Normalform

This entry is part 2 of 3 in the series Data Warehouse, 3NF und Dimensional Modelling

Im ersten Artikel dieser Reihe hatten wir uns die 1. Normalform angeschaut, in diesem Artikel soll es um die 2. Normalform gehen.

Die Wikipedia sagt zur 2. Normalform: „Eine Relation ist genau dann in der zweiten Normalform, wenn die erste Normalform vorliegt und kein Nichtprimärattribut (Attribut, das nicht Teil eines Schlüsselkandidaten ist) funktional von einer echten Teilmenge eines Schlüsselkandidaten abhängt.“

Das hört sich etwas sperrig an, am besten betrachten wir unser Beispiel, das wir in die 1. Normalform gebracht hatten, die Schlüsselspalten seien CD ID und Tracknummer.

CD-ID Interpret Album Erscheinungsjahr Geburtsjahr Tracknummer Titel
1234 Gabi Mustermann Gabi singt 2000 1963 1 Gabi singt laut
1234 Gabi Mustermann Gabi singt 2000 1963 2 Gabi singt leise
1234 Gabi Mustermann Gabi singt 2000 1963 3 Gabi singt weiter
2345 Max Mustermann Debütalbum 2001 1960 1 Von der Liebe
2345 Max Mustermann Debütalbum 2001 1960 2 Vom Leben
2345 Max Mustermann Debütalbum 2001 1960 3 Vom Ableben
2345 Max Mustermann Debütalbum 2001 1960 4 Duett mit Gabi

Wir erkennen, dass viele Informationen redundant sind, was leicht zu Dateninkonsistenzen führen kann. Ändern wir beispielsweise in einer Zeile den Albumtitel und nur den, so haben wir zwei unterschiedliche Albentitel für ein und das selbe Album. Außerdem hängen Albumtitel, Interpret und Erscheinungsjahr nur vom Schlüssel CD-ID ab, nicht von der Track-ID.

Man löst diese Probleme und Redundanzen auf, indem man die Daten auf zwei Tabellen aufteilt, CD und Titel.

CD

CD-ID Interpret Album Erscheinungsjahr Geburtsjahr
1234 Gabi Mustermann Gabi singt 2001 1963
2345 Max Mustermann Debütalbum 2001 1960

Titel

CD-ID Tracknummer Titel
1234 1 Gabi singt laut
1234 2 Gabi singt leise
1234 3 Gabi singt weiter
2345 1 Von der Liebe
2345 2 Vom Leben
2345 3 Vom Ableben
2345 4 Duett mit Gabi

Normalisierung von Datenbanktabellen – Die 1. Normalform

This entry is part 1 of 3 in the series Data Warehouse, 3NF und Dimensional Modelling

Am besten lernt man, wenn man lehrt! Da ich mich momentan intensiver mit Themen wie Data Warehouse, Data Vault, 3NF und Dimensional Modelling beschäftige, versuche ich in den Artikeln dieser Reihe für mich ein wenig Licht in das Begriffsdunkel zu bringen. Quellen dieser Artikel sind die entsprechenden Wikipedia-Artikel, Google Ergebnisse und die 7. Auflage des Buchs „Fundamentals of Database Systems“ von Elmasri und Navathe, das es bei Amazon für knapp 45 Euro gibt.

In diesem Artikel soll es um die Normalformen gehen, siehe dazu den Wikipedia-Artikel für die Grundlagen.

Betrachten wir als Beispiel einen Satz Musikdaten, bestehend jeweils aus Interpret, Albumtitel, Erscheinungsjahr, Geburtsjahr des Interpreten, Titelliste:


Gabi Mustermann - Gabi singt, 2000, 1963, {1. Gabi singt laut, 2. Gabi singt leise, 3. Gabi singt weiter}
Max Mustermann - Debütalbum, 2001, 1960, {1. Von der Liebe, 2. Vom Leben, 3. Vom Ableben, 4. Duett mit Gabi} 

Diese Daten werden wir im Folgenden in die entsprechenden Normalformen bringen.

1NF

Für die 1. Normalform gilt (laut Wikipedia): Jedes Attribut der Relation muss einen atomaren/atomischen Wertebereich haben, und die Relation muss frei von Wiederholungsgruppen sein.

  • Atomar heißt, dass zusammengesetzte, mengenwertige oder geschachtelte Wertebereiche (also relationenwertige Attributwertebereiche) nicht erlaubt sind. Der Wertebereich keines Attributs einer Relation in 1NF kann in weitere (sinnvolle) Teilbereiche aufgespaltet werden.
  • Frei von Wiederholungsgruppen bedeutet, dass Attribute, die gleiche oder gleichartige Information enthalten, in eine andere Relation ausgelagert werden müssen.

Angewandt auf unser Beispiel bedeutet

  • atomar, dass wir sinnvoll aufteilbar Attribute wie Interpret-Albumtitel in entsprechende einzelne Attribute aufteilen müssen.
  • frei von Wiederholungsgruppen, dass die Liste der Lieddaten auf einzelne Zeilen verteilt werden muss.

Für unsere Musikdaten ergibt sich daher die folgende 1NF:

Interpret Album Erscheinungsjahr Geburtsjahr Tracknummer Titel
Gabi Mustermann Gabi singt 2000 1963 1 Gabi singt laut
Gabi Mustermann Gabi singt 2000 1963 2 Gabi singt leise
Gabi Mustermann Gabi singt 2000 1963 3 Gabi singt weiter
Max Mustermann Debütalbum 2001 1960 1 Von der Liebe
Max Mustermann Debütalbum 2001 1960 2 Vom Leben
Max Mustermann Debütalbum 2001 1960 3 Vom Ableben
Max Mustermann Debütalbum 2001 1960 4 Duett mit Gabi

Die ersten Schritte mit Amazon RDS

Beruflich werde ich mich in der nächsten Zeit intensiver mit AWS Anwendungen beschäftigen, daher hier ein Überblick über die ersten Schritte mit Amazon RDS.

RDS steht für „Amazon Relational Database Service“ und unterstützt eine Reihe von Datenbanken, aktuell MySQL, MariaDB, Oracle, Postgres, MS SQL und andere.

Schritt 1 besteht darin, die zu nutzende Datenbank auszuwählen, ich nutze hier die preiswerteste MariaDB Instanz, die es im Rahmen von FreeTiers.

In Schritt 2 definieren wir den Namen der Datenbank-Instanz sowie Login und Passwort des Administrators.

In Schritt 3 legen wir die Instanzgröße und Speichergröße fest, die automatische Größenerweiterung habe ich deaktiviert.

In Schritt 4 wird die VPC definiert, keine Ahnung welchen Zweck das hat.

Danach benötigt RDS einige Minuten, um die Datenbank anzulegen. Aus dem Internet ist die Datenbank damit noch nicht erreichbar, wir müssen noch die Datenbank modifizieren und „Publicly accessible“ auf „YES“ setzen. Die Modifikation soll sofort erfolgen.

Jetzt kann man beispielsweise aus HeidiSQL die Datenbank mit den Parametern Endpoint, Benutzername und Passwort erreichen.