feat(borg): Layer 2 panel integration (module + two GUIs + hooks + installers)
Native ApisCP integration, adapted from apiscp-kopia's Layer 2 against the real borg Layer 1 subcommands: - Borg_Module_Surrogate: admin + PRIVILEGE_SITE verbs, sudo boundary, config whitelist (now enforced), site-owner verbs derive the site from the auth context only, ownsDatabase gate, confirm-guarded destructive import. - Admin GUI (apps/borg, "Borg Backups"): repository (BORG_REPO/passphrase/ encryption/RSH, check-access, init, one-time recovery-key panel + download + irrecoverable tick), backup selection, retention + excludes + prune-now, schedule, maintenance (compact) + verify (check), notifications, tabbed layout, running overlay, double-submit guard, consolidated Restore workflow. - Site-owner GUI (apps/myborgbackups, "My Borg Backups"): restore files / whole account / databases, run own backup, scoped to the caller's own site. - Account hooks, install-layer2.sh, uninstall.sh (safe default, --purge). Borg differences vs kopia are handled: no backend types/sftp/server, no browse/ subpath or point-in-time (restores newest archive; archive dates shown for info), retention via config + `borg prune` (no global policy), import-db takes no dumpfile. All PHP lints clean; controller verb calls all resolve to the module.
This commit is contained in:
parent
3ce5bf1a47
commit
02403b72c0
14 changed files with 2681 additions and 0 deletions
148
install-layer2.sh
Normal file
148
install-layer2.sh
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# install-layer2.sh: install the native ApisCP integration (module + GUI apps +
|
||||
# menu links + hooks) into their upgrade-safe locations.
|
||||
#
|
||||
# Every target is a git-ignored path ApisCP preserves across upgrades:
|
||||
# - module -> $CP_ROOT/lib/modules/surrogates/borg.php (.gitignore: surrogates/*)
|
||||
# - admin app -> $CP_ROOT/config/custom/apps/borg/ (config/custom is all-ignored)
|
||||
# - site app -> $CP_ROOT/config/custom/apps/myborgbackups/ (site-owner edition)
|
||||
# - admin menu-> $CP_ROOT/config/custom/templates/admin.php
|
||||
# - site menu -> $CP_ROOT/config/custom/templates/site.php
|
||||
#
|
||||
# Run as root on an ApisCP server AFTER install.sh (which installs the Layer 1
|
||||
# binaries the module calls). Idempotent.
|
||||
|
||||
set -eu
|
||||
|
||||
[ "$(id -u)" = 0 ] || { echo "run as root" >&2; exit 1; }
|
||||
SRC=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
|
||||
# Locate ApisCP.
|
||||
CP_ROOT="${CP_ROOT:-/usr/local/apnscp}"
|
||||
[ -d "$CP_ROOT/lib/modules" ] || { echo "ApisCP not found at $CP_ROOT (set CP_ROOT)" >&2; exit 1; }
|
||||
OWNER=apnscp
|
||||
|
||||
echo "CP_ROOT = $CP_ROOT"
|
||||
|
||||
# 1. Backend module.
|
||||
echo "installing module -> lib/modules/surrogates/borg.php"
|
||||
install -d -o "$OWNER" -g "$OWNER" "$CP_ROOT/lib/modules/surrogates"
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/modules/borg.php" "$CP_ROOT/lib/modules/surrogates/borg.php"
|
||||
|
||||
# 2. GUI app (whole tree).
|
||||
echo "installing app -> config/custom/apps/borg/"
|
||||
install -d -o "$OWNER" -g "$OWNER" "$CP_ROOT/config/custom/apps/borg/views"
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/apps/borg/borg.php" "$CP_ROOT/config/custom/apps/borg/borg.php"
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/apps/borg/application.yml" "$CP_ROOT/config/custom/apps/borg/application.yml"
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/apps/borg/views/index.blade.php" "$CP_ROOT/config/custom/apps/borg/views/index.blade.php"
|
||||
|
||||
# 2b. Site-owner GUI app (whole tree). Same module, scoped PRIVILEGE_SITE verbs.
|
||||
echo "installing site-owner app -> config/custom/apps/myborgbackups/"
|
||||
install -d -o "$OWNER" -g "$OWNER" "$CP_ROOT/config/custom/apps/myborgbackups/views"
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/apps/myborgbackups/myborgbackups.php" "$CP_ROOT/config/custom/apps/myborgbackups/myborgbackups.php"
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/apps/myborgbackups/application.yml" "$CP_ROOT/config/custom/apps/myborgbackups/application.yml"
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/apps/myborgbackups/views/index.blade.php" "$CP_ROOT/config/custom/apps/myborgbackups/views/index.blade.php"
|
||||
|
||||
# 3. Menu link. Append to an existing custom admin.php rather than clobber it.
|
||||
echo "installing menu link -> config/custom/templates/admin.php"
|
||||
install -d -o "$OWNER" -g "$OWNER" "$CP_ROOT/config/custom/templates"
|
||||
DEST="$CP_ROOT/config/custom/templates/admin.php"
|
||||
if [ -f "$DEST" ]; then
|
||||
if grep -q "/apps/borg" "$DEST"; then
|
||||
echo " link already present; leaving $DEST untouched"
|
||||
else
|
||||
echo " appending create_link() to existing $DEST"
|
||||
if grep -q '?>' "$DEST"; then
|
||||
echo " WARNING: $DEST contains a closing ?>; add the create_link() call by hand" >&2
|
||||
else
|
||||
cat >> "$DEST" <<'PHP'
|
||||
|
||||
// apiscp-borg
|
||||
$templateClass->create_link('Borg Backups', '/apps/borg', true, null, null);
|
||||
PHP
|
||||
fi
|
||||
fi
|
||||
else
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/templates/admin.php" "$DEST"
|
||||
fi
|
||||
|
||||
# 3b. Site-owner menu link. Same append-not-clobber logic for the site template.
|
||||
echo "installing site menu link -> config/custom/templates/site.php"
|
||||
SITE_DEST="$CP_ROOT/config/custom/templates/site.php"
|
||||
if [ -f "$SITE_DEST" ]; then
|
||||
if grep -q "/apps/myborgbackups" "$SITE_DEST"; then
|
||||
echo " link already present; leaving $SITE_DEST untouched"
|
||||
elif grep -q '?>' "$SITE_DEST"; then
|
||||
echo " WARNING: $SITE_DEST contains a closing ?>; add the create_link() call by hand" >&2
|
||||
else
|
||||
echo " appending create_link() to existing $SITE_DEST"
|
||||
cat >> "$SITE_DEST" <<'PHP'
|
||||
|
||||
// apiscp-borg (site-owner)
|
||||
$templateClass->create_link('My Borg Backups', '/apps/myborgbackups', true, null, 'account');
|
||||
PHP
|
||||
fi
|
||||
else
|
||||
install -m 0644 -o "$OWNER" -g "$OWNER" "$SRC/src/templates/site.php" "$SITE_DEST"
|
||||
fi
|
||||
|
||||
# 3c. Account-lifecycle hooks. ApisCP runs config/custom/hooks/<event>.sh after
|
||||
# create/suspend/delete with the site id as the first argument. Append-safe:
|
||||
# only install ours if not already present (do not clobber operator hooks).
|
||||
echo "installing account hooks -> config/custom/hooks/"
|
||||
install -d -o "$OWNER" -g "$OWNER" "$CP_ROOT/config/custom/hooks"
|
||||
for _hook in addDomain suspendDomain deleteDomain; do
|
||||
_dest="$CP_ROOT/config/custom/hooks/$_hook.sh"
|
||||
if [ -e "$_dest" ] && ! grep -q 'apiscp-borg' "$_dest" 2>/dev/null; then
|
||||
echo " $_hook.sh exists and is not ours; leaving it (add our call by hand if wanted)"
|
||||
else
|
||||
install -m 0755 -o "$OWNER" -g "$OWNER" "$SRC/src/hooks/$_hook.sh" "$_dest"
|
||||
echo " installed $_hook.sh"
|
||||
fi
|
||||
done
|
||||
|
||||
# 4. Sudoers. The panel runs the module as the unprivileged ApisCP user, but the
|
||||
# Borg repository and /home/virtual are root-only. Let that user run ONLY the
|
||||
# three plugin binaries as root, nothing else. The binaries validate their own
|
||||
# inputs and are the privilege boundary.
|
||||
PANEL_USER="${PANEL_USER:-apnscp}"
|
||||
echo "installing sudoers -> /etc/sudoers.d/apiscp-borg (user: $PANEL_USER)"
|
||||
_sudo_tmp=$(mktemp)
|
||||
cat > "$_sudo_tmp" <<EOF
|
||||
# Installed by apiscp-borg install-layer2.sh. Do not edit by hand.
|
||||
Defaults:$PANEL_USER !requiretty
|
||||
Cmnd_Alias APISCP_BORG_CMDS = /usr/local/bin/borg-apiscp-backup, /usr/local/bin/borg-apiscp-restore, /usr/local/bin/borg-apiscp-repo
|
||||
$PANEL_USER ALL=(root) NOPASSWD: APISCP_BORG_CMDS
|
||||
EOF
|
||||
if visudo -cf "$_sudo_tmp" >/dev/null 2>&1; then
|
||||
install -m 0440 -o root -g root "$_sudo_tmp" /etc/sudoers.d/apiscp-borg
|
||||
echo " sudoers installed and validated"
|
||||
else
|
||||
echo " WARNING: sudoers validation failed; the GUI will not be able to run as root" >&2
|
||||
visudo -cf "$_sudo_tmp" 2>&1 | sed 's/^/ /' >&2
|
||||
fi
|
||||
rm -f "$_sudo_tmp"
|
||||
|
||||
# Keep the engine config root-only; it holds the repository passphrase and is
|
||||
# read/written by the root binaries, never directly by the panel user.
|
||||
[ -f /etc/apiscp-borg/config ] && chown root:root /etc/apiscp-borg/config && chmod 0600 /etc/apiscp-borg/config
|
||||
|
||||
# Restart the panel so it picks up the new apps and menus (module is picked up
|
||||
# on next CLI/API call without a restart).
|
||||
echo "restarting ApisCP panel"
|
||||
systemctl restart apnscp || echo " restart apnscp yourself to see the new menu entry" >&2
|
||||
|
||||
cat <<'EOF'
|
||||
|
||||
Layer 2 installed. Verify:
|
||||
cpcmd borg:sites
|
||||
cpcmd borg:status
|
||||
Then load the appliance-admin panel; "Borg Backups" appears in the menu.
|
||||
|
||||
Site owners get a scoped "My Borg Backups" entry in the site panel (Account
|
||||
section) backed by the PRIVILEGE_SITE verbs (borg:my_site, my_snapshots,
|
||||
restore_my_files, my_databases, my_database_backups, restore_my_database,
|
||||
import_my_database, restore_my_account, run_my_backup). The menu is
|
||||
session-cached, so a site owner sees it after a fresh login.
|
||||
EOF
|
||||
Loading…
Add table
Add a link
Reference in a new issue