Sonntag, 5. März 2017

csrf Token und FHEM

Wenn ich es richtig verstanden habe, gibt es eine Bedrohung wenn man im Browser mehrere Seiten offen hat und eine davon ist "böse". Diese Seite könnte die aktive Anmeldung an einem anderen Webserver ausnutzen und diesen angreifen. Heise hat das hier ganz gut beschrieben.

In FHEM wurde Anfang des Jahres mit der Version 5.8 der csrfToken als eine Abwehr Maßnahme scharf geschaltet. Was leider dazu führt, dass man nicht mehr mit einem einfachen http Link auf das FHEMWEB zugreifen kann.
Was bisher einfach so ging:
curl http://localhost:8083/fhem?cmd=set%20Office%20on
muss jetzt um den Token ergänzt werden. Man beachte auch: die URL muss dafür in Anführungszeichen stehen!
curl "http://localhost:8083/fhem?cmd=set%20Office%20on&fwcsrf="
Mit dem Einzeiler
curl -s -D - 'http://localhost:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}'
kann man den aktuellle csrfToken aus dem Header extrahieren und muss ihn nur noch an den Aufruf anhängen. Das Ganze mit einmaliger Angabe des Hostnamen(IP) und dem FHEM Befehl mit normalen Leerzeichen, sieht so aus:
h='host:Port'; c='FHEM Befehl'; curl --data "fwcsrf=$(curl -s -D - http://$h/fhem?XHR=1 | awk '/X-FHEM-csrfToken/{print $2}')" http://$h/fhem?cmd=$(echo $c|sed 's/ /%20/g')

Da bei Headerfeldern immer ein cr+lf angehängt wird (danke für den Hinweis unten im Kommentar), kann man den ermittelten Token nicht so wie hier kurz gezeigt direkt anhängen.  Diese Zeile
curl "http://localhost:8083/fhem?cmd=set%20Office%20on&XHR=1&fwcsrf="`curl -s -D - 'http://localhost:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}'`
oder auch diese Schreibweise:
curl "http://localhost:8083/fhem?cmd=set%20Office%20on&XHR=1&fwcsrf="$(curl -s -D - 'http://localhost:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}')
führt zu dem Fehler: curl: (3) Illegal characters found in URL

So wird cr+lf verhindert ( Diskussion im Forum ):
curl "http://fhem.example.org:8083/fhem?cmd=set%20Office%20on&XHR=1&fwcsrf="`curl -s -D - 'http://fhem.example.org:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}' | tr -d "\r\n"`
Mir persönlich gefällt die Variante mit -d (--data) besser.

Der csrfToken wird bei einem Neustart von FHEM neu generiert. Bis dahin bleibt er gültig. Hat man mehrere Anweisungen in Folge, genügt es den Token einmal auszulesen und zu speichern. Hier zwei Varianten.
Bash Script:
token=$(curl -s -D - 'http://<host>:8083/fhem?XHR=1' | awk '/X-FHEM-csrfToken/{print $2}')
curl --data "fwcsrf=$token" http://<host>:8083/fhem?cmd=set%20Aktor1%20off
Powershell:
# Wenn man mehr aus dem Request herausziehen will, erstmal in ein Objekt
# UseBasicParsing verhindert die Verwendung des IE und dessen Securityabfragen
$wp=Invoke-WebRequest -UseBasicParsing -Uri "http://<host>:8083/fhem?XHR=1"
$token = $wp.Headers["X-FHEM-csrfToken"]
# Alternativ nur der Token
$token = Invoke-WebRequest -UseBasicParsing -Uri "http://<host>:8083/fhem?XHR=1" | %{$_.Headers["X-FHEM-csrfToken"]}
$URL="http://<host>:8083/fhem?cmd=set%20Aktor1%20on&fwcsrf=$token"
Invoke-WebRequest -UseBasicParsing -Uri $URL | out-null
Um den Zugriff im internen Netzwerk zu vereinfachen, kann man auch ein separates "API Web" ohne Token einrichten:
define WEBapi FHEMWEB <Port> global
attr WEBapi csrfToken none
attr WEBapi allowFrom 192.168.x.x|127.0.0.1
Im Attribute allowfrom wird wird mit einem regEx der IP Adressbereich angegeben. Einzelne Adressen kann man einfach mit | (oder) trennen, Bereiche werden etwas aufwendiger.
Ich würde es aber lesbar und übersichtlich halten, eine Hand voll Adressen geht einfach so:
192.168.178.(1|20|203|124)
Wichtig: Dieses Web für den Zugriff von Maschinen einzurichten, an denen man vom Browser aus arbeitet ist am Thema vorbei!

Donnerstag, 2. Februar 2017

FHEM - die Kommandozeile wird groß

Entwicklungen laufen ja manchmal unbemerkt an einem vorbei. Die Entwickler haben sich was gutes überlegt reden aber meist wenig darüber.
Seit einiger Zeit gibt es unter jeder definierten "Entität" den Punkt Raw definition
Wenn man da drauf klickt öffnet sich ein Fenster mit der Definition und allen zu Attributen und Status Informationen. Man kann die kopieren und an andere Stelle weiterverwenden Sehr einfach und praktisch.
Man kann aber auch einfach etwas verändern, oder auch alles rauslöschen und etwas völlig neues hineinschreiben/kopieren. In dem Moment wo man dies tut, erscheint unten ein neuer Button:
Execute commands. Das kann man wörtlich nehmen, es passiert in diesem Moment nichts unmittelbar in dieser Definition wo man steht, sondern die Zeilen werden geprüft und in FHEM übernommen! Man braucht also kein Telnet Fenster mehr um ganze Code Blöcke zu übernehmen, man muss dazu auch nicht die fhem.cfg direkt editieren (gar nicht zu empfehlen). Im Wiki ist die Sache näher beschrieben. Der kleine Codeblock zum ausprobieren erzeugt einen neuen Menüeintrag im FHEM Web
um schnell auf einen Eingabe Dummy zu springen.
define Import dummy
attr Import group Entwicklung
attr Import room Entwicklung
attr WEB menuEntries CodeImport,/fhem?detail=Import#
Diese Ergänzung wandert direkt in meine Standard Installation.

Mittwoch, 1. Februar 2017

EasyBox zerflashed - und nun?

Ich wollte schon immer mal OpenWrt auf einen alten Router flashen. Um zu sehen wie OpenWrt so geht ...
Man kann ja die alten Dinger durchaus einfach noch zum AccessPoint umkonfigurieren. Sie liegen eh in der Kiste und bevor man was neues kauft!?
Ok, die kleinen Repeater verbrauchen nur um die 2 Watt, so eine EasyBox verbraucht ca. 7 Watt. Die Wirtschaftlichkeit schwindet also innerhalb von 2 Jahren.
Die Anleitungen zum EasyBox 803A flashen sind schon etwas alt, nicht mehr ganz aktuell und die Quellen zu manchen Dateien ziemlich versteckt. Aber nicht nur das, ich habe auch erstmal nicht richtig kapiert was zu tun ist. Ein falscher Befehl und Box bootet nicht mehr, deswegen ein kurzes HowTo mit dem was ich getan habe.
Aktuell Juni 2018: Es gibt eine neues OpenWrt Seite und einen überarbeiteten Artikel, der ist wesentlich besser lesbar als der Alte und ich hoffe die Links sind aktuell.

Mittwoch, 18. Januar 2017

Ein Remote Taster in FHEM mit Rückmeldung

HowTo

Einfacher Taster

Zielstellung

Ein Dummy mit klickbarem Symbol für die Weboberfläche als "Taster" für verschiedenen Aktionen.

Als erste Komponente stattet man einen dummy mit dem Attribute devStateIcon aus:
define testdummy dummy
attr testdummy devStateIcon _start:on:stop _stop:off:start
attr testdummy event-on-change-reading state
set testdummy _stop

Als Zweites braucht man ein notify welches nur auf die Events start und stop vom testdummy reagiert:
define nty_test notify testdummy:(start|stop) set $NAME _$EVENT

Diese Konstruktion wirkt wie ein toggle Schalter, das Symbol beim dummy ist klickbar und bei jedem Klick schaltet es sichtbar on und off.

Die Rückmeldung des Schalters ist damit vorhanden nur er tut nicht wirklich etwas. Zur Demonstration lassen wir "noch etwas tun":
defmod nty_test notify testdummy:(start|stop) sleep 2;;set $NAME _$EVENT

Jetzt erfolgt die Reaktion auf den Klick mit kurzer Pause, stattdessen kann man natürlich auch etwas sinnvolles tun.

Remote Taster mit Rückmeldung

Zielstellung

Ein "Taster" in der Weboberfläche, der auf einem Remote System z.B. einen Dienst startet oder beendet. Mein Taster soll erst reagieren, wenn der Befehl vom Remote System ausgeführt wurde.

Die System sind mit ssh "gekoppelt", wie das geht steht in meinem vorherigen Beitrag.

Achtung! Seit  März 2017 funktioniert die hier beschriebene Steuerung von FHEM über die Webschnittstelle nicht mehr so einfach. Bitte meinen Beitrag im März beachten.

Demo Script

Auf dem Remote System wird ein Script im Home Verzeichnis des verwendeten Remote Benutzers erzeugt/abgelegt und mit chmod +x test.sh ausführbar gemacht:
#!/bin/sh
# Parameter Übergabe
host=$1
name=$2
event=$3
# Kontrollausgabe zum Test aktivieren
# echo $host $name $event
# Zur Demo ein Sleep
sleep 3
# Abfrage und Verzweigung
case "$event" in
    start)
 # hier kann etwas spezifisches bei Start passieren
    ;;

    stop)
 # hier kann etwas spezifisches bei Stop passieren
    ;;
    *)
       exit 1
    ;;

esac
# Web Kommando an FHEM absetzen
h=$host':8083'; c='set '$name' '$status; curl --data "fwcsrf=$(curl -s -D - http://$h/fhem?XHR=1 | awk '/X-FHEM-csrfToken/{print $2}')" http://$h/fhem?cmd=$(echo $c|sed 's/ /%20/g')

exit 0

Zum Test kann man dieses Script mit ./test.sh <FHEM hostname> testdummy start aufrufen. Dabei muss sich der Status unseres Dummy ändern.

Funktioniert dieser Test, wird das notify geändert, die hostnamen müssen angepasst werden:
defmod nty_test notify testdummy:(start|stop) "ssh pi@<remote hostname> ./test.sh <FHEM hostname>  $NAME $EVENT"

An der Reaktion des Dummy hat sich nicht viel geändert, da ich im Script sleep 3 gesetzt habe dauert es jetzt 1 Sekunde länger. Dafür passiert aber etwas auf einem anderen System!

Praktische Verwendung

Das Demo Script kann so als Grundgerüst verwendet werden. Im einfachsten Fall fügt man an den entsprechende Stellen einfach nur Befehle ein. Komplizierter wird es, wenn man z.B. vor Rückkehr (absetzen des Befehls an FHEM) wirklich überprüfen will/muss ob die gewünschet Aktion auch gelaufen ist.
Das notify kann für mehrere dummy verwendet werden.

Dienst steuern

Um einen Dienst zu starten und zu stoppen muss das Script und der dummy etwas angepasst werden. Zum Testen habe ich meinen Dienst aus diesem Beitrag genommen. Der Name muss entsprechend angepasst werden. Man könnte den Dienstnamen auch im notify übergeben.
Der Steuerbefehl (event) wird nochmal überprüft und übergeben. Nach kurzer Wartezeit wird der Status abgefragt und dieser direkt an FHEM übergeben. Der Status ist also direkt im dummy sichtbar
#!/bin/sh
# Parameter Übergabe
host=$1
name=$2
event=$3
service=<Dienst Name>
# Abfrage und Verzweigung
case "$event" in
    start|stop)
   # Sicherstellen das nur zulässige Events verwendet werden
   sudo systemctl $event $service 
   sleep 2
          status=$(systemctl is-active $service)
   # Web Kommando an FHEM absetzen (neu mit csrfToken)
   h=$host':8083'; c='set '$name' '$status; curl --data "fwcsrf=$(curl -s -D - http://$h/fhem?XHR=1 | awk '/X-FHEM-csrfToken/{print $2}')" http://$h/fhem?cmd=$(echo $c|sed 's/ /%20/g')

    ;;

    *)
      exit 1
    ;;

esac

exit 0

Wenn der Dienst läuft wird er als active erkannt, wenn er gestoppt ist liefert er unknown zurück. Das Mapping des devStateIcon wird einfach angepasst.

attr testdummy devStateIcon active:on:stop unknown:off:start

Montag, 16. Januar 2017

Per ssh Remote Befehle direkt ausführen

Hinweis 2021: Dieser Artikel ist etwas in die Jahre gekommen und ich habe mittlerweile einige Dinge neu beschrieben:
---

Dienst unter Raspbian einrichten

Wahrscheinlich passt das Thema unter jedem debian System, ich hab es aber auf dem Raspberry unter Jessie getestet.
Mein Ziel war es, selbst einen Dienst einzurichten und dabei die Unterschiede zwischen wheezy (SysVinit) und Jessie (SystemD) zu lernen.
Eine gute Beschreibung für meinen Start habe ich hier gefunden. Python ist wie Perl als Grundlage im Debian System schon vorhanden, man braucht also keine weitere Installation. Das Python Script aus dem genannten Artikel ist eine gute Grundlage und ich habe es etwas modifiziert.
Alle Scripte müssen im Unix Format (nur lf als Zeilenwechsel) gespeichert werden!

Das Demo-Script

Es schreibt kontinuierlich in eine "Tages"-Logdatei im /tmp Verzeichnis und kann bei normalen Start entweder mit ctrl-c oder im Hintergrund einfach mit kill beendet werden. Das Script demonstriert ein paar nützliche Techniken, ich habe lediglich den Abbruch am Ende entfernt und das Intervall auf eine Minute heraufgesetzt. So läuft das Script "ewig". Im Script selbst ist das Wichtigste dokumentiert.

#!/usr/bin/env python

import logging
import logging.handlers
import argparse
import sys
import time  # this is only being used as part of the example

# Defaults
LOG_FILENAME = "/tmp/myservice.log"
LOG_LEVEL = logging.INFO  # Could be e.g. "DEBUG" or "WARNING"

# Define and parse command line arguments
parser = argparse.ArgumentParser(description="My simple Python service")
parser.add_argument("-l", "--log", help="file to write log to (default '" + LOG_FILENAME + "')")

# If the log file is specified on the command line then override the default
args = parser.parse_args()
if args.log:
        LOG_FILENAME = args.log

# Configure logging to log to a file, making a new file at midnight and keeping the last 3 day's data
# Give the logger a unique name (good practice)
logger = logging.getLogger(__name__)
# Set the log level to LOG_LEVEL
logger.setLevel(LOG_LEVEL)
# Make a handler that writes to a file, making a new file at midnight and keeping 3 backups
handler = logging.handlers.TimedRotatingFileHandler(LOG_FILENAME, when="midnight", backupCount=3)
# Format each log message like this
formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
# Attach the formatter to the handler
handler.setFormatter(formatter)
# Attach the handler to the logger
logger.addHandler(handler)

# Make a class we can use to capture stdout and sterr in the log
class MyLogger(object):
        def __init__(self, logger, level):
                """Needs a logger and a logger level."""
                self.logger = logger
                self.level = level

        def write(self, message):
                # Only log if there is a message (not just a new line)
                if message.rstrip() != "":
                        self.logger.log(self.level, message.rstrip())

# Replace stdout with logging to file at INFO level
sys.stdout = MyLogger(logger, logging.INFO)
# Replace stderr with logging to file at ERROR level
sys.stderr = MyLogger(logger, logging.ERROR)

i = 0

# Loop forever, doing something useful hopefully:
while True:
        logger.info("The counter is now " + str(i))
        print "This is a print"
        i += 1
        time.sleep(60)

SysVinit konfigurieren 

Init-Script

