3 files added
12 files modified
| | |
| | | borgbutler-server/out |
| | | borgbutler-webapp/build |
| | | borgbutler-webapp/node_modules |
| | | borgbutler-docker/app/target |
| | |
| | | == 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` |
| | |
| | | :toc: |
| | | :toclevels: 4 |
| | | |
| | | Copyright (C) 2019 |
| | | Copyright (C) 2019-2021 |
| | | |
| | | ifdef::env-github,env-browser[:outfilesuffix: .adoc] |
| | | |
| | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * Representation of ~/.borgbutler/borgbutler-config.json. |
| | | */ |
| | | public class Configuration { |
| | | private Logger log = LoggerFactory.getLogger(Configuration.class); |
| | | /** |
| | |
| | | @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; |
| | |
| | | return repoConfigs; |
| | | } |
| | | |
| | | /** |
| | | * Optional borg command to use. |
| | | */ |
| | | public String getBorgCommand() { |
| | | return this.borgCommand; |
| | | } |
| | | |
| | | public String getBorgVersion() { |
| | | return borgVersion; |
| | | } |
| | | |
| | | public int getMaxArchiveContentCacheCapacityMb() { |
| | | return this.maxArchiveContentCacheCapacityMb; |
| | | } |
| | |
| | | return this; |
| | | } |
| | | |
| | | public void setBorgVersion(String borgVersion) { |
| | | this.borgVersion = borgVersion; |
| | | } |
| | | |
| | | public Configuration setShowDemoRepos(boolean showDemoRepos) { |
| | | this.showDemoRepos = showDemoRepos; |
| | | return this; |
| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | 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(); |
| New file |
| | |
| | | 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 |
| New file |
| | |
| | | #!/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'" |
| | |
| | | return; |
| | | } |
| | | } |
| | | borgVersion.setBinariesDownloadVersion(configuration.getBorgVersion()); |
| | | initialize(getBinary(RunningMode.getOSType())); |
| | | if (version(configuration)) { |
| | | return; |
| | |
| | | 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); |
| | |
| | | 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"}, |
| | |
| | | |
| | | 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. |
| | |
| | | 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; |
| | | } |
| | |
| | | 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("\\."); |
| | | } |
| | | } |
| | |
| | | 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 |
| | |
| | | return; |
| | | } |
| | | } |
| | | String applicationHome = System.getProperty("borgbutlerHome"); |
| | | if (applicationHome != null) { |
| | | ConfigurationHandler.init(applicationHome); |
| | | } |
| | | if (Desktop.isDesktopSupported()) { |
| | | RunningMode.setServerType(RunningMode.ServerType.DESKTOP); |
| | | } else { |
| | |
| | | } 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 |
| | |
| | | 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) { |
| | |
| | | server = new Server(); |
| | | |
| | | ServerConnector connector = new ServerConnector(server); |
| | | connector.setHost(HOST); |
| | | connector.setHost(BIND_ADDRESS); |
| | | connector.setPort(port); |
| | | server.setConnectors(new Connector[]{connector}); |
| | | |
| | |
| | | } |
| | | 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."); |
| | |
| | | } |
| | | |
| | | public String getUrl() { |
| | | return "http://" + HOST + ":" + port + "/"; |
| | | return "http://" + BIND_ADDRESS + ":" + port + "/"; |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | @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) { |
| | |
| | | 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."); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | 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 |
| New file |
| | |
| | | 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")); |
| | | } |
| | | } |
| | |
| | | apply plugin: 'maven' |
| | | |
| | | group = 'de.micromata.borgbutler' |
| | | version = '0.2-SNAPSHOT' |
| | | version = '0.3-SNAPSHOT' |
| | | } |
| | | |
| | | subprojects { |