FreshRSS

FreshRSS ist ein open Source RSS Feed Aggregator speziell für on premise Hosting.

Installation

Die Anwendung wird nach /usr/share/webapps/freshrss installiert. Hierzu muss man nur das Paket FreshRSS aus dem AUR installieren.

yaourt --noconfirm -S freshrss

Einrichtung NginX

# HTTP -> HTTPS
server {
    listen 80;
    server_name freshrss.xxx.org;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name freshrss.xxx.org;

    # SSL
    include /etc/nginx/ssl/*.xxx.org;

    # logging
    error_log /var/log/nginx/freshrss_error.log debug;
    access_log /var/log/nginx/freshrss_access.log;

    # the folder p of your FreshRSS installation
    root /usr/share/webapps/freshrss/p/;
    index index.php index.html index.htm;

    # php files handling
    # this regex is mandatory because of the API
    location ~ ^.+?\.php(/.*)?$ {
        fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        # By default, the variable PATH_INFO is not set under PHP-FPM
        # But FreshRSS API greader.php need it. If you have a “Bad Request” error, double check this var!
        fastcgi_param PATH_INFO $fastcgi_path_info;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    location / {
        try_files $uri $uri/ index.php;
    }
}

Automatisches aktualisieren der Feed im Hintergrund

Leider bringt die Software keine Möglichkeit mit die abonnierten Feed automatisch im Hintergrund zu aktualisieren. Dieses ist aber nicht weiter dramatisch, da ein Systemd Timer schon das Problem lösen kann.

Timer Task bestimmen

Es muss nur das mitgelieferte Skript actualize_script.php mit den Benutzerrechten des WebServers ausgeführt werden. Unter Arch Linux ist das in der Regel der User http in der Gruppe http.

[Unit]
Description=auto refresh FreshRSS feeds

[Service]
Type=oneshot
User=http
Group=http
ExecStart=/usr/bin/php -f /usr/share/webapps/freshrss/app/actualize_script.php > /tmp/FreshRSS.log 2>&1

Interval einstellen

Eine Aktualisierung alle 10 Minuten anstoßen.

[Unit]
Description=refresh FreshRSS feeds every 10 minutes

[Timer]
OnCalendar=*:0/10
Persistent=true

[Install]
WantedBy=timers.target

Den Timer starten

Jetzt muss nur noch der Timer gestartet werden.

systemctl enable --now freshrss.timer

Releases

Wenn ein Projekt auf GitHub eine neue Version veröffentlich, bekommt man das nicht unbedingt gleich mit. Ich nutze daher einen Dienst von GitHub und abonniere mir einen RSS-Feed von den Releases.

FEED URL

Wie kommt man an die Feed URL? Es ist ganz einfach. Gehen Sie auf die Releases Seite von dem gewünschten GitHub Projekt. Fügen Sie nun am Ende der URL noch .atom an und schon haben sie die URL für den Feed.

Das Gradle Deploy Plugin

Das https://gradle-ssh-plugin.github.io/docs/ gradle plguin eigent sich um zum Beispiel eine Spring Boot Jar auf einen remote Server zu deployen.

Upload per SSH auf einen Server

Hier nun ein Beispiel, wie man eine Datei auf einen remote Server kopiert. Dazu muss zunächst der Server definiert werden. Im Anschluss wird ein Deploy Task definiert, der von dem bootJar Task abhängt.

/**
 * This deployment script uses ssh to copy the spring boot jar to a remote server. The credentials are stored
 * in the credential store plugin from nu.studer. 
 *
 * set user and password for deployment
 *
 * to set username
 * ./gradlew addCredentials --key deployUsername --value sascha
 *
 * to set password
 * read -s -p "Enter Password: "; ./gradlew addCredentials --key deployPassword --value $REPLY; unset REPLY
 */


// define remote server
remotes {
    server {
        host = 'server'
        user     = credentials.deployUsername
        password = credentials.deployPassword
    }
}

// copy jar to server
task deploy(dependsOn: bootJar) {
    doLast {
        ssh.run {
            session(remotes.server) {
                put from: bootJar.archivePath.path, into: '/somejar-0.0.1.jar'
            }
        }
    }
}

Weitere Möglichkeiten gradle-ssh Plugin

Das war hier natürlich nur ein sehr einfach gehaltenes Bespiel. Es lassen sich durch die zahlreichen Funktionen wie das Ausführen, Kopieren und vielen mehr fast alles realisieren.

Das Vaadin Addon Repository

Damit man auf die Vielzahl von Addons für das Vaadin Framework drauf zugreifen kann, muss man das Repository bekanntmachen. Ich nutze das Vaadin Plugin com.devsoap.vaadin-flow, um das Repository einzubinden.

plugins {
    id 'com.devsoap.vaadin-flow' version '1.2'
}

Repository einbinden

Das Repository kann über die Methode addons() im Closure der Repositories eingefügt werden.

repositories {
    vaadin.addons()
}

Nun kann man auf eine Vielzahl von Addons für Vaadin Flow zurückgreifen.

DefaultNamingStrategy von Hibernate

Per default werden die Tabellen nach den Klassennamen und die Columns nach den Feldern benannt. Manchmal stört es einen oder es passt nicht zur der restlichen Struktur in der Datenbank.

Eine Custom NamingStrategy definieren

In diesem Beipsiel hat die Anwendung Tabellen die Über Hibernate verwaltet werden, sowie Tabellen die losgelöst davon im Schema vorhanden sind. Um diese Gruppen von Tabellen sauber zu trennen, kann man z.B. ein Prefix vor jeden Tabellenname einfügen und schon lassen sich schnell die Tabellen unterscheiden.

Tabellenname über @Table festelegen

Über die @Table Annoatation kann über den Parameter name der Tabellenname festgelegt werden. Dieses bietet natürlich größtmögliche flexibilität ist aber aufwändig, da jede Entität das angepasst werden muss.

@Table(name="hibernate_user")

Tabellenname über eine NamingStrategy festlegen

Wie oben beschrieben, wird es in den meisten Fällen nicht angebracht sein alles von Hand einzufügen. In Hibernate gibt es für den Zweck 2 Strategien, um die Generierung der Namen der Tabellen, Felder und Objekten zu beeinflussen. In diesem Beispiel interessiert uns die PhysicalnamingStrategy. Dazu implementieren wir das Interface PhysicalnamingStrategy wie folgt:

/**
 * Adds a prefix "hibernate_" to all tablenames.
 * @author sascha
 *
 */
public class PrefixNamingStrategy implements PhysicalNamingStrategy {

    @Override
    public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return stringToIdentifier("hibernate_" + name);
    }

    @Override
    public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    /**
     * String to Identifier
     */
    private Identifier stringToIdentifier(String name) {
        return Identifier.toIdentifier(name);
    }

Als Prefix habe ich hier hibernate_ hinzugefügt. Alle anderen Bezeichnungen werden 1:1 übernommen.

Einbindung über eine Bean

Damit die Strategy auch angewandt wird, muss diese gesetzt werden. Das kann man über die Properties oder eine Bean machen. Hier zeige ich kurz die Bean Variante:

@Bean
public PhysicalNamingStrategy physicalNamingStrategy() {
    return new PrefixNamingStrategy();
}

Git Credentials

Es ist auf Dauer müßig, immer und immer wieder die Credentials einzugeben. Daher gibt den internen Credentials Store von Git, aber dieser speichert nur SSH Credentials.

git config --global credentials.helper 'store --file ~/.git-credentials'

Lösung für HTTP(s) Verbindungen

Es gibt im AUR das Paket git-credential-gnome-keyring. Der Helper ermöglicht es auch für HTTP Verbindungen die Credentials zu speichern.

yaourt -S --noconfirm git-credential-gnome-keyring

Nun muss der Helper nur noch git bekannt gemacht werden:

git config --global credential.helper /usr/lib/git-core/git-credential-libsecret

Jetzt speichert Git die Credentials im Gnome-Keyring.

Einen bestimmten Test ausführen

Der Task test kennt Filter. Mit diesen Filtern kann man die auszuführenden Tests einschränken.

./gradlew test --tests *Soup.isBlackEyedBeansSoup -ds

Sternchen sind als Platzhalter erlaubt. Der Punkt trennt Klassen von Methode(n). Auch hier sind Platzhalter erlaubt. In dem Beispiel oben wird die Methode isBlackEyedBeanSoup in allen Klassen die mit Soup enden aufgerufen.

Zusätzliche Informationen ausgeben

Die normale Ausgabe unterdrückt Informationen die die Fehleranalyse ermöglichen.

  • -d, –debug Log in debug mode (includes normal stacktrace).

  • -s, –stacktrace Print out the stacktrace for all exceptions.

JSON

Der Aufruf von Restschnittstellen im Browser zeigt oftmals unformatiertes JSON an. Das macht das lesen der Response schwierig und dadurch implizit auch fehleranfälliger.

Chrome Plugin

Natürlich gibt es dafür schon Lösungen. Es gibt eine ganze Reihe von Plugins für Firefox und Chrome. Ich verwende
JSONView. Dieses kann das JSON parsen und verwendet hierfür intern jsonlint. Die Ausgabe ist nun übersichtlich und wird ordentlich dargestellt. Das Plugin ist performant und das Anzeigen von JSON Antworten macht richtig spaß.

Wildcard Zertifikat

Der freie DNS Dienst DuckDNS bietet Unterstützung für TXT records an. Zudem werden beliebige SubDomains unterstützt, sodass das zusammen für die Validierung von Wildcard Zertifikaten von Let’s Encrypt zu gebrauchen ist. Mit den Wildcard Zertifikat kann man lokale Dienste auch über SSL verschlüsseln. Dazu kann man den NginX oder den Apache WebServer als Reverse Proxy einrichten.

Einrichtung mit Certbot

Unter Arch Linux verwende ich Certbot, um den Request anzufordern und die Challange zu lösen. Das lösen der Challange und somit die Validierung der Domain, geschiet in dem man ein Secret (Key) als TXT record Let’s Encrypt auf der Domain bereit stellt.

1. Schritt – Anforderung eines Wildcard Zertifikat

Als erstes müssen wir mit dem Certbot ein Wildcard Zertifikat anfordern. Das macht der Parameter -d. Tragen Sie hier ihre DuckDNS Domain ein für die sie ein Wildcard Zertifikat beantragen möchten. In diesem Beispiel also *.XXXXXXXXX.duckdns.org.

#request wildcard certificate
certbot --server https://acme-v02.api.letsencrypt.org/directory -d *.XXXXXXXXX.duckdns.org --manual --preferred-challenges dns-01 certonly

Beantworten Sie die Frage nach dem IP-Logging mit y.

certbot --server https://acme-v02.api.letsencrypt.org/directory -d *.XXXXXXXXX.duckdns.org --manual --preferred-challenges dns-01 certonly
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator manual, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for XXXXXXXXX.duckdns.org

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.XXXXXXXXX.duckdns.org with the following value:

JAn7QDG3W4f-RqbztXnwkjET3DpU7GTImcysQfH60zE

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

Wichtig: Es muss nun zunächst der TXT record bei DuckDNS angelegt werden. Wechseln Sie zunächst in ein neues Terminal, bevor sie fortfahren.

2. Das Secret bereitstellen

Die Validierung, ob sie wirklich der Inhaber (oder berechtigter) sind, geschiet über ein Sekret welches sie in einem TXT record hinterlegen müssen. Wie sie der Ausgabe oben entnehmen können, ist das in diesem Fall der Key JAn7QDG3W4f-RqbztXnwkjET3DpU7GTImcysQfH60zE.

Die API von DuckDNS erlaubt es das setzen und löschen TXT records. Durch einen einfachen Aufruf mit curl sind wir in der Lage das Sekret zu setze.

 curl "https://www.duckdns.org/update?domains=XXXXXXXXX.duckdns.org&token=TTTTTTTT-TTTT-TTTT-TTTT-TTTTTTTTTTTT&txt=JAn7QDG3W4f-RqbztXnwkjET3DpU7GTImcysQfH60zE"

3. Überprüfen ob der TXT record gesetzt ist

In der UI von DuckDNS kann man leider die TXT records nicht erkennen. Daher können sie zum beispiel mit dig oder einem Online Dig Dienst dieses prüfen.

Ich nutze hier, wie von DuckDNS vorgeschlagen www.digwebinterface.com.

 https://www.digwebinterface.com/?hostnames=XXXXXXXXX.duckdns.org&type=TXT&ns=resolver&useresolver=8.8.4.4&nameservers=

Überprüfen sie mit dig, ob der TXT record verfügbar ist. Solange er nicht gesetzt ist, können sie mit dem nächsten Schritt nicht weiter machen.

4. Certbot Validierung fortsetzen

Nun kann die Validierung fortgesetzt werden und somit die Erstellung abgeschlossen werden.

Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/XXXXXXXXX.duckdns.org/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/XXXXXXXXX.duckdns.org/privkey.pem
   Your cert will expire on 2019-08-25. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

Nun stehen die Dateien unter /etc/letsencrypt/live/XXXXXXXXX.duckdns.org/ bereit.

5. Cleanup – TXT record löschen

Die API von DuckDNS erlaubt, wie oben beschrieben, auch das löschen von TXT records. Der Aufruf ist identisch, bis auf das zusätzlich der Parameter clear=true mit angehangen wird.

curl "https://www.duckdns.org/update?domains=XXXXXXXXX.duckdns.org&token=TTTTTTTT-TTTT-TTTT-TTTT-TTTTTTTTTTTT&txt=JAn7QDG3W4f-RqbztXnwkjET3DpU7GTImcysQfH60zE&clear=true"

Build Environment anzeigen

Welche Klassen sind im Classpath während des Buildvorgangs?

jre11 ./gradlew buildEnvironment

jre11 ist ein kurzes Shellskript welches mir die Java Umgebung in der Version 11 für das Programm zur Verfügung stellt und ist daher optional.

Welche Klassen werden in einer bestimmten Konfiguration geladen?

Gradle bindet je nach Aufgabe unterschiedliche Klassen ein. Welche sind diese? Das ist zum Beispiel die Frage die man sich stellt, wenn die Ausführung des Code Generators von Jooq unter JDK 11 nicht funktioniert.

Mit –configuration kann man den Dependency Tree auf eine Konfiguration einschränken. Hier ist es also die jooqRuntime die uns interressiert.

jre11 ./gradlew dependencies --configuration jooqRuntime

> Task :dependencies

------------------------------------------------------------
Root project
------------------------------------------------------------

jooqRuntime - The classpath used to invoke the jOOQ generator. Add your JDBC drivers or generator extensions here.
+--- org.jooq:jooq-codegen -> 3.11.5
|    +--- org.jooq:jooq:3.11.5
|    |    \--- javax.xml.bind:jaxb-api:2.2.12 -> 2.3.1
|    |         \--- javax.activation:javax.activation-api:1.2.0
|    \--- org.jooq:jooq-meta:3.11.5
|         \--- org.jooq:jooq:3.11.5 (*)
+--- org.xerial:sqlite-jdbc:3.23.1
+--- com.sun.xml.bind:jaxb-core:2.3.0.1
\--- com.sun.xml.bind:jaxb-impl:2.3.0.1

(*) - dependencies omitted (listed previously)

In dem Beipiel benötigt der Code Generator JAXB, welche ja aus dem JDK entfernt worden ist. So kann überprüft werden, ob die Klassen geladen werden.