Ich möchte den Dienst unter einem normalen User laufen lassen und nicht als root (default). Im Pfad /etc/init.d/ befinden sich die Scripte welche zur Dienste Steuerung verwendet werden. Dort gibt es auch ein Script Namens skeleton, welches als Template verwendet werden kann. Also zunächst mal das Steuerungs Script, wiederum aus dem eingangs erwähnten Artikel, leicht modifiziert. Wie zu sehen ist, soll der Dienst unter dem User pi ($DAEMON_USER) und im Homedirectory ($DIR) vom User laufen.

#!/bin/sh

### BEGIN INIT INFO
# Provides:          myservice
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Put a short description of the service here
# Description:       Put a long description of the service here
### END INIT INFO

# Change the next 3 lines to suit where you install your script and what you want to call it
DIR=/home/pi
DAEMON=$DIR/myservice.py
DAEMON_NAME=myservice

# Add any command line options for your daemon here
DAEMON_OPTS=""

# This next line determines what user the script runs as.
# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
DAEMON_USER=pi

# The process ID of the script when it runs is stored here:
PIDFILE=/var/run/$DAEMON_NAME.pid

. /lib/lsb/init-functions

do_start () {
    log_daemon_msg "Starting system $DAEMON_NAME daemon"
    start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --startas $DAEMON -- $DAEMON_OPTS
    log_end_msg $?
}
do_stop () {
    log_daemon_msg "Stopping system $DAEMON_NAME daemon"
    start-stop-daemon --stop --pidfile $PIDFILE --retry 10
    log_end_msg $?
}

case "$1" in

    start|stop)
        do_${1}
        ;;

    restart|reload|force-reload)
        do_stop
        do_start
        ;;

    status)
        status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
        ;;

    *)
        echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
        exit 1
        ;;

esac
exit 0

Scripte kopieren und einrichten

Mit winscp werden die Scripte ins Home Verzeichnis von pi kopiert. Dann mit putty ein ssh Terminal öffnen und das Init-Script an Ort und Stelle kopieren, Rechte setzen und den Dienst einrichten.
sudo cp myservice.sh /etc/init.d/
chmod +x myservice.py
chmod +x /etc/init.d/myservice.sh
sudo update-rc.d myservice.sh defaults
Jetzt kann man mit den üblichen Befehlen oder durch direkten Script Aufruf den Dienst starten, abfragen und beenden.
sudo /etc/init.d/myservice.sh start
service myservice status
sudo service myservice stop
Will man den Dienst wieder entfernen, hilft uns update-rc.d mit den Optionen disable und remove.

SystemD

SystemD wird unter jessie mit installiert und läuft kompatibel und transparent. Man kann also sofort den Dienst auch mit systemctl <start|status|stop> myservice kontrollieren. Ich will aber nun die "alte" Einrichtung deaktivieren und den Dienst unter SystemD einrichten.
Ein Dienst in SystemD wird mit sogenannten Unit Files eingerichtet.

1. Variante: einfach das init.d Script verwenden

Im FHEM Forum gibt es ein HowTo wie man den existierenden Service in einen SystemD Service umwandelt. Die folgende Datei wird als myservice.service gespeichert und mit winscp wieder nach /home/pi übertragen.
Wie man sieht, wird dabei gleich das alte SysVinit Script recycelt.

[Unit]
Description=Test myservice

[Service]
Type=forking
ExecStart=/etc/init.d/myservice.sh start
ExecStop=/etc/init.d/myservice.sh stop

[Install]
WantedBy=multi-user.target

Zunächst also den Dienst wieder entfernen, die Datei nach /etc/systemd/system kopieren und systemd über die Änderung informieren.
sudo update-rc.d myservice.sh disable
sudo cp myservice.service /etc/systemd/system/
sudo systemctl --system daemon-reload
Jetzt kann man zur Probe den Dienst mit systemctl <start|status|stop> myservice starten. Wenn alles funktioniert muss er noch aktiviert werden.
sudo systemctl enable myservice

2. Variante: extra Startscript verwenden 

