docs: migrate to vitepress

This commit is contained in:
Gareth
2026-02-22 13:42:25 -08:00
parent 0199440780
commit c9a9ed2d83
45 changed files with 29532 additions and 14125 deletions
-2
View File
@@ -1,2 +0,0 @@
shamefully-hoist=true
strict-peer-dependencies=false
-33
View File
@@ -1,33 +0,0 @@
// https://github.com/nuxt-themes/docus/blob/main/nuxt.schema.ts
export default defineAppConfig({
docus: {
title: "Backrest",
description: "Backrest is a web UI and orchestrator for restic backup.",
// image: 'https://user-images.githubusercontent.com/904724/185365452-87b7ca7b-6030-4813-a2db-5e65c785bf88.png',
socials: {
github: "garethgeorge/backrest",
},
github: {
dir: "docs/content",
branch: "main",
repo: "backrest",
owner: "garethgeorge",
edit: true,
},
aside: {
level: 0,
collapsed: false,
exclude: [],
},
main: {
padded: true,
fluid: true,
},
header: {
logo: false,
showLinkIcon: true,
exclude: [],
fluid: true,
},
},
});
-73
View File
@@ -1,73 +0,0 @@
---
title: Home
navigation: false
layout: page
main:
fluid: false
---
:ellipsis{right=0px width=75% blur=150px}
::block-hero
---
cta:
- Get started
- /introduction/getting-started
secondary:
- Open on GitHub →
- https://github.com/garethgeorge/backrest
---
#title
Web UI and orchestrator for [Restic](https://restic.net) backup.
#description
Backrest is a web-accessible backup solution built on top of [restic](https://restic.net/) and providing a WebUI which wraps the restic CLI and makes it easy to create repos, browse snapshots, and restore files. Additionally, Backrest can run in the background and take an opinionated approach to scheduling snapshots and orchestrating repo health operations.
#extra
::list
- Import your existing restic repositories
- Cron scheduled backups and health operations (e.g. prune and forget)
- UI for browsing and restoring files from snapshots
- Configurable backup notifications (e.g. Discord, Slack, Shoutrrr, Gotify)
- Add shell command hooks to run before and after backup operations.
- Compatible with rclone remotes
- Cross-platform support (Linux, macOS, Windows, FreeBSD, [Docker](https://hub.docker.com/r/garethgeorge/backrest))
- Backup to any restic supported storage (e.g. S3, B2, Azure, GCS, local, SFTP, and all [rclone remotes](https://rclone.org/))
::
#support
::code-group
```bash [MacOS]
brew tap garethgeorge/homebrew-backrest-tap
brew install backrest
brew services start backrest
```
```bash [Arch Linux]
paru -Sy backrest
sudo systemctl enable --now backrest@$USER.service
```
```yaml [docker-compose]
version: "3.2"
services:
backrest:
image: garethgeorge/backrest
container_name: backrest
hostname: backrest
volumes:
- ./backrest/data:/data
- ./backrest/config:/config
- ./backrest/cache:/cache
environment:
- BACKREST_DATA=/data
- BACKREST_CONFIG=/config/config.json
- XDG_CACHE_HOME=/cache
- TZ=America/Los_Angeles
restart: unless-stopped
ports:
- 9898:9898
```
::
::
-2
View File
@@ -1,2 +0,0 @@
icon: ph:star-duotone
navigation.redirect: /introduction/getting-started
-2
View File
@@ -1,2 +0,0 @@
title: "Docs"
icon: heroicons-outline:bookmark-alt
-2
View File
@@ -1,2 +0,0 @@
title: Cookbooks
icon: material-symbols:stockpot
-11
View File
@@ -1,11 +0,0 @@
export default defineNuxtConfig({
extends: ["@nuxt-themes/docus"],
devtools: { enabled: true },
ssr: true,
app: {
baseURL: "/backrest/",
},
compatibilityDate: "2025-02-19",
});
+854 -13885
View File
File diff suppressed because it is too large Load Diff
+5 -9
View File
@@ -3,19 +3,15 @@
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "nuxi dev",
"build": "nuxi build",
"generate": "nuxi generate",
"preview": "nuxi preview",
"docs:dev": "vitepress dev src",
"docs:build": "vitepress build src",
"docs:preview": "vitepress preview src",
"lint": "eslint ."
},
"devDependencies": {
"@nuxt-themes/docus": "^1.14.8",
"@nuxt/devtools": "^1.3.1",
"@nuxt/eslint-config": "^0.3.13",
"@nuxtjs/plausible": "^1.0.0",
"@types/node": "^20.12.12",
"eslint": "^8.57.0",
"nuxt": "^3.11.2"
"vitepress": "^1.0.0",
"vue": "^3.0.0"
}
}
+275
View File
@@ -0,0 +1,275 @@
import {
useMediaQuery
} from "./chunk-3JLMNKJG.js";
import {
computed,
ref,
shallowRef,
watch
} from "./chunk-SNNOYR6U.js";
// node_modules/vitepress/dist/client/theme-default/index.js
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/base.css";
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/icons.css";
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
import "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
import VPBadge from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import Layout from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/Layout.vue";
import { default as default2 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
import { default as default3 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
import { default as default4 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
import { default as default5 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPFeatures.vue";
import { default as default6 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeContent.vue";
import { default as default7 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
import { default as default8 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
import { default as default9 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
import { default as default10 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
import { default as default11 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPLink.vue";
import { default as default12 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPNavBarSearch.vue";
import { default as default13 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPSocialLink.vue";
import { default as default14 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPSocialLinks.vue";
import { default as default15 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
import { default as default16 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
import { default as default17 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
import { default as default18 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
import { default as default19 } from "/Users/garethgeorge/Documents/github/garethgeorge/backrest/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
import { onContentUpdated } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/composables/outline.js
import { getScrollOffset } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/support/utils.js
import { withBase } from "vitepress";
// node_modules/vitepress/dist/client/theme-default/composables/data.js
import { useData as useData$ } from "vitepress";
var useData = useData$;
// node_modules/vitepress/dist/client/theme-default/support/utils.js
function ensureStartingSlash(path) {
return path.startsWith("/") ? path : `/${path}`;
}
// node_modules/vitepress/dist/client/theme-default/support/sidebar.js
function getSidebar(_sidebar, path) {
if (Array.isArray(_sidebar))
return addBase(_sidebar);
if (_sidebar == null)
return [];
path = ensureStartingSlash(path);
const dir = Object.keys(_sidebar).sort((a, b) => {
return b.split("/").length - a.split("/").length;
}).find((dir2) => {
return path.startsWith(ensureStartingSlash(dir2));
});
const sidebar = dir ? _sidebar[dir] : [];
return Array.isArray(sidebar) ? addBase(sidebar) : addBase(sidebar.items, sidebar.base);
}
function getSidebarGroups(sidebar) {
const groups = [];
let lastGroupIndex = 0;
for (const index in sidebar) {
const item = sidebar[index];
if (item.items) {
lastGroupIndex = groups.push(item);
continue;
}
if (!groups[lastGroupIndex]) {
groups.push({ items: [] });
}
groups[lastGroupIndex].items.push(item);
}
return groups;
}
function addBase(items, _base) {
return [...items].map((_item) => {
const item = { ..._item };
const base = item.base || _base;
if (base && item.link)
item.link = base + item.link;
if (item.items)
item.items = addBase(item.items, base);
return item;
});
}
// node_modules/vitepress/dist/client/theme-default/composables/sidebar.js
function useSidebar() {
const { frontmatter, page, theme: theme2 } = useData();
const is960 = useMediaQuery("(min-width: 960px)");
const isOpen = ref(false);
const _sidebar = computed(() => {
const sidebarConfig = theme2.value.sidebar;
const relativePath = page.value.relativePath;
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];
});
const sidebar = ref(_sidebar.value);
watch(_sidebar, (next, prev) => {
if (JSON.stringify(next) !== JSON.stringify(prev))
sidebar.value = _sidebar.value;
});
const hasSidebar = computed(() => {
return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.layout !== "home";
});
const leftAside = computed(() => {
if (hasAside)
return frontmatter.value.aside == null ? theme2.value.aside === "left" : frontmatter.value.aside === "left";
return false;
});
const hasAside = computed(() => {
if (frontmatter.value.layout === "home")
return false;
if (frontmatter.value.aside != null)
return !!frontmatter.value.aside;
return theme2.value.aside !== false;
});
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);
const sidebarGroups = computed(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
});
function open() {
isOpen.value = true;
}
function close() {
isOpen.value = false;
}
function toggle() {
isOpen.value ? close() : open();
}
return {
isOpen,
sidebar,
sidebarGroups,
hasSidebar,
hasAside,
leftAside,
isSidebarEnabled,
open,
close,
toggle
};
}
// node_modules/vitepress/dist/client/theme-default/composables/outline.js
var ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/;
var resolvedHeaders = [];
function getHeaders(range) {
const headers = [
...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")
].filter((el) => el.id && el.hasChildNodes()).map((el) => {
const level = Number(el.tagName[1]);
return {
element: el,
title: serializeHeader(el),
link: "#" + el.id,
level
};
});
return resolveHeaders(headers, range);
}
function serializeHeader(h) {
let ret = "";
for (const node of h.childNodes) {
if (node.nodeType === 1) {
if (ignoreRE.test(node.className))
continue;
ret += node.textContent;
} else if (node.nodeType === 3) {
ret += node.textContent;
}
}
return ret.trim();
}
function resolveHeaders(headers, range) {
if (range === false) {
return [];
}
const levelsRange = (typeof range === "object" && !Array.isArray(range) ? range.level : range) || 2;
const [high, low] = typeof levelsRange === "number" ? [levelsRange, levelsRange] : levelsRange === "deep" ? [2, 6] : levelsRange;
return buildTree(headers, high, low);
}
function buildTree(data, min, max) {
resolvedHeaders.length = 0;
const result = [];
const stack = [];
data.forEach((item) => {
const node = { ...item, children: [] };
let parent = stack[stack.length - 1];
while (parent && parent.level >= node.level) {
stack.pop();
parent = stack[stack.length - 1];
}
if (node.element.classList.contains("ignore-header") || parent && "shouldIgnore" in parent) {
stack.push({ level: node.level, shouldIgnore: true });
return;
}
if (node.level > max || node.level < min)
return;
resolvedHeaders.push({ element: node.element, link: node.link });
if (parent)
parent.children.push(node);
else
result.push(node);
stack.push(node);
});
return result;
}
// node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
function useLocalNav() {
const { theme: theme2, frontmatter } = useData();
const headers = shallowRef([]);
const hasLocalNav = computed(() => {
return headers.value.length > 0;
});
onContentUpdated(() => {
headers.value = getHeaders(frontmatter.value.outline ?? theme2.value.outline);
});
return {
headers,
hasLocalNav
};
}
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
var theme = {
Layout,
enhanceApp: ({ app }) => {
app.component("Badge", VPBadge);
}
};
var without_fonts_default = theme;
export {
default2 as VPBadge,
default3 as VPButton,
default4 as VPDocAsideSponsors,
default5 as VPFeatures,
default6 as VPHomeContent,
default7 as VPHomeFeatures,
default8 as VPHomeHero,
default9 as VPHomeSponsors,
default10 as VPImage,
default11 as VPLink,
default12 as VPNavBarSearch,
default13 as VPSocialLink,
default14 as VPSocialLinks,
default15 as VPSponsors,
default16 as VPTeamMembers,
default17 as VPTeamPage,
default18 as VPTeamPageSection,
default19 as VPTeamPageTitle,
without_fonts_default as default,
useLocalNav,
useSidebar
};
//# sourceMappingURL=@theme_index.js.map
File diff suppressed because one or more lines are too long
+40
View File
@@ -0,0 +1,40 @@
{
"hash": "c9c7cea7",
"configHash": "74231d73",
"lockfileHash": "29c09cf8",
"browserHash": "475018ec",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "1d23f116",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/vitepress/node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "c061479d",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/vitepress/node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "4d372214",
"needsInterop": false
},
"@theme/index": {
"src": "../../../../node_modules/vitepress/dist/client/theme-default/index.js",
"file": "@theme_index.js",
"fileHash": "5d21843c",
"needsInterop": false
}
},
"chunks": {
"chunk-3JLMNKJG": {
"file": "chunk-3JLMNKJG.js"
},
"chunk-SNNOYR6U": {
"file": "chunk-SNNOYR6U.js"
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,583 @@
import {
DefaultMagicKeysAliasMap,
StorageSerializers,
TransitionPresets,
assert,
breakpointsAntDesign,
breakpointsBootstrapV5,
breakpointsElement,
breakpointsMasterCss,
breakpointsPrimeFlex,
breakpointsQuasar,
breakpointsSematic,
breakpointsTailwind,
breakpointsVuetify,
breakpointsVuetifyV2,
breakpointsVuetifyV3,
bypassFilter,
camelize,
clamp,
cloneFnJSON,
computedAsync,
computedEager,
computedInject,
computedWithControl,
containsProp,
controlledRef,
createEventHook,
createFetch,
createFilterWrapper,
createGlobalState,
createInjectionState,
createRef,
createReusableTemplate,
createSharedComposable,
createSingletonPromise,
createTemplatePromise,
createUnrefFn,
customStorageEventName,
debounceFilter,
defaultDocument,
defaultLocation,
defaultNavigator,
defaultWindow,
executeTransition,
extendRef,
formatDate,
formatTimeAgo,
get,
getLifeCycleTarget,
getSSRHandler,
hasOwn,
hyphenate,
identity,
increaseWithUnit,
injectLocal,
invoke,
isClient,
isDef,
isDefined,
isIOS,
isObject,
isWorker,
makeDestructurable,
mapGamepadToXbox360Controller,
noop,
normalizeDate,
notNullish,
now,
objectEntries,
objectOmit,
objectPick,
onClickOutside,
onElementRemoval,
onKeyDown,
onKeyPressed,
onKeyStroke,
onKeyUp,
onLongPress,
onStartTyping,
pausableFilter,
promiseTimeout,
provideLocal,
provideSSRWidth,
pxValue,
rand,
reactify,
reactifyObject,
reactiveComputed,
reactiveOmit,
reactivePick,
refAutoReset,
refDebounced,
refDefault,
refThrottled,
refWithControl,
resolveRef,
resolveUnref,
set,
setSSRHandler,
syncRef,
syncRefs,
templateRef,
throttleFilter,
timestamp,
toArray,
toReactive,
toRef,
toRefs,
toValue,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
tryOnScopeDispose,
tryOnUnmounted,
unrefElement,
until,
useActiveElement,
useAnimate,
useArrayDifference,
useArrayEvery,
useArrayFilter,
useArrayFind,
useArrayFindIndex,
useArrayFindLast,
useArrayIncludes,
useArrayJoin,
useArrayMap,
useArrayReduce,
useArraySome,
useArrayUnique,
useAsyncQueue,
useAsyncState,
useBase64,
useBattery,
useBluetooth,
useBreakpoints,
useBroadcastChannel,
useBrowserLocation,
useCached,
useClipboard,
useClipboardItems,
useCloned,
useColorMode,
useConfirmDialog,
useCountdown,
useCounter,
useCssVar,
useCurrentElement,
useCycleList,
useDark,
useDateFormat,
useDebounceFn,
useDebouncedRefHistory,
useDeviceMotion,
useDeviceOrientation,
useDevicePixelRatio,
useDevicesList,
useDisplayMedia,
useDocumentVisibility,
useDraggable,
useDropZone,
useElementBounding,
useElementByPoint,
useElementHover,
useElementSize,
useElementVisibility,
useEventBus,
useEventListener,
useEventSource,
useEyeDropper,
useFavicon,
useFetch,
useFileDialog,
useFileSystemAccess,
useFocus,
useFocusWithin,
useFps,
useFullscreen,
useGamepad,
useGeolocation,
useIdle,
useImage,
useInfiniteScroll,
useIntersectionObserver,
useInterval,
useIntervalFn,
useKeyModifier,
useLastChanged,
useLocalStorage,
useMagicKeys,
useManualRefHistory,
useMediaControls,
useMediaQuery,
useMemoize,
useMemory,
useMounted,
useMouse,
useMouseInElement,
useMousePressed,
useMutationObserver,
useNavigatorLanguage,
useNetwork,
useNow,
useObjectUrl,
useOffsetPagination,
useOnline,
usePageLeave,
useParallax,
useParentElement,
usePerformanceObserver,
usePermission,
usePointer,
usePointerLock,
usePointerSwipe,
usePreferredColorScheme,
usePreferredContrast,
usePreferredDark,
usePreferredLanguages,
usePreferredReducedMotion,
usePreferredReducedTransparency,
usePrevious,
useRafFn,
useRefHistory,
useResizeObserver,
useSSRWidth,
useScreenOrientation,
useScreenSafeArea,
useScriptTag,
useScroll,
useScrollLock,
useSessionStorage,
useShare,
useSorted,
useSpeechRecognition,
useSpeechSynthesis,
useStepper,
useStorage,
useStorageAsync,
useStyleTag,
useSupported,
useSwipe,
useTemplateRefsList,
useTextDirection,
useTextSelection,
useTextareaAutosize,
useThrottleFn,
useThrottledRefHistory,
useTimeAgo,
useTimeout,
useTimeoutFn,
useTimeoutPoll,
useTimestamp,
useTitle,
useToNumber,
useToString,
useToggle,
useTransition,
useUrlSearchParams,
useUserMedia,
useVModel,
useVModels,
useVibrate,
useVirtualList,
useWakeLock,
useWebNotification,
useWebSocket,
useWebWorker,
useWebWorkerFn,
useWindowFocus,
useWindowScroll,
useWindowSize,
watchArray,
watchAtMost,
watchDebounced,
watchDeep,
watchIgnorable,
watchImmediate,
watchOnce,
watchPausable,
watchThrottled,
watchTriggerable,
watchWithFilter,
whenever
} from "./chunk-3JLMNKJG.js";
import "./chunk-SNNOYR6U.js";
export {
DefaultMagicKeysAliasMap,
StorageSerializers,
TransitionPresets,
assert,
computedAsync as asyncComputed,
refAutoReset as autoResetRef,
breakpointsAntDesign,
breakpointsBootstrapV5,
breakpointsElement,
breakpointsMasterCss,
breakpointsPrimeFlex,
breakpointsQuasar,
breakpointsSematic,
breakpointsTailwind,
breakpointsVuetify,
breakpointsVuetifyV2,
breakpointsVuetifyV3,
bypassFilter,
camelize,
clamp,
cloneFnJSON,
computedAsync,
computedEager,
computedInject,
computedWithControl,
containsProp,
computedWithControl as controlledComputed,
controlledRef,
createEventHook,
createFetch,
createFilterWrapper,
createGlobalState,
createInjectionState,
reactify as createReactiveFn,
createRef,
createReusableTemplate,
createSharedComposable,
createSingletonPromise,
createTemplatePromise,
createUnrefFn,
customStorageEventName,
debounceFilter,
refDebounced as debouncedRef,
watchDebounced as debouncedWatch,
defaultDocument,
defaultLocation,
defaultNavigator,
defaultWindow,
computedEager as eagerComputed,
executeTransition,
extendRef,
formatDate,
formatTimeAgo,
get,
getLifeCycleTarget,
getSSRHandler,
hasOwn,
hyphenate,
identity,
watchIgnorable as ignorableWatch,
increaseWithUnit,
injectLocal,
invoke,
isClient,
isDef,
isDefined,
isIOS,
isObject,
isWorker,
makeDestructurable,
mapGamepadToXbox360Controller,
noop,
normalizeDate,
notNullish,
now,
objectEntries,
objectOmit,
objectPick,
onClickOutside,
onElementRemoval,
onKeyDown,
onKeyPressed,
onKeyStroke,
onKeyUp,
onLongPress,
onStartTyping,
pausableFilter,
watchPausable as pausableWatch,
promiseTimeout,
provideLocal,
provideSSRWidth,
pxValue,
rand,
reactify,
reactifyObject,
reactiveComputed,
reactiveOmit,
reactivePick,
refAutoReset,
refDebounced,
refDefault,
refThrottled,
refWithControl,
resolveRef,
resolveUnref,
set,
setSSRHandler,
syncRef,
syncRefs,
templateRef,
throttleFilter,
refThrottled as throttledRef,
watchThrottled as throttledWatch,
timestamp,
toArray,
toReactive,
toRef,
toRefs,
toValue,
tryOnBeforeMount,
tryOnBeforeUnmount,
tryOnMounted,
tryOnScopeDispose,
tryOnUnmounted,
unrefElement,
until,
useActiveElement,
useAnimate,
useArrayDifference,
useArrayEvery,
useArrayFilter,
useArrayFind,
useArrayFindIndex,
useArrayFindLast,
useArrayIncludes,
useArrayJoin,
useArrayMap,
useArrayReduce,
useArraySome,
useArrayUnique,
useAsyncQueue,
useAsyncState,
useBase64,
useBattery,
useBluetooth,
useBreakpoints,
useBroadcastChannel,
useBrowserLocation,
useCached,
useClipboard,
useClipboardItems,
useCloned,
useColorMode,
useConfirmDialog,
useCountdown,
useCounter,
useCssVar,
useCurrentElement,
useCycleList,
useDark,
useDateFormat,
refDebounced as useDebounce,
useDebounceFn,
useDebouncedRefHistory,
useDeviceMotion,
useDeviceOrientation,
useDevicePixelRatio,
useDevicesList,
useDisplayMedia,
useDocumentVisibility,
useDraggable,
useDropZone,
useElementBounding,
useElementByPoint,
useElementHover,
useElementSize,
useElementVisibility,
useEventBus,
useEventListener,
useEventSource,
useEyeDropper,
useFavicon,
useFetch,
useFileDialog,
useFileSystemAccess,
useFocus,
useFocusWithin,
useFps,
useFullscreen,
useGamepad,
useGeolocation,
useIdle,
useImage,
useInfiniteScroll,
useIntersectionObserver,
useInterval,
useIntervalFn,
useKeyModifier,
useLastChanged,
useLocalStorage,
useMagicKeys,
useManualRefHistory,
useMediaControls,
useMediaQuery,
useMemoize,
useMemory,
useMounted,
useMouse,
useMouseInElement,
useMousePressed,
useMutationObserver,
useNavigatorLanguage,
useNetwork,
useNow,
useObjectUrl,
useOffsetPagination,
useOnline,
usePageLeave,
useParallax,
useParentElement,
usePerformanceObserver,
usePermission,
usePointer,
usePointerLock,
usePointerSwipe,
usePreferredColorScheme,
usePreferredContrast,
usePreferredDark,
usePreferredLanguages,
usePreferredReducedMotion,
usePreferredReducedTransparency,
usePrevious,
useRafFn,
useRefHistory,
useResizeObserver,
useSSRWidth,
useScreenOrientation,
useScreenSafeArea,
useScriptTag,
useScroll,
useScrollLock,
useSessionStorage,
useShare,
useSorted,
useSpeechRecognition,
useSpeechSynthesis,
useStepper,
useStorage,
useStorageAsync,
useStyleTag,
useSupported,
useSwipe,
useTemplateRefsList,
useTextDirection,
useTextSelection,
useTextareaAutosize,
refThrottled as useThrottle,
useThrottleFn,
useThrottledRefHistory,
useTimeAgo,
useTimeout,
useTimeoutFn,
useTimeoutPoll,
useTimestamp,
useTitle,
useToNumber,
useToString,
useToggle,
useTransition,
useUrlSearchParams,
useUserMedia,
useVModel,
useVModels,
useVibrate,
useVirtualList,
useWakeLock,
useWebNotification,
useWebSocket,
useWebWorker,
useWebWorkerFn,
useWindowFocus,
useWindowScroll,
useWindowSize,
watchArray,
watchAtMost,
watchDebounced,
watchDeep,
watchIgnorable,
watchImmediate,
watchOnce,
watchPausable,
watchThrottled,
watchTriggerable,
watchWithFilter,
whenever
};
//# sourceMappingURL=vitepress___@vueuse_core.js.map
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}
+347
View File
@@ -0,0 +1,347 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-SNNOYR6U.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
nodeOps,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
patchProp,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map
+7
View File
@@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}
+51
View File
@@ -0,0 +1,51 @@
import { defineConfig } from 'vitepress'
export default defineConfig({
title: "Backrest",
description: "Web UI and orchestrator for restic backup",
base: "/backrest/",
cleanUrls: true,
themeConfig: {
logo: '/logo.svg', // Assuming there's a logo or comment out if none
nav: [
{ text: 'Home', link: '/' },
{ text: 'Docs', link: '/introduction/getting-started' },
],
sidebar: [
{
text: 'Introduction',
items: [
{ text: 'Getting Started', link: '/introduction/getting-started' },
{ text: 'Restore Files', link: '/introduction/restore-files' }
]
},
{
text: 'Documentation',
items: [
{ text: 'Operations', link: '/docs/operations' },
{ text: 'Hooks', link: '/docs/hooks' },
{ text: 'API', link: '/docs/api' }
]
},
{
text: 'Cookbooks',
items: [
{ text: 'Command Hook Examples', link: '/cookbooks/command-hook-examples' },
{ text: 'Reverse Proxy Examples', link: '/cookbooks/reverse-proxy-examples' },
{ text: 'Slack Hook (Build Kit)', link: '/cookbooks/slack-hook-build-kit-examples' },
{ text: 'SSH Remote', link: '/cookbooks/ssh-remote' }
]
}
],
socialLinks: [
{ icon: 'github', link: 'https://github.com/garethgeorge/backrest' }
],
editLink: {
pattern: 'https://github.com/garethgeorge/backrest/edit/main/docs/src/:path',
text: 'Edit this page on GitHub'
}
}
})
@@ -24,7 +24,7 @@ Notify a health monitoring service (e.g., healthchecks.io) about backup status.
**Event:** `CONDITION_SNAPSHOT_END`
**Error Behavior:** `ON_ERROR_IGNORE`
```bash
```bash v-pre
#!/bin/bash
{{ if .Error -}}
curl -fsS --retry 3 https://hc-ping.com/your-uuid/fail
@@ -41,7 +41,7 @@ Display system notifications for backup events.
**Events:** `CONDITION_SNAPSHOT_END`, `CONDITION_PRUNE_ERROR`, `CONDITION_CHECK_ERROR`
**Error Behavior:** `ON_ERROR_IGNORE`
```bash
```bash v-pre
#!/bin/bash
{{ if .Error -}}
osascript -e 'display notification "{{ .ShellEscape .Task }} failed" with title "Backrest"'
@@ -135,7 +135,7 @@ Display persistent error messages requiring user acknowledgment.
**Event:** `CONDITION_ANY_ERROR`
```sh
```sh v-pre
Add-Type -AssemblyName System.Windows.Forms
$options = [System.Windows.Forms.MessageBoxOptions]::ServiceNotification
$defbutton = [System.Windows.Forms.MessageBoxDefaultButton]::Button1
@@ -151,7 +151,7 @@ Show temporary warning notifications.
**Event:** `CONDITION_SNAPSHOT_WARNING`
```sh
```sh v-pre
Add-Type -AssemblyName System.Windows.Forms
$balloon = New-Object System.Windows.Forms.NotifyIcon
$balloon.Icon = [System.Drawing.SystemIcons]::Warning
@@ -171,7 +171,7 @@ Display temporary success notifications.
**Event:** `CONDITION_SNAPSHOT_SUCCESS`
```sh
```sh v-pre
Add-Type -AssemblyName System.Windows.Forms
$balloon = New-Object System.Windows.Forms.NotifyIcon
$balloon.Icon = [System.Drawing.SystemIcons]::Information
@@ -10,7 +10,7 @@ When using the Slack Hook you can provide a simple message or a [Slack Block Kit
<img src="/screenshots/slack-clean-job.png" alt="Settings View" style="width: 700px; height: auto;" />
</div>
```
```json v-pre
{
"blocks": [
{
@@ -4,9 +4,9 @@ Backrest provides a limited HTTP API for interacting with the backrest service.
All of Backrest's API endpoints are defined as a gRPC service and are exposed over HTTP by a JSON RPC gateway for easy scripting. For the full service definition see [service.proto](https://github.com/garethgeorge/backrest/blob/main/proto/v1/service.proto).
::alert{type="warning"}
::: warning
Only the APIs documented below are considered stable, other endpoints may be subject to change.
::
:::
### Backup API
@@ -33,6 +33,6 @@ curl -X POST 'localhost:9898/v1.Backrest/GetOperations' --data '{"selector": {"p
For details on the structure of operations returned see the [operations.proto](https://github.com/garethgeorge/backrest/blob/main/proto/v1/operations.proto).
::alert{type="warning"}
::: warning
The structure of the operation history is subject to change over time. Different fields may be added or removed in future versions.
::
:::
@@ -41,7 +41,20 @@ Backrest supports multiple notification services for hook delivery:
| Slack | Send notifications to Slack channels | [Slack Webhooks Guide](https://api.slack.com/messaging/webhooks) |
| Gotify | Send notifications via Gotify server | [Gotify Documentation](https://github.com/gotify/server) |
| Shoutrrr | Multi-provider notification service | [Shoutrrr Documentation](https://containrrr.dev/shoutrrr/v0.8/) |
| Command | Execute custom commands | See [command cookbook](../../cookbooks/command-hook-examples) |
| Healthchecks | Ping Healthchecks.io monitoring URLs | [Healthchecks API](https://healthchecks.io/docs/http_api/) |
| Command | Execute custom commands | See [command cookbook](../cookbooks/command-hook-examples) |
### Healthchecks.io Integration
The Healthchecks hook type is specifically designed to integrate with [Healthchecks.io](https://healthchecks.io/) or compatible self-hosted instances.
When configured, Backrest automatically appends the correct status endpoint to your webhook URL based on the event type:
- **Start events** (e.g., `CONDITION_SNAPSHOT_START`): Appends `/start` to the URL.
- **Error events** (e.g., `CONDITION_SNAPSHOT_ERROR`): Appends `/fail` to the URL.
- **Log events**: Appends `/log` to the URL.
- **Success & Other events**: Pings the base URL directly.
It also sends the formatted template summary as the HTTP POST body in plain text, which Healthchecks.io captures as the "ping payload". This is particularly useful for reading error messages or backup statistics directly from the Healthchecks.io dashboard.
## Error Handling
@@ -59,32 +72,34 @@ Hooks use Go templates for formatting notifications and scripts. The following v
| Variable | Type | Description | Example Usage |
| --------------- | ---------------------------- | --------------------------- | --------------------------------- |
| `Event` | `v1.Hook_Condition` | The triggering event | `{{ .Event }}` |
| `Task` | `string` | Task name | `{{ .Task }}` |
| `Repo` | `v1.Repo` | Repository information | `{{ .Repo.Id }}` |
| `Plan` | `v1.Plan` | Plan information | `{{ .Plan.Id }}` |
| `SnapshotId` | `string` | ID of associated snapshot | `{{ .SnapshotId }}` |
| `Event` | `v1.Hook_Condition` | The triggering event | <code v-pre>{{ .Event }}</code> |
| `Task` | `string` | Task name | <code v-pre>{{ .Task }}</code> |
| `Repo` | `v1.Repo` | Repository information | <code v-pre>{{ .Repo.Id }}</code> |
| `Plan` | `v1.Plan` | Plan information | <code v-pre>{{ .Plan.Id }}</code> |
| `SnapshotId` | `string` | ID of associated snapshot | <code v-pre>{{ .SnapshotId }}</code> |
| `SnapshotStats` | `restic.BackupProgressEntry` | Backup operation statistics | See example below |
| `CurTime` | `time.Time` | Current timestamp | `{{ .FormatTime .CurTime }}` |
| `Duration` | `time.Duration` | Operation duration | `{{ .FormatDuration .Duration }}` |
| `Error` | `string` | Error message if applicable | `{{ .Error }}` |
| `CurTime` | `time.Time` | Current timestamp | <code v-pre>{{ .FormatTime .CurTime }}</code> |
| `Duration` | `time.Duration` | Operation duration | <code v-pre>{{ .FormatDuration .Duration }}</code> |
| `Error` | `string` | Error message if applicable | <code v-pre>{{ .Error }}</code> |
### Helper Functions
| Function | Description | Example |
| ------------------ | ------------------------------- | ----------------------------------- |
| `.Summary` | Generates default event summary | `{{ .Summary }}` |
| `.FormatTime` | Formats timestamp | `{{ .FormatTime .CurTime }}` |
| `.FormatDuration` | Formats time duration | `{{ .FormatDuration .Duration }}` |
| `.FormatSizeBytes` | Formats byte sizes | `{{ .FormatSizeBytes 1048576 }}` |
| `.ShellEscape` | Escapes strings for shell usage | `{{ .ShellEscape "my string" }}` |
| `.JsonMarshal` | Converts value to JSON | `{{ .JsonMarshal .SnapshotStats }}` |
| `.Summary` | Generates default event summary | <code v-pre>{{ .Summary }}</code> |
| `.FormatTime` | Formats timestamp | <code v-pre>{{ .FormatTime .CurTime }}</code> |
| `.FormatDuration` | Formats time duration | <code v-pre>{{ .FormatDuration .Duration }}</code> |
| `.FormatSizeBytes` | Formats byte sizes | <code v-pre>{{ .FormatSizeBytes 1048576 }}</code> |
| `.ShellEscape` | Escapes strings for shell usage | <code v-pre>{{ .ShellEscape "my string" }}</code> |
| `.JsonMarshal` | Converts value to JSON | <code v-pre>{{ .JsonMarshal .SnapshotStats }}</code> |
## Default Summary Template
Below is the implementation of the `.Summary` function, which you can use as a reference for creating custom templates:
```
<div v-pre>
```text
Task: "{{ .Task }}" at {{ .FormatTime .CurTime }}
Event: {{ .EventName .Event }}
Repo: {{ .Repo.Id }}
@@ -112,3 +127,5 @@ Backup Statistics:
- Total duration: {{ .SnapshotStats.TotalDuration }}s
{{ end }}
{{ end }}
```
</div>
@@ -1,26 +1,31 @@
# Operations Guide
This guide details the core operations available in Backrest and how to configure them effectively.
This guide details the core operations available in Backrest and how to configure them effectively. "Operations" in Backrest refer to any task that interacts with your repository, such as creating backups, managing retention, verifying integrity, or restoring files.
## Restic Integration
Backrest executes operations through the [restic](https://restic.net) backup tool. Each operation maps to specific restic commands with additional functionality provided by Backrest.
### Binary Management
- **Location**: Backrest searches for restic in:
- **Location**: Backrest searches for the restic binary in the following order:
1. Data directory (typically `~/.local/share/backrest`)
2. `/bin/` directory
- **Naming**: Binary must be named `restic` , only restic versions >= to the latest version supported by backrest will be used.
- **Auto-download**: If no binary is found, Backrest downloads a verified version from [GitHub releases](https://github.com/restic/restic/releases)
- **Verification**: Downloads are verified using SHA256 checksums signed by restic maintainers
- **Override**: Set `BACKREST_RESTIC_COMMAND` environment variable to use a custom restic binary
3. The system `$PATH`
- **Version Requirement**: Backrest is only tested against the latest version of restic. It will selectively reject outdated versions.
- **Auto-download**: If no valid binary is found, Backrest downloads a verified version from [GitHub releases](https://github.com/restic/restic/releases).
- **Verification**: Downloads are verified using SHA256 checksums signed by restic maintainers.
- **Override**: Set `BACKREST_RESTIC_COMMAND` environment variable to use a custom restic binary.
### Command Execution
- **Environment**: Repository-specific environment variables are injected
- **Flags**: Repository-configured flags are appended to commands
- **Logging**:
- Error logs: Last ~500 bytes (split between start/end if longer)
- Full logs: Available via [View Logs] in the UI, truncated to 32KB (split if longer)
- Full logs: Available via **[View Logs]** in the UI, truncated to 32KB (split if longer)
::: info
If an operation fails, you can always find the full diagnostic logs in the Backrest UI by clicking on the specific operation block in the history tree.
:::
## Scheduling System
@@ -43,7 +48,7 @@ Backrest provides flexible scheduling options for all operations through policie
| UTC | UTC timezone wall-clock | Cross-timezone coordination |
| Last Run Time | Relative to previous execution | Infrequent operations, preventing skips |
::alert{type="info"}
::: info
**Scheduling Best Practices**
- **Backup Operations** (Plan Settings):
- Hourly or more frequent: Use "Local" clock
@@ -51,7 +56,7 @@ Backrest provides flexible scheduling options for all operations through policie
- **Prune/Check Operations** (Repo Settings):
- Run infrequently (e.g., monthly)
- Use "Last Run Time" clock to prevent skips
::
:::
## Operation Types
@@ -90,9 +95,9 @@ Manages snapshot retention using `restic forget --tag plan:{PLAN_ID}`.
- **By Time Period**: `--keep-{hourly,daily,weekly,monthly,yearly} {COUNT}`
### Prune
[Restic Documentation](https://restic.readthedocs.io/en/latest/060_forget.html)
[Restic Documentation](https://restic.readthedocs.io/en/latest/060_forget.html#removing-unreferenced-data)
Removes unreferenced data using `restic prune`.
Removes unreferenced data using `restic prune`. Like Backup, Prune operations trigger their respective lifecycle hooks (e.g., `CONDITION_PRUNE_START`).
**Configuration:**
- Scheduled in repo settings
@@ -101,12 +106,12 @@ Removes unreferenced data using `restic prune`.
- Schedule timing
- Max unused percent (controls repacking threshold)
::alert{type="info"}
::: info
**Optimization Tips:**
- Run infrequently (monthly recommended)
- Use higher max unused percent (5-10%) to reduce repacking
- Consider storage costs vs. cleanup frequency
::
:::
### Check
[Restic Documentation](https://restic.readthedocs.io/en/latest/080_check.html)
@@ -120,6 +125,6 @@ Verifies repository integrity using `restic check`.
- Schedule timing
- Read data percentage
::alert{type="warning"}
::: warning
A value of 100% for *read data%* will read/download every pack file in your repository. This can be very slow and, if your provider bills for egress bandwidth, can be expensive. It is recommended to set this to 0% or a low value (e.g. 10%) for most use cases.
::
:::
+98
View File
@@ -0,0 +1,98 @@
---
layout: home
hero:
name: "Backrest"
text: "Web UI and orchestrator for Restic backup."
tagline: "Backrest is a web-accessible backup solution built on top of restic and providing a WebUI which wraps the restic CLI and makes it easy to create repos, browse snapshots, and restore files. Additionally, Backrest can run in the background and take an opinionated approach to scheduling snapshots and orchestrating repo health operations."
actions:
- theme: brand
text: Get started
link: /introduction/getting-started
- theme: alt
text: Open on GitHub
link: https://github.com/garethgeorge/backrest
features:
- title: Existing Repositories
details: Import your existing restic repositories.
- title: Cron Scheduling
details: Cron scheduled backups and health operations (e.g. prune and forget).
- title: Browse & Restore
details: UI for browsing and restoring files from snapshots.
- title: Notifications
details: Configurable backup notifications (e.g. Discord, Slack, Shoutrrr, Gotify).
- title: Command Hooks
details: Add shell command hooks to run before and after backup operations.
- title: Storage Support
details: Backup to any restic supported storage (e.g. S3, B2, Azure, GCS, local, SFTP, and all rclone remotes). Cross-platform support.
---
## Installation
::: code-group
```bash [Linux (Script)]
# Download the latest release from https://github.com/garethgeorge/backrest/releases
curl -sLO https://github.com/garethgeorge/backrest/releases/latest/download/backrest_Linux_x86_64.tar.gz
mkdir backrest && tar -xzvf backrest_Linux_x86_64.tar.gz -C backrest
cd backrest && ./install.sh
```
```bash [Linux (systemd)]
sudo mv backrest /usr/local/bin/backrest
sudo tee /etc/systemd/system/backrest.service > /dev/null <<EOT
[Unit]
Description=Backrest
After=network.target
[Service]
Type=simple
User=$(whoami)
ExecStart=/usr/local/bin/backrest
Environment="BACKREST_PORT=127.0.0.1:9898"
[Install]
WantedBy=multi-user.target
EOT
sudo systemctl enable --now backrest
```
```bash [Arch Linux]
paru -Sy backrest
sudo systemctl enable --now backrest@$USER.service
```
```bash [MacOS (Homebrew)]
brew tap garethgeorge/homebrew-backrest-tap
brew install backrest
brew services start backrest
```
```bash [MacOS (Script)]
# Download the latest release from https://github.com/garethgeorge/backrest/releases
curl -sLO https://github.com/garethgeorge/backrest/releases/latest/download/backrest_Darwin_arm64.tar.gz
mkdir backrest && tar -xzvf backrest_Darwin_arm64.tar.gz -C backrest
cd backrest && ./install.sh
```
```yaml [docker-compose]
version: "3.8"
services:
backrest:
image: ghcr.io/garethgeorge/backrest:latest
container_name: backrest
hostname: backrest
volumes:
- ./backrest/data:/data
- ./backrest/config:/config
- ./backrest/cache:/cache
- ./backrest/tmp:/tmp
- ./backrest/rclone:/root/.config/rclone # Mount for rclone config
- /path/to/backup/data:/userdata # Mount local paths to backup
- /path/to/local/repos:/repos # Mount local repos (optional)
environment:
- BACKREST_DATA=/data
- BACKREST_CONFIG=/config/config.json
- XDG_CACHE_HOME=/cache
- TMPDIR=/tmp
- TZ=America/Los_Angeles
ports:
- "9898:9898"
restart: unless-stopped
```
:::
@@ -2,24 +2,31 @@
This guide will walk you through the basic steps to setup a new [Backrest](https://github.com/garethgeorge/backrest) instance.
## Prerequisites
Before diving into configuration, you should have:
- Backrest installed and running on your system.
- Your storage provider credentials ready (if using remote storage).
- Access to Backrest via your browser (typically `http://localhost:9898`).
## Installation
Please refer to the <a href="https://github.com/garethgeorge/backrest" target="_blank">GitHub README</a> for platform-specific installation instructions.
## Core Concepts
Before diving into configuration, let's understand some key terminology:
Let's understand some key terminology used within Backrest:
- **Restic Repository**: The underlying storage location where your backup data is kept. While Backrest manages this for you, understanding this concept allows you to interact directly with your backups using the restic CLI if needed.
- **Backrest Repository**: A configuration set that defines:
- **Backrest Repository**: A configuration set in Backrest that defines:
- Where your backup data is stored
- Encryption credentials
- Backup orchestration settings
- Associated hooks and options
- **Backup Plan**: A configuration that specifies:
- What to backup
- What local data to backup
- When to create snapshots
- How long to retain backups
- When to run maintenance operations
@@ -32,109 +39,104 @@ Before diving into configuration, let's understand some key terminology:
## Initial Setup
::alert{type="info"}
::: info
After installation, access Backrest at `http://localhost:9898` (or your configured port). You'll need to complete the initial setup process below.
::
:::
### 1. Instance Configuration
<img src="/screenshots/settings-view.png" alt="Settings View" style="max-width: 100%; border-radius: 8px; margin-bottom: 20px;">
#### Instance ID
- A unique identifier for your Backrest installation
- Used to distinguish snapshots from different Backrest instances
- **Important**: Cannot be changed after initial setup
- A unique identifier for your Backrest installation.
- Used to distinguish snapshots from different Backrest instances.
- **Important**: Cannot be changed via the UI after initial setup.
#### Authentication
- Set your username and password during first launch
- To reset credentials: delete the `"users"` key from:
- Set your username and password during first launch.
- To reset credentials, delete the `"users"` key from your configuration file and **restart the Backrest service**:
- Linux/macOS: `~/.config/backrest/config.json`
- Windows: `%appdata%\backrest\config.json`
- Authentication can be disabled for local installations or when using an authenticating reverse proxy
<div style="text-align: center;">
<img src="/screenshots/settings-view.png" alt="Settings View" style="width: 700px; height: auto;" />
</div>
- Authentication can be disabled for local installations or when using an authenticating reverse proxy.
### 2. Repository Setup
Click "Add Repo" to configure your backup storage location. You can either create a new repository or connect to an existing one.
Click **"Add Repo"** to configure your backup storage location. You can either create a new repository or connect to an existing one.
<img src="/screenshots/add-repo-view.png" alt="Add Repository View" style="max-width: 100%; border-radius: 8px; margin-bottom: 20px;">
#### Essential Repository Settings
1. **Repository Name**
- A human-readable identifier
- Cannot be changed after creation
- A human-readable identifier.
- Cannot be changed after creation.
2. **Repository URI**
- Specifies the backup storage location
- Specifies the backup storage location.
- Common formats:
- Backblaze B2: `b2:bucket` or `b2:bucket/prefix`
- AWS S3: `s3:bucket` or `s3:bucket/prefix`
- Google Cloud: `gs:bucket:/` or `gs:bucket:/prefix`
- SFTP: `sftp:user@host:/path/to/repo`
- Local: `/mnt/backupdisk/repo1`
- Rclone: `rclone:remote:path` (requires rclone installation)
- Rclone: `rclone:remote:path` (requires rclone installation. See the [Rclone documentation](https://rclone.org/) to configure remote backends).
3. **Environment Variables**
Storage provider credentials:
- S3: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`
- B2: `B2_ACCOUNT_ID`, `B2_ACCOUNT_KEY`
- Google Cloud: `GOOGLE_PROJECT_ID`, `GOOGLE_APPLICATION_CREDENTIALS`
- Storage provider credentials:
- S3: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`
- B2: `B2_ACCOUNT_ID`, `B2_ACCOUNT_KEY`
- Google Cloud: `GOOGLE_PROJECT_ID`, `GOOGLE_APPLICATION_CREDENTIALS`
4. **Optional Flags**
Common examples:
- SFTP key: `-o sftp.args="-i /path/to/key"`
- Disable locking: `--no-lock`
- Bandwidth limits: `--limit-upload 1000`, `--limit-download 1000`
- Common examples:
- SFTP key: `-o sftp.args="-i /path/to/key"`
- Disable locking: `--no-lock`
- Bandwidth limits: `--limit-upload 1000`, `--limit-download 1000`
5. **Maintenance Policies**
- **Prune Policy**: Schedule for cleaning unreferenced data
- **Check Policy**: Schedule for backup integrity verification
- **Prune Policy**: Schedule for cleaning unreferenced data.
- **Check Policy**: Schedule for backup integrity verification.
::alert{type="info"}
After adding a repository, use "Index Snapshots" to import existing backups. Continue to the next section to set up your backup plan.
::
<img src="/screenshots/add-repo-view.png" alt="Add Repository View" style="width: 700px; height: auto;">
::: info
Once you've saved the repository, navigate to the Repository View and click **"Index Snapshots"** to import any previous backups. Backrest will also automatically index snapshots the first time a backup plan runs successfully.
:::
### 3. Backup Plan Configuration
Create a backup plan by clicking "Add Plan" and configuring these settings:
Create a backup plan by clicking **"Add Plan"** and configuring these settings:
<img src="/screenshots/add-plan-view.png" alt="Add Plan View" style="max-width: 100%; border-radius: 8px; margin-bottom: 20px;">
#### Plan Settings
1. **Plan Name**
- Choose a descriptive, immutable name
- Recommended format: `[storage]-[content]` (e.g., `b2-documents`)
- Choose a descriptive, immutable name.
- Recommended format: `[storage]-[content]` (e.g., `b2-documents`).
2. **Repository**
- Select your target repository
- Cannot be changed after creation
- Select your target repository.
- Cannot be changed after creation.
3. **Backup Configuration**
- **Paths**: Directories/files to backup
- **Excludes**: Patterns or paths to skip (e.g., `*node_modules*`)
- **Paths**: Directories/files to backup.
- **Excludes**: Patterns or paths to skip (e.g., `*node_modules*`).
4. **Schedule**
Choose one:
- Hourly/daily intervals
- Cron expression (e.g., `0 0 * * *` for daily midnight backups)
- Clock options:
- UTC/Local: Wall-clock time
- Last Run Time: Relative to previous execution
- Choose one:
- Hourly/daily intervals.
- Cron expression (e.g., `0 0 * * *` for daily midnight backups). We highly recommend using [crontab.guru](https://crontab.guru/) to help format your cron schedules correctly.
- Clock options:
- UTC/Local: Wall-clock time.
- Last Run Time: Relative to previous execution.
5. **Retention Policy**
Controls snapshot lifecycle:
- **Count-based**: Keep N most recent snapshots
- **Time-based**: Keep snapshots by age (e.g., daily for 7 days, weekly for 4 weeks)
- **None**: Manual retention management
- Controls snapshot lifecycle:
- **Count-based**: Keep N most recent snapshots.
- **Time-based**: Keep snapshots by age (e.g., daily for 7 days, weekly for 4 weeks).
- **None**: Manual retention management.
::alert{type="success"}
Success! Now that Backrest is configured, you can sit back and let it manage your backups. Monitor the status of your backups in the UI and restore files from snapshots as needed.
::
::alert{type="warning"}
::: warning
Make sure to save a copy of your repository credentials and encryption keys (e.g., password) in a safe place. Losing these will prevent you from restoring your data. Consider storing your entire Backrest configuration (typically `~/.config/backrest/config.json`) in a secure location, such as a password manager or encrypted storage.
::
<img src="/screenshots/add-plan-view.png" alt="Add Plan View" style="width: 700px; height: auto;">
:::
@@ -29,9 +29,9 @@ Once your snapshots are indexed, they are visible in backrest in a tree view ord
To restore a snapshot start by browsing for the files you'd like to restore. Click on the "Snapshot Browser" shown in the red box in the image above. This view will expand to show the files in your snapshot.
::alert{type="warning"}
::: warning
If your repo is using remote storage browsing can be very slow as restic fetches pack files to index the directory structure of your snapshot.
::
:::
Once you have found the directory you'd like to restore, hover over the directory and click the restore icon and select "Restore to path". The restore location options are

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

-3
View File
@@ -1,3 +0,0 @@
import { defineTheme } from "pinceau";
export default defineTheme({});
+19 -1
View File
@@ -1,3 +1,21 @@
{
"extends": "./.nuxt/tsconfig.json"
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true
},
"include": [
"src/**/*.ts",
"src/**/*.vue",
"src/**/*.mts",
"src/.vitepress/**/*.ts",
"src/.vitepress/**/*.mts",
"*.ts"
]
}