README.adoc
@@ -16,14 +16,14 @@ { "cache_archive_content_max_disc_size_mb" : 1000, "repo-configs" : [ { "name" : "My-Laptop-Local-Backup", "repo" : "/Volumes/backup/my-laptop-backup", "display_name" : "My-Laptop-Local-Backup", "repo" : "/Volumes/backup/my-laptop-backup", }, { "name" : "My-Laptop-Remote-Backup", "repo" : "ssh://backup.acme.com/./backups/my-laptop", "display_name" : "My-Laptop-Remote-Backup", "repo" : "ssh://backup.acme.com/./backups/my-laptop", "passwordCommand" : "security find-generic-password -a $USER -s borg-passphrase -w", "rsh" : "ssh -i /Users/horst/.ssh/acme_rsa" "rsh" : "ssh -i /Users/horst/.ssh/acme_rsa" } ] } ---- @@ -47,13 +47,14 @@ === Work with borgbutler-server ==== npm 1. `gradle npmBuild` 2. `cd borgbutler-webapp` 3. `npm install` 4. `npm start` 5. Start borgbutler-server's Main. 1. `cd borgbutler-webapp` 2. `npm install` 3. `npm start` 4. Start borgbutler-server's Main. === Ideas ==== 2 factor authentication https://github.com/j256/two-factor-auth === Install server ==== Debian borgbutler-core/src/main/java/de/micromata/borgbutler/BorgCommands.java
@@ -8,9 +8,12 @@ import de.micromata.borgbutler.json.JsonUtils; import de.micromata.borgbutler.json.borg.*; import de.micromata.borgbutler.utils.DateUtils; import org.apache.commons.exec.*; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.PumpStreamHandler; import org.apache.commons.exec.environment.EnvironmentUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,10 +29,10 @@ private static Logger log = LoggerFactory.getLogger(BorgCommands.class); /** * Executes borg info repository * Executes borg info repository. * * @param repoConfig * @return Parsed repo config returned by Borg command. * @return Parsed repo config returned by Borg command (without archives). */ public static Repository info(BorgRepoConfig repoConfig) { String json = execute(repoConfig, "info", repoConfig.getRepo(), "--json"); @@ -37,27 +40,42 @@ return null; } BorgRepoInfo repoInfo = JsonUtils.fromJson(BorgRepoInfo.class, json); repoInfo.setOriginalJson(json); Repository repository = new Repository(); BorgRepository borgRepository = repoInfo.getRepository(); repository.setId(borgRepository.getId()) Repository repository = new Repository() .setId(borgRepository.getId()) .setName(repoConfig.getRepo()) .setDisplayName(repoConfig.getDisplayName()) .setLastModified(DateUtils.get(borgRepository.getLastModified())) .setLocation(borgRepository.getLocation()) .setName(borgRepository.getName()) .setCache(repoInfo.getCache()) .setEncryption(repoInfo.getEncryption()) .setSecurityDir(repoInfo.getSecurityDir()); return repository; } public static BorgRepoList list(BorgRepoConfig repoConfig) { String json = execute(repoConfig, "list", repoConfig.getRepo(), "--json"); /** * Executes borg list repository. * The given repository will be cloned and archives will be added. * The field {@link Repository#getLastModified()} of masterRepository will be updated. * * @param masterRepository Repository without archives. * @return Parsed repo config returned by Borg command including archives. */ public static Repository list(BorgRepoConfig repoConfig, Repository masterRepository) { String json = execute(repoConfig, "list", masterRepository.getName(), "--json"); if (json == null) { log.error("Can't load archives from repo '" + masterRepository.getName() + "'."); return null; } BorgRepoList repoList = JsonUtils.fromJson(BorgRepoList.class, json); repoList.setOriginalJson(json); return repoList; if (repoList == null) { log.error("Can't load archives from repo '" + masterRepository.getName() + "'."); return null; } masterRepository.setLastModified(repoList.getRepository().getLastModified()); Repository repository = ObjectUtils.clone(masterRepository) .setArchives(repoList.getArchives()); return repository; } /** @@ -73,7 +91,6 @@ return null; } BorgArchiveInfo archiveInfo = JsonUtils.fromJson(BorgArchiveInfo.class, json); archiveInfo.setOriginalJson(json); return archiveInfo; } borgbutler-core/src/main/java/de/micromata/borgbutler/cache/ButlerCache.java
@@ -7,11 +7,9 @@ import de.micromata.borgbutler.data.Repository; import de.micromata.borgbutler.json.borg.BorgArchive; import de.micromata.borgbutler.json.borg.BorgFilesystemItem; import de.micromata.borgbutler.json.borg.BorgRepoList; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.jcs.JCS; import org.apache.commons.jcs.access.CacheAccess; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,24 +38,53 @@ */ public Repository getRepository(String idOrName) { BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(idOrName); if (repoConfig != null) { return getRepository(repoConfig); } List<Repository> repositories = getAllRepositories(); if (CollectionUtils.isNotEmpty(repositories)) { for (Repository repository : repositories) { if (StringUtils.equals(idOrName, repository.getName()) || StringUtils.equals(idOrName, repository.getId())) { return repository; } } } log.warn("Repo with id or name '" + idOrName + "' not found."); return null; } /** * @param repoConfig * @return Repository without list of archives. */ private Repository getRepository(BorgRepoConfig repoConfig) { Repository repository = repoCacheAccess.get(repoConfig.getRepo()); if (repository == null || repository.getLocation() == null) { repository = BorgCommands.info(repoConfig); repoCacheAccess.put(repoConfig.getRepo(), repository); } if (repository == null) { log.warn("Repo with name '" + idOrName + "' not found."); log.warn("Repo with name '" + repoConfig.getRepo() + "' not found."); } return repository; } private BorgRepoConfig getBorgRepoConfig(String name) { for (BorgRepoConfig repoConfig : ConfigurationHandler.getConfiguration().getRepoConfigs()) { if (StringUtils.equals(repoConfig.getRepo(), name)) return repoConfig; } log.error("Repo config with name '" + name + "' not found."); return null; } /** * @return the list of all repositories without the list of archives. */ public List<Repository> getAllRepositories() { List<Repository> repositories = new ArrayList<>(); for (BorgRepoConfig repoConfig : ConfigurationHandler.getConfiguration().getRepoConfigs()) { Repository repository = getRepository(repoConfig.getName()); Repository repository = getRepository(repoConfig); if (repository == null) { continue; } @@ -84,25 +111,16 @@ * @return The repository including all archives. */ public Repository getRepositoryArchives(String idOrName) { BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(idOrName); //ArchiveInfo archiveInfo = BorgCommands.info(repoConfig, repoConfig.getRepo()); Repository plainRepository = repoArchivesCacheAccess.get(repoConfig.getRepo()); if (plainRepository != null) { return plainRepository; } plainRepository = repoCacheAccess.get(repoConfig.getRepo()); if (plainRepository == null) { log.warn("Repo with name '" + idOrName + "' not found."); Repository masterRepository = getRepository(idOrName); if (masterRepository == null) { return null; } BorgRepoList repoList = BorgCommands.list(repoConfig); if (repoList == null) { log.warn("Repo with name '" + idOrName + "' not found."); return null; Repository repository = repoArchivesCacheAccess.get(masterRepository.getName()); if (repository != null) { return repository; } Repository repository = ObjectUtils.clone(plainRepository); repository.setArchives(repoList.getArchives()); repoArchivesCacheAccess.put(repoConfig.getRepo(), repository); repository = BorgCommands.list(getBorgRepoConfig(masterRepository.getName()), masterRepository); repoArchivesCacheAccess.put(repository.getName(), repository); return repository; } borgbutler-core/src/main/java/de/micromata/borgbutler/config/BorgRepoConfig.java
@@ -1,5 +1,6 @@ package de.micromata.borgbutler.config; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; @@ -8,7 +9,8 @@ * A name describing this config. Only used for displaying purposes. */ @Getter @Setter private String name; @JsonProperty("display_name") private String displayName; @Getter @Setter private String repo; @Getter @Setter borgbutler-core/src/main/java/de/micromata/borgbutler/config/Configuration.java
@@ -24,12 +24,12 @@ repoConfigs.add(repoConfig); } public BorgRepoConfig getRepoConfig(String idOrName) { if (idOrName == null) { public BorgRepoConfig getRepoConfig(String name) { if (name == null) { return null; } for (BorgRepoConfig repoConfig : repoConfigs) { if (idOrName.equals(repoConfig.getRepo()) ||idOrName.equals(repoConfig.getName())) { if (name.equals(repoConfig.getRepo())) { return repoConfig; } } borgbutler-core/src/main/java/de/micromata/borgbutler/data/Repository.java
@@ -16,14 +16,22 @@ public class Repository implements Serializable, Cloneable { private static final long serialVersionUID = 1278802519434516280L; /** * A name describing this config. Only used for displaying purposes. This is automatically set with the name * of the repository configuration. * The repo configured for borg. * * @see BorgRepoConfig#getName() * @see BorgRepoConfig#getRepo() */ @Getter @Setter String name; /** * A name describing this config. Only used for displaying purposes. This is automatically set with the name * of the repository configuration. * * @see BorgRepoConfig#getDisplayName() */ @Getter @Setter String displayName; @Getter @Setter private String id; borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgArchive.java
@@ -1,9 +1,7 @@ package de.micromata.borgbutler.json.borg; import com.fasterxml.jackson.annotation.JsonIgnore; import de.micromata.borgbutler.json.JsonUtils; import lombok.Getter; import lombok.Setter; import java.io.Serializable; @@ -24,10 +22,6 @@ private String start; @Getter private String time; @Getter @Setter @JsonIgnore private String originalJson; public String toString() { return JsonUtils.toJson(this, true); borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgArchiveInfo.java
@@ -1,8 +1,6 @@ package de.micromata.borgbutler.json.borg; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import java.io.Serializable; import java.util.List; @@ -20,8 +18,4 @@ private BorgEncryption encryption; @Getter private BorgRepository repository; @Getter @Setter @JsonIgnore private String originalJson; } borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgRepoInfo.java
@@ -1,9 +1,7 @@ package de.micromata.borgbutler.json.borg; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import java.io.Serializable; @@ -21,8 +19,4 @@ private BorgEncryption encryption; @Getter private BorgRepository repository; @Getter @Setter @JsonIgnore protected String originalJson; } borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgRepoList.java
@@ -1,8 +1,6 @@ package de.micromata.borgbutler.json.borg; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; import java.io.Serializable; import java.util.List; @@ -18,8 +16,4 @@ private BorgEncryption encryption; @Getter private BorgRepository repository; @Getter @Setter @JsonIgnore protected String originalJson; } borgbutler-core/src/main/java/de/micromata/borgbutler/json/borg/BorgRepository.java
@@ -1,10 +1,7 @@ package de.micromata.borgbutler.json.borg; import com.fasterxml.jackson.annotation.JsonProperty; import de.micromata.borgbutler.config.BorgRepoConfig; import de.micromata.borgbutler.config.ConfigurationHandler; import lombok.Getter; import lombok.Setter; import java.io.Serializable; @@ -13,15 +10,6 @@ */ public class BorgRepository implements Serializable { private static final long serialVersionUID = 1278802519434516280L; /** * A name describing this config. Only used for displaying purposes. This is automatically set with the name * of the repository configuration. * * @see BorgRepoConfig#getName() */ @Getter @Setter String name; @Getter private String id; @Getter @@ -29,20 +17,4 @@ private String lastModified; @Getter private String location; /** * Sets also the name for this repository if available in the configuration. * * @param location * @return */ public BorgRepository setLocation(String location) { this.location = location; // It's ugly but efficiently ;-) BorgRepoConfig repoConfig = ConfigurationHandler.getConfiguration().getRepoConfig(location); if (repoConfig != null) { this.name = repoConfig.getName(); } return this; } } borgbutler-server/src/main/java/de/micromata/borgbutler/server/rest/ReposRest.java
@@ -24,16 +24,32 @@ @Produces(MediaType.APPLICATION_JSON) /** * * @param id * @param id id or name of repo. * @param prettyPrinter If true then the json output will be in pretty format. * @return Repository (without list of archives) as json string. * @see JsonUtils#toJson(Object, boolean) */ public String getTemplate(@QueryParam("id") String id, @QueryParam("prettyPrinter") boolean prettyPrinter) { public String getRepo(@QueryParam("id") String id, @QueryParam("prettyPrinter") boolean prettyPrinter) { Repository repository = ButlerCache.getInstance().getRepository(id); return JsonUtils.toJson(repository, prettyPrinter); } @GET @Path("repoArchiveList") @Produces(MediaType.APPLICATION_JSON) /** * * @param id id or name of repo. * @param prettyPrinter If true then the json output will be in pretty format. * @return Repository (including list of archives) as json string. * @see JsonUtils#toJson(Object, boolean) */ public String getRepoArchiveList(@QueryParam("id") String id, @QueryParam("prettyPrinter") boolean prettyPrinter) { Repository repository = ButlerCache.getInstance().getRepositoryArchives(id); return JsonUtils.toJson(repository, prettyPrinter); } @GET @Path("list") @Produces(MediaType.APPLICATION_JSON) /** borgbutler-webapp/src/components/views/repos/RepoArchiveListView.jsx
File was renamed from borgbutler-webapp/src/components/views/repos/RepoArchiveList.jsx @@ -4,7 +4,7 @@ import ErrorAlert from '../../general/ErrorAlert'; import {IconRefresh} from "../../general/IconComponents"; class RepoListView extends React.Component { class RepoArchiveListView extends React.Component { path = getRestServiceUrl('repos'); @@ -74,14 +74,6 @@ > <IconRefresh/> </div> <CardDeck> {this.state.repos.map(repo => { return <RepoCard key={repo.id} repo={repo} />; })} </CardDeck> </React.Fragment>; } @@ -101,4 +93,4 @@ } } export default RepoListView; export default RepoArchiveListView; borgbutler-webapp/src/components/views/repos/RepoCard.jsx
@@ -19,7 +19,7 @@ let repoText = this.buildItem(null, content); return <React.Fragment> <Card tag={Link} to={`/repos/${repo.id}`} outline color="success" className={'repo'} <Card tag={Link} to={`/repoArchives/${repo.id}`} outline color="success" className={'repo'} style={{backgroundColor: '#fff'}}> <CardHeader>{repo.name}</CardHeader> <CardBody> borgbutler-webapp/src/containers/WebApp.jsx
@@ -6,6 +6,7 @@ import Menu from '../components/general/Menu'; import Start from '../components/views/Start'; import RepoListView from '../components/views/repos/RepoListView'; import RepoArchiveListView from '../components/views/repos/RepoArchiveListView'; import ConfigurationPage from '../components/views/config/ConfigurationPage'; import RestServices from '../components/views/develop/RestServices'; import {isDevelopmentMode} from '../utilities/global'; @@ -50,6 +51,7 @@ /> )) } <Route path={'/repoArchives/:repoId'} component={RepoArchiveListView}/> </Switch> </div> <Footer versionInfo={this.props.version}/>