Hierauf basierend habe ich mir ein universelles Startscript erstellt. Zumindest mit meinem Pythonscript funktioniert das einwandfrei. Im Endeffekt ist es aber in der Funktion dem SysVinit adäquat. Die Funktion ist anders realisiert, ich finde es besser lesbar und der SysVinit Abschnitt am Anfang fehlt. Letztendlich könnte man sogar den Scriptaufruf als Parameter übergeben.
#!/bin/bash

PID=""
script="python myservice.py"

function get_pid {
   PID=`pidof $script`
}

function stop {
   get_pid
   if [ -z $PID ]; then
      echo "server is not running."
      exit 1
   else
      echo -n "Stopping server.."
      kill -9 $PID
      sleep 1
      echo ".. Done."
   fi
}


function start {
   get_pid
   if [ -z $PID ]; then
      echo  "Starting server.."
      $script &
      get_pid
      echo "Done. PID=$PID"
   else
      echo "server is already running, PID=$PID"
   fi
}

function restart {
   echo  "Restarting server.."
   get_pid
   if [ -z $PID ]; then
      start
   else
      stop
      sleep 5
      start
   fi
}


function status {
   get_pid
   if [ -z  $PID ]; then
      echo "Server is not running."
      exit 1
   else
      echo "Server is running, PID=$PID"
   fi
}

case "$1" in
   start)
      start
   ;;
   stop)
      stop
   ;;
   restart)
      restart
   ;;
   status)
      status
   ;;
   *)
      echo "Usage: $0 {start|stop|restart|status}"
esac

Praktisch gesehen ist der Unit Teil bei Systemd vom Startscript getrennt. Basierend auf dieser Dokumentation  habe ich  mal eine ziemlich "aufwändige" Variante erstellt. Hierbei wird start|stop|restart durch das obige Script gesteuert.

[Unit]
Description=Test myservice
After=network.target

[Service]
Type=forking
User=pi
WorkingDirectory=/home/pi
ExecStart=/home/pi/server.sh start
ExecStop=/home/pi/server.sh stop
ExecReload=/home/pi/server.sh restart
KillMode=none

[Install]
WantedBy=multi-user.target

3. Variante: Kurz und knapp einfach ein Unitfile erstellt

Hier übernimmt systemd die komplette Steuerung. Überraschenderweise funktioniert dies auch mit meinem Endlosscript. Ich habe die Anregung dazu von hier. Dort ist noch ein besseres Beispiel mit einem kleinem Webserver als Testscript dargestellt. Lässt man den User= Eintrag weg startet der Dienst als root.

[Unit]
Description=Test systemd myservice

[Service]
User=pi
ExecStart=/home/pi/myservice.py
StandardOutput=null

[Install]
WantedBy=multi-user.target

Sonntag, 15. Januar 2017

Code Blöcke formatieren und hervorheben

Der Editor von Blogger.com ist für mich für die meisten Dinge völlig ausreichend, allerdings möchte ich die Codeblöcke in meinem Blog gut lesbar formatieren.
Ich habe mehrere Möglichkeiten gefunden dies zu tun, leider ist so etwas ganz einfaches nicht dabei. Quasi als meinen Notizzettel, habe ich die Möglichkeiten, die ich so gefunden habe mal aufgeschrieben.
Generell unschön finde ich, dass man für alle Varianten vom "Verfassen" Modus in den "HTML" Modus wechseln muss, aber nur so kann man den für die Formatierung notwendigen HTML Code direkt eingeben. Eine andere Möglichkeit habe ich nicht gefunden.

Copy & Paste

Zunächst mal gibt es ein paar Seiten, die bieten einen "Code Formatierer" an.
Man kopiert seine Code Zeilen in die Zwischenablage, wechsel auf die Seite mit dem Formatierer und fügt sie in ein spezielles Fenster wieder ein. Man kann hier meist ein paar Optionen einstellen und beim Drücken eine Knopfes wird in einem weiteren Fenster die Formatierung gezeigt und in einem Dritten kann man den HTML Code kopieren und die Formatierten Codezeilen so einfach in seine Blog an die gewünschte Stelle einfügen.
Das geht eigentlich alles sehr simpel und benötigt überhaupt keine Vorbereitung.

