From 5e39c0040ddde260831a5b9f73c0bbfec3738f94 Mon Sep 17 00:00:00 2001
From: Kai Reinhard <K.Reinhard@micromata.de>
Date: Tue, 13 Apr 2021 23:20:23 +0000
Subject: [PATCH] Docker...

---
 .gitignore                                                                             |    1 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java               |    7 +
 borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java        |   21 ++++
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java   |    3 
 README.adoc                                                                            |    4 
 borgbutler-core/README.adoc                                                            |    4 
 borgbutler-docker/buildDocker.sh                                                       |   15 +++
 borgbutler-server/src/main/resources/log4j.properties                                  |    2 
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java  |   22 +++-
 build.gradle                                                                           |    2 
 borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java |   20 +++
 borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgVersionTest.java    |   20 ++++
 borgbutler-docker/app/Dockerfile                                                       |   35 +++++++
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java        |   53 +++++++++
 borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java    |   44 ++++++--
 15 files changed, 221 insertions(+), 32 deletions(-)

diff --git a/.gitignore b/.gitignore
index 611c1f7..6aba3e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,4 @@
 borgbutler-server/out
 borgbutler-webapp/build
 borgbutler-webapp/node_modules
+borgbutler-docker/app/target
diff --git a/README.adoc b/README.adoc
index a4e947f..70bdc88 100644
--- a/README.adoc
+++ b/README.adoc
@@ -38,7 +38,7 @@
 == Build and start from command line
 
 === Build distribution and start with Gradle
-1. `cd borgbackup-butler`
+1. `cd borgbackup-butler/borgbutler-webapp`
 2. `npm install`
 3. `cd ..`
 4. `gradle distZip`
@@ -92,4 +92,4 @@
 
 == Install server
 === Debian
-tbd.
\ No newline at end of file
+tbd.
diff --git a/borgbutler-core/README.adoc b/borgbutler-core/README.adoc
index 1074d70..a26d3c2 100644
--- a/borgbutler-core/README.adoc
+++ b/borgbutler-core/README.adoc
@@ -4,7 +4,7 @@
 :toc:
 :toclevels: 4
 
-Copyright (C) 2019
+Copyright (C) 2019-2021
 
 ifdef::env-github,env-browser[:outfilesuffix: .adoc]
 
@@ -18,4 +18,4 @@
 4. Execute script `./createFiles.sh` as root on debian host (borg is installed automatically)
 5. See and get the result files in `out.tar`:
    .. `cp out.tar /home/borgbutler`
-   .. From host system: `scp -P 2222 borgbutler@127.0.0.1:out.tar`
\ No newline at end of file
+   .. From host system: `scp -P 2222 borgbutler@127.0.0.1:out.tar`
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java
index 661e01f..b13063d 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java
@@ -11,6 +11,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
+/**
+ * Representation of ~/.borgbutler/borgbutler-config.json.
+ */
 public class Configuration {
     private Logger log = LoggerFactory.getLogger(Configuration.class);
     /**
@@ -21,11 +24,16 @@
     @JsonIgnore
     private File workingDir;
     /**
-     * The path of the borg command to use.
+     * The path of the borg command to use (optional).
      */
     private String borgCommand;
 
     /**
+     * The borg version to install from github (optional).
+     */
+    private String borgVersion;
+
+    /**
      * Default is 100 MB (approximately).
      */
     private int maxArchiveContentCacheCapacityMb = 100;
@@ -105,10 +113,17 @@
         return repoConfigs;
     }
 
+    /**
+     * Optional borg command to use.
+     */
     public String getBorgCommand() {
         return this.borgCommand;
     }
 
+    public String getBorgVersion() {
+        return borgVersion;
+    }
+
     public int getMaxArchiveContentCacheCapacityMb() {
         return this.maxArchiveContentCacheCapacityMb;
     }
@@ -131,6 +146,10 @@
         return this;
     }
 
