mirror of
https://github.com/Ravinou/borgwarehouse
synced 2026-03-15 14:55:45 +01:00
Compare commits
No commits in common. "main" and "v3.1.1" have entirely different histories.
41 changed files with 8600 additions and 6126 deletions
|
|
@ -1,4 +1,4 @@
|
|||
const config = {
|
||||
export default {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
'type-enum': [
|
||||
|
|
@ -26,5 +26,3 @@ const config = {
|
|||
},
|
||||
ignores: [(message) => message.includes('WIP'), (message) => message.includes('wip')],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ CONFIG_PATH=./config
|
|||
SSH_PATH=./ssh
|
||||
SSH_HOST=./ssh_host
|
||||
BORG_REPOSITORY_PATH=./repos
|
||||
TMP_PATH=./tmp
|
||||
LOGS_PATH=./logs
|
||||
|
||||
## Optional variables section ##
|
||||
|
||||
|
|
|
|||
6
.eslintrc.json
Normal file
6
.eslintrc.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"next/typescript"
|
||||
]
|
||||
}
|
||||
20
.github/dependabot.yml
vendored
20
.github/dependabot.yml
vendored
|
|
@ -1,18 +1,16 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'docker'
|
||||
directory: '/'
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
# Note: Dependabot uses "npm" ecosystem but automatically detects pnpm-lock.yaml
|
||||
# Make sure package-lock.json is gitignored to prevent confusion
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
interval: "daily"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: "daily"
|
||||
# Maintain dependencies for GitHub Actions
|
||||
# src: https://github.com/marketplace/actions/build-and-push-docker-images#keep-up-to-date-with-github-dependabot
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: 'daily'
|
||||
interval: "daily"
|
||||
|
|
|
|||
5
.github/workflows/bats.yml
vendored
5
.github/workflows/bats.yml
vendored
|
|
@ -1,8 +1,5 @@
|
|||
name: Bats
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
|
|
@ -19,7 +16,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
|
|
|||
5
.github/workflows/docker-image-develop.yml
vendored
5
.github/workflows/docker-image-develop.yml
vendored
|
|
@ -5,15 +5,12 @@ on:
|
|||
branches:
|
||||
- 'develop'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Update package.json version
|
||||
run: |
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
|
|
|
|||
4
.github/workflows/docker-image-latest.yml
vendored
4
.github/workflows/docker-image-latest.yml
vendored
|
|
@ -1,6 +1,4 @@
|
|||
name: Build and Push Docker Image
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -12,7 +10,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
|
|
|
|||
5
.github/workflows/docker-image-release.yml
vendored
5
.github/workflows/docker-image-release.yml
vendored
|
|
@ -5,15 +5,12 @@ on:
|
|||
types:
|
||||
- published
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
|
|
|
|||
5
.github/workflows/docker-image-test.yml
vendored
5
.github/workflows/docker-image-test.yml
vendored
|
|
@ -1,8 +1,5 @@
|
|||
name: Test to build docker container on Pull Request
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
|
|
@ -14,7 +11,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
|
|
|
|||
2
.github/workflows/shellcheck.yml
vendored
2
.github/workflows/shellcheck.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Run ShellCheck
|
||||
uses: ludeeus/action-shellcheck@master
|
||||
|
|
|
|||
30
.github/workflows/vitest.yml
vendored
30
.github/workflows/vitest.yml
vendored
|
|
@ -9,10 +9,6 @@ on:
|
|||
branches:
|
||||
- main
|
||||
- develop
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run Vitest
|
||||
|
|
@ -20,23 +16,18 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Run Vitest
|
||||
run: pnpm run test
|
||||
run: npm run test
|
||||
|
||||
lint:
|
||||
name: Run ESLint
|
||||
|
|
@ -44,20 +35,15 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: npm ci
|
||||
|
||||
- name: Run ESLint
|
||||
run: pnpm exec eslint
|
||||
run: npm run lint
|
||||
|
|
|
|||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -50,14 +50,6 @@ typings/
|
|||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# pnpm
|
||||
.pnpm-store/
|
||||
pnpm-debug.log*
|
||||
|
||||
# Lock files (pnpm-lock.yaml is used)
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
|
|
|
|||
7
.npmrc
7
.npmrc
|
|
@ -1,7 +0,0 @@
|
|||
# Configuration pnpm
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=false
|
||||
shamefully-hoist=false
|
||||
|
||||
# Force pnpm usage (prevent npm/yarn)
|
||||
package-manager=pnpm
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useMemo } from 'react';
|
||||
import { useState } from 'react';
|
||||
import classes from './Repo.module.css';
|
||||
import {
|
||||
IconSettings,
|
||||
|
|
@ -22,8 +22,6 @@ type RepoProps = Omit<Repository, 'unixUser' | 'displayDetails'> & {
|
|||
export default function Repo(props: RepoProps) {
|
||||
const isMobile = useMedia({ maxWidth: 1000 });
|
||||
|
||||
const currentDate = useMemo(() => new Date(), []);
|
||||
|
||||
//Load displayDetails from LocalStorage
|
||||
const displayDetailsFromLS = (): boolean => {
|
||||
const key = `displayDetailsRepo${props.id}`;
|
||||
|
|
@ -112,7 +110,7 @@ export default function Repo(props: RepoProps) {
|
|||
>
|
||||
{props.lastSave === 0
|
||||
? '-'
|
||||
: formatDistanceStrict(fromUnixTime(props.lastSave), currentDate, {
|
||||
: formatDistanceStrict(fromUnixTime(props.lastSave), Date.now(), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
|
|
@ -179,7 +177,7 @@ export default function Repo(props: RepoProps) {
|
|||
>
|
||||
{props.lastSave === 0
|
||||
? '-'
|
||||
: formatDistanceStrict(fromUnixTime(props.lastSave), currentDate, {
|
||||
: formatDistanceStrict(fromUnixTime(props.lastSave), Date.now(), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</div>
|
||||
|
|
@ -225,7 +223,7 @@ export default function Repo(props: RepoProps) {
|
|||
>
|
||||
{props.lastSave === 0
|
||||
? '-'
|
||||
: formatDistanceStrict(fromUnixTime(props.lastSave), currentDate, {
|
||||
: formatDistanceStrict(fromUnixTime(props.lastSave), Date.now(), {
|
||||
addSuffix: true,
|
||||
})}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,15 @@ function WizardStep1() {
|
|||
Vorta
|
||||
</a>
|
||||
.
|
||||
<br />
|
||||
Vorta runs on Linux, MacOS and Windows (via Windows’ Linux Subsystem (WSL)). Find the right
|
||||
way to install it{' '}
|
||||
<a href='https://vorta.borgbase.com/install/' target='_blank' rel='noreferrer'>
|
||||
just here
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
<img src='/vorta-demo.gif' alt='Vorta GIF' />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,8 +69,8 @@ function WizardStep2(props: WizardStepProps) {
|
|||
|
||||
<h2>Pika, Vorta...</h2>
|
||||
<div className={classes.description}>
|
||||
To "Initialize a new repository" or "Add existing repository", copy this
|
||||
into the field "Repository URL" of your graphical client :
|
||||
To "Initialize a new repository" or "Add existing repository", copy this into the field
|
||||
"Repository URL" of your graphical client :
|
||||
<br />
|
||||
<div
|
||||
style={{
|
||||
|
|
@ -98,7 +98,7 @@ function WizardStep2(props: WizardStepProps) {
|
|||
<b>Check the fingerprint of server</b>
|
||||
</div>
|
||||
To check that you are talking to the right server, please make sure to validate one of the
|
||||
following key's fingerprint when you first connect :
|
||||
following key's fingerprint when you first connect :
|
||||
<li>
|
||||
<span className={classes.sshPublicKey}>
|
||||
ECDSA : {wizardEnv?.SSH_SERVER_FINGERPRINT_ECDSA}
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ function WizardStep3(props: WizardStepProps) {
|
|||
}}
|
||||
>
|
||||
<div className={classes.code}>
|
||||
borg export-tar --tar-filter="gzip -9" ssh://
|
||||
borg export-tar --tar-filter="gzip -9" ssh://
|
||||
{UNIX_USER}@{FQDN}
|
||||
{SSH_SERVER_PORT}/./
|
||||
{props.selectedRepo?.repositoryName}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function WizardStep4(props: WizardStepProps) {
|
|||
//Needed to generate command for borg over LAN instead of WAN if env vars are set and option enabled.
|
||||
const { FQDN, SSH_SERVER_PORT } = lanCommandOption(wizardEnv, props.selectedRepo?.lanCommand);
|
||||
|
||||
const configBorgmatic = `
|
||||
const configBorgmatic = `location:
|
||||
# List of source directories to backup.
|
||||
source_directories:
|
||||
- /your-repo-to-backup
|
||||
|
|
@ -19,21 +19,24 @@ function WizardStep4(props: WizardStepProps) {
|
|||
|
||||
repositories:
|
||||
# Paths of local or remote repositories to backup to.
|
||||
- path: ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}
|
||||
- ssh://${UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.selectedRepo?.repositoryName}
|
||||
|
||||
archive_name_format: '{FQDN}-documents-{now}'
|
||||
encryption_passphrase: "YOUR PASSPHRASE"
|
||||
storage:
|
||||
archive_name_format: '{FQDN}-documents-{now}'
|
||||
encryption_passphrase: "YOUR PASSPHRASE"
|
||||
|
||||
# Retention policy for how many backups to keep.
|
||||
keep_daily: 7
|
||||
keep_weekly: 4
|
||||
keep_monthly: 6
|
||||
retention:
|
||||
# Retention policy for how many backups to keep.
|
||||
keep_daily: 7
|
||||
keep_weekly: 4
|
||||
keep_monthly: 6
|
||||
|
||||
# List of checks to run to validate your backups.
|
||||
checks:
|
||||
- name: repository
|
||||
- name: archives
|
||||
frequency: 2 weeks
|
||||
consistency:
|
||||
# List of checks to run to validate your backups.
|
||||
checks:
|
||||
- name: repository
|
||||
- name: archives
|
||||
frequency: 2 weeks
|
||||
|
||||
#hooks:
|
||||
# Custom preparation scripts to run.
|
||||
|
|
|
|||
|
|
@ -42,16 +42,8 @@ export default function RepoList() {
|
|||
const [displayRepoAdd, setDisplayRepoAdd] = useState(false);
|
||||
const [displayRepoEdit, setDisplayRepoEdit] = useState(false);
|
||||
const [wizardEnv, setWizardEnv] = useState<Optional<WizardEnvType>>();
|
||||
|
||||
const [sortOption, setSortOption] = useState<SortOption>(() => {
|
||||
const savedSort = localStorage.getItem('repoSort');
|
||||
return (savedSort as SortOption) || 'alias-asc';
|
||||
});
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState(() => {
|
||||
const savedSearch = localStorage.getItem('repoSearch');
|
||||
return savedSearch || '';
|
||||
});
|
||||
const [sortOption, setSortOption] = useState<SortOption>('alias-asc');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const toastOptions: ToastOptions = {
|
||||
position: 'top-right',
|
||||
|
|
@ -63,10 +55,21 @@ export default function RepoList() {
|
|||
progress: undefined,
|
||||
};
|
||||
|
||||
// Load filters from localStorage
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setDisplayRepoAdd(router.pathname === '/manage-repo/add');
|
||||
setDisplayRepoEdit(router.pathname.startsWith('/manage-repo/edit'));
|
||||
const savedSort = localStorage.getItem('repoSort');
|
||||
const savedSearch = localStorage.getItem('repoSearch');
|
||||
if (savedSort) setSortOption(savedSort as SortOption);
|
||||
if (savedSearch) setSearchQuery(savedSearch);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.pathname === '/manage-repo/add') {
|
||||
setDisplayRepoAdd(!displayRepoAdd);
|
||||
}
|
||||
if (router.pathname.startsWith('/manage-repo/edit')) {
|
||||
setDisplayRepoEdit(!displayRepoEdit);
|
||||
}
|
||||
|
||||
const fetchWizardEnv = async () => {
|
||||
try {
|
||||
|
|
@ -78,7 +81,7 @@ export default function RepoList() {
|
|||
}
|
||||
};
|
||||
fetchWizardEnv();
|
||||
}, [router.pathname]);
|
||||
}, []);
|
||||
|
||||
const fetcher = async (url: string) => await fetch(url).then((res) => res.json());
|
||||
const { data, error } = useSWR('/api/v1/repositories', fetcher);
|
||||
|
|
|
|||
|
|
@ -150,12 +150,8 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
const formSubmitHandler = async (dataForm: DataForm) => {
|
||||
setIsLoading(true);
|
||||
start();
|
||||
|
||||
// Clean SSH key by removing leading/trailing whitespace and line breaks
|
||||
const cleanedSSHKey = dataForm.sshkey.trim();
|
||||
|
||||
//Verify that the SSH key is unique
|
||||
if (!(await isSSHKeyUnique(cleanedSSHKey))) {
|
||||
if (!(await isSSHKeyUnique(dataForm.sshkey))) {
|
||||
stop();
|
||||
setIsLoading(false);
|
||||
return;
|
||||
|
|
@ -165,7 +161,7 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
const newRepo = {
|
||||
alias: dataForm.alias,
|
||||
storageSize: parseInt(dataForm.storageSize),
|
||||
sshPublicKey: cleanedSSHKey,
|
||||
sshPublicKey: dataForm.sshkey,
|
||||
comment: dataForm.comment,
|
||||
alert: dataForm.alert.value,
|
||||
lanCommand: dataForm.lanCommand,
|
||||
|
|
@ -204,7 +200,7 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
const dataEdited = {
|
||||
alias: dataForm.alias,
|
||||
storageSize: parseInt(dataForm.storageSize),
|
||||
sshPublicKey: cleanedSSHKey,
|
||||
sshPublicKey: dataForm.sshkey,
|
||||
comment: dataForm.comment,
|
||||
alert: dataForm.alert.value,
|
||||
lanCommand: dataForm.lanCommand,
|
||||
|
|
@ -337,14 +333,11 @@ export default function RepoManage(props: RepoManageProps) {
|
|||
defaultValue={props.mode == 'edit' ? targetRepo?.sshPublicKey : undefined}
|
||||
{...register('sshkey', {
|
||||
required: 'SSH public key is required.',
|
||||
validate: (value) => {
|
||||
const trimmedValue = value.trim();
|
||||
const pattern =
|
||||
/^(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?$/;
|
||||
return (
|
||||
pattern.test(trimmedValue) ||
|
||||
'Invalid public key. The key needs to be in OpenSSH format (rsa, ed25519, ed25519-sk)'
|
||||
);
|
||||
pattern: {
|
||||
value:
|
||||
/^(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?$/,
|
||||
message:
|
||||
'Invalid public key. The key needs to be in OpenSSH format (rsa, ed25519, ed25519-sk)',
|
||||
},
|
||||
})}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Select, { SingleValue } from 'react-select';
|
||||
import classes from './SetupWizard.module.css';
|
||||
import { Optional, SelectedRepoWizard, Repository, WizardEnvType } from '~/types';
|
||||
|
|
@ -36,21 +36,8 @@ function SetupWizard(props: SetupWizardProps) {
|
|||
'Content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
const repos = data.repoList;
|
||||
setRepoList(repos);
|
||||
setRepoList((await response.json()).repoList);
|
||||
setRepoListIsLoading(false);
|
||||
|
||||
// Auto-select first repository if available
|
||||
if (repos && repos.length > 0) {
|
||||
setSelectedItem({
|
||||
label: `${repos[0].alias} - ${repos[0].repositoryName}`,
|
||||
value: `${repos[0].alias} - ${repos[0].repositoryName}`,
|
||||
id: repos[0].id.toString(),
|
||||
repositoryName: repos[0].repositoryName,
|
||||
lanCommand: repos[0].lanCommand ? repos[0].lanCommand : false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Fetching datas error');
|
||||
}
|
||||
|
|
@ -76,24 +63,17 @@ function SetupWizard(props: SetupWizardProps) {
|
|||
//Component did update
|
||||
useEffect(() => {
|
||||
//Go to the step in the URL param when URL change
|
||||
if (props.step) {
|
||||
// eslint-disable-next-line react-hooks/set-state-in-effect
|
||||
setStep(props.step);
|
||||
}
|
||||
props.step && setStep(props.step);
|
||||
}, [props.step]);
|
||||
|
||||
//Options for react-select
|
||||
const options: Optional<Array<SelectedRepoWizard>> = useMemo(
|
||||
() =>
|
||||
repoList?.map((repo) => ({
|
||||
label: `${repo.alias} - ${repo.repositoryName}`,
|
||||
value: `${repo.alias} - ${repo.repositoryName}`,
|
||||
id: repo.id.toString(),
|
||||
repositoryName: repo.repositoryName,
|
||||
lanCommand: repo.lanCommand ? repo.lanCommand : false,
|
||||
})),
|
||||
[repoList]
|
||||
);
|
||||
const options: Optional<Array<SelectedRepoWizard>> = repoList?.map((repo) => ({
|
||||
label: `${repo.alias} - ${repo.repositoryName}`,
|
||||
value: `${repo.alias} - ${repo.repositoryName}`,
|
||||
id: repo.id.toString(),
|
||||
repositoryName: repo.repositoryName,
|
||||
lanCommand: repo.lanCommand ? repo.lanCommand : false,
|
||||
}));
|
||||
|
||||
//Step button (free selection of user)
|
||||
const changeStepHandler = (x: number) => router.push('/setup-wizard/' + x.toString());
|
||||
|
|
@ -148,7 +128,6 @@ function SetupWizard(props: SetupWizardProps) {
|
|||
isDisabled={repoListIsLoading}
|
||||
options={options}
|
||||
isSearchable
|
||||
value={selectedItem}
|
||||
placeholder='Select your repository...'
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
|
|
|
|||
|
|
@ -58,7 +58,6 @@ export default function AppriseAlertSettings() {
|
|||
}
|
||||
};
|
||||
getAppriseAlert();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
////Functions
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ export default function AppriseURLs() {
|
|||
}
|
||||
};
|
||||
getAppriseServices();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//Form submit handler to modify Apprise services
|
||||
|
|
|
|||
|
|
@ -56,7 +56,6 @@ export default function EmailAlertSettings() {
|
|||
}
|
||||
};
|
||||
dataFetch();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
////Functions
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ export default function Integrations() {
|
|||
////LifeCycle
|
||||
useEffect(() => {
|
||||
fetchTokenList();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Permissions handler
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ export default function UserSettings({ data }: UserSettingsProps) {
|
|||
if (tab === 'Integrations' && wizardEnv?.DISABLE_INTEGRATIONS === 'true') {
|
||||
setTab('General');
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [wizardEnv?.DISABLE_INTEGRATIONS]);
|
||||
}, [wizardEnv, tab]);
|
||||
|
||||
return (
|
||||
<div className={classes.containerSettings}>
|
||||
|
|
|
|||
17
Dockerfile
17
Dockerfile
|
|
@ -6,29 +6,23 @@ FROM node:22-bookworm-slim as base
|
|||
# build stage
|
||||
FROM base AS deps
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@9 --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
RUN pnpm install --frozen-lockfile --prod
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
RUN corepack enable && corepack prepare pnpm@9 --activate
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN sed -i "s/images:/output: 'standalone',images:/" next.config.ts
|
||||
|
||||
RUN pnpm run build
|
||||
RUN npm run build
|
||||
|
||||
# run stage
|
||||
FROM base AS runner
|
||||
|
|
@ -41,7 +35,7 @@ ENV HOSTNAME=
|
|||
|
||||
RUN echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list
|
||||
RUN apt-get update && apt-get install -y \
|
||||
supervisor curl jq jc borgbackup/bookworm-backports openssh-server && \
|
||||
supervisor curl jq jc borgbackup/bookworm-backports openssh-server rsyslog && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd -g ${GID} borgwarehouse && useradd -m -u ${UID} -g ${GID} borgwarehouse
|
||||
|
|
@ -56,6 +50,7 @@ COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/.next/standalone ./
|
|||
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/public ./public
|
||||
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker/supervisord.conf ./
|
||||
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker/rsyslog.conf /etc/rsyslog.conf
|
||||
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker/sshd_config ./
|
||||
|
||||
USER borgwarehouse
|
||||
|
|
|
|||
|
|
@ -79,13 +79,9 @@ Check the online documentation [just here](https://borgwarehouse.com/docs/admin-
|
|||
<a href="https://github.com/royalmoose"><img src="https://avatars.githubusercontent.com/royalmoose" style="width:50px; border-radius:50%;"/></a>
|
||||
<a href="https://github.com/dhenry123"><img src="https://avatars.githubusercontent.com/dhenry123" style="width:50px; border-radius:50%;"/></a>
|
||||
<a href="https://github.com/fphammerle"><img src="https://avatars.githubusercontent.com/fphammerle" style="width:50px; border-radius:50%;"/></a>
|
||||
<a href="https://github.com/MacH59-cos"><img src="https://avatars.githubusercontent.com/MacH59-cos" style="width:50px; border-radius:50%;"/></a>
|
||||
<a href="https://github.com/shrippen"><img src="https://avatars.githubusercontent.com/shrippen" style="width:50px; border-radius:50%;"/></a>
|
||||
<a href="https://github.com/daschmidt1994"><img src="https://avatars.githubusercontent.com/daschmidt1994" style="width:50px; border-radius:50%;"/></a>
|
||||
|
||||
#### Past sponsors
|
||||
|
||||
<a href="https://github.com/Drallibor"><img src="https://avatars.githubusercontent.com/Drallibor" style="width:25px; border-radius:50%;"/></a>
|
||||
<a href="https://github.com/shad-lp"><img src="https://avatars.githubusercontent.com/shad-lp" style="width:25px; border-radius:50%;"/></a>
|
||||
<a href="https://github.com/Magneticdud"><img src="https://avatars.githubusercontent.com/Magneticdud" style="width:25px; border-radius:50%;"/></a>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ services:
|
|||
- ${SSH_PATH:?SSH_PATH variable missing}:/home/borgwarehouse/.ssh
|
||||
- ${SSH_HOST:?SSH_HOST variable missing}:/etc/ssh
|
||||
- ${BORG_REPOSITORY_PATH:?BORG_REPOSITORY_PATH variable missing}:/home/borgwarehouse/repos
|
||||
- ${TMP_PATH:?TMP_PATH variable missing}:/home/borgwarehouse/tmp
|
||||
- ${LOGS_PATH:?LOGS_PATH variable missing}:/home/borgwarehouse/logs
|
||||
# Apprise is used to send notifications, it's optional. http://apprise:8000 is the URL to use in BorgWarehouse.
|
||||
apprise:
|
||||
container_name: apprise
|
||||
|
|
|
|||
40
docker/rsyslog.conf
Normal file
40
docker/rsyslog.conf
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# rsyslog configuration file
|
||||
|
||||
$WorkDirectory /home/borgwarehouse/tmp
|
||||
$FileOwner borgwarehouse
|
||||
$FileGroup borgwarehouse
|
||||
$FileCreateMode 0640
|
||||
$DirCreateMode 0755
|
||||
$Umask 0022
|
||||
|
||||
$RepeatedMsgReduction on
|
||||
|
||||
module(load="imfile" PollingInterval="10")
|
||||
|
||||
input(type="imfile"
|
||||
File="/home/borgwarehouse/tmp/borgwarehouse.log"
|
||||
Tag="BorgWarehouse"
|
||||
Severity="info"
|
||||
Facility="local7"
|
||||
ruleset="bwLogs")
|
||||
|
||||
input(type="imfile"
|
||||
File="/home/borgwarehouse/tmp/sshd.log"
|
||||
Tag="sshd"
|
||||
Severity="info"
|
||||
Facility="local7"
|
||||
ruleset="sshdLogs")
|
||||
|
||||
$template myFormat,"%timegenerated:::date-rfc3339% %syslogtag% %msg%\n"
|
||||
|
||||
ruleset(name="bwLogs") {
|
||||
action(type="omfile"
|
||||
File="/home/borgwarehouse/logs/borgwarehouse.log"
|
||||
Template="myFormat")
|
||||
}
|
||||
|
||||
ruleset(name="sshdLogs") {
|
||||
action(type="omfile"
|
||||
File="/home/borgwarehouse/logs/sshd.log"
|
||||
Template="myFormat")
|
||||
}
|
||||
|
|
@ -1,21 +1,24 @@
|
|||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/dev/stdout
|
||||
logfile_maxbytes=0
|
||||
logfile=/home/borgwarehouse/logs/supervisord.log
|
||||
loglevel=error
|
||||
pidfile=/home/borgwarehouse/tmp/supervisord.pid
|
||||
logfile_maxbytes=10MB
|
||||
logfile_backups=5
|
||||
|
||||
[program:sshd]
|
||||
command=/usr/sbin/sshd -D -e -f /etc/ssh/sshd_config
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
redirect_stderr=false
|
||||
stdout_logfile=/home/borgwarehouse/tmp/sshd.log
|
||||
stdout_logfile_maxbytes=10MB
|
||||
stdout_logfile_backups=5
|
||||
redirect_stderr=true
|
||||
|
||||
[program:borgwarehouse]
|
||||
command=/usr/local/bin/node server.js
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
redirect_stderr=false
|
||||
stdout_logfile=/home/borgwarehouse/tmp/borgwarehouse.log
|
||||
stdout_logfile_maxbytes=10MB
|
||||
stdout_logfile_backups=5
|
||||
redirect_stderr=true
|
||||
|
||||
[program:rsyslogd]
|
||||
command=rsyslogd -n -i /home/borgwarehouse/tmp/rsyslogd.pid -f /etc/rsyslog.conf
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||
import nextVitals from 'eslint-config-next/core-web-vitals';
|
||||
|
||||
const eslintConfig = defineConfig([
|
||||
...nextVitals,
|
||||
// Override default ignores of eslint-config-next.
|
||||
globalIgnores([
|
||||
// Default ignores of eslint-config-next:
|
||||
'.next/**',
|
||||
'out/**',
|
||||
'build/**',
|
||||
'next-env.d.ts',
|
||||
]),
|
||||
]);
|
||||
|
||||
export default eslintConfig;
|
||||
|
|
@ -77,7 +77,7 @@ else
|
|||
fi
|
||||
|
||||
## Add ssh public key in authorized_keys with borg restriction for only 1 repository and storage quota
|
||||
restricted_authkeys="command=\"cd ${pool};borg serve${appendOnlyMode} --restrict-to-repository ${pool}/${repositoryName} --storage-quota $2G\",restrict $1"
|
||||
restricted_authkeys="command=\"cd ${pool};borg serve${appendOnlyMode} --restrict-to-path ${pool}/${repositoryName} --storage-quota $2G\",restrict $1"
|
||||
echo "$restricted_authkeys" | tee -a "${authorized_keys}" >/dev/null
|
||||
|
||||
## Return the repositoryName
|
||||
|
|
|
|||
1
next-env.d.ts
vendored
1
next-env.d.ts
vendored
|
|
@ -1,6 +1,5 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
|
|
|
|||
8396
package-lock.json
generated
Normal file
8396
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
56
package.json
56
package.json
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"name": "borgwarehouse",
|
||||
"version": "3.1.2",
|
||||
"version": "3.1.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "pnpm exec eslint",
|
||||
"lint": "next lint",
|
||||
"test": "vitest",
|
||||
"setup": "pnpm install && pnpm run setup:hooks",
|
||||
"setup:hooks": "pnpm exec husky install",
|
||||
"setup": "npm install && npm run setup:hooks",
|
||||
"setup:hooks": "npx husky install",
|
||||
"format": "prettier --write \"{Components,Containers,helpers,pages,styles}/**/*.{js,jsx,ts,tsx,json,css,scss,md}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/icons-react": "^3.37.1",
|
||||
"@tabler/icons-react": "^3.34.0",
|
||||
"async-mutex": "^0.5.0",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"chart.js": "^4.5.1",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"chart.js": "^4.4.9",
|
||||
"date-fns": "^4.1.0",
|
||||
"lowdb": "^7.0.1",
|
||||
"next": "^16.1.6",
|
||||
"next-auth": "^4.24.13",
|
||||
"nodemailer": "^8.0.1",
|
||||
"next": "^15.3.4",
|
||||
"next-auth": "^4.24.10",
|
||||
"nodemailer": "^6.10.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "^19.2.4",
|
||||
"react-chartjs-2": "^5.3.1",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.71.2",
|
||||
"react-select": "^5.10.2",
|
||||
"react": "^19.1.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hook-form": "^7.59.0",
|
||||
"react-select": "^5.10.1",
|
||||
"react-toastify": "^11.0.5",
|
||||
"swr": "^2.4.1",
|
||||
"swr": "^2.3.3",
|
||||
"use-media": "^1.5.0",
|
||||
"uuid": "^13.0.0"
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^20.4.2",
|
||||
"@commitlint/config-conventional": "^20.4.2",
|
||||
"@types/node": "^25.3.3",
|
||||
"@types/nodemailer": "^7.0.11",
|
||||
"@commitlint/cli": "^19.8.1",
|
||||
"@commitlint/config-conventional": "^19.8.1",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^22.15.30",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/supertest": "^7.2.0",
|
||||
"eslint": "^9.39.3",
|
||||
"eslint-config-next": "^16.1.6",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"eslint-config-next": "^15.3.4",
|
||||
"husky": "^9.1.7",
|
||||
"node-mocks-http": "^1.17.2",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18"
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5857
pnpm-lock.yaml
generated
5857
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,2 +0,0 @@
|
|||
packages:
|
||||
- '.'
|
||||
|
|
@ -81,24 +81,24 @@ teardown() {
|
|||
|
||||
@test "Test createRepo.sh key ED25519 insertion in authorized_keys" {
|
||||
run bash /test/scripts/createRepo.sh "$SSH_KEY_ED25519" 10 false
|
||||
expected_line="command=\"cd ${home}/repos;borg serve --restrict-to-repository ${home}/repos/${output} --storage-quota 10G\",restrict $SSH_KEY_ED25519"
|
||||
expected_line="command=\"cd ${home}/repos;borg serve --restrict-to-path ${home}/repos/${output} --storage-quota 10G\",restrict $SSH_KEY_ED25519"
|
||||
grep -qF "$expected_line" /tmp/borgwarehouse/.ssh/authorized_keys
|
||||
}
|
||||
|
||||
@test "Test createRepo.sh key ED25519-SK insertion in authorized_keys" {
|
||||
run bash /test/scripts/createRepo.sh "$SSH_KEY_ED25519_SK" 10 false
|
||||
expected_line="command=\"cd ${home}/repos;borg serve --restrict-to-repository ${home}/repos/${output} --storage-quota 10G\",restrict $SSH_KEY_ED25519_SK"
|
||||
expected_line="command=\"cd ${home}/repos;borg serve --restrict-to-path ${home}/repos/${output} --storage-quota 10G\",restrict $SSH_KEY_ED25519_SK"
|
||||
grep -qF "$expected_line" /tmp/borgwarehouse/.ssh/authorized_keys
|
||||
}
|
||||
|
||||
@test "Test createRepo.sh key RSA insertion in authorized_keys" {
|
||||
run bash /test/scripts/createRepo.sh "$SSH_KEY_RSA" 10 false
|
||||
expected_line="command=\"cd ${home}/repos;borg serve --restrict-to-repository ${home}/repos/${output} --storage-quota 10G\",restrict $SSH_KEY_RSA"
|
||||
expected_line="command=\"cd ${home}/repos;borg serve --restrict-to-path ${home}/repos/${output} --storage-quota 10G\",restrict $SSH_KEY_RSA"
|
||||
grep -qF "$expected_line" /tmp/borgwarehouse/.ssh/authorized_keys
|
||||
}
|
||||
|
||||
@test "Test createRepo.sh key ED25519 insertion in authorized_keys with append only mode" {
|
||||
run bash /test/scripts/createRepo.sh "$SSH_KEY_ED25519" 10 true
|
||||
expected_line="command=\"cd ${home}/repos;borg serve --append-only --restrict-to-repository ${home}/repos/${output} --storage-quota 10G\",restrict $SSH_KEY_ED25519"
|
||||
expected_line="command=\"cd ${home}/repos;borg serve --append-only --restrict-to-path ${home}/repos/${output} --storage-quota 10G\",restrict $SSH_KEY_ED25519"
|
||||
grep -qF "$expected_line" /tmp/borgwarehouse/.ssh/authorized_keys
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"./*"
|
||||
]
|
||||
"~/*": ["./*"]
|
||||
},
|
||||
"target": "ES2017",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
|
@ -18,20 +13,12 @@
|
|||
"incremental": true,
|
||||
"module": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": [
|
||||
"vitest/globals"
|
||||
] // Auto import vitest types
|
||||
"jsx": "preserve",
|
||||
"types": ["vitest/globals"] // Auto import vitest types
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,15 +10,6 @@ export default defineConfig({
|
|||
reporter: ['text', 'json', 'html'],
|
||||
},
|
||||
globals: true,
|
||||
include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
exclude: [
|
||||
'**/node_modules/**',
|
||||
'**/dist/**',
|
||||
'**/.{idea,git,cache,output,temp}/**',
|
||||
'**/.next/**',
|
||||
'**/build/**',
|
||||
'**/repos/**',
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue