diff --git a/superx/WEB-INF/conf/edustore/db/bin/patch_apply.x b/superx/WEB-INF/conf/edustore/db/bin/patch_apply.x index cd7a9ca1..6d042f50 100755 --- a/superx/WEB-INF/conf/edustore/db/bin/patch_apply.x +++ b/superx/WEB-INF/conf/edustore/db/bin/patch_apply.x @@ -1,82 +1,270 @@ #!/bin/bash +# SuperX Patch installieren (klassische Installation und Webapp-Struktur) +# Kompatibel mit den bisherigen Patch-ZIPs und patch_generate.xsl. +# +# Aufrufe: +# patch_apply.x patch_12345_superx_utf8.zip +# patch_apply.x patch_12345_superx_utf8.zip kenn,sos +# patch_apply.x patch_12345_superx_utf8.zip --webapp /pfad/zur/webapp/superx +# patch_apply.x patch_12345_superx_iso.zip --superx-dir /home/superx +# +# Voraussetzung: Die SuperX-Umgebung (z.B. SQL_ENV) ist vor dem Aufruf +# geladen oder die Pfade werden mit --webapp / --superx-dir angegeben. +# Die Datenbankbefehle und ihre Umgebung werden durch die Modulskripte benoetigt. -#Variablen: +set -o pipefail -export PATCH_DIR_FILE=$1 +usage() { + cat <<'USAGE' +Aufruf: patch_apply.x PATCH_ZIP [MODUL1,MODUL2,...] [Optionen] -if test "$2#" != "#" ; then -export PATCH_MODULE_LIST_EINGABE=$2 -declare -a PATCH_MODULE_LIST_PARAM=(`echo ${PATCH_MODULE_LIST_EINGABE[*]}| tr ',' '\n'`) -fi +Optionen: + --webapp PFAD SuperX-Webapp-Verzeichnis (WEBAPP) + --superx-dir PFAD SuperX-DB-/Modulverzeichnis (SUPERX_DIR) + --encoding WERT utf8 oder iso (sonst aus ZIP-Namen / Inhalt) + -h, --help Diese Hilfe anzeigen -if [[ $LANG == *utf* ]] - then - ENCODING=utf8 - else - if [[ $LANG == *UTF* ]] - then - ENCODING=utf8 - else - ENCODING=iso -fi -fi +Die ZIP wird in das aktuelle Arbeitsverzeichnis entpackt; der Unterordner +mit der Patch-ID bleibt dort bestehen. Das Installationsprotokoll liegt +im aktuellen Verzeichnis als patch_[_MANDANTENID].log. + +Die reinen webapps_utf8.zip-Dateien sind nicht fuer patch_apply.x gedacht: +Sie werden direkt im Stammverzeichnis der SuperX-Webapp entpackt. +USAGE +} + +fail() { + printf 'ERROR: %s\n' "$*" >&2 + if [[ -n "${LOG:-}" ]]; then + printf 'ERROR: %s\n' "$*" >> "$LOG" + printf 'Protokoll: %s\n' "$LOG" >&2 + fi + exit 1 +} + +PATCH_DIR_FILE='' +PATCH_MODULE_LIST_EINGABE='' +ENCODING_OPTION='' +WEBAPP_OPTION='' +SUPERX_DIR_OPTION='' +LOG='' -#Testen ob unzip installiert ist -hash unzip 2>&- || { echo >&2 "Für dieses Script wird 'unzip' benötigt."; exit 1; } +while (($#)); do + case "$1" in + --webapp|--superx-dir|--encoding) + (($# >= 2)) || fail "Wert fuer $1 fehlt." + case "$1" in + --webapp) WEBAPP_OPTION=$2 ;; + --superx-dir) SUPERX_DIR_OPTION=$2 ;; + --encoding) ENCODING_OPTION=$2 ;; + esac + shift 2 + ;; + --webapp=*) WEBAPP_OPTION=${1#*=}; shift ;; + --superx-dir=*) SUPERX_DIR_OPTION=${1#*=}; shift ;; + --encoding=*) ENCODING_OPTION=${1#*=}; shift ;; + -h|--help) usage; exit 0 ;; + --*) fail "Unbekannte Option: $1" ;; + *) + if [[ -z "$PATCH_DIR_FILE" ]]; then + PATCH_DIR_FILE=$1 + elif [[ -z "$PATCH_MODULE_LIST_EINGABE" ]]; then + PATCH_MODULE_LIST_EINGABE=$1 + else + fail "Zu viele Parameter: $1" + fi + shift + ;; + esac +done -export PATCH_DIR=`pwd` +[[ -n "$PATCH_DIR_FILE" ]] || { usage >&2; fail 'Bitte als ersten Parameter die Patch-ZIP angeben.'; } +[[ -z "$ENCODING_OPTION" || "$ENCODING_OPTION" == utf8 || "$ENCODING_OPTION" == iso ]] || fail '--encoding muss utf8 oder iso sein.' -export LOG=$PATCH_DIR_FILE$MANDANTENID.log -echo "start" >$LOG +for tool in unzip; do + command -v "$tool" >/dev/null 2>&1 || fail "Das Programm '$tool' wird benoetigt." +done -#Test ob PatchID angegeben wurde -if [ "$PATCH_DIR_FILE" = "" ] ; then -echo "error: Bitte als ersten Parameter die Patchdatei angeben." -exit 1 +# Absoluten Pfad der ZIP bestimmen, ohne ihren Namen zu veraendern. +[[ -f "$PATCH_DIR_FILE" ]] || fail "Patch-ZIP nicht gefunden: $PATCH_DIR_FILE" +ZIP_BASENAME=$(basename -- "$PATCH_DIR_FILE") +ZIP_PARENT=$(cd -P -- "$(dirname -- "$PATCH_DIR_FILE")" && pwd -P) || fail 'ZIP-Verzeichnis nicht erreichbar.' +ZIP_FILE="$ZIP_PARENT/$ZIP_BASENAME" +PATCH_DIR=$(pwd -P) || fail 'Aktuelles Arbeitsverzeichnis konnte nicht bestimmt werden.' + +# Patch-ID DIREKT aus dem Archiv lesen. Eine alte patch_version.txt im +# Arbeitsverzeichnis darf auf keinen Fall eine andere Installation ausloesen. +PATCH_ID=$(unzip -p "$ZIP_FILE" patch_version.txt) || fail 'patch_version.txt fehlt in der ZIP. Ist es eine reine Webapp-ZIP?' +PATCH_ID=${PATCH_ID//$'\r'/} +[[ "$PATCH_ID" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || fail "Ungueltige Patch-ID in der ZIP: $PATCH_ID" + +# Log im Arbeitsverzeichnis; bei erneuter Ausfuehrung wird es ueberschrieben. +MANDANTEN_SUFFIX='' +if [[ -n "${MANDANTENID:-}" ]]; then + [[ "${MANDANTENID}" =~ ^[A-Za-z0-9._-]+$ ]] || fail 'MANDANTENID enthaelt ungueltige Zeichen fuer einen Log-Dateinamen.' + MANDANTEN_SUFFIX="_${MANDANTENID}" fi +LOG="$PATCH_DIR/patch_${PATCH_ID}${MANDANTEN_SUFFIX}.log" +: > "$LOG" || fail "Protokolldatei kann nicht geschrieben werden: $LOG" -echo >>$LOG -echo "Starte Installation von Patch $PATCH_DIR_FILE" >>$LOG -echo >>$LOG +# Das Archiv nur im Patch-Verzeichnis entpacken und keine fremden Pfade +# ausserhalb / zulassen. Ein vorhandener Patch-Ordner darf kein +# Symlink sein. +unzip -tqq "$ZIP_FILE" >>"$LOG" 2>&1 || fail "ZIP-Archiv ist beschaedigt: $ZIP_FILE" +ZIP_LIST=$(unzip -Z -1 "$ZIP_FILE") || fail 'ZIP-Inhaltsverzeichnis kann nicht gelesen werden.' +while IFS= read -r entry; do + [[ -n "$entry" ]] || continue + case "$entry" in + patch_version.txt|"$PATCH_ID"/|"$PATCH_ID"/*) ;; + *) fail "Unerwarteter ZIP-Pfad (nur $PATCH_ID/ erlaubt): $entry" ;; + esac + case "/$entry" in + */../*|*/./*|*/..|*/.) fail "Unsicherer ZIP-Pfad: $entry" ;; + esac +done <<< "$ZIP_LIST" +[[ ! -L "$PATCH_DIR/$PATCH_ID" ]] || fail "Patch-Verzeichnis ist ein Symlink: $PATCH_DIR/$PATCH_ID" -#Test ob Patch vorhanden -if test -f $PATCH_DIR_FILE ; then -echo "$PATCH_DIR_FILE wird nach $PATCH_DIR entpackt" >>$LOG -unzip -o $PATCH_DIR_FILE -d $PATCH_DIR >>$LOG 2>&1 +# Die Auswahl aus dem ZIP-Dateinamen ist unabhaengig von LANG und verhindert, +# dass eine ISO-ZIP auf einem UTF-8-System versehentlich als UTF-8 gestartet wird. +if [[ -n "$ENCODING_OPTION" ]]; then + PATCH_ENCODING=$ENCODING_OPTION +elif [[ "$ZIP_BASENAME" == *_superx_iso.zip ]]; then + PATCH_ENCODING=iso +elif [[ "$ZIP_BASENAME" == *_superx_utf8.zip ]]; then + PATCH_ENCODING=utf8 else -echo "error: Die Datei $PATCH_DIR_FILE wurde nicht gefunden." >>$LOG -echo "Installation wird abgebrochen!" >>$LOG -exit 1 + UTF_INSTALL="$PATCH_ID/patch_${PATCH_ID}_installieren_superx_utf8.sh" + ISO_INSTALL="$PATCH_ID/patch_${PATCH_ID}_installieren_superx_iso.sh" + if grep -Fxq -- "$UTF_INSTALL" <<< "$ZIP_LIST" && ! grep -Fxq -- "$ISO_INSTALL" <<< "$ZIP_LIST"; then + PATCH_ENCODING=utf8 + elif grep -Fxq -- "$ISO_INSTALL" <<< "$ZIP_LIST" && ! grep -Fxq -- "$UTF_INSTALL" <<< "$ZIP_LIST"; then + PATCH_ENCODING=iso + else + fail 'Kodierung nicht eindeutig: bitte --encoding utf8 oder --encoding iso angeben.' + fi fi -#Test ob patch_version.txt vorhanden -if test -f patch_version.txt ; then -export PATCH_ID=`cat patch_version.txt` -else -echo "error: Die Datei patch_version.txt wurde nicht gefunden." >>$LOG -echo "Installation wird abgebrochen!" >>$LOG -exit 1 +INSTALL_REL="$PATCH_ID/patch_${PATCH_ID}_installieren_superx_${PATCH_ENCODING}.sh" +grep -Fxq -- "$INSTALL_REL" <<< "$ZIP_LIST" || fail "Installationsskript fehlt in ZIP: $INSTALL_REL" + +# Optional eine Liste von Modulen (wie bisher als zweiter Parameter) annehmen. +# Die alten generierten Hauptskripte konnten Modulnamen wegen '-eq' nicht +# korrekt vergleichen und konnten Bash-Arrays nicht per export uebernehmen. +# Fuer genau diese Alt-Patches werden ausgewaehlte Module direkt gestartet. +declare -a PATCH_MODULE_LIST=() +if [[ -n "$PATCH_MODULE_LIST_EINGABE" ]]; then + IFS=',' read -r -a raw_modules <<< "$PATCH_MODULE_LIST_EINGABE" + for raw in "${raw_modules[@]}"; do + module=${raw#"${raw%%[![:space:]]*}"} + module=${module%"${module##*[![:space:]]}"} + [[ "$module" =~ ^[A-Za-z0-9_][A-Za-z0-9_-]*$ ]] || fail "Ungueltiger Modulname: $raw" + PATCH_MODULE_LIST+=("$module") + done + ((${#PATCH_MODULE_LIST[@]})) || fail 'Leere Modul-Auswahl.' + # Alle angeforderten Module vorab pruefen, damit keine Teilinstallation + # beginnt, wenn eines davon gar nicht im Patch enthalten ist. + for module in "${PATCH_MODULE_LIST[@]}"; do + MODUL_REL="$PATCH_ID/${module}_patch_${PATCH_ID}_superx_${PATCH_ENCODING}.sh" + grep -Fxq -- "$MODUL_REL" <<< "$ZIP_LIST" || fail "Modul $module ist nicht in diesem Patch enthalten." + done +fi + +# WEBAPP und SUPERX_DIR aus expliziten Argumenten oder Umgebung bestimmen. +# In einer Webapp-Struktur liegt SUPERX_DIR unter WEB-INF/conf/edustore. +[[ -z "$WEBAPP_OPTION" ]] || WEBAPP=$WEBAPP_OPTION +[[ -z "$SUPERX_DIR_OPTION" ]] || SUPERX_DIR=$SUPERX_DIR_OPTION +# Ein explizit uebergebenes Ziel hat Vorrang vor ggf. alten Umgebungswerten. +# Fuer Sonderstrukturen koennen beide Pfade explizit gesetzt werden. +if [[ -n "$WEBAPP_OPTION" && -z "$SUPERX_DIR_OPTION" && -d "$WEBAPP/WEB-INF/conf/edustore/db" ]]; then + SUPERX_DIR="$WEBAPP/WEB-INF/conf/edustore" +fi +if [[ -n "$SUPERX_DIR_OPTION" && -z "$WEBAPP_OPTION" ]]; then + if [[ "$SUPERX_DIR" == */WEB-INF/conf/edustore ]]; then + WEBAPP=${SUPERX_DIR%/WEB-INF/conf/edustore} + else + WEBAPP="$SUPERX_DIR/webserver/tomcat/webapps/superx" + fi +fi +if [[ -z "${WEBAPP:-}" && -n "${SUPERX_DIR:-}" ]]; then + if [[ "$SUPERX_DIR" == */WEB-INF/conf/edustore ]]; then + WEBAPP=${SUPERX_DIR%/WEB-INF/conf/edustore} + else + WEBAPP="$SUPERX_DIR/webserver/tomcat/webapps/superx" + fi fi +if [[ -z "${SUPERX_DIR:-}" && -n "${WEBAPP:-}" && -d "$WEBAPP/WEB-INF/conf/edustore/db" ]]; then + SUPERX_DIR="$WEBAPP/WEB-INF/conf/edustore" +fi +[[ -n "${SUPERX_DIR:-}" && -d "$SUPERX_DIR/db" ]] || fail "SUPERX_DIR/db nicht gefunden: ${SUPERX_DIR:-}" +[[ -n "${WEBAPP:-}" && -d "$WEBAPP/WEB-INF" ]] || fail "WEBAPP/WEB-INF nicht gefunden: ${WEBAPP:-}" +SUPERX_DIR=$(cd -P -- "$SUPERX_DIR" && pwd -P) || fail 'SUPERX_DIR nicht erreichbar.' +WEBAPP=$(cd -P -- "$WEBAPP" && pwd -P) || fail 'WEBAPP nicht erreichbar.' +export PATCH_DIR_FILE PATCH_DIR PATCH_ID PATCH_MODULE_LIST_EINGABE SUPERX_DIR WEBAPP LOG -#Test ob PatchVerzeichnis richtig angelegt wurde -if test ! -d "$PATCH_DIR"/"$PATCH_ID" ; then -echo "error: Das Verzeichnis $PATCH_DIR/$PATCH_ID wurde nicht gefunden." >>$LOG -echo "Eventuell wurde das Paket nicht richtig entpackt." >>$LOG -echo "Installation wird abgebrochen!" >>$LOG -exit 1 +# Das neue, generierte Patchskript verwendet rsync zum Kopieren der Dateien. +# Ein alter Patch benoetigt rsync nicht, kann also weiterhin ohne es laufen. +if unzip -p "$ZIP_FILE" "$INSTALL_REL" | grep -Fq '# Installationsskript eines SuperX-Patches'; then + command -v rsync >/dev/null 2>&1 || fail "'rsync' wird fuer diesen Patch benoetigt." fi -echo -echo +printf 'Patch: %s\nPatch-ID: %s\nKodierung: %s\nSUPERX_DIR: %s\nWEBAPP: %s\nArbeitsverzeichnis: %s\n' \ + "$ZIP_FILE" "$PATCH_ID" "$PATCH_ENCODING" "$SUPERX_DIR" "$WEBAPP" "$PATCH_DIR" | tee -a "$LOG" +if [[ -n "$PATCH_MODULE_LIST_EINGABE" ]]; then + printf 'Ausgewaehlte Module: %s\n' "$PATCH_MODULE_LIST_EINGABE" | tee -a "$LOG" +fi -$PATCH_DIR/$PATCH_ID/patch_"$PATCH_ID"_installieren_superx_$ENCODING.sh >>$LOG 2>&1 +# Vorhandene Unterordner bleiben fuer eine erneute Installation erhalten, +# muessen aber frei von Symlinks sein (kein Schreiben in fremde Verzeichnisse). +if [[ -d "$PATCH_DIR/$PATCH_ID" ]] && [[ -n "$(find "$PATCH_DIR/$PATCH_ID" -type l -print -quit)" ]]; then + fail "Vorhandenes Patch-Verzeichnis enthaelt Symlinks: $PATCH_DIR/$PATCH_ID" +fi +printf 'Entpacke ZIP nach %s ...\n' "$PATCH_DIR" | tee -a "$LOG" +unzip -o "$ZIP_FILE" -d "$PATCH_DIR" >> "$LOG" 2>&1 || fail 'Entpacken des Patch-ZIPs fehlgeschlagen.' +INSTALL_SCRIPT="$PATCH_DIR/$INSTALL_REL" +[[ -f "$INSTALL_SCRIPT" && ! -L "$INSTALL_SCRIPT" ]] || fail "Installationsskript nicht gefunden: $INSTALL_SCRIPT" +# Aeltere Hauptskripte rufen Modulskripte direkt auf. Auch ZIPs, bei denen +# die Ausfuehrungsrechte nicht mitgespeichert wurden, sollen funktionieren. +while IFS= read -r member; do + case "$member" in + "$PATCH_ID/"*"_patch_${PATCH_ID}_superx_${PATCH_ENCODING}.sh") + [[ -f "$PATCH_DIR/$member" && ! -L "$PATCH_DIR/$member" ]] || fail "Modulskript ungueltig: $member" + chmod u+x -- "$PATCH_DIR/$member" || fail "Modulskript kann nicht ausfuehrbar gemacht werden: $member" + ;; + esac +done <<< "$ZIP_LIST" -echo >>$LOG -echo "Installation von Patch $PATCH_DIR_FILE abgeschlossen." >>$LOG -echo >>$LOG +printf 'Starte Installation ...\n' | tee -a "$LOG" +INSTALL_STATUS=0 +# Selektiver Aufruf von Alt-Patches: direkter Aufruf der Module, um den +# bisherigen Fehler mit numerischem Stringvergleich (-eq) zu umgehen. +if ((${#PATCH_MODULE_LIST[@]})) && grep -Fq 'PATCH_MODULE_LIST_PARAM[@]' "$INSTALL_SCRIPT"; then + printf 'Altes Patch-Format erkannt: ausgewaehlte Module werden direkt gestartet.\n' | tee -a "$LOG" + for module in "${PATCH_MODULE_LIST[@]}"; do + MODUL_REL="$PATCH_ID/${module}_patch_${PATCH_ID}_superx_${PATCH_ENCODING}.sh" + if ! grep -Fxq -- "$MODUL_REL" <<< "$ZIP_LIST"; then + fail "Modul $module ist nicht in diesem Patch enthalten." + fi + printf 'Installiere Modul %s ...\n' "$module" | tee -a "$LOG" + bash "$PATCH_DIR/$MODUL_REL" 2>&1 | tee -a "$LOG" + INSTALL_STATUS=${PIPESTATUS[0]} + ((INSTALL_STATUS == 0)) || break + done +else + bash "$INSTALL_SCRIPT" 2>&1 | tee -a "$LOG" + INSTALL_STATUS=${PIPESTATUS[0]} +fi -echo "Script beendet, untersuche $LOG auf Fehler:" -cat $LOG | grep -i error +if ((INSTALL_STATUS != 0)); then + fail "Installation fehlgeschlagen (Exitcode $INSTALL_STATUS)." +fi + +# Aeltere generierte Installationsskripte melden manche Fehler nur im Text +# und liefern trotzdem Exitcode 0. Diese offensichtlichen Fehler erkennen. +if grep -Eiq '(^|[[:space:]])error[[:space:]]*:' "$LOG"; then + fail 'Im Installationsprotokoll wurden Fehlermeldungen gefunden.' +fi +printf 'Installation von Patch %s erfolgreich abgeschlossen.\nProtokoll: %s\n' "$PATCH_ID" "$LOG" | tee -a "$LOG" +exit 0 diff --git a/superx/WEB-INF/conf/edustore/db/bin/patch_scripts_create.x b/superx/WEB-INF/conf/edustore/db/bin/patch_scripts_create.x deleted file mode 100755 index 90bdc950..00000000 --- a/superx/WEB-INF/conf/edustore/db/bin/patch_scripts_create.x +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -# Script das aus einer XML-Datei Patch Script erzeugt -#(c) 2011 Andre Knieschewski - - -PATCH_ID=$1 -PATCH_DIR=$2 - -if [ "$PATCH_ID" == "" ] ; then -echo "Bitte Patch ID angeben!" -exit 1 -fi - -if [ "$WEBAPP" == "" ] ; then -#in SuperX 3.5 ist webapp immer hier: -WEBAPP=$SUPERX_DIR/webserver/tomcat/webapps/superx -fi - -if [ "$PATCH_DIR" == "" ] -then -if test -d /home/superx/git/superx/superx/WEB-INF/patch ; then -PATCH_DIR=/home/superx/git/superx/superx/WEB-INF/patch -else -echo "Bitte Patch Dir angeben!" -echo "Aufruf:" -echo "patch_scripts_create.x <> <>" -exit 1 -fi -fi - - -if test ! -f $PATCH_DIR/xml/patch_$PATCH_ID.xml ; then -echo "XML Datei $PATCH_DIR/xml/patch_$PATCH_ID.xml nicht gefunden!" -exit 1 -fi - -echo "Erzeuge Patch-Scripte" -echo "Pfad: $PATCH_DIR" -echo - - -ant -f $SUPERX_DIR/db/conf/patch_scripts_create_ant.xml -DPATCH_DIR=$PATCH_DIR -DPATCH_ID=$PATCH_ID -cd $PATCH_DIR/patch_tmp -patch_"$PATCH_ID"_kopieren.x -mv $PATCH_ID/*"$PATCH_ID"*.htm $PATCH_DIR/patch_archive -rm -R $PATCH_ID -#rm patch_"$PATCH_ID"_kopieren.x -#rm patch_version.txt -mv patch_"$PATCH_ID"*.zip $PATCH_DIR/patch_archive