feat: add borgbackup "append-only" mode as option to repo ! #160

This commit is contained in:
Ravinou 2024-05-12 16:35:45 +02:00
parent 6b43c38cc2
commit 76d11d83f7
No known key found for this signature in database
GPG key ID: EEEE670C40F6A4D7
9 changed files with 121 additions and 28 deletions

View file

@ -7,6 +7,7 @@ import {
IconChevronDown,
IconChevronUp,
IconBellOff,
IconLockPlus,
} from '@tabler/icons-react';
import timestampConverter from '../../helpers/functions/timestampConverter';
import StorageBar from '../UI/StorageBar/StorageBar';
@ -72,6 +73,16 @@ export default function Repo(props) {
}
};
const appendOnlyModeIndicator = () => {
if (props.appendOnlyMode) {
return (
<div className={classes.appendOnlyModeIcon}>
<IconLockPlus size={16} color='grey' />
</div>
);
}
};
return (
<>
{displayDetails ? (
@ -80,6 +91,7 @@ export default function Repo(props) {
<div className={classes.openFlex}>
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
{appendOnlyModeIndicator()}
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
@ -155,6 +167,7 @@ export default function Repo(props) {
<div className={classes.closeFlex}>
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
{appendOnlyModeIndicator()}
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>

View file

@ -157,6 +157,13 @@
margin-left: 10px;
}
.appendOnlyModeIcon {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 10px;
}
/* GENERAL */
.alias {
font-weight: bold;

View file

@ -123,6 +123,7 @@ export default function RepoList() {
sshPublicKey={repo.sshPublicKey}
comment={repo.comment}
lanCommand={repo.lanCommand}
appendOnlyMode={repo.appendOnlyMode}
repoManageEditHandler={() => repoManageEditHandler(repo.id)}
wizardEnv={wizardEnv}
></Repo>

View file

@ -170,6 +170,7 @@ export default function RepoManage(props) {
comment: dataForm.comment,
alert: dataForm.alert.value,
lanCommand: dataForm.lanCommand,
appendOnlyMode: dataForm.appendOnlyMode,
};
//POST API to send new repo
await fetch('/api/repo/add', {
@ -210,6 +211,7 @@ export default function RepoManage(props) {
comment: dataForm.comment,
alert: dataForm.alert.value,
lanCommand: dataForm.lanCommand,
appendOnlyMode: dataForm.appendOnlyMode,
};
await fetch('/api/repo/id/' + router.query.slug + '/edit', {
method: 'PUT',
@ -426,7 +428,7 @@ export default function RepoManage(props) {
</span>
)}
{/* LAN COMMAND GENERATION */}
<div className={classes.lanCommandWrapper}>
<div className={classes.optionCommandWrapper}>
<input
type='checkbox'
name='lanCommand'
@ -455,6 +457,36 @@ export default function RepoManage(props) {
/>
</Link>
</div>
{/* APPEND-ONLY MODE */}
<div className={classes.optionCommandWrapper}>
<input
type='checkbox'
name='appendOnlyMode'
defaultChecked={
props.mode == 'edit'
? targetRepo.appendOnlyMode
: false
}
{...register('appendOnlyMode')}
/>
<label htmlFor='appendOnlyMode'>
Enable append-only mode.
</label>
<Link
style={{
alignSelf: 'baseline',
marginLeft: '5px',
}}
href='https://borgwarehouse.com/docs/user-manual/repositories/#append-only-mode'
rel='noreferrer'
target='_blank'
>
<IconExternalLink
size={16}
color='#6c737f'
/>
</Link>
</div>
{/* ALERT */}
<label
style={{ margin: '25px auto 10px auto' }}

View file

@ -126,23 +126,23 @@
margin-top: 3px;
}
.lanCommandWrapper {
.optionCommandWrapper {
display: flex;
margin-top: 20px;
color: #494b7a;
}
.lanCommandWrapper label {
.optionCommandWrapper label {
margin: 0;
}
.lanCommandWrapper input[type='checkbox'] {
.optionCommandWrapper input[type='checkbox'] {
width: auto;
margin-right: 8px;
cursor: pointer;
accent-color: #6d4aff;
}
.lanCommandWrapper input[type='checkbox']:focus {
.optionCommandWrapper input[type='checkbox']:focus {
outline: 0;
box-shadow: none;
accent-color: #6d4aff;

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Shell created by Raven for BorgWarehouse.
# This shell takes 2 arguments : [SSH pub key] X [quota]
# This shell takes 2 arguments : [SSH pub key] X [quota] x [append only mode (boolean)]
# Main steps are :
# - check if args are present
# - check the ssh pub key format
@ -31,8 +31,8 @@ pool="${home}/repos"
authorized_keys="${home}/.ssh/authorized_keys"
# Check args
if [ "$1" == "" ] || [ "$2" == "" ];then
echo -n "This shell takes 2 arguments : SSH Public Key, Quota in Go [e.g. : 10] "
if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" != "true" ] && [ "$3" != "false" ];then
echo -n "This shell takes 3 arguments : SSH Public Key, Quota in Go [e.g. : 10], Append only mode [true|false]"
exit 1
fi
@ -45,6 +45,12 @@ then
exit 2
fi
## Check if authorized_keys exists
if [ ! -f "${authorized_keys}" ];then
echo -n "${authorized_keys} must be present"
exit 5
fi
# Check if SSH pub key is already present in authorized_keys
if grep -q "$1" "$authorized_keys"; then
echo -n "SSH pub key already present in authorized_keys"
@ -63,14 +69,15 @@ randRepositoryName () {
}
repositoryName=$(randRepositoryName)
## Check if authorized_keys exists
if [ ! -f "${authorized_keys}" ];then
echo -n "${authorized_keys} must be present"
exit 5
# Append only mode
if [ "$3" == "true" ]; then
appendOnlyMode=" --append-only"
else
appendOnlyMode=""
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 --restrict-to-path ${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

View file

@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Shell created by Raven for BorgWarehouse.
# This shell takes 3 args: [repositoryName] [new SSH pub key] [quota]
# This shell takes 4 args: [repositoryName] [new SSH pub key] [quota] [append-only mode (boolean)]
# This shell updates the SSH key and the quota for a repository.
# Exit when any command fails
@ -16,8 +16,8 @@ fi
: "${home:=/home/borgwarehouse}"
# Check args
if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ]; then
echo -n "This shell takes 3 args: [repositoryName] [new SSH pub key] [quota]"
if [ "$1" == "" ] || [ "$2" == "" ] || [ "$3" == "" ] || [ "$4" != "true" ] && [ "$4" != "false" ]; then
echo -n "This shell takes 4 args: [repositoryName] [new SSH pub key] [quota] [Append only mode [true|false]]"
exit 1
fi
@ -68,5 +68,12 @@ if [ "$found" = true ]; then
exit 5
fi
# Append only mode
if [ "$4" == "true" ]; then
sed -ri "/command=\".*${repositoryName}.*\",restrict/ {/borg serve .*--append-only /! s|(borg serve )|\1--append-only |}" "$home/.ssh/authorized_keys"
elif [ "$4" == "false" ]; then
sed -ri "/command=\".*${repositoryName}.*\",restrict/ s|(--append-only )||g" "$home/.ssh/authorized_keys"
fi
# Modify authorized_keys for the repositoryName: update the line with the quota and the SSH pub key
sed -ri "s|(command=\".*${repositoryName}.*--storage-quota ).*G\",restrict .*|\\1$3G\",restrict $2|g" "$home/.ssh/authorized_keys"
sed -ri "s|(command=\".*${repositoryName}.*--storage-quota ).*G\",restrict .*|\\1$3G\",restrict $2|g" "$home/.ssh/authorized_keys"

View file

@ -16,10 +16,23 @@ export default async function handler(req, res) {
}
//The data we expect to receive
const { alias, sshPublicKey, size, comment, alert, lanCommand } =
req.body;
const {
alias,
sshPublicKey,
size,
comment,
alert,
lanCommand,
appendOnlyMode,
} = req.body;
//We check that we receive data for each variable. Only "comment" and "lanCommand" are optional in the form.
if (!alias || !sshPublicKey || !size || (!alert && alert !== 0)) {
if (
!alias ||
!sshPublicKey ||
!size ||
typeof appendOnlyMode !== 'boolean' ||
(!alert && alert !== 0)
) {
//If a variable is empty.
res.status(422).json({
message: 'Unexpected data',
@ -60,6 +73,7 @@ export default async function handler(req, res) {
comment: comment,
displayDetails: true,
lanCommand: lanCommand,
appendOnlyMode: appendOnlyMode,
};
////Call the shell : createRepo.sh
@ -67,7 +81,7 @@ export default async function handler(req, res) {
const shellsDirectory = path.join(process.cwd(), '/helpers');
//Exec the shell
const { stdout } = await exec(
`${shellsDirectory}/shells/createRepo.sh "${newRepo.sshPublicKey}" ${newRepo.storageSize}`
`${shellsDirectory}/shells/createRepo.sh "${newRepo.sshPublicKey}" ${newRepo.storageSize} ${newRepo.appendOnlyMode}`
);
newRepo.repositoryName = stdout.trim();

View file

@ -16,15 +16,26 @@ export default async function handler(req, res) {
}
//The data we expect to receive
const { alias, sshPublicKey, size, comment, alert, lanCommand } =
req.body;
//We check that we receive data for each variable. Only "comment" and "lanCommand" are optional in the form.
if (!alias || !sshPublicKey || !size || (!alert && alert !== 0)) {
//If a variable is empty.
const {
alias,
sshPublicKey,
size,
comment,
alert,
lanCommand,
appendOnlyMode,
} = req.body;
//Only "comment" and "lanCommand" are optional in the form.
if (
!alias ||
!sshPublicKey ||
!size ||
typeof appendOnlyMode !== 'boolean' ||
(!alert && alert !== 0)
) {
res.status(422).json({
message: 'Unexpected data',
});
//A return to make sure we don't go any further if data are incorrect.
return;
}
@ -49,7 +60,7 @@ export default async function handler(req, res) {
const shellsDirectory = path.join(process.cwd(), '/helpers');
// //Exec the shell
await exec(
`${shellsDirectory}/shells/updateRepo.sh ${repoList[repoIndex].repositoryName} "${sshPublicKey}" ${size}`
`${shellsDirectory}/shells/updateRepo.sh ${repoList[repoIndex].repositoryName} "${sshPublicKey}" ${size} ${appendOnlyMode}`
);
//Find the ID in the data and change the values transmitted by the form
@ -63,6 +74,7 @@ export default async function handler(req, res) {
comment: comment,
alert: alert,
lanCommand: lanCommand,
appendOnlyMode: appendOnlyMode,
}
: repo
);