Merge pull request #122 from Ravinou/develop

v2.1.0
This commit is contained in:
Ravinou 2024-01-01 16:01:55 +01:00 committed by GitHub
commit 8a771df290
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 290 additions and 126 deletions

41
.env.sample Normal file
View file

@ -0,0 +1,41 @@
## Required variables section ##
# Host port mappings
WEB_SERVER_PORT=3000
SSH_SERVER_PORT=2222
# Hostname and URL
FQDN=your.domain.com
NEXTAUTH_URL=https://your.domain.com
# Secrects
NEXTAUTH_SECRET=your-secret
CRONJOB_KEY=your-other-secret
# UID:GID must match the user and group ID of the host folders and must be > 1000
# If you want to use a different user than 1001:1001, you must rebuild the image yourself.
UID=1001
GID=1001
# Config and data folders (volume mounts)
# The host folders must be owned by the user with UID and GID specified above
CONFIG_PATH=./config
SSH_PATH=./ssh
SSH_HOST=./ssh_host
BORG_REPOSITORY_PATH=./repos
TMP_PATH=./tmp
LOGS_PATH=./logs
## Optional variables section ##
# LAN feature
FQDN_LAN=
SSH_SERVER_PORT_LAN=
# SMTP server settings
MAIL_SMTP_FROM=
MAIL_SMTP_HOST=
MAIL_SMTP_PORT=
MAIL_SMTP_LOGIN=
MAIL_SMTP_PWD=
MAIL_REJECT_SELFSIGNED_TLS=

View file

@ -0,0 +1,29 @@
name: Build and Push Docker Image for Develop Branch
on:
push:
branches:
- 'develop'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7
tags: borgwarehouse/borgwarehouse:develop

View file

@ -1,34 +1,29 @@
name: Build and Push Docker Image
on:
push:
branches:
- 'main'
push:
branches:
- 'main'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64,linux/arm
tags: borgwarehouse/borgwarehouse:latest
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7
tags: borgwarehouse/borgwarehouse:latest

View file

@ -1,38 +1,32 @@
name: Build and Push Docker Image on Release
on:
release:
types:
- published
release:
types:
- published
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
-
name: Set up QEMU
uses: docker/setup-qemu-action@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Get Release Tag
id: get_release_tag
run: echo "::set-output name=TAG::${{ github.event.release.tag_name }}"
-
name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: borgwarehouse/borgwarehouse:${{ steps.get_release_tag.outputs.TAG }}
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Get Release Tag
id: get_release_tag
run: echo "::set-output name=TAG::${{ github.event.release.tag_name }}"
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64,linux/arm/v7
tags: borgwarehouse/borgwarehouse:${{ steps.get_release_tag.outputs.TAG }}

View file

@ -18,4 +18,4 @@ jobs:
uses: docker/setup-buildx-action@v3
- name: Build Docker Container
run: |
docker buildx build --platform linux/amd64,linux/arm64 -t borgwarehouse:pr-${{ github.event.pull_request.number }} .
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t borgwarehouse:pr-${{ github.event.pull_request.number }} .

View file

@ -50,11 +50,13 @@ export default function QuickCommands(props) {
<div className={classes.copyValid}>Copied !</div>
) : (
<div className={classes.tooltip}>
ssh://{wizardEnv.UNIX_USER}@
{FQDN}:{SSH_SERVER_PORT}/./
ssh://{wizardEnv.UNIX_USER}@{FQDN}:{SSH_SERVER_PORT}/./
{props.repositoryName}
</div>
)}
{props.lanCommand && <div className={classes.lanBadge}>LAN</div>}
<div className={classes.icons}>
<button onClick={handleCopy} className={classes.copyButton}>
<IconCopy color='#65748b' stroke={1.25} />

View file

