diff --git a/Dockerfile b/Dockerfile index b14f3c6..28ebafb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -105,17 +105,42 @@ FROM system COPY --from=builder /src/web/dist/ /usr/local/lib/web/frontend/ -# Install QGIS +# Install STKO + + +RUN apt update \ + && apt upgrade -y \ + && apt install build-essential -y RUN apt update \ && apt install -y \ - xterm \ - && apt autoclean \ - && apt autoremove \ - && rm -rf /var/lib/apt/lists/* + python3-dev \ + tcl8.6-dev \ + tk8.6-dev \ + libtogl-dev \ + libglu1-mesa-dev \ + freeglut3-dev \ + mesa-common-dev \ + mesa-utils \ + libxi-dev \ + libxmu-dev \ + xterm -# Add our STKO (currently using glxgears as example) -RUN apt update && apt-get install -y mesa-utils +RUN apt update \ + && apt install -y \ + libxkbcommon-x11-0 + +# Add stko executable +ADD STKO-Install /STKO-Install +# Use/overwrite script so that it uses qt libs that will be installed by online installer +COPY STKO.sh /STKO-Install/STKO.sh + +# Install qt 5.12.4 using online installer +COPY qt_install_utils/ /qt_temp +ADD http://download.qt.io/official_releases/qt/5.12/5.12.4/qt-opensource-linux-x64-5.12.4.run /qt_temp/qt-opensource-linux-x64-5.12.4.run +RUN chmod +x /qt_temp/qt-opensource-linux-x64-5.12.4.run +RUN /qt_temp/qt-opensource-linux-x64-5.12.4.run --script /qt_temp/qt-installer.qs -platform minimal +RUN rm -rf /qt_temp COPY image / EXPOSE 6080 diff --git a/README_STKO.md b/README_STKO.md new file mode 100644 index 0000000..a0267ec --- /dev/null +++ b/README_STKO.md @@ -0,0 +1,17 @@ +STKO app +-------- +Here are some notes on this app's image. + + +Building Docker image +================ +Get STKO-Install.zip from developers and unzip into this diretory +``` +docker build -t tagname . +``` + +Other files of interest used in image +========================= +STKO.sh is an edited version of STKO-Install/STKO.sh (provided by STKO devs) which uses a system-installed qt version instead of the libs they provided +`qt_install_utils/qt-installer.qs` is an input script to online qt installer (see Dockerfile for more details on how its used) +`qt_install_utils/extract-qt-installer` can be used to see a list of modules which might be helpful when editing qt-installer.qs (`./extract-qt-installer --list qt-opensource-linux-x64-5.12.4.run`) diff --git a/STKO.sh b/STKO.sh new file mode 100755 index 0000000..c8833a7 --- /dev/null +++ b/STKO.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +appname=`basename $0 | sed s,\.sh$,,` + +dirname=`dirname $0` +tmp="${dirname#?}" + +if [ "${dirname%$tmp}" != "/" ]; then +dirname=$PWD/$dirname +fi + +core=$dirname/lib/core +coredep=$dirname/lib/coredep +export QTTOOLDIR=/opt/Qt5.12.4/5.12.4/gcc_64/bin +export QTLIBDIR=/opt/Qt5.12.4/5.12.4/gcc_64/lib +LD_LIBRARY_PATH=$dirname:$core:$coredep:$QTLIBDIR:$LD_LIBRARY_PATH +export LD_LIBRARY_PATH +#export QT_DEBUG_PLUGINS=1 +#echo "$0 = "$0 +#echo "appname = "$appname +#echo "dirname = "$dirname +#echo "tmp = "$tmp +echo "LD_LIBRARY_PATH = "$LD_LIBRARY_PATH + +$dirname/$appname "$@" diff --git a/image/etc/supervisor/conf.d/supervisord.conf b/image/etc/supervisor/conf.d/supervisord.conf index 2724106..a5f3793 100644 --- a/image/etc/supervisor/conf.d/supervisord.conf +++ b/image/etc/supervisor/conf.d/supervisord.conf @@ -63,7 +63,7 @@ stdout_logfile=/var/log/novnc.log [program:stko] priority=35 directory=/home/ubuntu/mydata -command=xterm -r -ls -geometry 80x24+10+10 -title '*** Exit this window to kill your QGIS session ***' -e 'xterm' +command=xterm -r -ls -geometry 80x24+10+10 -title '*** Exit this window to kill your STKO session ***' -e '/STKO-Install/STKO.sh' user=ubuntu autostart=true autorestart=false diff --git a/qt_install_utils/extract-qt-installer b/qt_install_utils/extract-qt-installer new file mode 100755 index 0000000..36e5b14 --- /dev/null +++ b/qt_install_utils/extract-qt-installer @@ -0,0 +1,340 @@ +#!/bin/bash +# QT-CI Project +# License: Apache-2.0 +# https://github.com/benlau/qtci + +function usage() { + echo "Usage:" + echo "extract-qt-installer qt-installer output_path" + echo "extract-qt-installer --list-packages qt-installer" + exit -1 +} + +LIST_PACKAGES=0 + +getopt --test > /dev/null 2>&1 +GETOPT_RET_CODE=$? + +set -e #quit on error + +if [ "$GETOPT_RET_CODE" != "4" ] +then + echo "Warning: gnu-getopt is not installed. Long parameter like '--list-package' will not be working. Please install gnu-getopt by 'brew install gnu-getopt'" +else + + OPTS=`getopt -o l --long list-packages --long disable-progress-report -n "extract-qt-installer" -- "$@"` + + eval set -- "$OPTS" + + while true + do + case "$1" in + --list-packages) + LIST_PACKAGES=1 + shift;; + --disable-progress-report) + DISABLE_PROGRESS_REPORT=1 + shift;; + --) shift;break;; + *) shift;; + esac + done +fi + + +export PATH=$PATH:$PWD +export WORKDIR=$PWD +INSTALLER=$1 +OUTPUT=$2 +SCRIPT="$(mktemp /tmp/tmp.XXXXXXXXX)" +PACKAGES=$QT_CI_PACKAGES + +if [ $LIST_PACKAGES -gt 0 ] +then + + if [ $# -lt 1 ] + then + usage + fi + + OUTPUT="/tmp/Qt" + +else + + if [ $# -lt 2 ] + then + usage + fi + + if [[ ! "${OUTPUT:0:1}" = "/" ]] + then + echo output path must be an absolute path + exit -1 + fi + +fi + +cat < $SCRIPT + +function abortInstaller() +{ + installer.setDefaultPageVisible(QInstaller.Introduction, false); + installer.setDefaultPageVisible(QInstaller.TargetDirectory, false); + installer.setDefaultPageVisible(QInstaller.ComponentSelection, false); + installer.setDefaultPageVisible(QInstaller.ReadyForInstallation, false); + installer.setDefaultPageVisible(QInstaller.StartMenuSelection, false); + installer.setDefaultPageVisible(QInstaller.PerformInstallation, false); + installer.setDefaultPageVisible(QInstaller.LicenseCheck, false); + + var abortText = "" + qsTr("Installation failed:") + ""; + + var error_list = installer.value("component_errors").split(";;;"); + abortText += ""; + installer.setValue("FinishedText", abortText); +} + +function log() { + var msg = ["QTCI: "].concat([].slice.call(arguments)); + console.log(msg.join(" ")); +} + +function printObject(object) { + var lines = []; + for (var i in object) { + lines.push([i, object[i]].join(" ")); + } + log(lines.join(",")); +} + +var status = { + widget: null, + finishedPageVisible: false, + installationFinished: false +} + +function tryFinish() { + if (status.finishedPageVisible && status.installationFinished) { + if (status.widget.LaunchQtCreatorCheckBoxForm) { + // Disable this checkbox for minimal platform + status.widget.LaunchQtCreatorCheckBoxForm.launchQtCreatorCheckBox.setChecked(false); + } + if (status.widget.RunItCheckBox) { + // LaunchQtCreatorCheckBoxForm may not work for newer versions. + status.widget.RunItCheckBox.setChecked(false); + } + log("Press Finish Button"); + gui.clickButton(buttons.FinishButton); + } +} + +function Controller() { + installer.installationFinished.connect(function() { + status.installationFinished = true; + gui.clickButton(buttons.NextButton); + tryFinish(); + }); + installer.setMessageBoxAutomaticAnswer("OverwriteTargetDirectory", QMessageBox.Yes); + installer.setMessageBoxAutomaticAnswer("installationErrorWithRetry", QMessageBox.Ignore); + installer.setMessageBoxAutomaticAnswer("XcodeError", QMessageBox.Ok); + + // Allow to cancel installation for arguments --list-packages + installer.setMessageBoxAutomaticAnswer("cancelInstallation", QMessageBox.Yes); +} + +Controller.prototype.WelcomePageCallback = function() { + log("Welcome Page"); + + gui.clickButton(buttons.NextButton); + + var widget = gui.currentPageWidget(); + + /* + Online installer 3.0.6 + - It must disconnect the completeChanged callback after used, otherwise it will click the 'next' button on another pages + */ + var callback = function() { + gui.clickButton(buttons.NextButton); + widget.completeChanged.disconnect(callback); + } + + widget.completeChanged.connect(callback); +} + +Controller.prototype.CredentialsPageCallback = function() { + + var login = installer.environmentVariable("QT_CI_LOGIN"); + var password = installer.environmentVariable("QT_CI_PASSWORD"); + + if (login === "" || password === "") { + gui.clickButton(buttons.CommitButton); + } + + var widget = gui.currentPageWidget(); + + widget.loginWidget.EmailLineEdit.setText(login); + + widget.loginWidget.PasswordLineEdit.setText(password); + + gui.clickButton(buttons.CommitButton); +} + +Controller.prototype.ComponentSelectionPageCallback = function() { + log("ComponentSelectionPageCallback"); + + function list_packages() { + var components = installer.components(); + log("Available components: " + components.length); + var packages = ["Packages: "]; + + for (var i = 0 ; i < components.length ;i++) { + packages.push(components[i].name); + } + log(packages.join(" ")); + } + + if ($LIST_PACKAGES) { + list_packages(); + gui.clickButton(buttons.CancelButton); + return; + } + + log("Select components"); + + function trim(str) { + return str.replace(/^ +/,"").replace(/ *$/,""); + } + + var widget = gui.currentPageWidget(); + + var packages = trim("$PACKAGES").split(","); + if (packages.length > 0 && packages[0] !== "") { + widget.deselectAll(); + var components = installer.components(); + var allfound = true; + for (var i in packages) { + var pkg = trim(packages[i]); + var found = false; + for (var j in components) { + if (components[j].name === pkg) { + found = true; + break; + } + } + if (!found) { + allfound = false; + log("ERROR: Package " + pkg + " not found."); + } else { + log("Select " + pkg); + widget.selectComponent(pkg); + } + } + if (!allfound) { + list_packages(); + // TODO: figure out how to set non-zero exit status. + gui.clickButton(buttons.CancelButton); + return; + } + } else { + log("Use default component list"); + } + + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.IntroductionPageCallback = function() { + log("Introduction Page"); + log("Retrieving meta information from remote repository"); + + /* + Online installer 3.0.6 + - Don't click buttons.NextButton directly. It will skip the componenet selection. + */ + + if (installer.isOfflineOnly()) { + gui.clickButton(buttons.NextButton); + } +} + +Controller.prototype.TargetDirectoryPageCallback = function() { + log("Set target installation page: $OUTPUT"); + var widget = gui.currentPageWidget(); + + if (widget != null) { + widget.TargetDirectoryLineEdit.setText("$OUTPUT"); + } + + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.LicenseAgreementPageCallback = function() { + log("Accept license agreement"); + var widget = gui.currentPageWidget(); + + if (widget != null) { + widget.AcceptLicenseRadioButton.setChecked(true); + } + + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.ReadyForInstallationPageCallback = function() { + log("Ready to install"); + + // Bug? If commit button pressed too quickly finished callback might not show the checkbox to disable running qt creator + // Behaviour started around 5.10. You don't actually have to press this button at all with those versions, even though gui.isButtonEnabled() returns true. + + gui.clickButton(buttons.CommitButton, 200); +} + +Controller.prototype.PerformInstallationPageCallback = function() { + log("PerformInstallationPageCallback"); + gui.clickButton(buttons.CommitButton); +} + +Controller.prototype.FinishedPageCallback = function() { + log("FinishedPageCallback"); + + var widget = gui.currentPageWidget(); + + // Bug? Qt 5.9.5 and Qt 5.9.6 installer show finished page before the installation completed + // Don't press "finishButton" immediately + + status.finishedPageVisible = true; + status.widget = widget; + tryFinish(); +} + +EOF + +chmod u+x $1 +if [ -n "$QT_CI_DEBUG" ] +then + $INSTALLER -v --script $SCRIPT | grep "\(QTCI\|operation\)" + exit 0 +fi + +unset DISPLAY + +if [ -n "$DISABLE_PROGRESS_REPORT" ] +then + QT_QPA_PLATFORM=minimal $INSTALLER --script $SCRIPT +else + ARGS="-v" + + if [ -n "$VERBOSE" ] + then + QT_QPA_PLATFORM=minimal $INSTALLER $ARGS --script $SCRIPT + else + QT_QPA_PLATFORM=minimal $INSTALLER $ARGS --script $SCRIPT | grep "\(QTCI\|operation\)" + fi +fi + + diff --git a/qt_install_utils/qt-installer.qs b/qt_install_utils/qt-installer.qs new file mode 100755 index 0000000..4af905c --- /dev/null +++ b/qt_install_utils/qt-installer.qs @@ -0,0 +1,66 @@ +// installer script for the Qt installer. Launch with (-platform is for headless) +// ./installer -platform minimal --script path/to/qt-installer.qs + +function Controller() { + installer.autoRejectMessageBoxes(); + installer.installationFinished.connect(function() { + gui.clickButton(buttons.NextButton); + }) +} + +Controller.prototype.WelcomePageCallback = function() { + // click delay here because the next button is initially disabled for ~1 second + gui.clickButton(buttons.NextButton, 3000); +} + +Controller.prototype.CredentialsPageCallback = function() { + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.IntroductionPageCallback = function() { + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.TargetDirectoryPageCallback = function() { + gui.currentPageWidget().TargetDirectoryLineEdit.setText("/opt/Qt5.12.4"); + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.ComponentSelectionPageCallback = function() { + var widget = gui.currentPageWidget(); + widget.deselectAll(); + // Package names are a mystery but can be extracted using the following script with a --list-packages flag: + // https://raw.githubusercontent.com/benlau/qtci/master/bin/extract-qt-installer + // Here is the full list for 5.12: + // qt qt.qt5.5124 qt.tools qt.installer.changelog qt.license.gplv3except qt.license.python qt.license.thirdparty qt.license.lgpl qt.qt5.5124.gcc_64 qt.qt5.5124.android_x86 qt.qt5.5124.android_arm64_v8a qt.qt5.5124.android_armv7 qt.qt5.5124.src qt.qt5.5124.qtcharts qt.qt5.5124.qtdatavis3d qt.qt5.5124.qtpurchasing qt.qt5.5124.qtvirtualkeyboard qt.qt5.5124.qtwebengine qt.qt5.5124.qtnetworkauth qt.qt5.5124.qtwebglplugin qt.qt5.5124.qtscript qt.qt5.5124.examples qt.qt5.5124.doc qt.qt5.5124.qtcharts.android_x86 qt.qt5.5124.qtcharts.android_arm64_v8a qt.qt5.5124.qtcharts.android_armv7 qt.qt5.5124.qtcharts.gcc_64 qt.qt5.5124.qtdatavis3d.android_x86 qt.qt5.5124.qtdatavis3d.android_arm64_v8a qt.qt5.5124.qtdatavis3d.android_armv7 qt.qt5.5124.qtdatavis3d.gcc_64 qt.qt5.5124.qtpurchasing.android_arm64_v8a qt.qt5.5124.qtpurchasing.android_x86 qt.qt5.5124.qtpurchasing.gcc_64 qt.qt5.5124.qtpurchasing.android_armv7 qt.qt5.5124.qtvirtualkeyboard.gcc_64 qt.qt5.5124.qtwebengine.gcc_64 qt.qt5.5124.qtnetworkauth.android_x86 qt.qt5.5124.qtnetworkauth.gcc_64 qt.qt5.5124.qtnetworkauth.android_armv7 qt.qt5.5124.qtnetworkauth.android_arm64_v8a qt.qt5.5124.qtwebglplugin.gcc_64 qt.qt5.5124.qtscript.android_armv7 qt.qt5.5124.qtscript.android_arm64_v8a qt.qt5.5124.qtscript.android_x86 qt.qt5.5124.qtscript.gcc_64 qt.qt5.5124.examples.qtdatavis3d qt.qt5.5124.examples.qtpurchasing qt.qt5.5124.examples.qtcharts qt.qt5.5124.examples.qtwebengine qt.qt5.5124.examples.qtscript qt.qt5.5124.examples.qtnetworkauth qt.qt5.5124.examples.qtvirtualkeyboard qt.qt5.5124.doc.qtwebengine qt.qt5.5124.doc.qtpurchasing qt.qt5.5124.doc.qtdatavis3d qt.qt5.5124.doc.qtcharts qt.qt5.5124.doc.qtvirtualkeyboard qt.qt5.5124.doc.qtnetworkauth qt.qt5.5124.doc.qtscript qt.tools.qtcreator + // qt qt.qt5.5124 qt.tools qt.installer.changelog qt.license.gplv3except qt.license.python qt.license.thirdparty qt.license.lgpl qt.qt5.5124.gcc_64 qt.qt5.5124.android_x86 qt.qt5.5124.android_arm64_v8a qt.qt5.5124.android_armv7 qt.qt5.5124.src qt.qt5.5124.qtcharts qt.qt5.5124.qtdatavis3d qt.qt5.5124.qtpurchasing qt.qt5.5124.qtvirtualkeyboard qt.qt5.5124.qtwebengine qt.qt5.5124.qtnetworkauth qt.qt5.5124.qtwebglplugin qt.qt5.5124.qtscript ... + widget.selectComponent("qt.qt5.5124.gcc_64"); + //widget.selectComponent("qt.qt5.5124.qtcharts"); + //widget.selectComponent("qt.qt5.5124.qtdatavis3d"); + //widget.selectComponent("qt.qt5.5124.qtvirtualkeyboard"); + //widget.selectComponent("qt.qt5.5124.qtwebengine"); + //widget.selectComponent("qt.qt5.5124.qtnetworkauth"); + //widget.selectComponent("qt.qt5.5124.qtwebglplugin"); + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.LicenseAgreementPageCallback = function() { + gui.currentPageWidget().AcceptLicenseRadioButton.setChecked(true); + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.StartMenuDirectoryPageCallback = function() { + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.ReadyForInstallationPageCallback = function() { + gui.clickButton(buttons.NextButton); +} + +Controller.prototype.FinishedPageCallback = function() { + var checkBoxForm = gui.currentPageWidget().LaunchQtCreatorCheckBoxForm; + if (checkBoxForm && checkBoxForm.launchQtCreatorCheckBox) { + checkBoxForm.launchQtCreatorCheckBox.checked = false; + } + gui.clickButton(buttons.FinishButton); +}