2025

Allgemein

Zum Update des Goobi viewer Indexers auf die neuste Version immer die folgenden Kommandos benutzen:

mkdir -p /root/BACKUP/$(date -I)
systemctl stop solrindexer
mv /opt/digiverso/indexer/solrIndexer.jar /root/BACKUP/$(date -I)
wget -O /opt/digiverso/indexer/solrIndexer.jar https://github.com/intranda/goobi-viewer-indexer/releases/latest/download/solrIndexer.jar
systemctl start solrindexer

25.01

Das Januar Release hat viele Breaking-Changes. Bitte die Anleitung sorgfältig durchgehen.

Allgemein

Der Goobi viewer benötigt jetzt Java 21, Tomcat 10 und Solr 9.8.0. Die folgenden Punkte beziehen sich auf ein System, das von Ubuntu Linux Server 22.04 auf 24.04 aktualisiert wurde. Bei alternativen Distributionen sind die Schritte entsprechend der dortigen Pfade und Befehle anzupassen.

Java

Der Goobi viewer läuft jetzt mit Java 21. Dafür sicherstellen, dass das entsprechende Paket installiert und diese Version auch in Verwendung ist:

apt install openjdk-21-jre-headless
update-alternatives --config java

Eventuell vorhandene ältere Java Versionen können nach Prüfung entfernt werden.

Tomcat

Der Goobi viewer benötigt jetzt Tomcat 10:

apt install tomcat10

Anschließend müssen die Einstellungen, die für den Tomcat 9 galten ebenfalls für den Tomcat 10 gesetzt werden:

SYSTEMD_EDITOR=tee systemctl edit tomcat10 << "EOF"
[Service]
LogsDirectoryMode=755
CacheDirectoryMode=755
ProtectSystem=full
NoNewPrivileges=true
ReadWritePaths=
EOF
patch /etc/default/tomcat10 << "EOF"
@@ -6,11 +6,20 @@
 # You may pass JVM startup parameters to Java here. If you run Tomcat with
 # Java 8 instead of 9 or newer, add "-XX:+UseG1GC" to select a suitable GC.
 # If unset, the default options will be: -Djava.awt.headless=true
-JAVA_OPTS="-Djava.awt.headless=true"
+#JAVA_OPTS="-Djava.awt.headless=true"
 
 # To enable remote debugging uncomment the following line.
 # You will then be able to use a Java debugger on port 8000.
 #JAVA_OPTS="${JAVA_OPTS} -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n"
+JAVA_OPTS="-Djava.awt.headless=true -Xmx4g -Xms4g"
+JAVA_OPTS="${JAVA_OPTS} -XX:+UseG1GC"
+JAVA_OPTS="${JAVA_OPTS} -XX:+ParallelRefProcEnabled"
+JAVA_OPTS="${JAVA_OPTS} -XX:+DisableExplicitGC"
+JAVA_OPTS="${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom"
+JAVA_OPTS="${JAVA_OPTS} -Dfile.encoding='utf-8'"
+JAVA_OPTS="${JAVA_OPTS} --add-exports=java.desktop/sun.awt.image=ALL-UNNAMED"
+
+UMASK=0022
 
 # Java compiler to use for translating JavaServer Pages (JSPs). You can use all
 # compilers that are accepted by Ant's build.compiler property.
@@ -20,4 +29,4 @@
 #SECURITY_MANAGER=true
 
 # Whether to compress logfiles older than today's
-#LOGFILE_COMPRESS=1
+LOGFILE_COMPRESS=1
EOF
patch /etc/tomcat10/context.xml << "EOF"
@@ -28,4 +28,7 @@
     <!--
     <Manager pathname="SESSIONS.ser" />
     -->
+
+    <!-- Set mode for the JSESSONID cookie. Google authentication needs "lax" -->
+    <CookieProcessor sameSiteCookies="strict" />
 </Context>
EOF
patch /etc/tomcat10/server.xml << "EOF"
@@ -65,49 +65,22 @@
          AJP  Connector: /docs/config/ajp.html
          Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
     -->
-    <Connector port="8080" protocol="HTTP/1.1"
-               connectionTimeout="20000"
-               redirectPort="8443"
-               maxParameterCount="1000"
-               />
-    <!-- A "Connector" using the shared thread pool-->
-    <!--
-    <Connector executor="tomcatThreadPool"
-               port="8080" protocol="HTTP/1.1"
-               connectionTimeout="20000"
-               redirectPort="8443"
-               maxParameterCount="1000"
-               />
-    -->
-    <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
-         This connector uses the NIO implementation. The default
-         SSLImplementation will depend on the presence of the APR/native
-         library and the useOpenSSL attribute of the AprLifecycleListener.
-         Either JSSE or OpenSSL style configuration may be used regardless of
-         the SSLImplementation selected. JSSE style configuration is used below.
-    -->
-    <!--
-    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
-               maxThreads="150" SSLEnabled="true"
-               maxParameterCount="1000"
-               >
-        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
-        <SSLHostConfig>
-            <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
-                         type="RSA" />
-        </SSLHostConfig>
-    </Connector>
-    -->
 
-    <!-- Define an AJP 1.3 Connector on port 8009 -->
-    <!--
-    <Connector protocol="AJP/1.3"
-               address="::1"
-               port="8009"
-               redirectPort="8443"
-               maxParameterCount="1000"
-               />
-    -->
+        <Connector address="127.0.0.1" port="8080" protocol="HTTP/1.1"
+                server=" "
+                connectionTimeout="20000"
+                maxThreads="400"
+                URIEncoding="UTF-8"
+                enableLookups="false"
+                disableUploadTimeout="true"
+                proxyName="VIEWER.EXAMPLE.ORG"
+                proxyPort="80" />
+  
+        <Connector address="127.0.0.1" port="8009" protocol="AJP/1.3"
+                secretRequired="false"
+                connectionTimeout="20000"
+                maxThreads="400"
+                URIEncoding="UTF-8" />
 
     <!-- An Engine represents the entry point (within Catalina) that processes
          every request.  The Engine implementation for Tomcat stand alone
@@ -150,9 +123,14 @@
         <!-- Access log processes all example.
              Documentation at: /docs/config/valve.html
              Note: The pattern used is equivalent to using pattern="common" -->
+        <!--
         <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
                prefix="localhost_access_log" suffix=".txt"
                pattern="%h %l %u %t &quot;%r&quot; %s %b" />
+        -->
+       <Valve className="org.apache.catalina.valves.CrawlerSessionManagerValve"
+               crawlerUserAgents=".*[bB]ot.*|.*Yahoo! Slurp.*|.*Feedfetcher-Google.*|.*Apache-HttpClient.*|.*[Ss]pider.*|.*[Cc]rawler.*|.*nagios.*|.*Yandex.*|.*facebookexternalhit.*|.*bytedance.com.*|.*Turnitin.*|.*GoogleOther.*|.*python-requests.*|.*check_http.*"
+               sessionInactiveInterval="60"/>
 
       </Host>
     </Engine>
EOF

Nun die zum Goobi viewer dazugehörige Konfigurationsdatei übernehmen:

