mirror of
https://github.com/garethgeorge/backrest.git
synced 2025-12-16 18:45:36 +00:00
fix: crashing bug on partial backup (#39)
This commit is contained in:
@@ -193,11 +193,9 @@ func (s *Server) GetOperationEvents(ctx context.Context, req *connect.Request[em
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := resp.Send(event); err != nil {
|
||||
errorChan <- fmt.Errorf("failed to send event: %w", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
s.oplog.Subscribe(&callback)
|
||||
defer s.oplog.Unsubscribe(&callback)
|
||||
|
||||
@@ -85,7 +85,7 @@ func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCa
|
||||
|
||||
summary, err := r.repo.Backup(ctx, progressCallback, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to backup: %w", err)
|
||||
return summary, fmt.Errorf("failed to backup: %w", err)
|
||||
}
|
||||
|
||||
r.l.Debug("Backup completed", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime)))
|
||||
@@ -115,16 +115,12 @@ func (r *RepoOrchestrator) Forget(ctx context.Context, plan *v1.Plan) ([]*v1.Res
|
||||
return nil, fmt.Errorf("plan %q has no retention policy", plan.Id)
|
||||
}
|
||||
|
||||
l := r.l.With(zap.String("plan", plan.Id))
|
||||
|
||||
l.Debug("Forget snapshots", zap.Any("policy", policy))
|
||||
result, err := r.repo.Forget(
|
||||
ctx, protoutil.RetentionPolicyFromProto(plan.Retention),
|
||||
restic.WithFlags("--tag", tagForPlan(plan)), restic.WithFlags("--group-by", "tag"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get snapshots for repo %v: %w", r.repoConfig.Id, err)
|
||||
}
|
||||
l.Debug("Forget result", zap.Any("result", result))
|
||||
|
||||
var forgotten []*v1.ResticSnapshot
|
||||
for _, snapshot := range result.Remove {
|
||||
@@ -135,6 +131,8 @@ func (r *RepoOrchestrator) Forget(ctx context.Context, plan *v1.Plan) ([]*v1.Res
|
||||
forgotten = append(forgotten, snapshotProto)
|
||||
}
|
||||
|
||||
zap.L().Debug("Forgot snapshots", zap.String("plan", plan.Id), zap.Int("count", len(forgotten)), zap.Any("policy", policy))
|
||||
|
||||
return forgotten, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ func (r *Repo) Backup(ctx context.Context, progressCallback func(*BackupProgress
|
||||
if err := cmd.Wait(); err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
if exitErr.ExitCode() == 1 {
|
||||
if exitErr.ExitCode() == 3 {
|
||||
cmdErr = ErrPartialBackup
|
||||
} else {
|
||||
cmdErr = fmt.Errorf("exit code %v: %w", exitErr.ExitCode(), ErrBackupFailed)
|
||||
@@ -151,7 +151,7 @@ func (r *Repo) Backup(ctx context.Context, progressCallback func(*BackupProgress
|
||||
wg.Wait()
|
||||
|
||||
if cmdErr != nil || readErr != nil {
|
||||
return nil, newCmdErrorPreformatted(cmd, output.String(), errors.Join(cmdErr, readErr))
|
||||
return summary, newCmdErrorPreformatted(cmd, output.String(), errors.Join(cmdErr, readErr))
|
||||
}
|
||||
|
||||
return summary, nil
|
||||
|
||||
@@ -126,7 +126,8 @@ export const OperationList = ({
|
||||
size="small"
|
||||
dataSource={backups}
|
||||
renderItem={(backup) => {
|
||||
const ops = backup.operations;
|
||||
const ops = [...backup.operations];
|
||||
ops.reverse();
|
||||
return (
|
||||
<Card size="small" style={{ margin: "5px" }}>
|
||||
{ops.map((op) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
BackupInfo,
|
||||
BackupInfoCollector,
|
||||
colorForStatus,
|
||||
displayTypeToString,
|
||||
getOperations,
|
||||
getTypeForDisplay,
|
||||
@@ -260,27 +261,9 @@ const buildTreeDay = (keyPrefix: string, operations: BackupInfo[]): OpTreeNode[]
|
||||
|
||||
const buildTreeLeaf = (operations: BackupInfo[]): OpTreeNode[] => {
|
||||
const entries = _.map(operations, (b): OpTreeNode => {
|
||||
let iconColor = "grey";
|
||||
let iconColor = colorForStatus(b.status);
|
||||
let icon: React.ReactNode | null = <QuestionOutlined />;
|
||||
|
||||
switch (b.status) {
|
||||
case OperationStatus.STATUS_PENDING:
|
||||
iconColor = "grey";
|
||||
break;
|
||||
case OperationStatus.STATUS_SUCCESS:
|
||||
iconColor = "green";
|
||||
break;
|
||||
case OperationStatus.STATUS_ERROR:
|
||||
iconColor = "red";
|
||||
break;
|
||||
case OperationStatus.STATUS_INPROGRESS:
|
||||
iconColor = "blue";
|
||||
break;
|
||||
case OperationStatus.STATUS_USER_CANCELLED:
|
||||
iconColor = "orange";
|
||||
break;
|
||||
}
|
||||
|
||||
if (b.status === OperationStatus.STATUS_ERROR) {
|
||||
icon = <ExclamationOutlined style={{ color: iconColor }} />;
|
||||
} else {
|
||||
|
||||
@@ -91,7 +91,7 @@ export class BackupInfoCollector {
|
||||
private createBackup(operations: Operation[]): BackupInfo {
|
||||
// deduplicate and sort operations.
|
||||
operations.sort((a, b) => {
|
||||
return Number(b.unixTimeStartMs - a.unixTimeStartMs);
|
||||
return Number(a.unixTimeStartMs - b.unixTimeStartMs);
|
||||
});
|
||||
|
||||
// use the lowest ID of all operations as the ID of the backup, this will be the first created operation.
|
||||
@@ -258,6 +258,26 @@ export const displayTypeToString = (type: DisplayType) => {
|
||||
return "Unknown";
|
||||
}
|
||||
};
|
||||
|
||||
export const colorForStatus = (status: OperationStatus) => {
|
||||
switch (status) {
|
||||
case OperationStatus.STATUS_PENDING:
|
||||
return "grey";
|
||||
case OperationStatus.STATUS_INPROGRESS:
|
||||
return "blue";
|
||||
case OperationStatus.STATUS_ERROR:
|
||||
return "red";
|
||||
case OperationStatus.STATUS_WARNING:
|
||||
return "orange";
|
||||
case OperationStatus.STATUS_SUCCESS:
|
||||
return "green";
|
||||
case OperationStatus.STATUS_USER_CANCELLED:
|
||||
return "orange";
|
||||
default:
|
||||
return "grey";
|
||||
}
|
||||
}
|
||||
|
||||
// detailsForOperation returns derived display information for a given operation.
|
||||
export const detailsForOperation = (
|
||||
op: Operation
|
||||
|
||||
@@ -49,7 +49,6 @@ export const AddPlanModal = ({
|
||||
|
||||
try {
|
||||
let config = await fetchConfig();
|
||||
config.plans = config.plans || [];
|
||||
|
||||
if (!template) {
|
||||
throw new Error("template not found");
|
||||
@@ -82,10 +81,9 @@ export const AddPlanModal = ({
|
||||
setConfirmLoading(true);
|
||||
|
||||
try {
|
||||
let plan = await validateForm<Plan>(form);
|
||||
let plan = new Plan(await validateForm<Plan>(form));
|
||||
|
||||
let config = await fetchConfig();
|
||||
config.plans = config.plans || [];
|
||||
|
||||
// Merge the new plan (or update) into the config
|
||||
if (template) {
|
||||
@@ -103,6 +101,7 @@ export const AddPlanModal = ({
|
||||
showModal(null);
|
||||
} catch (e: any) {
|
||||
alertsApi.error("Operation failed: " + e.message, 15);
|
||||
console.error(e);
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user