diff --git a/Duplicati/Server/LogWriteHandler.cs b/Duplicati/Server/LogWriteHandler.cs index 0d7d264ba..e37857dfd 100644 --- a/Duplicati/Server/LogWriteHandler.cs +++ b/Duplicati/Server/LogWriteHandler.cs @@ -235,7 +235,7 @@ namespace Duplicati.Server } } - public LogEntry[] AfterID(long id, LogMessageType level) + public LogEntry[] AfterID(long id, LogMessageType level, int pagesize) { RenewTimeout(level); UpdateLogLevel(); @@ -245,7 +245,15 @@ namespace Duplicati.Server if (m_buffer == null) return new LogEntry[0]; - return m_buffer.FlatArray((x) => x.ID > id && x.Type >= level ); + var buffer = m_buffer.FlatArray((x) => x.ID > id && x.Type >= level ); + // Return the newest entries + if (buffer.Length > pagesize) { + var index = buffer.Length - pagesize; + return buffer.Skip(index).Take(pagesize).ToArray(); + } + else { + return buffer; + } } } diff --git a/Duplicati/Server/WebServer/AuthenticationHandler.cs b/Duplicati/Server/WebServer/AuthenticationHandler.cs index 0744807b8..ccc232444 100644 --- a/Duplicati/Server/WebServer/AuthenticationHandler.cs +++ b/Duplicati/Server/WebServer/AuthenticationHandler.cs @@ -286,7 +286,7 @@ namespace Duplicati.Server.WebServer else { response.Status = System.Net.HttpStatusCode.BadRequest; - response.Reason = "Missing XSRF Token"; + response.Reason = "Missing XSRF Token. Please reload the page"; return true; } diff --git a/Duplicati/Server/WebServer/RESTMethods/Backup.cs b/Duplicati/Server/WebServer/RESTMethods/Backup.cs index 485cba0f8..64d76c08a 100644 --- a/Duplicati/Server/WebServer/RESTMethods/Backup.cs +++ b/Duplicati/Server/WebServer/RESTMethods/Backup.cs @@ -574,7 +574,7 @@ namespace Duplicati.Server.WebServer.RESTMethods var backup = Program.DataConnection.GetBackup(key); if (backup == null) { - info.ReportClientError("Invalid or missing backup id"); + info.ReportClientError("Invalid or missing backup id", System.Net.HttpStatusCode.NotFound); return; } diff --git a/Duplicati/Server/WebServer/RESTMethods/LogData.cs b/Duplicati/Server/WebServer/RESTMethods/LogData.cs index 552cc2a7c..f87bbe2cb 100644 --- a/Duplicati/Server/WebServer/RESTMethods/LogData.cs +++ b/Duplicati/Server/WebServer/RESTMethods/LogData.cs @@ -29,13 +29,19 @@ namespace Duplicati.Server.WebServer.RESTMethods var level_str = input["level"].Value ?? ""; var id_str = input["id"].Value ?? ""; + int pagesize; + if (!int.TryParse(info.Request.QueryString["pagesize"].Value, out pagesize)) + pagesize = 100; + + pagesize = Math.Max(1, Math.Min(500, pagesize)); + Library.Logging.LogMessageType level; long id; long.TryParse(id_str, out id); Enum.TryParse(level_str, true, out level); - info.OutputOK(Program.LogHandler.AfterID(id, level)); + info.OutputOK(Program.LogHandler.AfterID(id, level, pagesize)); } else { diff --git a/Duplicati/Server/webroot/ngax/less/style.less b/Duplicati/Server/webroot/ngax/less/style.less index 8fff83afb..0e0188c8b 100755 --- a/Duplicati/Server/webroot/ngax/less/style.less +++ b/Duplicati/Server/webroot/ngax/less/style.less @@ -204,19 +204,14 @@ ul.tabs { } .entries { - div.entryline { + div.entryline.clickable { cursor: pointer; } } .entries.livedata { - li { - height: 1.2em; - } - li.expanded { height: auto; - overflow: auto; } } @@ -1492,7 +1487,7 @@ body { ul.entries { li { - padding-top: 30px; + padding-top: 10px; } } } diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js index 07737e88f..a526cc70e 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/EditBackupController.js @@ -315,21 +315,26 @@ backupApp.controller('EditBackupController', function ($rootScope, $scope, $rout return; } - if ($scope.KeepType == 'time' || $scope.KeepType == '') - { - delete opts['keep-versions']; - delete opts['retention-policy']; + // Retention options are mutual exclusive -> allow only one to be selected at a time + function resetAllRetentionOptionsExcept(optionToKeep = '') { + ['keep-versions', 'keep-time', 'retention-policy'].forEach(function(entry) { + if (entry != optionToKeep) { + delete opts[entry]; + } + }); } - if ($scope.KeepType == 'versions' || $scope.KeepType == '') - { - delete opts['keep-time']; - delete opts['retention-policy']; - } - if ($scope.KeepType == 'smart' || $scope.KeepType == '') - { - delete opts['keep-versions']; - delete opts['keep-time']; - delete opts['retention-policy']; + + if ($scope.KeepType == 'time') { + resetAllRetentionOptionsExcept('keep-time'); + + } else if ($scope.KeepType == 'versions') { + resetAllRetentionOptionsExcept('keep-versions'); + + } else if ($scope.KeepType == 'smart' || $scope.KeepType == 'custom') { + resetAllRetentionOptionsExcept('retention-policy'); + + } else { + resetAllRetentionOptionsExcept(); // keep none } if ($scope.KeepType == 'time' && (opts['keep-time'] || '').trim().length == 0) diff --git a/Duplicati/Server/webroot/ngax/scripts/controllers/LogController.js b/Duplicati/Server/webroot/ngax/scripts/controllers/LogController.js index 168e7c5b4..74eec186a 100644 --- a/Duplicati/Server/webroot/ngax/scripts/controllers/LogController.js +++ b/Duplicati/Server/webroot/ngax/scripts/controllers/LogController.js @@ -32,7 +32,8 @@ backupApp.controller('LogController', function($scope, $routeParams, $timeout, S resp.data.reverse(); $scope.LiveData.unshift.apply($scope.LiveData, resp.data); - $scope.LiveData.Length = Math.min(1000, $scope.LiveData.length); + $scope.LiveData.Length = Math.min(300, $scope.LiveData.length); + $scope.LiveData = $scope.LiveData.slice(0,$scope.LiveData.Length) $scope.LiveRefreshing = false; if ($scope.LiveRefreshPending) diff --git a/Duplicati/Server/webroot/ngax/templates/log.html b/Duplicati/Server/webroot/ngax/templates/log.html index 6b3d13bd1..6b2baa31a 100644 --- a/Duplicati/Server/webroot/ngax/templates/log.html +++ b/Duplicati/Server/webroot/ngax/templates/log.html @@ -34,7 +34,7 @@ diff --git a/Installer/Docker/README.md b/Installer/Docker/README.md new file mode 100644 index 000000000..3047e2f69 --- /dev/null +++ b/Installer/Docker/README.md @@ -0,0 +1,64 @@ +# [Duplicati](https://www.duplicati.com) +Duplicati is a free, open source, backup client that securely stores encrypted, incremental, compressed backups on cloud storage services and remote file servers. It works with: + +*Amazon S3, OneDrive, Google Drive, Rackspace Cloud Files, HubiC, Backblaze (B2), Amazon Cloud Drive (AmzCD), Swift / OpenStack, WebDAV, SSH (SFTP), FTP, and more!* + +Duplicati is licensed under LGPL and available for Windows, OSX and Linux (.NET 4.5+ or Mono required). + +## Available tags + +* `beta` - the most recent beta release +* `experimental` - the most recent experimental release +* `canary` - the most recent canary release +* `latest` - an alias for `beta` +* specific versions like `2.0.2.1_beta_2017-08-01` + +Images for the following OS/architecture combinations are available: + +* `linux-amd64` +* `linux-arm32v7` - 32-bit ARMv7 devices like the Raspberry Pi 2 + +The default architecture is `linux-amd64`. To pull an image for another architecture, prepend the architecture string to the image tag, e.g. `linux-arm32v7-beta`. + +## How to use this image + +```console +$ docker run -p 8200:8200 -v /some/path:/some/path duplicati/duplicati +``` + +Then, open [http://localhost:8200](http://localhost:8200) on the host to access the Duplicati web interface and configure backups. Any host directory that you want to back up needs to be mounted into the container using the `-v` option. + +### Preserving configuration + +All configuration is stored in `/data` inside the container, so you can mount a volume at that path to preserve the configuration: + +```console +$ docker run --name=duplicati -v duplicati-data:/data duplicati/duplicati +``` + +This allows you to delete and recreate the container without losing your configuration: + +```console +$ docker rm duplicati +$ docker run --name=duplicati -v duplicati-data:/data duplicati/duplicati +``` + +### Using Duplicati CLI + +Run the `duplicati-cli` command to use the Duplicati command-line interface: + +```console +$ docker run --rm duplicati/duplicati duplicati-cli help +See duplicati.commandline.exe help for more information. + General: example, changelog +... +$ docker run --rm -v /home:/backup/home duplicati/duplicati duplicati-cli backup ssh://user@host /backup/home +``` + +### Specifying server arguments + +To launch the Duplicati server with additional arguments, run the `duplicati-server` command: + +```console +$ docker run duplicati/duplicati duplicati-server --log-level=debug +``` diff --git a/Installer/Docker/build-images.sh b/Installer/Docker/build-images.sh new file mode 100755 index 000000000..33500ee77 --- /dev/null +++ b/Installer/Docker/build-images.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +if [ ! -f "$1" ]; then + echo "Please provide the filename of an existing zip build as the first argument" + exit +fi + +ARCHITECTURES="amd64 arm32v7" +DEFAULT_ARCHITECTURE=amd64 +DEFAULT_CHANNEL=beta +REPOSITORY=duplicati/duplicati + +ARCHIVE_NAME=$(basename -s .zip $1) +VERSION=$(echo "${ARCHIVE_NAME}" | cut -d "-" -f 2-) +CHANNEL=$(echo "${ARCHIVE_NAME}" | cut -d "_" -f 2) +DIRNAME=duplicati + +if [ -d "${DIRNAME}" ]; then + rm -rf "${DIRNAME}" +fi + +unzip -d "${DIRNAME}" "$1" + +for n in "../oem" "../../oem" "../../../oem" +do + if [ -d $n ]; then + echo "Installing OEM files" + cp -R $n "${DIRNAME}/webroot/" + fi +done + +for n in "oem-app-name.txt" "oem-update-url.txt" "oem-update-key.txt" "oem-update-readme.txt" "oem-update-installid.txt" +do + for p in "../$n" "../../$n" "../../../$n" + do + if [ -f $p ]; then + echo "Installing OEM override file" + cp $p "${DIRNAME}" + fi + done +done + +for arch in ${ARCHITECTURES}; do + tags="linux-${arch}-${VERSION} linux-${arch}-${CHANNEL}" + if [ ${CHANNEL} = ${DEFAULT_CHANNEL} ]; then + tags="linux-${arch}-latest ${tags}" + fi + if [ ${arch} = ${DEFAULT_ARCHITECTURE} ]; then + tags="${VERSION} ${CHANNEL} ${tags}" + fi + if [ ${CHANNEL} = ${DEFAULT_CHANNEL} -a ${arch} = ${DEFAULT_ARCHITECTURE} ]; then + tags="latest ${tags}" + fi + + args="" + for tag in ${tags}; do + args="-t ${REPOSITORY}:${tag} ${args}" + done + + docker build \ + ${args} \ + --build-arg ARCH=${arch}/ \ + --build-arg VERSION=${VERSION} \ + --build-arg CHANNEL=${CHANNEL} \ + --file context/Dockerfile \ + . + + for tag in ${tags}; do + docker push ${REPOSITORY}:${tag} + done +done diff --git a/Installer/Docker/context/Dockerfile b/Installer/Docker/context/Dockerfile new file mode 100644 index 000000000..8d36797e2 --- /dev/null +++ b/Installer/Docker/context/Dockerfile @@ -0,0 +1,31 @@ +ARG ARCH= +FROM ${ARCH}mono:5-slim + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + curl \ + libmono-sqlite4.0-cil \ + libmono-system-drawing4.0-cil \ + referenceassemblies-pcl && \ + rm -rf /var/lib/apt/lists && \ + cert-sync /etc/ssl/certs/ca-certificates.crt + +ENV TINI_VERSION v0.16.1 +RUN curl -L -o /usr/sbin/tini https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini-$(dpkg --print-architecture) && \ + chmod 0755 /usr/sbin/tini +ENTRYPOINT ["/usr/sbin/tini", "--"] + +ENV XDG_CONFIG_HOME=/data +VOLUME /data + +COPY context/duplicati-cli context/duplicati-server /usr/bin/ +RUN chmod 0755 /usr/bin/duplicati-cli /usr/bin/duplicati-server + +ARG CHANNEL= +ARG VERSION= +ENV DUPLICATI_CHANNEL=${CHANNEL} +ENV DUPLICATI_VERSION=${VERSION} +COPY duplicati /opt/duplicati + +EXPOSE 8200 +CMD ["/usr/bin/duplicati-server", "--webservice-port=8200", "--webservice-interface=any"] diff --git a/Installer/Docker/context/duplicati-cli b/Installer/Docker/context/duplicati-cli new file mode 100755 index 000000000..5bec19f01 --- /dev/null +++ b/Installer/Docker/context/duplicati-cli @@ -0,0 +1,4 @@ +#!/bin/bash +EXE_FILE=/opt/duplicati/Duplicati.CommandLine.exe +APP_NAME=Duplicati.CommandLine +exec -a "$APP_NAME" mono "$EXE_FILE" "$@" diff --git a/Installer/Docker/context/duplicati-server b/Installer/Docker/context/duplicati-server new file mode 100755 index 000000000..c603da265 --- /dev/null +++ b/Installer/Docker/context/duplicati-server @@ -0,0 +1,4 @@ +#!/bin/bash +EXE_FILE=/opt/duplicati/Duplicati.Server.exe +APP_NAME=DuplicatiServer +exec -a "$APP_NAME" mono "$EXE_FILE" "$@" diff --git a/Installer/OSX/run-with-mono.sh b/Installer/OSX/run-with-mono.sh index 60af3dd1f..ae4890d0b 100644 --- a/Installer/OSX/run-with-mono.sh +++ b/Installer/OSX/run-with-mono.sh @@ -26,7 +26,7 @@ REQUIRED_MINOR=0 VERSION_TITLE="Cannot launch $APP_NAME" VERSION_MSG="$APP_NAME requires the Mono Framework version $REQUIRED_MAJOR.$REQUIRED_MINOR or later." -DOWNLOAD_URL="http://www.go-mono.com/mono-downloads/download.html" +DOWNLOAD_URL="http://www.mono-project.com/download/stable/#download-mac" # Try to find system default Mono if an override was not supplied if [ "z${MONO_BIN}" == "z" ]; then diff --git a/build-installers.sh b/build-installers.sh index 8e07abb3f..92f0d177f 100755 --- a/build-installers.sh +++ b/build-installers.sh @@ -136,6 +136,17 @@ mv "Installer/fedora/${RPMNAME}" "${UPDATE_TARGET}" echo "Done building rpm package" +echo "" +echo "" +echo "Building Docker images ..." + +cd Installer/Docker +bash build-images.sh ../../$1 +cd ../.. + +echo "Done building Docker images" + + echo "" echo "" echo "Building Windows instance in virtual machine"