systemctl stop tomcat10
mv /etc/tomcat9/Catalina/localhost/*.xml /etc/tomcat10/Catalina/localhost/

Zu diesem Zeitpunkt muss eine aktualisierte, neue viewer.war mit dem 25.01 Release vorliegen, da eine ältere Version des Goobi viewers im Tomcat 10 nicht mehr startet.

Sofern auf dem System der Bash Alias cata gesetzt ist, muss der auf den neuen Pfad beziehungsweise die neue Unitfile angepasst werden.

Zuletzt noch sicherstellen, dass für den Benutzeraccount tomcatder richtige Pfad zum Homeverzeichnis gesetzt ist und - sofern vorhanden - das entsprechende .ssh Verzeichnis verschieben:

mkdir /var/lib/tomcat/
chown tomcat: /var/lib/tomcat/
usermod -d /var/lib/tomcat tomcat
[ -d /var/lib/tomcat9/.ssh ] && mv /var/lib/tomcat9/.ssh /var/lib/tomcat/

Goobi viewer Indexer

Aktualisierung der Systemd Unit file

Um die Abhängigkeit von Tomcat 9 zu Tomcat 10 zu ändern muss die Systemd Unitfile aktualisiert werden:

mkdir -p /root/BACKUP/$(date -I)
systemctl stop solrindexer
cp /etc/systemd/system/solrindexer.service /root/BACKUP/$(date -I)
wget -O /etc/systemd/system/solrindexer.service https://raw.githubusercontent.com/intranda/goobi-viewer-indexer/master/goobi-viewer-indexer/src/main/resources/other/solrindexer.service
systemctl daemon-reload
systemctl start solrindexer

Aktualisierung der Konfigurationsdatei

Die Konfigurationsdatei des Indexers muss an die vereinfachte XPATH-Konfiguration angepasst werden. Dafür können die folgenden Befehle und Skript verwendet werden:

cd /opt/digiverso/indexer/
cp config_indexer.xml /root/BACKUP/$(date -I)/
apt install xmlstarlet
file=config_indexer.xml
XSL=$(cat << "EOF"
<?xml version="1.0" ?>
<xsl:stylesheet version="1.0" encoding="UTF-8" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output encoding="UTF-8" version="1.0" indent="yes"/>

    <xsl:template match="root/fields/child::*/list/item">
        <xsl:copy>
            <xsl:for-each select="xpath/list/item">
                <xpath><xsl:value-of select="."/></xpath>
            </xsl:for-each>
            <xsl:apply-templates select="comment() | @* | *[not(self::xpath/list/item)] "/>
        </xsl:copy>
    </xsl:template>

    <!-- IdentityTransform -->
    <xsl:template match="/ | @* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()" />
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>
EOF
)

xmlstarlet tr <(echo "$XSL") $file | xmlstarlet fo -s 4 > ${file}.tmp
mv ${file}.tmp ${file}

Anlegen des Statistikordners

Sofern im Goobi viewer die Statistikfunktionalität aktiviert ist, werden die jetzt erzeugten JSON Dateien nach der Indexierung nicht mehr gelöscht, sondern in einen indexed_statistics Ordner gelegt. Dafür muss das Verzeichnis angelegt und eine zusätzliche Zeile in die config_indexer.xml eingetragen werden:

Verzeichnis anlegen:

mkdir /opt/digiverso/viewer/indexed_statistics/
chown tomcat: /opt/digiverso/viewer/indexed_statistics/

Zeile hinzufügen:

config_indexer.xml
<init>
    <indexedStatistics>indexed_statistics</indexedStatistics>
</init>

Statistiken

Damit bei dem Solr Update keine indexierten Statistiken verloren gehen, müssen diese aus dem Suchindex extrahiert und als JSON Dateien im Dateisystem abgelegt werden. Mit der folgenden Abfrage kann ermittelt werden, ob die Migration notwendig ist. Das ist der Fall, wenn die Ausgabe einen Wert größer 0 enthält:

apt install curl jq
curl -s 'http://localhost:8983/solr/collection1/select?q=DOCTYPE%3ASTATISTICS_USAGE&rows=0' | jq -r '.response.numFound'

Wenn eine Migration stattfinden muss, dann kann dafür das folgende Skript verwendet werden:

migrate_statistics.py
import json
import requests
from datetime import datetime
from collections import defaultdict

# Solr URL and query parameters
SOLR_URL = "http://localhost:8983/solr/collection1/select"
QUERY = "DOCTYPE:STATISTICS_USAGE"
ROWS = 1000

def query_solr(url, query, rows):
    """
    Query Solr and return the response JSON.
    """
    params = {
        "q": query,
        "rows": rows,
        "wt": "json"
    }
    response = requests.get(url, params=params)
    if response.status_code != 200:
        raise Exception(f"Solr query failed: {response.status_code} - {response.text}")
    return response.json()

