When I first set this blog up my approach was to only host on IPFS. Users without IPFS would see the site through a gateway while users with IPFS could directly load the site. The problem was - and probably still is - that most public IPFS gateways are slow as hell. You might be lucky loading IPFS content directly using the CID but as soon as you would use IPNS, public gateways take ages to load.
This time users with browsers lacking IPFS support will get inden.one delivered directly by a standard web server. Dead simple, really fast. But as soon as you have a browser with integrated IPFS support or one with the IPFS Companion extension, loading inden.one will lookup the DNS TXT entry containing a link to an IPNS entry for the most current version of my site and all content will be served through IPFS.
If you have any comments or think that something might not be working correctly with my blog and IPFS please don’t hesitate to drop me a line using the envelope on the top right! (Who needs dynamic sites with comments when you can have mail?)
Btw, if your browser does not transform the URL automatically, this is the way to go: ipns://inden.one
I really hope to come up with some real and less meta or even content at all in future :)
]]>You can request specific data from the appliance at http://IP_OF_YOUR_APPLIANCE/lala.cgi using a POST request determining which information should be responded with.
A basic python dict for the request body that gathers the most interesting information looks like this:
BASIC_REQUEST = {
'STATISTIC': {
'CURRENT_STATE': '', # Current state of the system (int, see SYSTEM_STATE_NAME)
'LIVE_BAT_CHARGE_MASTER': '', # Battery charge amount since installation (kWh)
'LIVE_BAT_DISCHARGE_MASTER': '', # Battery discharge amount since installation (kWh)
'LIVE_GRID_EXPORT': '', # Grid export amount since installation (kWh)
'LIVE_GRID_IMPORT': '', # Grid import amount since installation (kWh)
'LIVE_HOUSE_CONS': '', # House consumption since installation (kWh)
'LIVE_PV_GEN': '', # PV generated power since installation (kWh)
'MEASURE_TIME': '' # Unix timestamp for above values (ms)
},
'ENERGY': {
'GUI_BAT_DATA_CURRENT': '', # Battery charge current: negative if discharging, positiv if charging (A)
'GUI_BAT_DATA_FUEL_CHARGE': '', # Remaining battery (percent)
'GUI_BAT_DATA_POWER': '', # Battery charge power: negative if discharging, positiv if charging (W)
'GUI_BAT_DATA_VOLTAGE': '', # Battery voltage (V)
'GUI_GRID_POW': '', # Grid power: negative if exporting, positiv if importing (W)
'GUI_HOUSE_POW': '', # House power consumption (W)
'GUI_INVERTER_POWER': '', # PV production (W)
'STAT_HOURS_OF_OPERATION': '' # Appliance hours of operation
},
'BMS': {
'CHARGED_ENERGY': '', # List: Charged energy per battery
'DISCHARGED_ENERGY': '', # List: Discharged energy per battery
'CYCLES': '' # List: Cycles per battery
},
'PV1': {
'MPP_CUR': '', # List: MPP current (A)
'MPP_POWER': '', # List: MPP power (W)
'MPP_VOL': '', # List: MPP voltage (V)
'POWER_RATIO': '', # Grid export limit (percent)
'P_TOTAL': '' # ?
},
'FACTORY': {
'DESIGN_CAPACITY': '', # Battery design capacity (Wh)
'MAX_CHARGE_POWER_DC': '', # Battery max charging power (W)
'MAX_DISCHARGE_POWER_DC': '' # Battery max discharging power (W)
}
}
If this is interesting for you, feel free to use my python library for your projects: https://gist.github.com/smashnet/82ad0b9d7f0ba2e5098e6649ba08f88a
]]>Eigentlich ist es recht einfach einer statischen Seite einen neuen Blogpost hinzuzufügen. Leider braucht es dafür aber oft Dinge die man auf einem iPad meistens nicht zur Verfügung hat. Werfen wir einen Blick auf die einzelnen Schritte:
Wie so eine Ordnerstruktur einer Jekyllseite aussieht ist hier bereits gut beschrieben, daher gehe ich darauf nicht mehr explizit ein.
Der erste Schritt ist auf dem iPad noch problemlos realisierbar. Ich schreibe hier gerade mit Textastic diesen Blogpost, und bin damit sehr zufrieden.
Im zweiten Schritt fangen die Probleme an. Jekyll ist ein Kommandozeilen-Tool welches - einfach gesprochen - aus meinen .md Dateien eine HTML Seite generiert. Jekyll gibt es weder als App, noch lässt Apple uns eine Kommandozeile auf dem iPad nutzen. Soweit so gut (oder auch nicht).
Der dritte Schritt wäre theoretisch auf dem iPad machbar. Es gibt genug Apps die mir erlauben Daten auf einen Webserver hochzuladen. Das würde sogar direkt mit meiner Schreib-App Textastic gehen. Bringt nur leider nichts, wenn ich wegen Schritt zwei meine Seite auf dem iPad nicht generieren kann.
Um dennoch einen Workflow zu finden, der es mir erlaubt neue Posts direkt vom iPad (oder sogar iPhone) aus zu schreiben, bin ich auf Lösungssuche gegangen. Eine wichtige Komponente hatte ich dabei schon zunächst unbewusst an Bord: GitHub
Ich nutze Github zur Versionierung meiner Seite in einem Github Repo, und mit Github Actions ist kürzlich ein Feature für alle Nutzer verfügbar geworden, welches unser Problem löst. Actions erlaubt es uns bestimmte Dinge automatisiert durchzuführen, wenn z.B. neuer Inhalt ins Repo gepusht wurde. Bingo! Wir können also mit Actions das Generieren der HTML Seite mit Jekyll, und das Aktualisieren auf dem Webserver ins Internet verlagern!
Bleibt noch die Frage wie ich auf dem iPad komfortabel mein Repo auf Github mit neuen Posts aktualisieren kann? Dafür nutze ich aktuell die App Working Copy. Diese habe ich mit meinem Github-Account verknüpft, und kann an all meinen Repos arbeiten. Prinzipiell geht das direkt direkt auch mit dem Editor in Working Copy, nur arbeite ich lieber mit Textastic. Der Clou ist nun, dass Working Copy in iOS als “Ort” in der Dateien-App auftaucht. Damit ist die Integration in Textastic sehr einfach:
Mein Repo taucht nun vollständig in Textastic auf, und jede Änderung wir automatisch zu Working Copy gespiegelt.
Ich kann also nun, nachdem ich jetzt diese letzten Worte geschrieben habe einfach von Textastic in die Working Copy App wechseln, dort meinen neuen Post commiten und pushen, und schon sollte sich automagisch meine Website aktualisieren.
Aber Moment, wie war das noćh mit dem Actions Teil? Wie genau funktioniert das? Nun, das würde sicher einen komplett eigenen Blogpost rechtfertigen und füllen, daher hier nur kurz der für mich funktionierende Workflow mit kurzer Erläuterung:
# This is a basic workflow to help you get started with Actions
name: CI
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout
uses: actions/checkout@v2
# Hier führe ich das insert_date.sh Skript aus meinem Repo aus, um das Datum des Deployments im Footer meiner Seite zu integrieren
- name: Insert date of last update
run: sh insert_date.sh
# Hier wird mit Jekyll die HTML Seite gebaut
- name: Build Jekyll
uses: jerryjvl/jekyll-build-action@v1
# Hier wird die fertige Seite per SSH zum Webserver geschoben
- name: ssh deploy
uses: easingthemes/ssh-deploy@v2.1.2
env:
# Private Key
SSH_PRIVATE_KEY: $
SOURCE: "_site/"
REMOTE_HOST: $
REMOTE_USER: $
TARGET: $
Wenn ihr euch den Aufbau dieser Seite im konkreten Beispiel anschauen wollt, werft gerne einen Blick darauf: https://github.com/smashnet/smashblog
]]>In this blog post I’d like to take you on the journey of setting up an own instance of Mastodon using Docker(-Compose) and Traefik v2.9.
Why do I think we need yet another tutorial for this? Well, at first there seem to be not so many tutorials for Traefik v2 around yet. Searching the internet mostly yields Traefik v1 related guides and tutorials. Secondly, there are two things I just couldn’t achieve using the once existing Mastodon docker guide (by the time of writing this guide, Mastodon removed its docker guide completely):
Despite of having a good documentation, there is a design decision I dislike in the Mastodon docker guide. That is they place the nginx reverse-proxy outside of docker hence requiring the administrator to manually setup and configure a separate nginx on her box.
So, this guide goes another way :)
This guide shows how you can setup your own instance of Mastodon using a single docker-compose file.
In the former Mastodon docker guide and the docker-compose.yml
from Mastodons repository they place the nginx reverse-proxy outside of docker hence requiring the administrator to manually setup and configure a separate nginx on her box.
I really like keeping things as simple as possible so I tried reducing the complexity by integrating Traefik as reverse-proxy and its configuration into the docker-compose file ending up with a single file that could fire up the complete Mastodon instance :)
Additional features (over the original docker-compose from the repo):
web
and streaming
containers of MastodonBefore we start there are some things that we need to prepare:
<DOMAIN>
pointing to your box (like social.yourdomain.com
)social.yourdomain.com A ip.of.your.box
80
and 443
docker-compose
installed on our box
Before you continue, make sure these things are done.
If you just want to get things running, follow these steps:
touch .env.production
sudo chown 991:991 .env.production
docker-compose run --rm -v $(pwd)/.env.production:/opt/mastodon/.env.production web bundle exec rake mastodon:setup
Users
, Secrets
, etc. don’t worry.docker-compose up -d
That should be it. You now have an instance of Mastodon running behind a traefik reverse-proxy handling HTTPS redirection, TLS termination and automagic setup and renewal of Let’s Encrypt certificates. Persistence data from the containers is stored in folders located in the same directory as your docker-compose.yml
.
Well, there are a lot of things going on in the docker-compose.yml
that you might want to understand. Basically, it’s the whole setup of traefik
and the corresponding Mastodon related configuration.
So let’s go through the services being started in the docker-compose file and see what happens.
At first, we start traefik
so we have someone answering requests from outside. More specifically, traefik’s job will be to route requests headed to your <DOMAIN>
further to your Mastodon instance and back outside. While doing this, traefik handles:
web
and streaming
containers of MastodonLet’s have a look at the traefik
part of the docker-compose.yml
:
traefik:
image: traefik:2.9
container_name: "traefik"
restart: always
command:
# - "--log.level=DEBUG"
- "--api.dashboard=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=<LETSENCRYPT_MAIL_ADDRESS>"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
labels:
- "traefik.enable=true"
# Dashboard
- "traefik.http.routers.traefik.rule`(Host(`<DOMAIN>`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`)))"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.middlewares=dashboardauth"
- "traefik.http.middlewares.dashboardauth.basicauth.users=admin:<TRAEFIK_DASHBOARD_ADMIN_PASSWORD>"
# HTTPS Redirect
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https@docker"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./letsencrypt:/letsencrypt
networks:
- external_network
At first glance, we see there is a lot of configuration covered by commands
and labels
. This is intended, as our goal is to have a docker-compose.yml
that is as self-contained as possible. To understand why certain things are commands
and others are labels
we must know that Traefiks configuration is composed of a static
part and a dynamic
part. For further details, there are some great explanations in the Traefik documentation.
The static
configuration deals with settings that are required at startup time. In this case that are all settings set as commands
in our docker-compose.yml
:
--api.dashboard=true
--entrypoints.web.address=:80
80
--entrypoints.websecure.address=:443
443
--providers.docker=true
providers
are sources of dynamic configuration. So this command tells Treafik to accept dynamic configuration found in docker labels--providers.docker.exposedbydefault=false
false
.traefik.enable=true
for every service that should be routed through Traefik.--certificatesresolvers.letsencrypt.acme.httpchallenge=true
letsencrypt
80
.--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
80
is called web
(--entrypoints.WEB.address=:80
, remember?) so we have to set it here for the HTTP challenge--certificatesresolvers.letsencrypt.acme.email=<LETSENCRYPT_MAIL_ADDRESS>
--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
bind
or volume
otherwise your certificate and keys will be lost on each container restart.That’s it for the static configuration. We have successfully set up the Traefik dashboard, endpoints for HTTP and HTTPS, docker as provider for dynamic configuration, and a certificate resolver handling Let’s Encrypt stuff.
Let’s move on to the dynamic
configuration found in the labels
section. These configuration items are related to “How do I access Traefiks dashboard?” and “HTTPS redirection”.
We start with the relevant labels for accessing the Traefik dashboard:
traefik.enable=true
traefik.http.routers.traefik.rule=(Host(
<DOMAIN>
) && (PathPrefix(
/api
) || PathPrefix(
/dashboard
)))
traefik
handling all requests to <DOMAIN>
with paths /api
and /dashboard
(and sub paths).traefik.http.routers.traefik.service=api@internal
traefik
should forward to is api@internal
. That’s the internal service providing the dashboard.traefik.http.routers.traefik.tls.certresolver=letsencrypt
<DOMAIN>
, use the certificate resolver letsencrypt
(which we created in our static configuration) to get onetraefik.http.routers.traefik.entrypoints=websecure
traefik
should listen on our endpoint websecure
(which basically means port 443)traefik.http.routers.traefik.middlewares=dashboardauth
dashboardauth
to our router traefik
traefik.http.middlewares.dashboardauth.basicauth.users=admin:<TRAEFIK_DASHBOARD_ADMIN_PASSWORD>
dashboardauth
is created and should do basicauth
with the following users
That’s all we need to have our Traefik dashboard being accessible via HTTPS.
The next labels make Traefik redirect HTTP requests on port 80 to HTTPS.
traefik.http.routers.http-catchall.rule=hostregexp(
{host:.+})
http-catchall
and defines a rule that all requests should be handled by this routertraefik.http.routers.http-catchall.entrypoints=web
http-catchall
should handle requests coming from the entrypoint web
(Port 80)traefik.http.routers.http-catchall.middlewares=redirect-to-https@docker
redirect-to-https
and define it in the next line. The @docker
is optional and tells Traefik that the middleware is defined in the dynamic configuration from the provider docker
, meaning here in the labels section.traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
redirect-to-https
should use the pre-defined redirectscheme
https
And that’s it for the HTTPS redirection. We now have completed the configuration of Traefik in our docker-compose.yml
.
We wanted to do something meaningful over just firing up Traefik, remember? The rest of our docker-compose.yml is composed of services required by Mastodon. I won’t go into detail here. The part worth looking at are the services web
and streaming
as those must be accessible from the outside and hence need configuration for Traefik. We need web
to deliver a nice UI for using Mastodon, and we need streaming
to realize all the inter instance communication.
Luckily, the Traefik configuration is straight forward for both services and we know all the required parts from the labels setting up the Traefik dashboard.
[...]
web:
[...]
labels:
- "traefik.enable=true"
- "traefik.http.routers.mastodon-web.rule=Host(`<DOMAIN>`)"
- "traefik.http.routers.mastodon-web.entrypoints=websecure"
- "traefik.http.routers.mastodon-web.tls.certresolver=letsencrypt"
[...]
You might recognize these labels, so I will just decribe in a few words what they do:
web
to be accessible through Traefikmastodon-web
with a rule
that lets the router react on requests coming in on your <DOMAIN>
websecure
(port 443)
http://<DOMAIN>
(without s
) are redirected to the websecure
endpoint by our HTTPS redirection router and middleware defined in the traefik
labels section, remember?letsencrypt
to acquire or renew the TLS certificate[...]
streaming:
[...]
labels:
- "traefik.enable=true"
- "traefik.http.routers.mastodon-streaming.rule=(Host(`<DOMAIN>`) && PathPrefix(`/api/v1/streaming`))"
- "traefik.http.routers.mastodon-streaming.entrypoints=websecure"
- "traefik.http.routers.mastodon-streaming.tls.certresolver=letsencrypt"
[...]
For Mastodons streaming service this is very similar, let’s see:
streaming
to be accessible through Traefikmastodon-streaming
with a rule
that lets the router react on requests coming in on your <DOMAIN>
AND have a path that starts with /api/v1/streaming
websecure
(port 443)letsencrypt
to acquire or renew the TLS certificateI was not quite happy with the assumptions made by Mastodon regarding instance setup. Especially, that they make instance admins go through a hell of nginx configuration. My goal was to make the process of setting up a new Mastodon instance as easy as possible. The solution is the combination of Mastodon with Traefik instead of Nginx and a self-contained docker-compose.yml that sets up everything necessary.
I sincerely hope this guide is useful for other upcoming Mastodon admins or Traefik fans :)
]]>Gewünschte Java Version(en) installieren:
brew tap AdoptOpenJDK/openjdk
brew search /adoptopenjdk/
brew cask install adoptopenjdk8
(oder irgendeine andere Version aus dem vorherigen Schritt)
Benutze jenv
(setzt JAVA_HOME) um bequem zwischen den Versionen zu springen:
brew install jenv
(jenv
installieren)
jenv add /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home/
(Java Version mit jenv
bekannt machen)
jenv versions
(bekannte Versionen anzeigen)
jenv global openjdk64-1.8.0.232
(zu verwendende Version auswählen)
Quellen:
Hinweis
Entegegen der Anleitung von dzone.com gibt es aktuell alle Java Versionen nur noch als cask
und nicht mehr als formulae
.
Seit der Cookie-Richtlinie und der DSGVO (oder GDPR in den USA) kennt man sie, die Einblendungen beim Besuch von Websites, die dem Nutzer das Akzeptieren aller Cookies mit nur einem Klick besonders leicht machen wollen. Klar, als Betreiber habe ich ein Interesse
Damit soll jetzt nach dem Urteil des EuGH Schluss sein. Sämtliche Cookies, unabhängig vom Zweck, müssen nun vom Nutzer einzeln akzeptiert werden. Das Surfen könnte also bald ein wenig Umständlicher werden, wenn man sich beim Besuch einer Seite nicht mehr nur mit einem Button zum Akzeptieren, sondern auf einmal mit wesentlich mehr Buttons auseinandersetzen muss.
Aber nicht nur der Nutzer hat einen Mehraufwand, auch die Seitenbetreiber müssen nun dafür sorgen, dass ihr Cookie-Opt-In-Dialog konform zum Gesetz des EuGH funktioniert.
Die Intention hinter dem Gesetz ist sicherlich die Richtige. Allerdings glaube ich nicht, dass mit dem Gesetz irgendetwas besser wird. Wer sich mit der Materie auseinander setzen möchte um seine Privatspähre zu schützen, der konnte das schon vorher tun per Opt-out. Wer allerdings einfach nicht genervt werden möchte, ist jetzt trotzdem gezwungen sich mit der Materie auseinanderzusetzen, denn ein einfaches Wegklicken des Opt-in-Dialogs dürfte die Funktion vieler Websites stark einschränken.
]]>Find current ideas and issue status here: https://miro.com/app/board/o9J_kxu6Y1o=/ LibreBlogging on GitHub: https://github.com/smashnet/LibreBlogging
The benefits of publishing on IPFS are:
However, from a content creators perspective there are certain things that make publishing on IPFS rather difficult in comparison to regular blogging platforms:
QmTPixNxp6y3iWs2i5BTcv6EzNsX3MaEty1Yys2Nm4W8JD
With LibreBlogging we try to come over these hassles and make it simple and easy to have a blog on IPFS while staying as decentralized as possible.
Ein wesentlicher Grund, warum Sketchnotes so gut funktionieren, ist die Dual Coding Theory. Diese besagt, dass Informationen aus einem visuellen Teil und einem verbalen Teil bestehen. Unser Kopf kann sich Dinge genau dann gut merken wenn es beide Teile für die Information gibt. Dabei ist der visuelle wichtiger als der verbale.
Umgangssprachlich kennt man das schon aus dem Spruch “Ein Bild sagt mehr als tausend Worte”. Allerdings ist das nur ein Teil der Wahrheit (wenn auch ein Großteil), denn am Besten funktioniert das Merken in der Kombination aus Beidem.
Hier mein Sketchnote dazu:
]]>Es gab wieder eine Menge spannende Themen die im Konferenz-Style von den Kollegen vorgestellt wurden. Einige davon sind:
Hier noch ein paar Eindrücke und zwei großartige Sketchnotes von meiner Kollegin Joy Heron:
]]>Beim Syntax-Highlighting war meine erste Anlaufstelle highlighter.js, da ich es noch aus CodiMD kannte. Allerdings integrierte sich das nicht so schön mit Jekyll. Netterweise bringt Jekyll allerdings die Möglichkeit mit z.B. Rouge zu integrieren. Das hat den großen Vorteil, dass Rouge bereits beim Bauen der Seite die verwendete Sprache erkennt, und das HTML entsprechend bauen kann. Highlighter JS fährt hier den Ansatz das Dokument auf Clientseite nachträglich zu analysieren und per JS umzubauen. Nicht so schön…
Weiterhin sind noch ein paar Kleinigkeiten wie besser sichtbarer inline-code
, schönere Headings und
Tabellen | mit |
---|---|
sichtbaren | Rändern |
und gestreiften | Zeilen |
… außerdem wird das CSS nun in der main.css
importiert, sodass im HTML nur noch ein Aufruf notwendig ist.