Compare commits

..

No commits in common. "main" and "v3.1.1" have entirely different histories.

41 changed files with 8600 additions and 6126 deletions

View file

@ -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;

View file

@ -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
View file

@ -0,0 +1,6 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}

View file

@ -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"

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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

View file

@ -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>

View file

@ -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>
);
}

View file

@ -69,8 +69,8 @@ function WizardStep2(props: WizardStepProps) {
<h2>Pika, Vorta...</h2>
<div className={classes.description}>
To &quot;Initialize a new repository&quot; or &quot;Add existing repository&quot;, copy this
into the field &quot;Repository URL&quot; 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&apos;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}

View file

@ -102,7 +102,7 @@ function WizardStep3(props: WizardStepProps) {
}}
>
<div className={classes.code}>
borg export-tar --tar-filter=&quot;gzip -9&quot; ssh://
borg export-tar --tar-filter="gzip -9" ssh://
{UNIX_USER}@{FQDN}
{SSH_SERVER_PORT}/./
{props.selectedRepo?.repositoryName}

View file

@ -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.

View file

@ -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);

View file

@ -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)',
},
})}
/>

View file

@ -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,

View file

@ -58,7 +58,6 @@ export default function AppriseAlertSettings() {
}
};
getAppriseAlert();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
////Functions

View file

@ -47,7 +47,6 @@ export default function AppriseURLs() {
}
};
getAppriseServices();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//Form submit handler to modify Apprise services

View file

@ -56,7 +56,6 @@ export default function EmailAlertSettings() {
}
};
dataFetch();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
////Functions

View file

@ -84,7 +84,6 @@ export default function Integrations() {
////LifeCycle
useEffect(() => {
fetchTokenList();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// Permissions handler

View file

@ -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}>

View file

@ -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

View file

@ -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>

View file

@ -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
View 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")
}

View file

@ -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

View file

@ -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;

View file

@ -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
View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -1,2 +0,0 @@
packages:
- '.'

View file

@ -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
}

View file

@ -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"]
}

View file

@ -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: {