Move the admin and site menu entries into a shared 'Backups' sidebar category (created idempotently by each sibling plugin) with a distinct icon per engine, instead of a top-level link. Each plugin tags its category+link lines with an apiscp-borg marker so its uninstaller removes only its own, leaving the shared category for any other backup plugin still installed.
148 lines
7.9 KiB
Bash
148 lines
7.9 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'
|
|
|
|
$templateClass->create_category('Backups', true, '<svg role="img" fill="currentColor" class="ui-menu-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7l-4-4zm-5 16a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm3-10H5V5h10v4z"/></svg>', 'backups'); // apiscp-borg
|
|
$templateClass->create_link('Borg', '/apps/borg', true, '<svg role="img" fill="currentColor" class="ui-menu-inline-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/></svg>', 'backups'); // apiscp-borg
|
|
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'
|
|
|
|
$templateClass->create_category('Backups', true, '<svg role="img" fill="currentColor" class="ui-menu-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7l-4-4zm-5 16a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm3-10H5V5h10v4z"/></svg>', 'backups'); // apiscp-borg
|
|
$templateClass->create_link('Borg', '/apps/myborgbackups', true, '<svg role="img" fill="currentColor" class="ui-menu-inline-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/></svg>', 'backups'); // apiscp-borg
|
|
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
|