def create_json_structure(docs):
    """
    Group documents by date and create the desired JSON structure.
    """
    grouped_data = defaultdict(list)

    # Group documents by date
    for doc in docs:
        date = doc.get("STATISTICS_DATE", "").split("T")[0]  # Extract date part
        if not date:
            continue  # Skip documents without a valid date

        viewer_name = doc.get("STATISTICS_VIEWERNAME", "unknown")
        for key, value in doc.items():
            if key.startswith("STATISTICS_RECORD_"):
                record_id = key.replace("STATISTICS_RECORD_", "")
                grouped_data[date].append({
                    "counts": value[:4],  # Use only the first 4 values
                    "pi": record_id
                })

    # Create JSON structure for each date
    json_files = {}
    for date, records in grouped_data.items():
        json_files[date] = {
            "date": date,
            "records": records,
            "viewer-name": viewer_name  # Use the last viewer name encountered
        }

    return json_files

def save_json_to_file(json_data, filename):
    """
    Save JSON data to a file.
    """
    with open(filename, "w") as f:
        json.dump(json_data, f, indent=4)

def main():
    # Query Solr
    print(f"Querying Solr at {SOLR_URL}...")
    solr_response = query_solr(SOLR_URL, QUERY, ROWS)

    # Process the Solr response
    docs = solr_response.get("response", {}).get("docs", [])
    if not docs:
        print("No documents found.")
        return

    # Create JSON structure grouped by date
    json_files = create_json_structure(docs)

    # Save JSON files
    for date, json_data in json_files.items():
        filename = f"statistics-usage-{date}.json"
        save_json_to_file(json_data, filename)
        print(f"Created JSON file: {filename}")

if __name__ == "__main__":
    main()

Das kann in dem folgenden Ordner abgelegt, einmalig aufgerufen und danach wieder gelöscht werden:

cd /opt/digiverso/viewer/indexed_statistics/ 
vim migrate_statistics.py
python3 migrate_statistics.py
rm migrate_statistics.py

Zookeeper

In der /etc/zookeeper/conf/zoo.cfg sicherstellen, dass das dataDir auf den richtigen Pfad zeigt und die clientPortAddress auf localhost gesetzt ist:

dataDir=/var/lib/zookeeper
clientPortAddress=127.0.0.1

Anschließend den folgenden Symlink entfernen:

rm /var/lib/zookeeper/myid

Sollte Zookeeper jetzt nicht starten, kann es sein, dass noch eine Snapshot-Datei benötigt wird:

# Prüfen ob Datei vorhanden ist
ll /var/lib/zookeeper/version-2/snapshot.0

# Wenn nicht, dann ablegen
wget -O /var/lib/zookeeper/version-2/snapshot.0 https://issues.apache.org/jira/secure/attachment/12928686/snapshot.0
chown zookeeper: /var/lib/zookeeper/version-2/snapshot.0

Solr

Mit dem 25.01 Release wird auf eine aktuelle Solr Version aktualisiert werden. Die hier festgehaltenen Schritte sind als Anleitung zu verstehen bei denen die Schritte im Einzelnen durchgespielt und bei Bedarf an lokale Pfade und Gegebenheiten angepasst werden müssen.

# Stop Solr Service and create Backup
systemctl stop solr
mkdir /root/BACKUP/$(date -I)
cd /opt/digiverso/
tar -czf /root/BACKUP/$(date -I)/solr.tar.gz solr/
mv /opt/digiverso/solr/log4j2.xml /root/BACKUP/$(date -I)/
grep ^SOLR_HEAP /etc/default/solr.in.sh # remind for later ;-)
mv /etc/default/solr.in.sh /root/BACKUP/$(date -I)/


# Download and Install
cd /tmp
wget https://archive.apache.org/dist/solr/solr/9.8.0/solr-9.8.0.tgz
tar -xzf solr-9.8.0.tgz solr-9.8.0/bin/install_solr_service.sh --strip-components=2
./install_solr_service.sh solr-9.8.0.tgz -d /opt/digiverso/solr -i /opt/digiverso/solr -p 8983 -s solr  -u solr -f -n

