summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jakob@odersky.com>2020-02-25 19:42:07 +0100
committerJakob Odersky <jakob@odersky.com>2020-02-25 20:08:31 +0100
commit595131b8e3ffe99e9187c7adb5e73e922bd931e1 (patch)
treee2ce6d110858d8077517b602c33637ec704a1ad5
downloadresteasy-595131b8e3ffe99e9187c7adb5e73e922bd931e1.tar.gz
resteasy-595131b8e3ffe99e9187c7adb5e73e922bd931e1.tar.bz2
resteasy-595131b8e3ffe99e9187c7adb5e73e922bd931e1.zip
Initial commit
-rw-r--r--debian/changelog5
-rw-r--r--debian/control22
-rw-r--r--debian/copyright27
-rw-r--r--debian/resteasy-desktop.install1
-rw-r--r--debian/resteasy-desktop.postinst39
-rw-r--r--debian/resteasy-desktop.user.service9
-rw-r--r--debian/resteasy.install2
-rw-r--r--debian/resteasy.postinst42
-rw-r--r--debian/resteasy.service8
-rw-r--r--debian/resteasy.udev6
-rwxr-xr-xdebian/rules11
-rw-r--r--debian/source/format1
-rw-r--r--readme.md16
-rwxr-xr-xresteasy-desktop/libexec/resteasy-desktop-monitor31
-rw-r--r--resteasy.8.md186
-rwxr-xr-xresteasy/libexec/resteasy-find-uuid22
-rw-r--r--resteasy/libexec/resteasy-helper.sh45
-rwxr-xr-xresteasy/libexec/resteasy-inhibit13
-rwxr-xr-xresteasy/libexec/resteasy-init16
-rwxr-xr-xresteasy/libexec/resteasy-mount27
-rwxr-xr-xresteasy/libexec/resteasy-prepare-drive35
-rwxr-xr-xresteasy/libexec/resteasy-run-backup74
-rwxr-xr-xresteasy/libexec/resteasy-umount12
-rwxr-xr-xresteasy/libexec/resteasy-uninhibit13
-rwxr-xr-xresteasy/sbin/resteasy32
25 files changed, 695 insertions, 0 deletions
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..80d7307
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,5 @@
+resteasy (0) unstable; urgency=medium
+
+ * Initial Release.
+
+ -- Jakob Odersky <jakob@odersky.com> Sun, 16 Feb 2020 01:20:26 +0100
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..f5477b1
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,22 @@
+Source: resteasy
+Section: admin
+Priority: optional
+Maintainer: Jakob Odersky <jakob@odersky.com>
+Build-Depends: debhelper-compat (= 12), pandoc
+Standards-Version: 4.4.1
+
+Package: resteasy
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, borgbackup
+Description: simple plug and play backup system
+ Resteasy is a collection of wrapper scripts and services around borg, that
+ provide an easy-to-use backup solution.
+
+Package: resteasy-desktop
+Architecture: all
+Depends: ${shlibs:Depends}, ${misc:Depends}, resteasy, libnotify-bin, dbus, python3
+Description: simple plug and play backup system, desktop notification component
+ Resteasy is a collection of wrapper scripts and services around borg, that
+ provide an easy-to-use backup solution.
+ .
+ This package provides utilities for notifying users of running backups.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..42027a3
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,27 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: resteasy
+
+Files: *
+Copyright: 2020 Jakob Odersky <jakob@odersky.com>
+License: GPL-3.0+
+
+Files: debian/*
+Copyright: 2020Jakob Odersky <jakob@odersky.com>
+License: GPL-3.0+
+
+License: GPL-3.0+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+ .
+ This package is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+ .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ .
+ On Debian systems, the complete text of the GNU General
+ Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
diff --git a/debian/resteasy-desktop.install b/debian/resteasy-desktop.install
new file mode 100644
index 0000000..92b6301
--- /dev/null
+++ b/debian/resteasy-desktop.install
@@ -0,0 +1 @@
+resteasy-desktop/libexec/* usr/libexec/resteasy/
diff --git a/debian/resteasy-desktop.postinst b/debian/resteasy-desktop.postinst
new file mode 100644
index 0000000..bbbe8cf
--- /dev/null
+++ b/debian/resteasy-desktop.postinst
@@ -0,0 +1,39 @@
+#!/bin/sh
+# postinst script for resteasy
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <postinst> `abort-remove'
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ configure)
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/resteasy-desktop.user.service b/debian/resteasy-desktop.user.service
new file mode 100644
index 0000000..c05fc6f
--- /dev/null
+++ b/debian/resteasy-desktop.user.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=monitor backup system
+
+[Service]
+Type=simple
+ExecStart=/usr/sbin/resteasy desktop-monitor
+
+[Install]
+WantedBy=default.target
diff --git a/debian/resteasy.install b/debian/resteasy.install
new file mode 100644
index 0000000..2031a55
--- /dev/null
+++ b/debian/resteasy.install
@@ -0,0 +1,2 @@
+resteasy/libexec/* usr/libexec/resteasy/
+resteasy/sbin usr/
diff --git a/debian/resteasy.postinst b/debian/resteasy.postinst
new file mode 100644
index 0000000..3bd788c
--- /dev/null
+++ b/debian/resteasy.postinst
@@ -0,0 +1,42 @@
+#!/bin/sh
+# postinst script for resteasy
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <postinst> `abort-remove'
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see https://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ configure)
+ touch /etc/resteasy
+ chown root:root /etc/resteasy
+ chmod 0600 /etc/resteasy
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+# dh_installdeb will replace this with shell code automatically
+# generated by other debhelper scripts.
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/resteasy.service b/debian/resteasy.service
new file mode 100644
index 0000000..f2a81ae
--- /dev/null
+++ b/debian/resteasy.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=backup system
+
+[Service]
+Type=oneshot
+ExecStart=/usr/sbin/resteasy mount
+ExecStart=/usr/sbin/resteasy run-backup
+ExecStopPost=/usr/sbin/resteasy umount
diff --git a/debian/resteasy.udev b/debian/resteasy.udev
new file mode 100644
index 0000000..a6c2bcb
--- /dev/null
+++ b/debian/resteasy.udev
@@ -0,0 +1,6 @@
+ACTION=="add", \
+SUBSYSTEM=="block", \
+ENV{ID_FS_USAGE}=="filesystem", \
+ENV{ID_PART_ENTRY_TYPE}=="8e8e0bae-5627-4acd-8fd9-70873806d42e", \
+TAG+="systemd", \
+ENV{SYSTEMD_WANTS}="resteasy.service"
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..1a0e016
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,11 @@
+#!/usr/bin/make -f
+
+%:
+ dh $@
+
+override_dh_installman:
+ mkdir -p debian/resteasy/usr/share/man/man8/
+ pandoc --standalone --from markdown-smart --to man resteasy.8.md | \
+ gzip -9 --no-name --stdout > \
+ debian/resteasy/usr/share/man/man8/resteasy.8.gz
+ dh_installman
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..89ae9db
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (native)
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..f8b7718
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,16 @@
+# Resteasy
+
+simple plug and play backup system
+
+See the [manpage](./resteasy.8.md) for a description.
+
+## Building
+
+Resteasy is implemented as a debian source package. It can be built with all the
+regular tools, for instance `debuild -uc -us`.
+
+### Build and install locally
+
+```
+debuild -uc -us && sudo dpkg --purge resteasy resteasy-desktop && sudo dpkg --install ../*.deb
+```
diff --git a/resteasy-desktop/libexec/resteasy-desktop-monitor b/resteasy-desktop/libexec/resteasy-desktop-monitor
new file mode 100755
index 0000000..867eb5d
--- /dev/null
+++ b/resteasy-desktop/libexec/resteasy-desktop-monitor
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+import subprocess
+import gi
+gi.require_version('Notify', '0.7')
+from gi.repository import Notify
+from gi.repository import GLib
+from pydbus.generic import signal
+from pydbus import SystemBus
+
+loop = GLib.MainLoop()
+bus = SystemBus()
+
+
+def handle_signal(sender, object, iface, signal, args):
+ message = str(args[0])
+ Notify.init("resteasy")
+ notification = Notify.Notification.new("resteasy", message,
+ "drive-harddisk")
+ notification.set_urgency(
+ 2
+ ) # 2 highest level, shouldn't go away until a user acknowledges the message
+ notification.show()
+ subprocess.call(["pkill", "-SIGRTMIN+10", "i3blocks"])
+
+
+if __name__ == "__main__":
+ # subscribe to bus to monitor for server signal emissions
+ bus.subscribe(object="/io/crashbox/resteasy/Main",
+ iface="io.crashbox.resteasy.Info",
+ signal_fired=handle_signal)
+ loop.run()
diff --git a/resteasy.8.md b/resteasy.8.md
new file mode 100644
index 0000000..94ed5aa
--- /dev/null
+++ b/resteasy.8.md
@@ -0,0 +1,186 @@
+% resteasy(8)
+% Jakob Odersky <jakob@odersky.com>
+% February 2020
+
+# NAME
+
+resteasy \- plug and play backups with borg
+
+# SYNOPSIS
+
+**resteasy** `<command>` `[<args>]`
+
+# DESCRIPTION
+
+Resteasy is a set of commands and services around Borg Backup. It provides a
+system for automating backups to external hard drives without user interaction.
+
+The idea is that a user simply plugs in a prepared drive, and a backup is
+automatically started.
+
+Note that although resteasy supports backing up the same system to many prepared
+drives, only one may be connected at a time. This is because resteasy is geared
+towards simple command invocation and hence always mounts drives to a hardcoded
+location. No data loss will occur if multiple drives are connected at the same
+time (and any currently running backups will not be interrupted), however many
+resteasy commands will not work until only one compatible drive is connected.
+
+## Internals
+
+Resteasy works with GPT-formatted (GUID Partition Table) drives. When a block
+device with a partition type of 8e8e0bae-5627-4acd-8fd9-70873806d42e is
+detected, it is mounted and a backup is created using borg.
+
+Resteasy is implemented as a set of many individual commands (see [RESTEASY
+COMMANDS](#RESTEASY-COMMANDS) section). Their implementation structure is
+heavily inspired by Git.
+
+See also the output of **systemctl cat resteasy.service** for a list of commands
+the automatic backup service runs, and **/usr/libexec/resteasy/** for their
+implementations.
+
+## Security Considerations
+
+Automatically mounting and executing a backup when a drive is connected has
+security implications. An attacker could simply insert a resteasy drive to
+exfiltrate all data. Note however that this vector is mitigated because:
+
+1. Resteasy uses encrypted borg repositories.
+2. Physical access is required for this attack.
+3. Someone with physical access could do something much more nefarious, for
+ example steal the machine.
+
+# OPTIONS
+
+**--exec-path**
+: Path to wherever your core resteasy programs are installed. This will print
+the current setting and then exit.
+
+# RESTEASY COMMANDS
+
+Resteasy is divided into high level ("porcelain") commands and low level
+("plumbing") commands.
+
+# HIGH-LEVEL COMMANDS
+
+**resteasy-inhibit**
+
+: Pause the automatic backup service temporarily, until **resteasy-uninhibit** is
+called or the system is rebooted. Note that this will also stop any backups that
+are in progress.
+
+**resteasy-init**
+
+: Initialize a borg repository. The drive containing the repository should have
+previously been mounted with **resteasy-mount**.
+
+**resteasy-mount** `[ro]`
+
+: Find and mount a connected resteasy drive. Resteasy drives are GPT partitions
+identified by type UUID `8e8e0bae-5627-4acd-8fd9-70873806d42e`.
+
+**resteasy-prepare-drive** `<drive>`
+
+: Prepare a drive to be recognized by the resteasy system.
+
+ Once prepared, drives will trigger automatic mounts and backups. It is hence
+ recommended that the autobackup service be inhibited before running this
+ operation (otherwise a backup will be attempted on an uninitialized repo,
+ causing an error to be reported). **Note that this operation reformats the
+ drive.**
+
+**resteasy-umount**
+
+: Unmount a previously mounted resteasy drive.
+
+**resteasy-uninhibit**
+
+: Resume the automatic backup service. Note that this will NOT start new backup
+runs; any connected resteasy drives must be disconnected and reconnected to
+start a new backup.
+
+# LOW-LEVEL COMMANDS
+
+**resteasy-find-uuid**
+
+: Find the GPT partition UUID of a connected resteasy drive. Note that exactly
+one resteasy drive mamusty be connected to the system. This command will fail if
+it detects more than one.
+
+**resteasy-run-backup**
+
+: Trigger a backup. Note that this is typically not run by the user but by the
+init system when a resteasy drive is connected. It requires that a drive be
+mounted with **resteasy-mount** beforehand.
+
+# FILES
+
+/etc/resteasy
+
+: Contains the passphrase used by borg to encrypt backups. Trailing new lines
+are stripped.
+
+# EXAMPLES
+
+## Prepare a New Drive
+
+```
+# temporarily stop the automatic backup service
+resteasy inhibit
+
+# <connect drive>, assumed /dev/sdc in this example
+
+# set the passphrase for encrypting resteasy backups
+echo "1234" > /etc/resteasy
+
+# wipe and prepare the new drive to be recognized by resteasy
+resteasy prepare-drive /dev/sdc
+
+# initialize new borg repo
+resteasy mount
+resteasy init
+resteasy umount
+
+# re-enable automatic backup service
+resteasy uninhibit
+```
+
+## Create a Backup
+
+Simply plug in the drive!
+
+You can watch backup logs by running:
+
+```
+journalctl -u resteasy.service -f
+```
+
+Unplug drive when done.
+
+## Create a Backup Manually
+
+```
+resteasy mount
+resteasy run-backup
+resteasy umount
+```
+
+## Restore Data
+
+```
+resteasy inhibit
+# <connect resteasy drive>
+resteasy mount ro
+
+# perform restore operations
+# e.g. borg mount /run/resteasy/mounts/... /mnt
+# borg extract...
+# ...
+
+resteasy umount
+resteasy uninhibit
+```
+
+# SEE ALSO
+
+Borg Backup https://borgbackup.readthedocs.io/en/stable/
diff --git a/resteasy/libexec/resteasy-find-uuid b/resteasy/libexec/resteasy-find-uuid
new file mode 100755
index 0000000..331d10a
--- /dev/null
+++ b/resteasy/libexec/resteasy-find-uuid
@@ -0,0 +1,22 @@
+#!/bin/bash
+USAGE=""
+
+# shellcheck source=resteasy-helper.sh
+source "$(resteasy --exec-path)/resteasy-helper.sh"
+
+set -o errexit
+set -o nounset
+
+[[ "$#" -eq 0 ]] || die_with_usage "too many arguments"
+
+mapfile -t uuids < <(lsblk \
+ --noheadings \
+ --list \
+ --output PARTTYPE,PARTUUID \
+ | grep "$RESTEASY_TYPE_UUID" \
+ | cut -d' ' -f 2)
+
+[[ ${#uuids[@]} -gt 0 ]] || die "no resteasy device found"
+[[ ${#uuids[@]} -lt 2 ]] || die "more than one resteasy devices found; this command only supports one device plugged in at a time"
+
+echo "${uuids[0]}"
diff --git a/resteasy/libexec/resteasy-helper.sh b/resteasy/libexec/resteasy-helper.sh
new file mode 100644
index 0000000..6547626
--- /dev/null
+++ b/resteasy/libexec/resteasy-helper.sh
@@ -0,0 +1,45 @@
+# shellcheck shell=bash
+# shellcheck disable=SC2034
+
+# This shell-scriptlet provides utility functions to other resteasy scripts.
+# It is meant to be sourced, and should never be executed directly.
+
+# override $0 to the command's pretty name
+BASH_ARGV0=$(basename "$0" | sed 's/-/ /')
+
+RESTEASY_TYPE_UUID=8e8e0bae-5627-4acd-8fd9-70873806d42e
+RESTEASY_MOUNT_POINT=/run/resteasy/mount
+
+die() {
+ printf >&2 '%s\n' "$*"
+ exit 1
+}
+
+usage() {
+ echo "usage: $0 $USAGE"
+ exit 1
+}
+
+die_with_usage() {
+ printf >&2 '%s\n' "$*"
+ echo "usage: $0 $USAGE" >&2
+ exit 1
+}
+
+
+log_status() {
+ echo "status: $1" >&2
+ # Send status to a dbus channel, useful for integration with desktop
+ # notifications.
+ # Note the "|| true" prevents this function from failing in case dbus is not
+ # available.
+ dbus-send --system --type=signal \
+ /io/crashbox/resteasy/Main \
+ "io.crashbox.resteasy.Info.Status" \
+ "string:$1" || true
+}
+
+if [[ ${1:-} == '-h' ]] || [[ ${1:-} == '--help' ]]; then
+ echo "usage: $0 $USAGE"
+ exit 0
+fi
diff --git a/resteasy/libexec/resteasy-inhibit b/resteasy/libexec/resteasy-inhibit
new file mode 100755
index 0000000..0a4b5e6
--- /dev/null
+++ b/resteasy/libexec/resteasy-inhibit
@@ -0,0 +1,13 @@
+#!/bin/bash
+USAGE=""
+
+# shellcheck source=resteasy-helper.sh
+source "$(resteasy --exec-path)/resteasy-helper.sh"
+
+set -o errexit
+set -o nounset
+
+[[ "$#" -eq 0 ]] || die_with_usage "too many arguments"
+[[ $EUID -eq 0 ]] || die "must be root"
+
+systemctl --no-ask-password --quiet mask --now --runtime resteasy.service
diff --git a/resteasy/libexec/resteasy-init b/resteasy/libexec/resteasy-init
new file mode 100755
index 0000000..3b98bc5
--- /dev/null
+++ b/resteasy/libexec/resteasy-init
@@ -0,0 +1,16 @@
+#!/bin/bash
+USAGE=""
+
+# shellcheck source=resteasy-helper.sh
+source "$(resteasy --exec-path)/resteasy-helper.sh"
+
+set -o errexit
+set -o nounset
+
+[[ $# -eq 0 ]] || die_with_usage "too many arguments"
+mountpoint -q "$RESTEASY_MOUNT_POINT" || die "no device mounted on $RESTEASY_MOUNT_POINT; run 'resteasy mount' beforehand"
+export BORG_REPO="$RESTEASY_MOUNT_POINT/borgbackup/$HOSTNAME"
+export BORG_PASSCOMMAND="cat /etc/resteasy"
+
+mkdir -p "$BORG_REPO"
+borg init --encryption=repokey
diff --git a/resteasy/libexec/resteasy-mount b/resteasy/libexec/resteasy-mount
new file mode 100755
index 0000000..05bbb2a
--- /dev/null
+++ b/resteasy/libexec/resteasy-mount
@@ -0,0 +1,27 @@
+#!/bin/bash
+USAGE="[ro]"
+
+# shellcheck source=resteasy-helper.sh
+source "$(resteasy --exec-path)/resteasy-helper.sh"
+
+set -o errexit
+set -o nounset
+
+flags=
+if [[ "$#" -ge 1 ]]; then
+ case "$1" in
+ ro)
+ shift
+ flags+=--read-only
+ ;;
+ *)
+ die_with_usage "unknown option: $1"
+ ;;
+ esac
+fi
+
+uuid=$(resteasy find-uuid)
+mkdir -p "$RESTEASY_MOUNT_POINT"
+mount $flags "/dev/disk/by-partuuid/$uuid" "$RESTEASY_MOUNT_POINT"
+
+echo "$RESTEASY_MOUNT_POINT"
diff --git a/resteasy/libexec/resteasy-prepare-drive b/resteasy/libexec/resteasy-prepare-drive
new file mode 100755
index 0000000..6da4899
--- /dev/null
+++ b/resteasy/libexec/resteasy-prepare-drive
@@ -0,0 +1,35 @@
+#!/bin/bash
+USAGE="<block_device>"
+
+# shellcheck source=resteasy-helper.sh
+source "$(resteasy --exec-path)/resteasy-helper.sh"
+
+set -o errexit
+set -o nounset
+
+[[ $# -eq 1 ]] || die_with_usage "no device given"
+device="$1"
+[[ -b $device ]] || die "$device is not a block device"
+
+echo "WARNING: this will erase all data on $device." >&2
+read -r -p "Continue? yes/[no]" yn >&2
+[[ $yn == "yes" ]] || die "aborting"
+
+sgdisk \
+ --zap-all \
+ --clear \
+ --new=1:0:0 \
+ --change-name=1:resteasy \
+ --typecode=1:"$RESTEASY_TYPE_UUID" \
+ "$device"
+
+# the kernel needs some time to update partition information
+sleep 5
+sgdisk --info=1 "$device"
+
+# note the '2p': this extract the second line of lsblk, which will correspond to
+# the first partition on the device
+partition_uuid=$(lsblk "$device" --noheadings --output=PARTUUID | sed --quiet 2p)
+[[ -n $partition_uuid ]] || die "cannot determine partition UUID after reformatting"
+
+mkfs.ext4 "/dev/disk/by-partuuid/$partition_uuid"
diff --git a/resteasy/libexec/resteasy-run-backup b/resteasy/libexec/resteasy-run-backup
new file mode 100755
index 0000000..4d2cfb2
--- /dev/null
+++ b/resteasy/libexec/resteasy-run-backup
@@ -0,0 +1,74 @@
+#!/bin/bash
+USAGE=""
+
+# shellcheck source=resteasy-helper.sh
+source "$(resteasy --exec-path)/resteasy-helper.sh"
+
+set -o errexit
+set -o nounset
+
+[[ "$#" -eq 0 ]] || die_with_usage "too many arguments"
+[[ $EUID -eq 0 ]] || die "must be root"
+
+failed() {
+ log_status "errors encountered during backup"
+}
+trap failed EXIT
+
+borg --version
+
+# location of the mounted borg repository
+export BORG_REPO="$RESTEASY_MOUNT_POINT/borgbackup/$HOSTNAME"
+
+# set some environment variables to prevent borg from asking interactive questions
+export BORG_RELOCATED_REPO_ACCESS_IS_OK=no # relocated repos aren't cache-friendly
+export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=no
+export BORG_PASSCOMMAND="cat /etc/resteasy"
+
+# backup the system into an archive named after
+# the machine this script is currently running on
+log_status "starting backup"
+borg create \
+ --verbose \
+ --filter AME \
+ --list \
+ --stats \
+ --show-rc \
+ --compression lz4 \
+ --exclude-caches \
+ --exclude '/var/cache/' \
+ --exclude '/var/tmp/' \
+ --exclude '/root/.cache/' \
+ --exclude '/var/lib/docker/devicemapper/' \
+ --exclude '/home/*/.cache/' \
+ --exclude '/home/*/.local/share/Steam' \
+ ::'{hostname}-{now}' \
+ /etc \
+ /home \
+ /root \
+ /srv \
+ /usr/local \
+ /var
+
+# unnecessary, but just to be extra sure that data has made it onto disk
+sync
+
+log_status "pruning repository"
+
+# Use the `prune` subcommand to maintain archives of THIS machine. The
+# '{hostname}-' prefix is very important to limit prune's operation to this
+# machine's archives and not apply to other machines' archives too
+borg prune \
+ --list \
+ --prefix '{hostname}-' \
+ --show-rc \
+ --keep-daily 7 \
+ --keep-weekly 4 \
+ --keep-monthly 12 \
+ --keep-yearly 10
+
+borg check
+
+log_status "backup completed successfully"
+trap - EXIT
+
diff --git a/resteasy/libexec/resteasy-umount b/resteasy/libexec/resteasy-umount
new file mode 100755
index 0000000..08c0451
--- /dev/null
+++ b/resteasy/libexec/resteasy-umount
@@ -0,0 +1,12 @@
+#!/bin/bash
+USAGE=""
+
+# shellcheck source=resteasy-helper.sh
+source "$(resteasy --exec-path)/resteasy-helper.sh"
+
+set -o errexit
+set -o nounset
+
+[[ "$#" -eq 0 ]] || die_with_usage "too many arguments"
+
+umount "$RESTEASY_MOUNT_POINT"
diff --git a/resteasy/libexec/resteasy-uninhibit b/resteasy/libexec/resteasy-uninhibit
new file mode 100755
index 0000000..08371ec
--- /dev/null
+++ b/resteasy/libexec/resteasy-uninhibit
@@ -0,0 +1,13 @@
+#!/bin/bash
+USAGE=""
+
+# shellcheck source=resteasy-helper.sh
+source "$(resteasy --exec-path)/resteasy-helper.sh"
+
+set -o errexit
+set -o nounset
+
+[[ "$#" -eq 0 ]] || die_with_usage "too many arguments"
+[[ $EUID -eq 0 ]] || die "must be root"
+
+systemctl --no-ask-password --quiet unmask --runtime resteasy.service
diff --git a/resteasy/sbin/resteasy b/resteasy/sbin/resteasy
new file mode 100755
index 0000000..5b3237d
--- /dev/null
+++ b/resteasy/sbin/resteasy
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+set -o errexit
+
+exec_path="/usr/libexec/resteasy"
+
+case $1 in
+ --exec-path)
+ echo "$exec_path"
+ exit 0
+ ;;
+ -*)
+ echo "resteasy: unrecognized option $1" >&2
+ exit 1
+ ;;
+esac
+
+if [[ -z "$1" ]]; then
+ echo "usage: resteasy <command> <args>"
+ exit 1
+elif command -v "resteasy-$1" > /dev/null; then
+ cmd="resteasy-$1"
+ shift
+ exec "$cmd" "$@"
+elif [[ -x $exec_path/resteasy-$1 ]]; then
+ cmd="$exec_path/resteasy-$1"
+ shift
+ exec "$cmd" "$@"
+else
+ echo "resteasy: '$1' is not a valid command"
+ exit 1
+fi