fix backup

This commit is contained in:
2026-10-26 16:21:27 -06:00
parent 8c8c59d6d5
commit 488b085713
7 changed files with 180 additions and 115 deletions
+2 -25
View File
@@ -6,6 +6,7 @@ DESCRIPTION="Superpanel, Tailscale, Nextcloud, email, files and Element"
# What you entered last time, so running this again only does what's missing
CORE_CONF="$HOME/.config/coredesktop/core.conf"
GOA_ACCOUNT="$COREDESKTOP/configure.d/lib/goa-account"
source "$COREDESKTOP/configure.d/lib/nextcloud.sh"
[ -f "$CORE_CONF" ] && source "$CORE_CONF"
# CORE_DOMAIN, CORE_EMAIL and CORE_NAME come from core.conf; the rest follow
@@ -70,30 +71,6 @@ save_conf() {
} > "$CORE_CONF"
}
# Basic auth header for curl, read from stdin (-H @-) so the password isn't
# in the process list
auth_header() {
printf 'Authorization: Basic %s\n' "$(printf '%s:%s' "$1" "$2" | base64 -w0)"
}
# Create a Nextcloud app password for the Online Accounts account and the
# files mount, so they don't keep your Core password. It shows in Nextcloud
# under Settings > Security as "CoreDesktop on <this computer>", where it can
# be revoked. Returns 2 if Nextcloud says the password is wrong.
nextcloud_app_password() {
local response code
response="$(auth_header "$CORE_USER" "$PASSWORD" | curl -s -w '\n%{http_code}' -H @- \
-H 'OCS-APIRequest: true' -A "CoreDesktop on $HOSTNAME" \
"$NEXTCLOUD/ocs/v2.php/core/getapppassword?format=json")"
code="${response##*$'\n'}"
case "$code" in
200) APP_PASSWORD="$(jq -r '.ocs.data.apppassword // empty' <<< "${response%$'\n'*}")"
[ -n "$APP_PASSWORD" ] ;;
401) return 2 ;;
*) return 1 ;;
esac
}
desktop_entry() {
local file="$1" name="$2" comment="$3" url="$4" icon="$5"
mkdir -p "$(dirname "$file")"
@@ -332,7 +309,7 @@ run_configure() {
for tries in 1 2 3; do
read -rsp "Your Core password: " PASSWORD
echo
nextcloud_app_password
nextcloud_app_password "CoreDesktop on $HOSTNAME"
case $? in
0) break ;;
2) echo "Nextcloud says that password is wrong."; PASSWORD="" ;;
+73 -56
View File
@@ -6,86 +6,103 @@ DESCRIPTION="Back up your home folder daily to your Nextcloud files"
CORE_CONF="$HOME/.config/coredesktop/core.conf"
BACKUP_CONF="$HOME/.config/coredesktop/backup.conf"
UNITS="$HOME/.config/systemd/user"
source "$COREDESKTOP/configure.d/lib/nextcloud.sh"
[ -f "$CORE_CONF" ] && source "$CORE_CONF"
[ -f "$BACKUP_CONF" ] && source "$BACKUP_CONF"
is_installed() {
command -v borg > /dev/null && command -v secret-tool > /dev/null
}
# The passphrase is in the keyring under the repository's path, so the
# backup can run without asking
passphrase() {
secret-tool lookup coredesktop backup repo "$1"
command -v restic > /dev/null && command -v rclone > /dev/null && command -v secret-tool > /dev/null
}
is_configured() {
[ -n "$BORG_REPO" ] && [ -n "$(passphrase "$BORG_REPO")" ] \
&& systemctl --user is-enabled -q coredesktop-backup.timer
source "$COREDESKTOP/system/backup-env.sh" > /dev/null || return 1
[ -n "$RESTIC_PASSWORD_COMMAND" ] && systemctl --user is-enabled -q coredesktop-backup.timer
}
# system/backup uses borg 1.2 to 1.4; borg 2 changed its commands
borg_supported() {
local version
version="$(borg --version | awk '{print $2}')"
[[ "$version" =~ ^1\.([2-9]|[1-9][0-9])\. ]] && return 0
echo "This needs borg 1.2 to 1.4, and borg $version is installed."
return 1
save_secret() {
printf '%s' "$3" | secret-tool store --label "CoreDesktop backup $1 ($2)" \
coredesktop backup item "$1" url "$2"
}
save_passphrase() {
printf '%s' "$2" | secret-tool store --label "CoreDesktop backup ($1)" coredesktop backup repo "$1"
# The backup gets its own app password, so revoking it stops only backups
nextcloud_login() {
local tries
[ -n "$(backup_secret nextcloud "$WEBDAV")" ] \
&& rclone lsf --max-depth 1 ":webdav:" > /dev/null 2>&1 && return 0
for tries in 1 2 3; do
read -rsp "Your Core password: " PASSWORD
echo
nextcloud_app_password "CoreDesktop backup on $HOSTNAME"
case $? in
0) break ;;
2) echo "Nextcloud says that password is wrong." ;;
*) echo "Couldn't reach Nextcloud at $NEXTCLOUD."; return 1 ;;
esac
done
unset PASSWORD
[ -n "$APP_PASSWORD" ] || return 1
save_secret nextcloud "$WEBDAV" "$APP_PASSWORD" || return 1
unset APP_PASSWORD
RCLONE_WEBDAV_PASS="$(backup_secret nextcloud "$WEBDAV" | rclone obscure -)"
}
new_passphrase() {
local pass
pass="$(head -c 24 /dev/urandom | base64)"
echo "Your backup is encrypted with this passphrase:"
echo
echo " $pass"
echo
echo "Save it somewhere other than this computer, such as Bitwarden. Without it, the"
echo "backup can't be read if this computer is lost. It's kept in your keyring so the"
echo "daily backup can run."
until confirm "Have you saved it?"; do :; done
save_secret passphrase "$WEBDAV/$BACKUP_DIR" "$pass"
}
run_configure() {
local mount="/mnt/$CORE_DOMAIN" repo pass
borg_supported || return 1
if [ -z "$CORE_DOMAIN" ] || ! mountpoint -q "$mount"; then
echo "Backups go to your Nextcloud files, so set those up first (configure core), and check they're mounted."
local pass
if [ -z "$CORE_DOMAIN" ] || [ -z "$CORE_EMAIL" ]; then
echo "Backups go to your Nextcloud files, so set up your Core first: configure core"
return 1
fi
repo="$mount/.backup-$HOSTNAME"
if [ -f "$repo/config" ]; then
# Already there, e.g. after reinstalling: it needs the passphrase from then
pass="$(passphrase "$repo")"
if [ -z "$pass" ] || ! BORG_PASSPHRASE="$pass" borg info "$repo" &> /dev/null; then
echo "There's already a backup at $repo."
read -rsp "Its passphrase: " pass
echo
BORG_PASSPHRASE="$pass" borg info "$repo" > /dev/null || { echo "borg couldn't open it with that passphrase."; return 1; }
save_passphrase "$repo" "$pass" || return 1
fi
echo "Using the backup at $repo."
else
pass="$(head -c 24 /dev/urandom | base64)"
echo "Your backup is encrypted with this passphrase:"
echo
echo " $pass"
echo
echo "Save it somewhere other than this computer, such as Bitwarden. Without it, the"
echo "backup can't be read if this computer is lost. It's kept in your keyring so the"
echo "daily backup can run."
until confirm "Have you saved it?"; do :; done
save_passphrase "$repo" "$pass" || return 1
echo "Creating the backup at $repo..."
BORG_PASSPHRASE="$pass" borg init --encryption=repokey-blake2 "$repo" || return 1
# davfs2 copies each file to a local cache before uploading it, and some
# servers refuse large uploads, so keep borg's files to 50 MB, not 500
BORG_PASSPHRASE="$pass" borg config "$repo" max_segment_size 52428800 || return 1
fi
CORE_USER="${CORE_EMAIL%@*}"
NEXTCLOUD="https://nextcloud.$CORE_DOMAIN"
WEBDAV="$NEXTCLOUD/remote.php/dav/files/$CORE_USER"
BACKUP_DIR=".backup-$HOSTNAME"
mkdir -p "$(dirname "$BACKUP_CONF")"
{
printf 'BORG_REPO=%q\n' "$repo"
printf 'BACKUP_MOUNT=%q\n' "$mount"
printf 'BACKUP_WEBDAV=%q\n' "$WEBDAV"
printf 'BACKUP_USER=%q\n' "$CORE_USER"
printf 'BACKUP_DIR=%q\n' "$BACKUP_DIR"
} > "$BACKUP_CONF"
source "$COREDESKTOP/system/backup-env.sh"
nextcloud_login || { echo "Couldn't sign in to Nextcloud."; return 1; }
if rclone lsf --files-only ":webdav:$BACKUP_DIR" 2> /dev/null | grep -qx config; then
# Already there, e.g. after reinstalling: it needs the passphrase from then
if [ -z "$RESTIC_PASSWORD_COMMAND" ] || ! restic cat config &> /dev/null; then
echo "There's already a backup in $BACKUP_DIR in your Nextcloud files."
read -rsp "Its passphrase: " pass
echo
RESTIC_PASSWORD_COMMAND= RESTIC_PASSWORD="$pass" restic cat config > /dev/null \
|| { echo "restic couldn't open it with that passphrase."; return 1; }
save_secret passphrase "$WEBDAV/$BACKUP_DIR" "$pass" || return 1
fi
echo "Using the backup in $BACKUP_DIR."
else
new_passphrase || return 1
source "$COREDESKTOP/system/backup-env.sh"
echo "Creating the backup in $BACKUP_DIR in your Nextcloud files..."
restic init || return 1
fi
mkdir -p "$UNITS"
cp "$COREDESKTOP"/system/systemd/coredesktop-backup.{service,timer} "$UNITS/" || return 1
systemctl --user daemon-reload
systemctl --user enable --now coredesktop-backup.timer || return 1
echo "Your home folder will be backed up daily to $repo."
echo "Your home folder will be backed up daily to $BACKUP_DIR in your Nextcloud files."
if confirm_yes "Start the first backup now? It runs in the background and can take hours."; then
systemctl --user start --no-block coredesktop-backup.service
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
#
# Purpose : Nextcloud app passwords for configure.d items. Source this file.
# Basic auth header for curl, read from stdin (-H @-) so the password isn't
# in the process list
auth_header() {
printf 'Authorization: Basic %s\n' "$(printf '%s:%s' "$1" "$2" | base64 -w0)"
}
# Create a Nextcloud app password, so what CoreDesktop sets up doesn't keep
# your Core password. It shows in Nextcloud under Settings > Security with the
# name given, where it can be revoked. Uses CORE_USER, PASSWORD and NEXTCLOUD;
# sets APP_PASSWORD. Returns 2 if Nextcloud says the password is wrong.
nextcloud_app_password() {
local name="$1" response code
response="$(auth_header "$CORE_USER" "$PASSWORD" | curl -s -w '\n%{http_code}' -H @- \
-H 'OCS-APIRequest: true' -A "$name" \
"$NEXTCLOUD/ocs/v2.php/core/getapppassword?format=json")"
code="${response##*$'\n'}"
case "$code" in
200) APP_PASSWORD="$(jq -r '.ocs.data.apppassword // empty' <<< "${response%$'\n'*}")"
[ -n "$APP_PASSWORD" ] ;;
401) return 2 ;;
*) return 1 ;;
esac
}