README.adoc
@@ -60,7 +60,7 @@ BorgButler working directory `$HOME/BorgButler` is assumed, but you may define any other. 1. Create your local BorgButler directory: `mkdir $HOME/BorgButler` (for config, caches, backups and restoring of backuped files and directories) 2. `docker run -v $HOME/BorgButler:/Borgbutler -p 127.0.0.1:9042:9042 --name borgbuttler kreinhard/borgbutler` (exporting of `.ssh` is useful for ssh remotes, otherwise skip this setting.) 2. `docker run -v $HOME/BorgButler:/BorgButler -v $HOME/.ssh:/home/borgbutler/.ssh -p 127.0.0.1:9042:9042 --name borgbuttler kreinhard/borgbutler` (exporting of `.ssh` is useful for ssh remotes, otherwise skip this setting.) 3. Stopping: simly click `CTRL-C`. 4. Restart: `docker start` 5. Stop: `docker stop` borgbutler-core/src/main/java/de/micromata/borgbutler/BorgJob.java
File was deleted borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
@@ -3,8 +3,8 @@ import de.micromata.borgbutler.BorgCommandResult; import de.micromata.borgbutler.BorgCommands; import de.micromata.borgbutler.config.BorgRepoConfig; import org.micromata.borgbutler.config.Configuration; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.Configuration; import de.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.data.Archive; import de.micromata.borgbutler.data.ArchiveShortInfo; import de.micromata.borgbutler.data.FileSystemFilter; borgbutler-core/src/main/java/de/micromata/borgbutler/cache/JCSCache.java
@@ -1,6 +1,6 @@ package de.micromata.borgbutler.cache; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import org.apache.commons.jcs.JCS; import org.apache.commons.jcs.access.CacheAccess; import org.slf4j.Logger; borgbutler-core/src/main/java/de/micromata/borgbutler/demo/DemoRepos.java
@@ -3,7 +3,7 @@ import de.micromata.borgbutler.BorgCommand; import de.micromata.borgbutler.BorgJob; import de.micromata.borgbutler.config.BorgRepoConfig; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.Definitions; import de.micromata.borgbutler.data.Repository; import de.micromata.borgbutler.jobs.JobResult; borgbutler-core/src/main/java/de/micromata/borgbutler/jobs/AbstractCommandLineJob.java
File was deleted borgbutler-core/src/main/kotlin/de/micromata/borgbutler/BorgJob.kt
New file @@ -0,0 +1,135 @@ package de.micromata.borgbutler import de.micromata.borgbutler.config.ConfigurationHandler.Companion.getConfiguration import de.micromata.borgbutler.demo.DemoRepos import de.micromata.borgbutler.jobs.AbstractCommandLineJob import de.micromata.borgbutler.jobs.JobResult import de.micromata.borgbutler.json.JsonUtils import de.micromata.borgbutler.json.borg.ProgressInfo import org.apache.commons.exec.CommandLine import org.apache.commons.exec.environment.EnvironmentUtils import org.apache.commons.lang3.StringUtils import org.slf4j.LoggerFactory import java.io.IOException /** * A queue is important because Borg doesn't support parallel calls for one repository. * For each repository one single queue is allocated. */ open class BorgJob<T> : AbstractCommandLineJob, Cloneable { private val log = LoggerFactory.getLogger(BorgJob::class.java) var command: BorgCommand? = null private set /** * Some jobs may store here the result of the command (e. g. [BorgCommands.listArchiveContent]). */ @JvmField var payload: T? = null var progressInfo: ProgressInfo? = null private set constructor(command: BorgCommand) { this.command = command setWorkingDirectory(command.workingDir) setDescription(command.description) } private constructor() {} override fun buildCommandLine(): CommandLine? { if (command == null) { return null } val borgCommand = getConfiguration()!!.borgCommand if (StringUtils.isBlank(borgCommand)) { log.error("Can't run empty borg command.") return null } val commandLine = CommandLine(borgCommand) commandLine.addArgument(command!!.command) if (command!!.params != null) { for (param in command!!.params) { if (param != null) commandLine.addArgument(param) } } if (command!!.repoArchive != null) { commandLine.addArgument(command!!.repoArchive) } if (command!!.args != null) { for (arg in command!!.args) { if (arg != null) commandLine.addArgument(arg) } } return commandLine } override fun processStdErrLine(line: String, level: Int) { if (StringUtils.startsWith(line, "{\"message")) { val message = JsonUtils.fromJson(ProgressInfo::class.java, line) if (message != null) { progressInfo = message return } } super.processStdErrLine(line, level) } // For MacOS BORG_PASSCOMMAND="security find-generic-password -a $USER -s borg-passphrase -w" @get:Throws(IOException::class) override val environment: Map<String?, String?>? protected get() { val repoConfig = command!!.repoConfig ?: return null val env = EnvironmentUtils.getProcEnvironment() val variables = repoConfig.getEnvironmentVariables(true) for (variable in variables) { // For MacOS BORG_PASSCOMMAND="security find-generic-password -a $USER -s borg-passphrase -w" val environmentVariable = variable.replace("\$USER", System.getProperty("user.name")) addEnvironmentVariable(env, environmentVariable) } return env } override fun execute(): JobResult<String>? { return if (command!!.repoConfig != null && DemoRepos.isDemo(command!!.repoConfig.repo)) { DemoRepos.execute(this) } else super.execute() } public override fun clone(): BorgJob<*> { val clone: BorgJob<*> = BorgJob<Any>() if (command != null) { // Needed for getting environment variables: JsonJob of borgbutler-server. clone.command = BorgCommand().setRepoConfig(command!!.repoConfig) } clone.uniqueJobNumber = uniqueJobNumber clone.title = title clone.setExecuteStarted(isExecuteStarted) clone.setCommandLineAsString(getCommandLineAsString()) clone.isCancellationRequested = isCancellationRequested clone.status = status clone.setWorkingDirectory(workingDirectory) clone.setDescription(description) if (progressInfo != null) { clone.setProgressInfo(progressInfo!!.clone()) } clone.createTime = createTime clone.startTime = startTime clone.stopTime = stopTime return clone } override fun cleanUp() { super.cleanUp() payload = null } fun getPayload(): T? { return payload } protected fun setProgressInfo(progressInfo: ProgressInfo?): BorgJob<T> { this.progressInfo = progressInfo return this } } borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/Configuration.kt
File was renamed from borgbutler-core/src/main/kotlin/org/micromata/borgbutler/config/Configuration.kt @@ -1,4 +1,4 @@ package org.micromata.borgbutler.config package de.micromata.borgbutler.config import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonProperty borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/ConfigurationHandler.kt
File was renamed from borgbutler-core/src/main/kotlin/org/micromata/borgbutler/config/ConfigurationHandler.kt @@ -1,4 +1,4 @@ package org.micromata.borgbutler.config package de.micromata.borgbutler.config import de.micromata.borgbutler.config.Definitions import de.micromata.borgbutler.json.JsonUtils borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/Legacy.kt
File was renamed from borgbutler-core/src/main/kotlin/org/micromata/borgbutler/config/Legacy.kt @@ -1,12 +1,9 @@ package org.micromata.borgbutler.config package de.micromata.borgbutler.config import de.micromata.borgbutler.config.Definitions import de.micromata.borgbutler.json.JsonUtils import mu.KotlinLogging import org.apache.commons.io.FileUtils import org.slf4j.LoggerFactory import java.io.File import java.io.IOException import java.text.SimpleDateFormat import java.util.* borgbutler-core/src/main/kotlin/de/micromata/borgbutler/config/YamlUtils.kt
File was renamed from borgbutler-core/src/main/kotlin/org/micromata/borgbutler/config/YamlUtils.kt @@ -1,4 +1,4 @@ package org.micromata.borgbutler.config package de.micromata.borgbutler.config import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.core.type.TypeReference borgbutler-core/src/main/kotlin/de/micromata/borgbutler/jobs/AbstractCommandLineJob.kt
New file @@ -0,0 +1,194 @@ package de.micromata.borgbutler.jobs import de.micromata.borgbutler.config.Definitions import mu.KotlinLogging import org.apache.commons.exec.* import org.apache.commons.exec.environment.EnvironmentUtils import org.apache.commons.io.output.ByteArrayOutputStream import org.apache.commons.lang3.StringUtils import java.io.File import java.io.IOException private val log = KotlinLogging.logger {} /** * A queue is important because Borg doesn't support parallel calls for one repository. * For each repository one single queue is allocated. */ abstract class AbstractCommandLineJob : AbstractJob<String>() { private var watchdog: ExecuteWatchdog? = null var isExecuteStarted = false private set private var commandLine: CommandLine? = null /** * The command line as string. This property is also used as ID for detecting multiple borg calls. */ private var commandLineAsString: String? = null var workingDirectory: File? = null private set var description: String? = null private set protected var outputStream = ByteArrayOutputStream() protected var errorOutputStream = ByteArrayOutputStream() @JvmField protected var logError = true protected abstract fun buildCommandLine(): CommandLine? override fun getId(): Any { return getCommandLineAsString()!! } fun getCommandLineAsString(): String? { if (commandLine == null) { commandLine = buildCommandLine() } if (commandLine == null) { return null } if (commandLineAsString == null) { commandLineAsString = commandLine!!.executable + " " + StringUtils.join( commandLine!!.arguments, " " ) } return commandLineAsString } override fun execute(): JobResult<String>? { getCommandLineAsString() if (commandLine == null) { return null } val executor = DefaultExecutor() if (workingDirectory != null) { executor.workingDirectory = workingDirectory } //executor.setExitValue(2); watchdog = ExecuteWatchdog(ExecuteWatchdog.INFINITE_TIMEOUT) executor.watchdog = watchdog // ExecuteResultHandler handler = new DefaultExecuteResultHandler(); val streamHandler = PumpStreamHandler(object : LogOutputStream() { override fun processLine(line: String, level: Int) { processStdOutLine(line, level) } }, object : LogOutputStream() { override fun processLine(line: String, level: Int) { processStdErrLine(line, level) } }) executor.streamHandler = streamHandler val msg = if (StringUtils.isNotBlank(description)) "$description ('$commandLineAsString')..." else "Executing '$commandLineAsString'..." log.info(msg) //log.info("Environment: ${environment?.entries?.joinToString { "${it.key}='${it.value}'" }}") isExecuteStarted = true val result = JobResult<String>() try { executor.execute(commandLine, environment) result.status = JobResult.Status.OK log.info("$msg Done.") } catch (ex: Exception) { result.status = JobResult.Status.ERROR if (logError && !isCancellationRequested && status != Status.CANCELLED) { log.error("Execution failed for job: '" + commandLineAsString + "': " + ex.message) log.error( "Error output of job '" + commandLineAsString + "': " + getErrorString(2000) ) } failed() } result.resultObject = outputStream.toString(Definitions.STD_CHARSET) return result } /** * @param maxlength The result string will be abbreviated (in the middle). * @return * @see StringUtils.abbreviateMiddle */ fun getErrorString(maxlength: Int): String { return StringUtils.abbreviateMiddle( errorOutputStream.toString(Definitions.STD_CHARSET), "\n [... ***** error log abbreviated ***** ...]\n", maxlength ) } open fun processStdOutLine(line: String, level: Int) { //log.info(line); try { outputStream.write(line.toByteArray()) outputStream.write("\n".toByteArray()) } catch (ex: IOException) { log.error(ex.message, ex) } } open fun processStdErrLine(line: String, level: Int) { //log.info(line); try { errorOutputStream.write(line.toByteArray()) errorOutputStream.write("\n".toByteArray()) } catch (ex: IOException) { log.error(ex.message, ex) } } override fun cancelRunningProcess() { if (watchdog != null) { log.info("Cancelling job #$uniqueJobNumber: $id") watchdog!!.destroyProcess() watchdog = null } } @get:Throws(IOException::class) protected open val environment: Map<String?, String?>? get() = null protected fun addEnvironmentVariable(env: Map<String?, String?>?, name: String, value: String) { if (StringUtils.isNotBlank(value)) { EnvironmentUtils.addVariableToEnvironment(env, "$name=$value") } } /** * @param env * @param variable Variable in format "variable=value". */ protected fun addEnvironmentVariable(env: Map<String?, String?>?, variable: String?) { if (StringUtils.isNotBlank(variable)) { EnvironmentUtils.addVariableToEnvironment(env, variable) } } /** * Frees the output streams. * Should be called after a job was done, failed or cancelled while running. */ open fun cleanUp() { log.debug("Freeing resources of job: $commandLineAsString") outputStream = ByteArrayOutputStream() errorOutputStream = ByteArrayOutputStream() } protected fun setExecuteStarted(executeStarted: Boolean): AbstractCommandLineJob { isExecuteStarted = executeStarted return this } protected fun setCommandLineAsString(commandLineAsString: String?): AbstractCommandLineJob { this.commandLineAsString = commandLineAsString return this } fun setWorkingDirectory(workingDirectory: File?): AbstractCommandLineJob { this.workingDirectory = workingDirectory return this } fun setDescription(description: String?): AbstractCommandLineJob { this.description = description return this } } borgbutler-core/src/test/java/de/micromata/borgbutler/cache/CacheTest.java
@@ -1,8 +1,8 @@ package de.micromata.borgbutler.cache; import de.micromata.borgbutler.config.BorgRepoConfig; import org.micromata.borgbutler.config.Configuration; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.Configuration; import de.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.data.Archive; import de.micromata.borgbutler.data.Repository; import de.micromata.borgbutler.json.borg.BorgFilesystemItem; borgbutler-core/src/test/java/de/micromata/borgbutler/config/ConfigHandlerTest.java
@@ -2,7 +2,7 @@ import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import java.io.File; import java.io.IOException; borgbutler-docker/app/Dockerfile
@@ -30,6 +30,6 @@ # 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"] ENTRYPOINT ["java", "-Xms4g", "-Xmx4g", "-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-server/src/main/java/de/micromata/borgbutler/server/BorgInstallation.java
@@ -1,8 +1,8 @@ package de.micromata.borgbutler.server; import de.micromata.borgbutler.BorgCommands; import org.micromata.borgbutler.config.Configuration; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.Configuration; import de.micromata.borgbutler.config.ConfigurationHandler; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; borgbutler-server/src/main/java/de/micromata/borgbutler/server/Main.java
@@ -1,7 +1,7 @@ package de.micromata.borgbutler.server; import de.micromata.borgbutler.cache.ButlerCache; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.json.borg.BorgFilesystemItem; import de.micromata.borgbutler.server.jetty.JettyServer; import de.micromata.borgbutler.server.user.SingleUserManager; borgbutler-server/src/main/java/de/micromata/borgbutler/server/ServerConfiguration.java
@@ -1,8 +1,8 @@ package de.micromata.borgbutler.server; import org.apache.commons.lang3.StringUtils; import org.micromata.borgbutler.config.Configuration; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.Configuration; import de.micromata.borgbutler.config.ConfigurationHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ArchivesRest.java
@@ -3,7 +3,7 @@ import de.micromata.borgbutler.BorgCommands; import de.micromata.borgbutler.cache.ButlerCache; import de.micromata.borgbutler.config.BorgRepoConfig; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.data.Archive; import de.micromata.borgbutler.data.DiffFileSystemFilter; import de.micromata.borgbutler.data.FileSystemFilter; borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/BorgRepoConfigsRest.java
@@ -4,7 +4,7 @@ import de.micromata.borgbutler.BorgCommands; import de.micromata.borgbutler.cache.ButlerCache; import de.micromata.borgbutler.config.BorgRepoConfig; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.data.Repository; import de.micromata.borgbutler.jobs.JobResult; import de.micromata.borgbutler.json.JsonUtils; borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ConfigurationRest.java
@@ -1,7 +1,7 @@ package de.micromata.borgbutler.server.rest; import de.micromata.borgbutler.cache.ButlerCache; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.json.JsonUtils; import de.micromata.borgbutler.server.BorgInstallation; import de.micromata.borgbutler.server.ServerConfiguration; borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/JobsRest.java
@@ -3,7 +3,7 @@ import de.micromata.borgbutler.BorgJob; import de.micromata.borgbutler.BorgQueueExecutor; import de.micromata.borgbutler.config.BorgRepoConfig; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.jobs.AbstractJob; import de.micromata.borgbutler.json.JsonUtils; import de.micromata.borgbutler.json.borg.ProgressInfo; borgbutler-server/src/test/java/de/micromata/borgbutler/server/BorgInstallationTest.java
@@ -1,6 +1,6 @@ package de.micromata.borgbutler.server; import org.micromata.borgbutler.config.ConfigurationHandler; import de.micromata.borgbutler.config.ConfigurationHandler; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory;