Initscript mit Tiny Tiny RSS 1.5.10

Mit der Veröffentlichung der Version 1.5.10 des webbasierten Feed-Aggregators Tiny Tiny RSS fiel mir auf, dass das sich das optional beziehbare Initscript nicht mehr zum Starten des Update-Daemons nutzen lies. Beim Ausführen des Startkommandos geschah schlichtweg gar nichts.

Erste Versuche, das betreffende PHP-Script händisch zu starten schlugen weitestgehend fehl. Das Daemon-Script fror ständig ein, nachdem ich die SSH-Verbindung trennte, mit der ich meinen Server administriere. Folgende Fehlermeldung wies mich darauf hin:

Der Aktualisierungs Daemon braucht zu lange um eine Aktualisierung durchzuführen. Dies könnte auf ein Problem wie einen Absturz oder eine Blockierung hinweisen. Bitte überprüfen Sie den Prozess des Daemons oder benachrichtigen Sie den Besitzer des Instanz.

Und tatsächlich konnte das Update durch Töten des Prozesses und Löschen der lock-Dateien zeitweise wiederbeleben. Eine dauerhafte Lösung konnte dies aber nicht sein.

Also befasste ich mich erneut mit dem Initscript, welches das Hilfsprogramm start-stop-daemon zum Starten des Update-Daemons nutzt. Nach genauer Betrachtung fiel mir auf, dass nirgends ein Wechsel zu einem unprivilegierten Nutzer stattfand, der Daemon lief daher bisher immer mit Superuser-Rechten. Dass aus dies aus Sicherheitsgründen eine schlechte Idee ist dürfte ersichtlich sein.

Jedenfalls wurde diesem Verfahren mit der neuesten Version von Tiny Tiny RSS ein Riegel vorgeschoben, der im Änderungsprotokoll nicht aufgeführt aber durchaus im Versionierungsprotokoll durch diese Änderung ersichtlich ist. Der Versuch wird nun entsprechend quittiert:

[sanity_check] Please don’t run this script as root.

Mit diesem Wissen erweiterte ich das Initscript und seine Datei für Standardeinstellungen um eine neue Variable, welcher ein Nutzername oder eine -ID zugewiesen werden kann. Der Update-Daemon wird nun mit diesem Nutzer (www-data hier) ausgeführt und verfügt nicht mehr über systemweite Rechte. Es folgen die aktualisierten Dateien.

Die Einstellungsdatei /etc/default/tt-rss

## Defaults for Tiny Tiny RSS update daemon init.d script
 
# Set DISABLED to 1 to prevent the daemon from starting.
DISABLED=0
 
# Emplacement of your Tiny Tiny RSS installation.
TTRSS_PATH="/var/www/feeds"
 
# Set FORKING to 1 to use the forking daemon (update_daemon2.php) in stead of
# the standard one.
# This option is only available for Tiny Tiny RSS 1.2.20 and over.
FORKING=1
 
# Set USER to the user/uid to run the daemon as
USER=www-data

Das Initscript /etc/init.d/tt-rss

#! /bin/sh
### BEGIN INIT INFO
# Provides:          ttrss-DOMAIN
# Required-Start:    $local_fs $remote_fs networking
# Required-Stop:     $local_fs $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Tiny Tiny RSS update daemon for DOMAIN
# Description:       Update the Tiny Tiny RSS subscribed syndication feeds.
### END INIT INFO
 
# Author: Pierre-Yves Landuré 
 
# Do NOT "set -e"
 
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Tiny Tiny RSS update daemon"
NAME=$(command basename "${0}")
DISABLED=0
FORKING=0
USER=$(id --user)
 
# Read configuration variable file if it is present
[ -r "/etc/default/${NAME}" ] && . "/etc/default/${NAME}"
 
DAEMON_SCRIPT="update.php --daemon"
 
if [ "$FORKING" != "0" ]; then
  DAEMON_SCRIPT="update_daemon2.php"
fi
 
DAEMON=/usr/bin/php
DAEMON_ARGS="${TTRSS_PATH}/${DAEMON_SCRIPT}"
DAEMON_DIR="${TTRSS_PATH}"
PIDFILE="/var/run/${NAME}.pid"
SCRIPTNAME="/etc/init.d/${NAME}"
 
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
 
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
 
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
. /lib/lsb/init-functions
 
