mirror of https://github.com/micromata/borgbackup-butler.git

Kai Reinhard
14.20.2021 5e39c0040ddde260831a5b9f73c0bbfec3738f94
Docker...
3 files added
12 files modified
253 ■■■■ changed files
.gitignore 1 ●●●● patch | view | raw | blame | history
README.adoc 4 ●●●● patch | view | raw | blame | history
borgbutler-core/README.adoc 4 ●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java 21 ●●●●● patch | view | raw | blame | history
borgbutler-core/src/main/java/de/micromata/borgbutler/config/ConfigurationHandler.java 20 ●●●●● patch | view | raw | blame | history
borgbutler-docker/app/Dockerfile 35 ●●●●● patch | view | raw | blame | history
borgbutler-docker/buildDocker.sh 15 ●●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java 3 ●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/BorgVersion.java 53 ●●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java 7 ●●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/jetty/JettyServer.java 22 ●●●● patch | view | raw | blame | history
borgbutler-server/src/main/java/de/micromata/borgbutler/server/user/UserFilter.java 44 ●●●● patch | view | raw | blame | history
borgbutler-server/src/main/resources/log4j.properties 2 ●●● patch | view | raw | blame | history
borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgVersionTest.java 20 ●●●●● patch | view | raw | blame | history
build.gradle 2 ●●● patch | view | raw | blame | history
.gitignore
@@ -41,3 +41,4 @@
borgbutler-server/out
borgbutler-webapp/build
borgbutler-webapp/node_modules
borgbutler-docker/app/target
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.
tbd.
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`
   .. From host system: `scp -P 2222 borgbutler@127.0.0.1:out.tar`
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;
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();
borgbutler-docker/app/Dockerfile
New file
@@ -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
borgbutler-docker/buildDocker.sh
New file
@@ -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'"
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);
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("\\.");
    }
}
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
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 + "/";
    }
}
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.");
        }
    }
}
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
borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgVersionTest.java
New file
@@ -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"));
    }
}
build.gradle
@@ -10,7 +10,7 @@
    apply plugin: 'maven'
    group = 'de.micromata.borgbutler'
    version = '0.2-SNAPSHOT'
    version = '0.3-SNAPSHOT'
}
subprojects {