+    public void setBorgVersion(String borgVersion) {
+        this.borgVersion = borgVersion;
+    }
+
     public Configuration setShowDemoRepos(boolean showDemoRepos) {
         this.showDemoRepos = showDemoRepos;
         return this;
diff --git a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java
index ee1fb32..e6c82e9 100644
--- a/borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java
+++ b/borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java
@@ -20,9 +20,17 @@
     private File configFile;
     private File configBackupDir;
     private File workingDir;
+    private File butlerHomeDir;
     private Configuration configuration;
     private static Class<? extends Configuration> configClazz = Configuration.class;
 
+    public static void init(String butlerHomeDir) {
+        if (instance != null) {
+            throw new RuntimeException("ConfigurationHandler already initialized");
+        }
+        instance = new ConfigurationHandler(butlerHomeDir);
+    }
+
     public static ConfigurationHandler getInstance() {
         if (instance == null) instance = new ConfigurationHandler();
         return instance;
@@ -97,8 +105,16 @@
     }
 
     private ConfigurationHandler() {
-        File userHome = new File(System.getProperty("user.home"));
-        workingDir = new File(userHome, BUTLER_HOME_DIR);
+        this(null);
+    }
+
+    private ConfigurationHandler(String butlerHomeDir) {
+        if (butlerHomeDir != null) {
+            workingDir = new File(butlerHomeDir);
+        } else {
+            workingDir = new File(System.getProperty("user.home"), BUTLER_HOME_DIR);
+        }
+        log.info("Using directory '" + workingDir.getAbsolutePath() + "' as BorgButler's home directory.");
         if (!workingDir.exists()) {
             log.info("Creating borg-butlers working directory: " + workingDir.getAbsolutePath());
             workingDir.mkdirs();
diff --git a/borgbutler-docker/app/Dockerfile b/borgbutler-docker/app/Dockerfile
new file mode 100644
index 0000000..49a3341
--- /dev/null
+++ b/borgbutler-docker/app/Dockerfile
@@ -0,0 +1,35 @@
+FROM openjdk:11
+
+# See: https://spring.io/guides/gs/spring-boot-docker/
+
+# This is a Debian system, update system packages (if needed)
+RUN apt-get update && apt-get -y upgrade
+
+RUN addgroup borgbutler && adduser --ingroup borgbutler borgbutler
+# ProjectForge's base dir: must be mounted on host file system:
+RUN mkdir /BorgButler
+# Grant access for user projectforge:
+RUN chown borgbutler.borgbutler /BorgButler
+VOLUME /BorgButler
+EXPOSE 9042
+
+USER borgbutler:borgbutler
+
+# Don't put fat jar files in docker images: https://phauer.com/2019/no-fat-jar-in-docker-image/
+ARG DEPENDENCY=target/dependency/borgbutler-server-0.3-SNAPSHOT
+COPY ${DEPENDENCY}/lib /app/lib
+COPY ${DEPENDENCY}/web /app/web
+#COPY ${DEPENDENCY}/META-INF /app/META-INF
+#COPY ${DEPENDENCY}/BOOT-INF/classes /app
+
+#ARG JAVA_OPTS="-Xms1g -Xmx1g"
+#ENV JAVA_OPTS_VAR=$JAVA_OPTS
+
+# -Dloader.path=${HOME}/ProjectForge/resources/milton ${DEBUGOPTS}
+
+# Variable expansion doesn't work for ENTRYPOINT definition as array, but array is required, because graceful shutdown of
+# container isn't given if java is started via 'sh -c' as it will be done by ENTRYPOINT java .....
+# Java options are modifiable by user through own ENTRYPOINT definition on docker run or in docker-compose.yml.
+ENTRYPOINT ["java", "-Xms1g", "-Xmx1g", "-cp", "app/web/*:app/lib/*", "-DborgbutlerHome=/BorgButler/", "-DapplicationHome=/app", "-DbindAddress=0.0.0.0", "-DallowedClientIps=172.17.", "de.micromata.borgbutler.server.Main", "-q"]
+
+MAINTAINER Micromata
diff --git a/borgbutler-docker/buildDocker.sh b/borgbutler-docker/buildDocker.sh
new file mode 100755
index 0000000..3072f3e
--- /dev/null
+++ b/borgbutler-docker/buildDocker.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+echo "Make sure, that you've run 'gradle dist' on top directory first"
+
+echo "Unpacking distribution zip to target/dependency..."
+rm -rf app/target/dependency
+mkdir -p app/target/dependency && (cd app/target/dependency; unzip ../../../../borgbutler-server/build/distributions/borgbutler-server-*.zip)
+
+# Will be done by application after starting: (cd app; wget https://github.com/borgbackup/borg/releases/download/1.1.16/borg-linux64.tgz)
+
+echo "Building docker file..."
+(cd app; docker build -t kreinhard/borgbutler .)
+
+echo "Push: docker push kreinhard/boprgbutler:tagname"
+echo "Run with 'docker run -p 127.0.0.1:9042:9042 -v $HOME/.borgbutler:/Borgbutler kreinhard/borgbutler'"
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java
index 7c15849..2f288ff 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java
@@ -35,6 +35,7 @@
                 return;
             }
         }
+        borgVersion.setBinariesDownloadVersion(configuration.getBorgVersion());
         initialize(getBinary(RunningMode.getOSType()));
         if (version(configuration)) {
             return;
@@ -95,7 +96,7 @@
         String msg = null;
         if (versionString != null) {
             borgVersion.setVersion(versionString);
-            int cmp = versionString.compareTo(borgVersion.getMinimumRequiredBorgVersion());
+            int cmp = BorgVersion.compareVersions(versionString, borgVersion.getMinimumRequiredBorgVersion());
             if (cmp < 0) {
                 msg = "Found borg version '" + versionString + "' is less than minimum required version '" + borgVersion.getMinimumRequiredBorgVersion() + "'.";
                 log.info(msg);
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java
index a134915..0f4c91f 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java
@@ -1,8 +1,14 @@
 package de.micromata.borgbutler.server;
 
+import org.apache.commons.lang3.StringUtils;
+
 public class BorgVersion {
-    private String binariesDownloadVersion = "1.1.8";
-    private String binariesDownloadUrl = "https://github.com/borgbackup/borg/releases/download/" + binariesDownloadVersion + "/";
+    public static final String BORG_DEFAULT_DOWNLOAD_VERSION = "1.1.16";
+
+    private static final String BORG_VERSION = BORG_DEFAULT_DOWNLOAD_VERSION;
+
+    private String binariesDownloadVersion = BORG_DEFAULT_DOWNLOAD_VERSION;
+
     private String[][] borgBinaries = {
             {"freebsd64", "FreeBSD 64"},
             {"linux32", "Linux 32"},
@@ -11,6 +17,10 @@
 
     private String minimumRequiredBorgVersion = "1.1.8";
 
+    public String getBinariesDownloadUrl() {
+        return "https://github.com/borgbackup/borg/releases/download/" + binariesDownloadVersion + "/";
+    }
+
     /**
      * One of the values "macosx64", "linux64" etc. for using a binary provided by BorgButler or null / "manual" for
      * using a manual installed borg version.
@@ -33,14 +43,21 @@
         return this.binariesDownloadVersion;
     }
 
-    public String getBinariesDownloadUrl() {
-        return this.binariesDownloadUrl;
+    public void setBinariesDownloadVersion(String binariesDownloadVersion) {
+        if (StringUtils.isNotBlank(binariesDownloadVersion)) {
+            this.binariesDownloadVersion = binariesDownloadVersion;
+        } else {
+            this.binariesDownloadVersion = BORG_DEFAULT_DOWNLOAD_VERSION;
+        }
     }
 
     public String[][] getBorgBinaries() {
         return this.borgBinaries;
     }
 
+    /**
+     * @return The minimal required borg version (installed on host).
+     */
     public String getMinimumRequiredBorgVersion() {
         return this.minimumRequiredBorgVersion;
     }
@@ -80,4 +97,32 @@
         this.statusMessage = statusMessage;
         return this;
     }
+
+    public static int compareVersions(String thisVersion, String otherVersion) {
+        String[] thisParts = checkVersion(thisVersion);
+        String[] otherParts = checkVersion(otherVersion);
+        int length = Math.max(thisParts.length, otherParts.length);
+        for (int i = 0; i < length; i++) {
+            int thisPart = i < thisParts.length ?
+                    Integer.parseInt(thisParts[i]) : 0;
+            int thatPart = i < otherParts.length ?
+                    Integer.parseInt(otherParts[i]) : 0;
+            if (thisPart < thatPart)
+                return -1;
+            if (thisPart > thatPart)
+                return 1;
+        }
+        return 0;
+    }
+
+    // https://stackoverflow.com/questions/198431/how-do-you-compare-two-version-strings-in-java
+    public static String[] checkVersion(String version) {
+        if (version == null) {
+            throw new IllegalArgumentException("Version can not be null");
+        }
+        if (!version.matches("[0-9]+(\\.[0-9]+)*")) {
+            throw new IllegalArgumentException("Invalid version format: " + version);
+        }
+        return version.split("\\.");
+    }
 }
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
index 93f77ce..036d7fe 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
@@ -54,6 +54,7 @@
         options.addOption("p", "port", true, "The default port for the web server.");
         options.addOption("q", "quiet", false, "Don't open browser automatically.");
         options.addOption("h", "help", false, "Print this help screen.");
+        //options.addOption("homeDir", true, "Specify own home directory of butler. Default is $HOME/.borgbutler");
         CommandLineParser parser = new DefaultParser();
         try {
             // parse the command line arguments
@@ -82,6 +83,10 @@
                     return;
                 }
             }
+            String applicationHome = System.getProperty("borgbutlerHome");
+            if (applicationHome != null) {
+                ConfigurationHandler.init(applicationHome);
+            }
             if (Desktop.isDesktopSupported()) {
                 RunningMode.setServerType(RunningMode.ServerType.DESKTOP);
             } else {
@@ -104,6 +109,8 @@
                 } catch (Exception ex) {
                     log.info("Can't open web browser: " + ex.getMessage());
                 }
+            } else {
+                log.info("Please open your browser: " + server.getUrl().replace("0.0.0.0", "127.0.0.1")); // 0.0.0.0 for Docker installations.
             }
         } catch (ParseException ex) {
             // oops, something went wrong
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java
index 4f04b29..5f9c29c 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java
@@ -29,12 +29,24 @@
 import java.util.EnumSet;
 
 public class JettyServer {
-    private Logger log = LoggerFactory.getLogger(JettyServer.class);
-    private static final String HOST = "127.0.0.1";
+    private static Logger log = LoggerFactory.getLogger(JettyServer.class);
+    private static String BIND_ADDRESS = "127.0.0.1";
     private static final int MAX_PORT_NUMBER = 65535;
     private Server server;
     private int port;
 
+    static {
+        String bindAddress = System.getProperty("bindAddress");
+        if (bindAddress != null) {
+            BIND_ADDRESS = bindAddress;
+        }
+        log.info("Binding server to address: " + BIND_ADDRESS);
+    }
+
+    public static String getBindAddress() {
+        return BIND_ADDRESS;
+    }
+
     public void start(String... restPackageNames) {
         port = findFreePort();
         if (port == -1) {
@@ -44,7 +56,7 @@
         server = new Server();
 
         ServerConnector connector = new ServerConnector(server);
-        connector.setHost(HOST);
+        connector.setHost(BIND_ADDRESS);
         connector.setPort(port);
         server.setConnectors(new Connector[]{connector});
 
@@ -150,7 +162,7 @@
         }
         for (int i = port; i < port + 10; i++) {
             try (ServerSocket socket = new ServerSocket()) {
-                socket.bind(new InetSocketAddress(HOST, i));
+                socket.bind(new InetSocketAddress(BIND_ADDRESS, i));
                 return i;
             } catch (Exception ex) {
                 log.info("Port " + i + " already in use or not available. Trying next port.");
@@ -170,7 +182,7 @@
     }
 
     public String getUrl() {
-        return "http://" + HOST + ":" + port + "/";
+        return "http://" + BIND_ADDRESS + ":" + port + "/";
     }
 }
 
diff --git a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
index df4b45c..9e03d68 100644
--- a/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
+++ b/borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java
@@ -21,19 +21,7 @@
 
     @Override
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
-        String remoteAddr = request.getRemoteAddr();
-        if (remoteAddr == null || !remoteAddr.equals("127.0.0.1")) {
-            log.warn("****************************************");
-            log.warn("***********                   **********");
-            log.warn("*********** SECURITY WARNING! **********");
-            log.warn("***********                   **********");
-            log.warn("*********** Externa access:   **********");
-            log.warn("*********** " + remoteAddr + " **********");
-            log.warn("***********                   **********");
-            log.warn("****************************************");
-            log.warn("Only access from local host yet supported due to security reasons.");
-            throw new RuntimeException("Server is only available for localhost due to security reasons. A remote access is not yet available.");
-        }
+        checkClientIp(request);
         try {
             UserData userData = UserUtils.getUser();
             if (userData != null) {
@@ -63,4 +51,34 @@
     public void destroy() {
     }
 
+    private void checkClientIp(ServletRequest request) {
+        String remoteAddr = request.getRemoteAddr();
+        boolean allowed = false;
+        String allowedClientIps = System.getProperty("allowedClientIps");
+        if (remoteAddr != null) {
+            if (remoteAddr.equals("127.0.0.1")) {
+                allowed = true;
+            } else {
+                if (allowedClientIps != null && remoteAddr.startsWith(allowedClientIps)) {
+                    allowed = true;
+                }
+            }
+        }
+        if (!allowed) {
+            log.warn("****************************************");
+            log.warn("***********                   **********");
+            log.warn("*********** SECURITY WARNING! **********");
+            log.warn("***********                   **********");
+            log.warn("*********** Externa access:   **********");
+            log.warn("*********** " + remoteAddr + " **********");
+            log.warn("***********                   **********");
+            log.warn("****************************************");
+            if (allowedClientIps == null) {
+                log.warn("Only access from local host yet supported due to security reasons. You may configure client address ranges by -DallowedClientIps=172.17.0.1 or -DallowedClientIps=172.17.");
+            } else {
+                log.warn("Only access from local host and " + allowedClientIps + " (option -DallowedClientIps) yet supported due to security reasons.");
+            }
+            throw new RuntimeException("Server is only available for localhost due to security reasons. A remote access is not yet available.");
+        }
+    }
 }
diff --git a/borgbutler-server/src/main/resources/log4j.properties b/borgbutler-server/src/main/resources/log4j.properties
index 03a0e14..ee7031b 100644
--- a/borgbutler-server/src/main/resources/log4j.properties
+++ b/borgbutler-server/src/main/resources/log4j.properties
@@ -14,7 +14,7 @@
 
 log4j.appender.file=org.apache.log4j.RollingFileAppender
 
-log4j.appender.file.File=borgbutler.log
+log4j.appender.file.File=${borgbutlerHome}borgbutler.log
 log4j.appender.file.MaxFileSize=10MB
 log4j.appender.file.MaxBackupIndex=5
 log4j.appender.file.layout=org.apache.log4j.PatternLayout
diff --git a/borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgVersionTest.java b/borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgVersionTest.java
new file mode 100644
index 0000000..0d233c7
--- /dev/null
+++ b/borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgVersionTest.java
@@ -0,0 +1,20 @@
+package de.micromata.borgbutler.server;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class BorgVersionTest {
+
+    @Test
+    void versionCompareTest() {
+        Assertions.assertThrows(IllegalArgumentException.class, () -> {
+            BorgVersion.compareVersions(null, "");
+        });
+        Assertions.assertThrows(IllegalArgumentException.class, () -> {
+            BorgVersion.compareVersions("", "");
+        });
+        Assertions.assertEquals(-1, BorgVersion.compareVersions("1.1.8", "1.1.16"));
+        Assertions.assertEquals(0, BorgVersion.compareVersions("1.1.8", "1.1.8"));
+        Assertions.assertEquals(1, BorgVersion.compareVersions("1.1.16", "1.1.8"));
+    }
+}
diff --git a/build.gradle b/build.gradle
index 5c6fc0f..d92fa1f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@
     apply plugin: 'maven'
 
     group = 'de.micromata.borgbutler'
-    version = '0.2-SNAPSHOT'
+    version = '0.3-SNAPSHOT'
 }
 
 subprojects {

--
Gitblit v1.10.0