Eine relative schlichte Optik kann man auf dieser Seite erzeugen:
http://codeformatter.blogspot.de/2009/06/how-to-format-my-source-code-for.html

 Codezeile eins 
 Codezeile zwei

Sehr auffällig und bunt geht es auf dieser Seite:
http://hilite.me/

1
print 'hello world!'

Template oder Vorlage editieren

Etwas aufwendiger ist es die Stylesheets zu editieren. Dabei wird ein Stück HTML Code in der Vorlage zum Blog (die austauschbar ist und die Optik des Blogs wesentlich bestimmt) eingebaut und an entsprechender Stelle wird dann lediglich noch ein HTML-Tag eingefügt. Der Code zur Formatierung wird zentral abgelegt und der Code Block bloß mit einem Kurzen Tag gekennzeichnet. Das hat noch den Vorteil, man kann das Aussehen der Formatierung an einer Stelle für den gesamten Blog ändern. Dazu ein einfaches Beispiel, welches auf diesen Artikel basiert.

Mit Hilfe von dem Tag textarea kann man eine kleine Box einfügen:
Einfach folgenden Code in der HTML Ansicht einfügen
<textarea name="textarea" cols="40" rows="4" wrap="VIRTUAL">
YOUR CODE GOES HERE
</textarea>
Das Ergebnis sieht so aus:



Der Vorteil dieses kleinen Textrahmens ist, das man ihn in der normalen Editor Ansicht sieht. Er hat in der Editor Ansicht auch "editierende" Eigenschaften:
Man kann an der kleinen Ecke unten rechts ziehen und den Rahmen optisch anpassen. Es wird dabei automatisch dieser HTML Code zwiswchen rows= und wrap eingefügt:
style="height: 64px; margin: 0px; width: 301px;"
Man kann hier eine definierte Höhe und Breite eintragen, oder anstatt mit Pixeln(px) auch mit auto oder 100% arbeiten.

Um eine Codebox als CSS in den Blog einzufügen, muss man folgendes tun:

  1. Am Dashboard anmelden und links auf Vorlage navigieren.
  2. Unter der Vorlage auf Anpassen klicken
  3. Links oben auf Erweitert und daneben nach unten scrollen und auf Add CSS klicken
  4. In dem Fenster Benutzerspezifisches CSS hinzufügen gibt man den unten stehenden Code Block ein
  5. Anschließend oben rechts auf "Auf Blog anwenden" drücken.

Jetzt kann man Seinen Code Block in der HTML Ansicht einfach mit diesen Tags "einrahmen":



Großer Nachteil: Den Erfolg sieht man erst in der Vorschau, in der normalen Editor Ansicht sieht man leider nichts und man muss verdammt aufpassen wenn man mal was korrigiert. Zwei Parameter sorgen für wichtige Eigenschaften:
  • overflow: auto - bewirkt, dass Scrollbalken angezeigt werden, sobald die max-height überschritten wurde. 
  • white-space: nowrap - sorgt dafür, dass kein automatischer Textumbruch stattfindet.

Code Block

Java Script einbetten

Eine relativ elegante Methode ist die Formatierung mit dem Javascript Prettify, der Quellcode und Beschreibung findet man hier: https://github.com/google/code-prettify
Als erstes muss man den Script Code im Blog verfügbar machen. Dazu muss man nicht die Vorlage editieren, man kann es einfach im Layout einbetten. Dazu fügt man im Layout (Dashboard/Layout) einfach an unauffälliger Stelle ein Gadget hinzu. Man wählt das Gadget "HTML/JavaScript konfigurieren" aus der Liste aus, gibt den Titel PrettyPrint ein und im Content wird folgender Code eingefügt:
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>

Die Code Blöcke werden in der HTML Ansicht einfach in



eingepackt. Jetzt wird abhängig von der Programmiersprache auch entsprechend hervorgehoben.

Feintuning

Kombination aus Prettify und Codeformatter