if [ "$DISABLED" != "0" -a "$1" != "stop" ]; then
  log_warning_msg "Not starting $DESC - edit /etc/default/tt-rss-DOMAIN and change DISABLED to be 0.";
  exit 0;
fi
 
#
# Function that starts the daemon/service
#
do_start()
{
  # Return
  #   0 if daemon has been started
  #   1 if daemon was already running
  #   2 if daemon could not be started
  start-stop-daemon --start --make-pidfile --background --quiet --chdir "$DAEMON_DIR" --pidfile "$PIDFILE" --chuid "$USER" --exec "$DAEMON" --test > /dev/null \
    || return 1
 
  start-stop-daemon --start --make-pidfile --background --quiet --chdir "$DAEMON_DIR" --pidfile "$PIDFILE" --chuid "$USER" --exec "$DAEMON" -- \
    $DAEMON_ARGS \
    || return 2
  # Add code here, if necessary, that waits for the process to be ready
  # to handle requests from services started subsequently which depend
  # on this one.  As a last resort, sleep for some time.
}
 
#
# Function that stops the daemon/service
#
do_stop()
{
  # Return
  #   0 if daemon has been stopped
  #   1 if daemon was already stopped
  #   2 if daemon could not be stopped
  #   other if a failure occurred
  start-stop-daemon --stop --make-pidfile --quiet --retry=TERM/1/KILL/5 --pidfile $PIDFILE --name $NAME
  RETVAL="$?"
  [ "$RETVAL" = 2 ] && return 2
  # Wait for children to finish too if this is a daemon that forks
  # and if the daemon is only ever run from this initscript.
  # If the above conditions are not satisfied then add some other code
  # that waits for the process to drop all resources that could be
  # needed by services started subsequently.  A last resort is to
  # sleep for some time.
  start-stop-daemon --stop --quiet --oknodo --retry=0/1/KILL/5 --exec $DAEMON
  [ "$?" = 2 ] && return 2
  # Many daemons don't delete their pidfiles when they exit.
  rm -f $PIDFILE
  return "$RETVAL"
}
 
case "$1" in
  start)
  [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
  do_start
  case "$?" in
    0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
    2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
  esac
  ;;
  stop)
  [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
  do_stop
  case "$?" in
    0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
    2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
  esac
  ;;
  restart|force-reload)
  #
  # If the "reload" option is implemented then remove the
  # 'force-reload' alias
  #
  log_daemon_msg "Restarting $DESC" "$NAME"
  do_stop
  case "$?" in
    0|1)
    do_start
    case "$?" in
      0) log_end_msg 0 ;;
      1) log_end_msg 1 ;; # Old process is still running
      *) log_end_msg 1 ;; # Failed to start
    esac
    ;;
    *)
      # Failed to stop
    log_end_msg 1
    ;;
  esac
  ;;
  *)
  echo "Usage: ${SCRIPTNAME} {start|stop|restart|force-reload}" >&2
  exit 3
  ;;
esac
 
:

Let’s bring the magic to Extbase

Beim Durchstöbern der Meldungen des Extension-Builder bin ich eher zufällig auf etwas gestoßen, was die Entwicklung von TYPO3-Erweiterungen mit Extbase revolutionieren könnte:

Workpaper: Magic for Domain Models
– A Plan for the Future of Models in Extbase

Um die Idee hinter dem Vorhaben zu verstehen, hier einmal eine kurze Auflistung der zum Hinzufügen eines einzelnen Felds zu einer einzigen Tabelle typischerweise notwendigen Schritte:

  1. Definition des Datenbank-Feldes (plus einer weiteren Tabelle bei MM-Beziehungen) in der ext_tables.sql,
  2. Hinzufügen der Eigenschaft nebst Getter und Setter zum betreffenden Model (plus ggf. Methoden zum Hinzufügen oder Entfernen von Elementen bei MM-Beziehungen),
  3. Hinzufügen des Felds zum TYPO3-Backend durch Anpassung der TCA-Definition der Tabelle,
  4. Ergänzen der notwendigen Übersetzungen (locallang_db.xml) und ggf. Beschreibungen/Hilfetexte (locallang_csh_*.xml)

Weitere Schritte ergeben sich, wenn z.B. MM-Beziehungen auch auf der Gegenseite bearbeitet/abgerufen werden können sollen. Insgesamt also eine Menge Schritte für eine eigentlich lapidare Erweiterung. Zudem verteilen sich die Schritte über mehrere Dateien deren Zusammenhang nicht unbedingt direkt ersichtlich ist. All dies zentral und mit minimalen Anpassungen vornehmen zu können würde Zeit und Aufwand sparen sowie mögliche Fehlerquellen reduzieren. Genau dies stellt das Projekt „Magic for Domain Models“ in Aussicht:

The core purpose is to make extension development, particularly modelling, more centralized and less prone to errors – and as a side bonus making it dynamic if so requested.

Erreicht werden soll dies um stark erweiterte Kennzeichnungen in den Kommentaren für Domain-Model und dessen Eigenschaften, was sich stark an die Möglichkeiten in FLOW3 anlehnt. Eine Beispiel-Konfiguration von der Projekt-Seite:

/**
 * Refrigerator Model
 *
 * (description and model annotiations ... )
 */
class Tx_Fedexample_Domain_Model_Refrigerator extends Tx_Extbase_DomainObject_AbstractEntity {
 
 // Other properties ...
 
 /**
 * Food content
 *
 * The parameters given to the "OneToMany" annotation are the default values which
 * will be used if not overridden - so they are shown for illustration only. You
 * can leave them out (just use @Magic\OneToMany) to use the table name in the relation
 * (tx_fedexample_domain_model_fooditem because Tx_FedExample_Domain_Model_FoodItem
 * is the type of relation. And the target field name from this Model (refrigerator).
 *
 * Note that a @Magic\Database annotations is not required here, it is automatically
 * applied based on the relation type. Although we use @Magic\Inline here there are
 * other options - like @Magic\Select and @Magic\Group, each with their own options.
 *
 * @var Tx_Extbase_Persistence_ObjectStorage<Tx_Fedexample_Domain_Model_FoodItem>
 * @Magic\Field(type="relation")
 * @Magic\OneToMany(foreignTable="tx_fedexample_domain_model_fooditem", foreignField="refrigerator")
 * @Magic\Inline(maxItems="1000")
 * @Magic\Column(provider="Tx_Magic_Provider_Column_InlineProvider", dynamic="TRUE")
 */
 protected $food;
 
}

Aus diesen Angaben kann automatisch geschlussfolgert werden, dass für die Datenbank-Tabelle tx_fedexample_domain_model_refrigerator ein Feld namens food definiert, als MM-Beziehung gekennzeichnet und im TYPO3-Backend via IRRE bearbeitet werden soll.

Darüber hinaus soll es aber auch möglich sein, das Bearbeiten von Feldern im TYPO3-Backend durch neue und benutzerdefinierte Widgets zu verbessern. Im Endeffekt sollen Datenbank-Definition, TCA-Konfiguration und Backend-Bearbeitungsmöglichkeiten an einer einzigen zentralen Stelle zusammengeführt werden: dem Model. Alle dafür von TYPO3 benötigten Angaben sollen automatisch und dynamisch generiert werden. All dies soll gänzlich optional sein, wobei nach einer gewissen Reifephase sicher nichts dagegen spricht, dies zum Standardverhalten zu machen. Wer spart sich nicht gerne Arbeit und Zeit. ;-)

Um das ganze performant zu halten, soll das Ergebnis der automatischen Code-Generierung natürlich Cache-fähig sein, was eingeplant, aber momentan noch ein offener Punkt ist. Der aktuelle Entwicklungsverlauf kann im TYPO3-Git-Repository der Magic-Erweiterung verfolgt werden. Initiiert und voran getrieben wird die Entwicklung von keinem geringeren als Claus Due, dem Kopf hinter den unschätzbar hilfreichen fed- und flux-Erweiterungen. Die Wahrscheinlichkeit, dass dieses Projekt also Realität wird ist sehr hoch. Hilfe in jeder Form ist allerdings sehr willkommen.

So muss Magie der Neuzeit aussehen.