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

WIP
Kai Reinhard
17.09.2021 dbc0a8433134a341f6a359234e62b744f1a9555c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package de.micromata.borgbutler.server
 
import de.micromata.borgbutler.cache.ButlerCache
import de.micromata.borgbutler.config.ConfigurationHandler
import de.micromata.borgbutler.config.ConfigurationHandler.Companion.init
import de.micromata.borgbutler.config.ConfigurationHandler.Companion.setConfigClazz
import de.micromata.borgbutler.server.user.SingleUserManager
import de.micromata.borgbutler.server.user.UserManager
import mu.KotlinLogging
import org.apache.commons.cli.*
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import org.apache.commons.lang3.StringUtils
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.event.ApplicationReadyEvent
import org.springframework.context.event.EventListener
import java.awt.Desktop
import java.io.*
import java.net.URI
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import javax.annotation.PreDestroy
 
private val log = KotlinLogging.logger {}
 
@SpringBootApplication
open class BorgButlerApplication {
 
    @Value("\${server.address}")
    private var serverAddress: String = "127.0.0.1"
 
    @Value("\${server.port}")
    private var serverPort = 9042
 
    private fun _start(args: Array<out String>) {
        setConfigClazz(ServerConfiguration::class.java)
        var borgButlerHome = System.getProperty("BorgButlerHome")
        if (borgButlerHome.isNullOrBlank()) {
            borgButlerHome = File(System.getProperty("user.home"), ".borgbutler").absolutePath
            System.setProperty("BorgButlerHome", borgButlerHome)
        }
        // Preread configuration to get setting development mode for WebConfig (logging not yet initialized, so
        // reread configuration later after logging is available for getting log information on errors etc.)
        val configuration = ConfigurationHandler.readJsonConfigfile(File(borgButlerHome))
        RunningMode.webDevelopment = RunningMode.runningInIDE || (configuration as? ServerConfiguration)?.webDevelopmentMode == true
        if (System.getProperty("LOG_PATH").isNullOrBlank()) {
            // Needed by logback-spring.xml
            System.setProperty("LOG_PATH", borgButlerHome)
        }
        SpringApplication.run(BorgButlerApplication::class.java, *args)
 
        if (borgButlerHome != null) {
            init(borgButlerHome)
        }
        // create Options object
        val options = Options()
        options.addOption(
            "e",
            "extract-archive-content",
            true,
            "Extracts the content of an archive cache file only (doesn't start the server). A complete file list of the archive will be extracted to stdout."
        )
        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");
        val parser: CommandLineParser = DefaultParser()
        try {
            // parse the command line arguments
            val line = parser.parse(options, args)
            if (line.hasOption('h')) {
                printHelp(options)
                return
            }
            if (line.hasOption('e')) {
                val file = line.getOptionValue("e")
                printArchiveContent(file)
                return
            }
            if (line.hasOption('p')) {
                // initialise the member variable
                val portString = line.getOptionValue("p")
                try {
                    val port = portString.toInt()
                    if (port < 1 || port > 65535) {
                        System.err.println("Port outside range.")
                        return
                    }
                    ServerConfiguration.get().port = port
                } catch (ex: NumberFormatException) {
                    printHelp(options)
                    return
                }
            }
            RunningMode.logMode()
 
            UserManager.setUserManager(SingleUserManager())
 
            BorgInstallation.getInstance().initialize()
 
            // 0.0.0.0 for Docker installations.
            val url = "http://$serverAddress:$serverPort/".replace("0.0.0.0", "127.0.0.1")
            val uri = URI.create(url)
            val quietMode = line.hasOption('q')
            if (!quietMode && RunningMode.desktopSupportsBrowse && RunningMode.headlessMode) {
                try {
                    log.info { "Trying to open your local web browser: $uri" }
                    Desktop.getDesktop().browse(uri)
                } catch (ex: Exception) {
                    log.info("Can't open web browser: " + ex.message, ex)
                    log.info("Desktop not available. Please open your browser manually: $uri")
                }
            } else {
                if (quietMode) {
                    log.info("Server started in quiet mode (option -q). Please open your browser manually: $uri")
                } else if (!RunningMode.desktopSupportsBrowse) {
                    log.info("Desktop not available. Please open your browser manually: $uri")
                } else if (RunningMode.headlessMode) {
                    log.info("Desktop not available in headless mode. Please open your browser manually: $uri")
                }
            }
        } catch (ex: ParseException) {
            // oops, something went wrong
            System.err.println("Parsing failed.  Reason: " + ex.message)
            printHelp(options)
        }
    }
 
    @EventListener(ApplicationReadyEvent::class)
    open fun startApp() {
    }
 
    @PreDestroy
    open fun shutdownApp() {
        log.info("Shutting down BorgButler web server...")
        ButlerCache.getInstance().shutdown()
    }
 
    companion object {
        private val main = BorgButlerApplication()
 
        @JvmStatic
        fun main(vararg args: String) {
            main._start(args)
        }
 
        private fun printHelp(options: Options) {
            val formatter = HelpFormatter()
            formatter.printHelp("borgbutler-server", options)
        }
 
        private fun printArchiveContent(fileName: String) {
            val file = File(fileName)
            val fileList = ButlerCache.getInstance().getArchiveContent(file)
            var parseFormatExceptionPrinted = false
            if (fileList != null && fileList.size > 0) {
                val tz = TimeZone.getTimeZone("UTC")
                val iso: DateFormat =
                    SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'") // Quoted "Z" to indicate UTC, no timezone offset
                iso.timeZone = tz
                val df: DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.S")
                val out = File(FilenameUtils.getBaseName(fileName) + ".txt.gz")
                log.info("Writing file list to: " + out.absolutePath)
                try {
                    PrintWriter(BufferedOutputStream(GzipCompressorOutputStream(FileOutputStream(out)))).use { writer ->
                        for (item in fileList) {
                            var time = item.mtime
                            if (time.indexOf('T') > 0) {
                                try {
                                    val date = df.parse(item.mtime)
                                    time = iso.format(date)
                                } catch (ex: java.text.ParseException) {
                                    if (!parseFormatExceptionPrinted) {
                                        parseFormatExceptionPrinted = true
                                        log.error("Can't parse date: " + item.mtime)
                                    }
                                }
                            }
                            writer.write(
                                item.mode + " " + item.user + " "
                                        + StringUtils.rightPad(FileUtils.byteCountToDisplaySize(item.size), 10)
                                        + " " + time + " " + item.path
                            )
                            writer.write("\n")
                        }
                    }
                } catch (ex: IOException) {
                    log.error("Can't write file '" + out.absolutePath + "': " + ex.message)
                }
            }
            // 2018-12-04T22:44:58.924642
        }
    }
}