Damit der Text gut lesbar bleibt, sollten Code Passagen nicht allzu lang sein, bzw. sollte man sie im Text in Scrollboxen optisch einkürzen. Dazu verwende ich einfach noch die style Eigenschaft aus dem Codeformatter. Das style Attribute height:auto passt das Fenster der Anzahl Zeilen automatisch an, das Attribute max-height:300px; begrenzt die Box und fügt Scrollbalken ein.
<pre class="prettyprint" style="height: auto; max-height: 300px; overflow: auto; width: auto;">
Code Block
</pre>
Der style Teil kann mit in das beschriebene Gadget eingefügt werden:
<style>
pre {
  height: auto;
  max-height: 300px;
  overflow: auto;
  width: auto;
}
</style>
Damit der Code einfach markiert werden kann wird noch ein spezieller Code tag eingefügt:
<pre class="prettyprint"><code contenteditable="true">Code Block
</code></pre>

Quelle


Hier ein Beispiel, dies ist kein funktionierender Code!:

#!/bin/bash

# Laufzeit des Scripts messen 
# Zeitmessung Start.
REAL_STARTTIME=$(date +%s) #(($STARTTIME - $PRE_NETWORK_DURATION))
echo ""
echo "Installation started at $(date --date="@$REAL_STARTTIME" --utc)."

#Grundlage für FHEM installieren
apt-get update && apt-get -y install libdevice-serialport-perl libwww-perl libio-socket-ssl-perl
#Meine Pakete installieren
apt-get -y install bluez libcrypt-rijndael-perl libdigest-sha-perl libgd-graph-perl libgd-text-perl libimage-info-perl libimage-librsvg-perl libjson-perl libjson-xs-perl libmime-base64-perl libnet-ssleay-perl libnet-telnet-perl libnet-telnet-perl libnet-upnp-perl libsoap-lite-perl libsoap-lite-perl libxml-parser-lite-perl mplayer msttcorefonts samba samba-common-bin sendEmail telnet

# Manuelle Installation
# wget https://debian.fhem.de/fhem.deb
# dpkg -i fhem.deb

# von debian.fhem.de installieren 
# Erstmal https für apt nachinstallieren
# https Transport installieren und Sourcellisten sichern
apt-get -y install apt-transport-https
wget -qO - https://debian.fhem.de/archive.key | apt-key add -
cp /etc/apt/sources.list /etc/apt/sources.list.bak
# Manuelle Installation
# wget https://debian.fhem.de/fhem.deb
# dpkg -i fhem.deb

# von debian.fhem.de installieren 
# Erstmal https für apt nachinstallieren
# https Transport installieren und Sourcellisten sichern
apt-get -y install apt-transport-https
wget -qO - https://debian.fhem.de/archive.key | apt-key add -
cp /etc/apt/sources.list /etc/apt/sources.list.bak
# Manuelle Installation
# wget https://debian.fhem.de/fhem.deb
# dpkg -i fhem.deb

# von debian.fhem.de installieren 
# Erstmal https für apt nachinstallieren
# https Transport installieren und Sourcellisten sichern
apt-get -y install apt-transport-https
wget -qO - https://debian.fhem.de/archive.key | apt-key add -
cp /etc/apt/sources.list /etc/apt/sources.list.bak

# reboot

Oder mit Zeilenummer:
<pre class="prettyprint linenums" style="height: auto; max-height: 300px; overflow: auto; width: auto;">
Code Block
</pre>

Formatierung über Gist und GitHub

Für Powershell funktioniert prettyprint leider nicht sehr gut. Ich habe eine einfache Möglichkeit hier gefunden https://gist.github.com/ Die ist hier kurz beschrieben.
Leider ist die Hervorhebung von Powershell auch nicht optimal. Größter Nachteil ist: Beim Erstellen des Blogbeitrages sieht man vom Code gar nichts.

Um die Höhe der Scriptfenster etwas zu begrenzen kann man im css Bereich noch etwas konfigurieren (Blogbeitrag):

.gist .blob-wrapper.data {
   max-height:300px;
   overflow:auto;
}


Ob man das Alles noch eleganter machen kann, weiß ich derzeit noch nicht.

Zeilenumbruch

Manchmal ist es nützlich in HTML Ansicht Zeilenumbrüche einzufügen um an einer definierten Stellen Leerräume in der Normalen Ansicht zu haben. Einfach diesen Tag einfügen
<br />