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.
148 lines
6.8 KiB
Bash
148 lines
6.8 KiB
Bash
#!/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
|