@ -7,7 +7,7 @@
.icons {
position: relative;
bottom: 14px;
bottom: 13px;
}
.quickSetting {
@ -16,6 +16,15 @@
opacity: 1;
}
.lanBadge {
border-radius: 5px;
border: 1px solid #6d4aff;
color: #6d4aff;
font-size: 0.9em;
padding: 2px 5px;
margin-right: 8px;
}
.tooltip {
visibility: hidden;
opacity: 0;
@ -91,6 +100,15 @@
opacity: 0;
}
.container:hover .lanBadge {
visibility: hidden;
opacity: 0;
width: 0;
height: 0;
margin: 0;
padding: 0;
}
@media all and (max-width: 1000px) {
.container {
display: none;

View file

@ -6,6 +6,7 @@ import {
IconInfoCircle,
IconChevronDown,
IconChevronUp,
IconBellOff,
} from '@tabler/icons-react';
import timestampConverter from '../../helpers/functions/timestampConverter';
import StorageBar from '../UI/StorageBar/StorageBar';
@ -53,22 +54,33 @@ export default function Repo(props) {
setDisplayDetails(boolean);
};
//Status indicator
const statusIndicator = () => {
return props.status
? classes.statusIndicatorGreen
: classes.statusIndicatorRed;
};
//Alert indicator
const alertIndicator = () => {
if (props.alert === 0) {
return (
<div className={classes.alertIcon}>
<IconBellOff size={16} color='grey' />
</div>
);
}
};
return (
<>
{displayDetails ? (
<>
<div className={classes.RepoOpen}>
<div className={classes.openFlex}>
{props.status ? (
<div
className={classes.statusIndicatorGreen}
></div>
) : (
<div
className={classes.statusIndicatorRed}
></div>
)}
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='grey' />
@ -141,16 +153,9 @@ export default function Repo(props) {
<>
<div className={classes.RepoClose}>
<div className={classes.closeFlex}>
{props.status ? (
<div
className={classes.statusIndicatorGreen}
></div>
) : (
<div
className={classes.statusIndicatorRed}
></div>
)}
<div className={statusIndicator()} />
<div className={classes.alias}>{props.alias}</div>
{alertIndicator()}
{props.comment && (
<div className={classes.comment}>
<IconInfoCircle size={16} color='#637381' />

View file

@ -148,6 +148,15 @@
}
}
/* Alert icon */
.alertIcon {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 10px;
}
/* GENERAL */
.alias {
font-weight: bold;

View file

@ -116,6 +116,7 @@ export default function RepoList() {
alias={repo.alias}
status={repo.status}
lastSave={repo.lastSave}
alert={repo.alert}
repositoryName={repo.repositoryName}
storageSize={repo.storageSize}
storageUsed={repo.storageUsed}

View file

@ -23,6 +23,7 @@ export default function RepoManage(props) {
} = useForm({ mode: 'onChange' });
//List of possible times for alerts
const alertOptions = [
{ value: 0, label: 'Disabled' },
{ value: 3600, label: '1 hour' },
{ value: 21600, label: '6 hours' },
{ value: 43200, label: '12 hours' },
@ -471,7 +472,7 @@ export default function RepoManage(props) {
x.value ===
targetRepo.alert
)
: alertOptions[3]
: alertOptions[4]
}
control={control}
render={({

View file

@ -1,5 +1,8 @@
FROM node:20-bookworm-slim as base
ARG UID=1001
ARG GID=1001
# build stage
FROM base AS deps
@ -27,31 +30,26 @@ FROM base AS runner
ENV NODE_ENV production
RUN apt-get update && apt-get install -y \
curl jq jc borgbackup openssh-server sudo cron && \
supervisor \
curl jq jc borgbackup openssh-server sudo cron rsyslog && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN echo "borgwarehouse ALL=(ALL) NOPASSWD: /usr/sbin/service ssh restart" >> /etc/sudoers
RUN echo "borgwarehouse ALL=(ALL) NOPASSWD: /usr/sbin/service cron restart" >> /etc/sudoers
RUN groupadd borgwarehouse
RUN useradd -m -g borgwarehouse borgwarehouse
RUN groupadd -g ${GID} borgwarehouse && useradd -m -u ${UID} -g ${GID} borgwarehouse
RUN cp /etc/ssh/sshd_config /etc/ssh/moduli /home/borgwarehouse/
WORKDIR /home/borgwarehouse/app
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker-bw-init.sh /app/LICENSE ./
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/docker/docker-bw-init.sh /app/LICENSE ./
COPY --from=builder --chown=borgwarehouse:borgwarehouse /app/helpers/shells ./helpers/shells
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
USER borgwarehouse
EXPOSE 3000 22
ENTRYPOINT ["./docker-bw-init.sh"]
CMD ["node", "server.js"]
ENTRYPOINT ["./docker-bw-init.sh"]

View file

@ -6,25 +6,23 @@ services:
#build:
# context: .
# dockerfile: Dockerfile
# args:
# - UID=${UID}
# - GID=${GID}
image: borgwarehouse/borgwarehouse
# UID:GID must match the user and group ID of the host folders and must be > 1000
user: '1001:1001'
user: '${UID:?UID variable missing}:${GID:?GID variable missing}'
ports:
- '3000:3000'
- '2222:22'
environment:
- NEXTAUTH_URL=https://your.domain.com
- NEXTAUTH_SECRET=your-secret
- CRONJOB_KEY=your-other-secret
# The SSH_SERVER_PORT must match the port exposed above
- SSH_SERVER_PORT=2222
- FQDN=your.domain.com
- '${WEB_SERVER_PORT:?WEB_SERVER_PORT variable missing}:3000'
- '${SSH_SERVER_PORT:?SSH_SERVER_PORT variable missing}:22'
env_file:
- .env
volumes:
# The host folders must be owned by the user with UID and GID specified above
- <host-folder>/config:/home/borgwarehouse/app/config
- <host-folder>/ssh:/home/borgwarehouse/.ssh
- <host-folder>/ssh_host:/etc/ssh
- <host-folder>/repos:/home/borgwarehouse/repos
- ${CONFIG_PATH:?CONFIG_PATH variable missing}:/home/borgwarehouse/app/config
- ${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

View file

@ -47,12 +47,6 @@ check_repos_directory() {
fi
}
add_cron_job() {
print_green "Adding cron job..."
local CRON_JOB="* * * * * curl --request POST --url 'http://$HOSTNAME:3000/api/cronjob/checkStatus' --header 'Authorization: Bearer $CRONJOB_KEY'; curl --request POST --url 'http://$HOSTNAME:3000/api/cronjob/getStorageUsed' --header 'Authorization: Bearer $CRONJOB_KEY'"
echo "$CRON_JOB" | crontab -u borgwarehouse -
}
get_SSH_fingerprints() {
print_green "Getting SSH fingerprints..."
RSA_FINGERPRINT=$(ssh-keygen -lf /etc/ssh/ssh_host_rsa_key | awk '{print $2}')
@ -82,10 +76,6 @@ init_ssh_server
check_ssh_directory
create_authorized_keys_file
check_repos_directory
add_cron_job
get_SSH_fingerprints
sudo service ssh restart
sudo service cron restart
exec "$@"
exec supervisord -c /home/borgwarehouse/app/supervisord.conf

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

24
docker/supervisord.conf Normal file
View file

@ -0,0 +1,24 @@
[supervisord]
nodaemon=true
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 -o PidFile=/home/borgwarehouse/tmp/sshd.pid -o SyslogFacility=AUTH -o LogLevel=INFO -o PasswordAuthentication=no -o ChallengeResponseAuthentication=no -o UsePAM=no -o PermitRootLogin=no
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=/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

@ -26,6 +26,8 @@ export const authOptions = {
password:
'$2a$12$20yqRnuaDBH6AE0EvIUcEOzqkuBtn1wDzJdw2Beg8w9S.vEqdso0a',
roles: ['admin'],
emailAlert: false,
appriseAlert: false,
},
])
);

View file

@ -102,6 +102,7 @@ export default async function handler(req, res) {
for (let index in newRepoList) {
if (
!newRepoList[index].status &&
newRepoList[index].alert !== 0 &&
(!newRepoList[index].lastStatusAlertSend ||
date - newRepoList[index].lastStatusAlertSend > 90000)
) {

View file

@ -19,7 +19,7 @@ export default async function handler(req, res) {
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) {
if (!alias || !sshPublicKey || !size || (!alert && alert !== 0)) {
//If a variable is empty.
res.status(422).json({
message: 'Unexpected data',

View file

@ -19,7 +19,7 @@ export default async function handler(req, res) {
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) {
if (!alias || !sshPublicKey || !size || (!alert && alert !== 0)) {
//If a variable is empty.
res.status(422).json({
message: 'Unexpected data',

View file

@ -0,0 +1,16 @@
import packageInfo from '../../../package.json';
export default async function handler(req, res) {
if (req.method === 'GET') {
try {
res.status(200).json({ version: packageInfo.version });
return;
} catch (error) {
res.status(500).json({
status: 500,
message: 'API error, contact the administrator !',
});
return;
}
}
}