patch /etc/default/solr.in.sh  << "EOF"
@@ -32,7 +32,7 @@
 #SOLR_START_WAIT="$SOLR_STOP_WAIT"
 
 # Increase Java Heap as needed to support your indexing / query needs
-#SOLR_HEAP="512m"
+SOLR_HEAP="2048m"
 
 # Expert: If you want finer control over memory options, specify them directly
 # Comment out SOLR_HEAP if you are using this though, that takes precedence
@@ -50,25 +50,19 @@
 #  -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime"
 
 # These GC settings have shown to work well for a number of common Solr workloads
-#GC_TUNE=" \
-#-XX:+ExplicitGCInvokesConcurrent \
-#-XX:SurvivorRatio=4 \
-#-XX:TargetSurvivorRatio=90 \
-#-XX:MaxTenuringThreshold=8 \
-#-XX:+UseConcMarkSweepGC \
-#-XX:ConcGCThreads=4 -XX:ParallelGCThreads=4 \
-#-XX:+CMSScavengeBeforeRemark \
-#-XX:PretenureSizeThreshold=64m \
-#-XX:+UseCMSInitiatingOccupancyOnly \
-#-XX:CMSInitiatingOccupancyFraction=50 \
-#-XX:CMSMaxAbortablePrecleanTime=6000 \
-#-XX:+CMSParallelRemarkEnabled \
-#-XX:+ParallelRefProcEnabled        etc.
+GC_TUNE=" \
+-XX:+ExplicitGCInvokesConcurrent \
+-XX:SurvivorRatio=4 \
+-XX:TargetSurvivorRatio=90 \
+-XX:MaxTenuringThreshold=8 \
+-XX:ConcGCThreads=4 -XX:ParallelGCThreads=4 \
+-XX:PretenureSizeThreshold=64m \
+-XX:+ParallelRefProcEnabled"
 
 # Set the ZooKeeper connection string if using an external ZooKeeper ensemble
 # e.g. host1:2181,host2:2181/chroot
 # Leave empty if not using SolrCloud
-#ZK_HOST=""
+ZK_HOST="127.0.0.1:2181"
 
 # Set to true if your ZK host has a chroot path, and you want to create it automatically.
 #ZK_CREATE_CHROOT=true
@@ -129,7 +123,7 @@
 
 # Changes the logging level. Valid values: ALL, TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF. Default is INFO
 # This is an alternative to changing the rootLogger in log4j2.xml
-#SOLR_LOG_LEVEL=INFO
+SOLR_LOG_LEVEL=ERROR
 
 # Location where Solr should write logs to. Absolute or relative to solr start dir
 #SOLR_LOGS_DIR=logs
@@ -293,7 +287,7 @@
 # SOLR_OPTS="$SOLR_OPTS -Dlog4j2.formatMsgNoLookups=true"
 
 # The bundled plugins in the "modules" folder can easily be enabled as a comma-separated list in SOLR_MODULES variable
-# SOLR_MODULES=extraction,ltr
+SOLR_MODULES=analysis-extras
 
 # Configure the default replica placement plugin to use if one is not configured in cluster properties
 # See https://solr.apache.org/guide/solr/latest/configuration-guide/replica-placement-plugins.html for details
EOF


# Copy old configset and create new one:
cp -a /opt/digiverso/solr/solr-9.5.0/server/solr/configsets/goobiviewer /opt/digiverso/solr/solr-9.8.0/server/solr/configsets/
cp -a /opt/digiverso/solr/solr-9.5.0/server/solr/configsets/goobiviewer /opt/digiverso/solr/solr-9.8.0/server/solr/configsets/goobiviewer_25.01
cp /opt/digiverso/solr/solr-9.8.0/server/solr/configsets/_default/conf/solrconfig.xml /opt/digiverso/solr/solr-9.8.0/server/solr/configsets/goobiviewer_25.01/conf/

patch /opt/digiverso/solr/solr-9.8.0/server/solr/configsets/goobiviewer_25.01/conf/solrconfig.xml  << "EOF"
@@ -36,6 +36,7 @@
        affect both how text is indexed and queried.
   -->
   <luceneMatchVersion>9.11</luceneMatchVersion>
+  <schemaFactory class="ClassicIndexSchemaFactory"/>
 
   <!-- Data Directory
 
@@ -629,7 +630,7 @@
   <!-- Shared parameters for multiple Request Handlers -->
   <initParams path="/update/**,/query,/select,/spell">
     <lst name="defaults">
-      <str name="df">_text_</str>
+      <str name="df">DEFAULT</str>
     </lst>
   </initParams>
 
@@ -907,7 +908,7 @@
   
 
   <!-- The update.autoCreateFields property can be turned to false to disable schemaless mode -->
-  <updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:true}"
+  <updateRequestProcessorChain name="add-unknown-fields-to-the-schema" default="${update.autoCreateFields:false}"
            processor="uuid,remove-blank,field-name-mutating,max-fields,parse-boolean,parse-long,parse-double,parse-date,add-schema-fields">
     <processor class="solr.LogUpdateProcessorFactory"/>
     <processor class="solr.DistributedUpdateProcessorFactory"/>
EOF


wget -O /opt/digiverso/solr/solr/server/solr/configsets/goobiviewer_25.01/conf/schema.xml https://raw.githubusercontent.com/intranda/goobi-viewer-indexer/master/goobi-viewer-indexer/src/main/resources/other/schema.xml
chown solr: /opt/digiverso/solr/solr/server/solr/configsets/goobiviewer_25.01/conf/schema.xml


cd /opt/digiverso/solr/solr/server/solr-webapp/webapp/WEB-INF/lib/
wget https://github.com/locationtech/jts/releases/download/1.17.0/jts-core-1.17.0.jar
chown solr: jts-core-1.17.0.jar

## ACHTUNG - PRÜFEN OB NOCH EIN ALTER SOLR PROZESS LÄUFT UND GGFS STOPPEN ##
systemctl start solr

cd /opt/digiverso/solr/solr
chmod 755 bin/solr
sudo -u solr bin/solr zk upconfig --solr-url "http://localhost:8983" -n goobiviewer -d server/solr/configsets/goobiviewer/
sudo -u solr bin/solr zk upconfig --solr-url "http://localhost:8983" -n goobiviewer_25.01 -d server/solr/configsets/goobiviewer_25.01/

sudo -u solr bin/solr create -c collection_25.01 -n goobiviewer_25.01
curl "http://localhost:8983/solr/admin/collections?action=CREATEALIAS&name=current&collections=collection_25.01&wt=xml"

Jetzt muss der Indexer angepasst werden. Dabei muss der Wert von <solrUrl /> auf den Alias der neuen Collection gesetzt, und der Wert von <oldSolrUrl /> die URL der bisherigen <solrUrl /> enthalten. Beispiel:

<!-- Example -->
<solrUrl>http://localhost:8983/solr/current</solrUrl>
<oldSolrUrl>http://localhost:8983/solr/collection1</oldSolrUrl>

Nun alle Daten aus den indexed_* Ordnern in den Hotfolder kopieren, zum mit dem folgenden Kommando:

# rsync -av /opt/digiverso/viewer/indexed_*/* /opt/digiverso/viewer/hotfolder/
find /opt/digiverso/viewer/indexed_*/ -type f | xargs cp -at /opt/digiverso/viewer/hotfolder/

Sobald die Neuindexierung abgeschlossen ist, muss in der config_viewer.xml ebenfalls die solrUrl auf den neuen Alias geändert werden.

XSLT

Das XSLT für das Filtern der METS Dateien wurde angepasst, so dass die fileGrp PRESENTATION ebenfalls herausgefiltert wird. Außerdem ist die Datei umbenannt worden:

mv /opt/digiverso/viewer/config/METS_metadata_filter.xsl /root/BACKUP/$(date -I)
wget -O /opt/digiverso/viewer/config/METS_filter.xsl https://raw.githubusercontent.com/intranda/goobi-viewer-core-config/refs/heads/master/goobi-viewer-core-config/src/main/resources/METS_filter.xsl

Last updated