feat(desudo): helper-ize backup-engine + app-config installs; retire standalone WireGuard

Bring the remaining deferred subsystems under the scoped sudoers, and drop
the one that's redundant.

Backup engines + app configs -> root-owned helpers (same pattern as
ownership/dns/ssh/socket/svc):
- scripts/system/libreportal-bininstall: install <restic|kopia> — does the
  whole pkg-manager/signed-download install itself for a fixed, validated
  engine name (no blanket sudo apt-get/install). restic_install/kopia_install
  call it.
- scripts/system/libreportal-appcfg: {adguard-auth <user> <bcrypt>|
  crowdsec-priority|owncloud-config <public> <host> <ip> <public_ip>} —
  faithful ports of the AdGuard yaml / CrowdSec bouncer / ownCloud config.php
  rewrites, fixed paths + validated args. adguard_auth/crowdsec_fix_priority/
  owncloud_setup_config call it.
- run_privileged: runBinInstall / runAppCfg; init.sh installs + allowlists both.

Retire standalone (host-level) WireGuard — it's a duplicate of the
containerized containers/wireguard app (+ headscale mesh), its slirp4netns
speed rationale is largely moot with a better rootless net backend / typical
WAN-bound throughput, and it was the heaviest host-root subsystem (apt +
sysctl + iptables + /etc/wireguard), the worst fit for the rootless/
least-privilege direction:
- moved scripts/wireguard/ + manage_wireguard.sh + check_wireguard.sh to
  scripts/unused/; dropped the install-path call, the Tools menu 'w' entry,
  and the requirement check; removed the half-built libreportal-wg helper.
- generate_arrays.sh now also skips system/ (root-owned helpers, never
  sourced); arrays regenerated (files_wireguard.sh pruned).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-24 19:22:22 +01:00
parent 048e967ec1
commit cd4fd55a6d
27 changed files with 261 additions and 324 deletions

View File

@ -713,7 +713,9 @@ Cmnd_Alias LP_HELPERS = /usr/local/sbin/libreportal-ownership, \\
/usr/local/sbin/libreportal-dns, \\
/usr/local/sbin/libreportal-ssh-access, \\
/usr/local/sbin/libreportal-socket, \\
/usr/local/sbin/libreportal-svc
/usr/local/sbin/libreportal-svc, \\
/usr/local/sbin/libreportal-bininstall, \\
/usr/local/sbin/libreportal-appcfg
Cmnd_Alias LP_SYSTEM = /usr/bin/systemctl, /usr/sbin/ufw, /usr/local/bin/ufw-docker, \\
/usr/sbin/nft, /usr/sbin/sysctl, /sbin/sysctl, \\
/usr/bin/loginctl, /usr/sbin/service
@ -752,7 +754,7 @@ EOF
initRootHelpers()
{
local helper helper_src helper_dst helper_tmp
for helper in libreportal-ownership libreportal-dns libreportal-ssh-access libreportal-socket libreportal-svc; do
for helper in libreportal-ownership libreportal-dns libreportal-ssh-access libreportal-socket libreportal-svc libreportal-bininstall libreportal-appcfg; do
helper_src="$script_dir/scripts/system/$helper"
helper_dst="/usr/local/sbin/$helper"
if [[ ! -f "$helper_src" ]]; then

View File

@ -16,26 +16,12 @@ authAdapter_adguard_setPassword() {
bcrypt=$(htpasswd -bnBC 10 "" "$password" | tr -d ':\n')
[[ -z "$bcrypt" ]] && { isError "bcrypt failed."; return 1; }
local tmp
tmp=$(runSystem mktemp)
if ! runSystem awk -v u="$user" -v pw="$bcrypt" '
/^users:/ { in_users=1; print; next }
in_users && /^[^[:space:]-]/ { in_users=0 }
in_users && /^[[:space:]]+name:/ && !done_user {
match($0, /^[[:space:]]+/); print substr($0, RSTART, RLENGTH) "name: " u; done_user=1; next
}
in_users && /^[[:space:]]+password:/ && !done_pw {
match($0, /^[[:space:]]+/); print substr($0, RSTART, RLENGTH) "password: " pw; done_pw=1; next
}
{ print }
END { exit (done_pw ? 0 : 1) }
' "$yaml" | runSystem tee "$tmp" >/dev/null; then
runSystem rm -f "$tmp"
isError "AdGuardHome.yaml does not contain a 'users:' password line."
# The yaml is owned by the in-container uid, so the rewrite runs in the
# root-owned appcfg helper (fixed path, validated user + bcrypt).
if ! runAppCfg adguard-auth "$user" "$bcrypt"; then
isError "Could not update AdGuardHome.yaml (no users password line, or invalid input)."
return 1
fi
runSystem cp "$tmp" "$yaml"
runSystem rm -f "$tmp"
authPersistCfg adguard ADMIN_USER "$user"
authPersistCfg adguard ADMIN_PASSWORD "$password"

View File

@ -7,30 +7,10 @@ appCrowdSecFixPriority() {
return 1
fi
local target_priority="-100"
runSystem cp "$cfg" "${cfg}.bak.$(date +%Y%m%d-%H%M%S)"
checkSuccess "Backed up $cfg"
# nftables section in the yaml has ipv4: and ipv6: subsections; each may
# carry a priority: line. Set both to target_priority, inserting the key
# if it isn't present. We hand the file to a small awk pass so the YAML
# indentation is preserved.
runSystem awk -v p="$target_priority" '
BEGIN { in_v4=0; in_v6=0; v4_done=0; v6_done=0 }
/^[[:space:]]*ipv4:/ { in_v4=1; in_v6=0; print; next }
/^[[:space:]]*ipv6:/ { in_v6=1; in_v4=0; print; next }
/^[a-zA-Z]/ {
# Top-level key — close any open subsection. If we never saw
# priority inside the subsection, inject it now (rare).
in_v4=0; in_v6=0
}
in_v4 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p); v4_done=1 }
in_v6 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p); v6_done=1 }
{ print }
' "$cfg" | runSystem tee "${cfg}.new" >/dev/null
runSystem mv "${cfg}.new" "$cfg"
checkSuccess "Patched nftables priority to $target_priority in $cfg"
# The bouncer yaml is root-owned under /etc/crowdsec; the backup + nftables
# ipv4/ipv6 priority rewrite (to -100) runs in the root-owned appcfg helper.
runAppCfg crowdsec-priority
checkSuccess "Patched nftables priority to -100 in $cfg"
runSystem systemctl restart crowdsec-firewall-bouncer
checkSuccess "Restarted crowdsec-firewall-bouncer"

View File

@ -11,121 +11,16 @@ appOwnCloudSetupConfig()
# If container is healthy
if dockerCheckContainerHealth "owncloud"; then
isSuccessful "OwnCloud container is healthy, continuing with the install."
local app_name="owncloud"
local tmp_folder="/tmp/owncloud_setup_temp"
local owncloud_config_folder="$containers_dir$app_name/files/config"
local owncloud_config="${owncloud_config_folder}/config.php"
local owncloud_timeout=60
local owncloud_wait_time=5 # seconds
# Remove the temporary folder in /tmp
result=$(runSystem rm -rf "$owncloud_config")
checkSuccess "Removed default config.php"
# Check when owncloud is setup.
# Loop to check for the existence of the file every second
local owncloud_counter=0
while [ ! -f "$containers_dir$app_name/files/config/objectstore.config.php" ]; do
if [ "$owncloud_counter" -ge "$owncloud_timeout" ]; then
isNotice "File not found after $owncloud_timeout seconds. Exiting..."
break
fi
isNotice "Waiting 5 seconds for the objectstore.config.php to appear..."
sleep $owncloud_wait_time
local owncloud_counter=$((owncloud_counter + 1))
done
# Loop to check for the existence of the file every second
local owncloud_counter=0
# File does not exist or is 0 KB
while [ ! -f "$containers_dir$app_name/files/config/config.php" ] || [ $(stat -c %s "$containers_dir$app_name/files/config/config.php") -eq 0 ]; do
if [ "$owncloud_counter" -ge "$owncloud_timeout" ]; then
isNotice "File not found after $owncloud_timeout seconds. Exiting..."
break
fi
isNotice "Waiting 5 seconds for the config.php to appear..."
sleep $owncloud_wait_time
local owncloud_counter=$((owncloud_counter + 1))
done
# Backups and Creation of config files
# Copy the original config.php to the temporary file
# Create a temporary folder in /tmp
result=$(runSystem mkdir -p "$tmp_folder")
checkSuccess "Created temporary folder: $tmp_folder"
# Backups and Creation of config files
# Copy the original config.php to the temporary file in /tmp
result=$(runSystem cp "$owncloud_config" "$tmp_folder/config.php.tmp")
checkSuccess "Copy the original config.php contents to the temporary file"
result=$(runSystem cp "$owncloud_config" "$owncloud_config_folder/config.php.backup")
checkSuccess "Backing up the original config.php file"
local result=$(runSystem chmod -R 777 "$tmp_folder")
checkSuccess "Set permissions to for temp folder & files."
local result=$(runSystem chown -R $docker_install_user:$docker_install_user "$tmp_folder")
checkSuccess "Updating ownership on temp folder to $docker_install_user"
# Create another temporary file for awk output
local tmp_awk_output="$tmp_folder/config_awk_output.tmp"
# Use awk to delete lines for 'trusted_domains' from the temporary file
result=$(runSystem awk '/'"'trusted_domains'"'/,/\),/{next} {print}' "$tmp_folder/config.php.tmp" > "$tmp_awk_output")
checkSuccess "Use awk to delete lines for 'trusted_domains' from the temporary file"
# Remove the line containing 'overwrite.cli.url'
result=$(runSystem sed -i '/overwrite\.cli\.url/d' "$tmp_awk_output")
checkSuccess "Remove line containing 'overwrite.cli.url'"
# Remove the existing ');' from the end of the file
result=$(runSystem sed -i '$s/);//' "$tmp_awk_output")
checkSuccess "Remove ');' from the end of the file"
# Remove empty lines from the temporary file
result=$(runSystem sed -i '/^ *$/d' "$tmp_awk_output")
checkSuccess "Remove empty lines from the temporary file"
if [[ $public == "true" ]]; then
# Add new lines at the end of the file
runSystem tee -a "$tmp_awk_output" > /dev/null <<EOL
'overwrite.cli.url' => 'https://$host_setup/',
'Overwriteprotocol' => 'https',
'trusted_domains' =>
array(
0 => '$host_setup',
1 => '$ip_setup',
2 => '$public_ip_v4',
),
);
EOL
checkSuccess "Add overwrite and trusted_domain (public) lines to the config"
elif [[ $public == "false" ]]; then
# Add new lines at the end of the file
runSystem tee -a "$tmp_awk_output" > /dev/null <<EOL
'trusted_domains' =>
array(
0 => '$ip_setup',
),
);
EOL
checkSuccess "Add overwrite and trusted_domain (public) lines to the config"
fi
# Update permissions
# Move the modified temporary file back to the original location
result=$(runSystem mv "$tmp_awk_output" "$owncloud_config")
checkSuccess "Overwrite the original config.php with the updated content"
result=$(runSystem chown 165568:$docker_install_user $owncloud_config)
checkSuccess "Update permissions of ownCloud config to reflect container needs."
# Remove the temporary folder in /tmp
result=$(runSystem rm -rf "$tmp_folder")
checkSuccess "Removed temporary folder: $tmp_folder"
# config.php is owned by the in-container uid (165568), so the whole
# normalise (wait for ownCloud to write it, strip the default
# trusted_domains/overwrite.cli.url, append the LibrePortal ones, fix
# ownership) runs in the root-owned appcfg helper with validated args.
if runAppCfg owncloud-config "$public" "$host_setup" "$ip_setup" "$public_ip_v4"; then
isSuccessful "ownCloud config.php normalised (trusted_domains + overwrite.cli.url)."
else
isError "Failed to normalise ownCloud config.php."
fi
else
isError "Container is not healthy. Setup seems to have failed."
fi

View File

@ -11,40 +11,13 @@ kopiaInstall()
isHeader "Installing Kopia backup engine"
local arch
arch=$(uname -m)
case "$arch" in
x86_64) arch=x64 ;;
aarch64) arch=arm64 ;;
armv7l) arch=arm ;;
*) isError "Unsupported architecture for Kopia: $arch"; return 1 ;;
esac
local version url tmp
version=$(curl -sL https://api.github.com/repos/kopia/kopia/releases/latest 2>/dev/null \
| grep -oE '"tag_name":\s*"v[0-9.]+"' | head -1 | grep -oE '[0-9.]+')
if [[ -z "$version" ]]; then
isError "Could not look up the latest Kopia release. Install manually (https://kopia.io/) and re-run init."
return 1
# The privileged download + install runs in the root-owned
# libreportal-bininstall helper so the manager-run runtime needs no blanket
# sudo install/tar to /usr/local/bin.
if runBinInstall install kopia; then
checkSuccess "Kopia installed to /usr/local/bin/kopia"
return 0
fi
url="https://github.com/kopia/kopia/releases/download/v${version}/kopia-${version}-linux-${arch}.tar.gz"
tmp=$(mktemp -d)
if ! curl -sL "$url" -o "$tmp/kopia.tgz"; then
rm -rf "$tmp"
isError "Failed to download Kopia v${version} for ${arch}"
return 1
fi
runSystem tar xzf "$tmp/kopia.tgz" -C "$tmp"
local bin
bin=$(find "$tmp" -name kopia -type f -executable | head -1)
if [[ -z "$bin" ]]; then
rm -rf "$tmp"
isError "Kopia binary not found in archive"
return 1
fi
runSystem install -m 0755 "$bin" /usr/local/bin/kopia
rm -rf "$tmp"
checkSuccess "Kopia v${version} installed to /usr/local/bin/kopia"
isError "Failed to install Kopia"
return 1
}

View File

@ -11,39 +11,13 @@ resticInstall()
isHeader "Installing restic"
if command -v apt-get >/dev/null 2>&1; then
runSystem apt-get update -qq >/dev/null
if runSystem apt-get install -y restic >/dev/null 2>&1; then
checkSuccess "restic installed via apt"
runSystem restic self-update >/dev/null 2>&1 || true
return 0
fi
elif command -v dnf >/dev/null 2>&1; then
runSystem dnf install -y restic && return 0
elif command -v pacman >/dev/null 2>&1; then
runSystem pacman -S --noconfirm restic && return 0
fi
isNotice "Package manager install unavailable — downloading restic binary"
local arch
arch=$(uname -m)
case "$arch" in
x86_64) arch=amd64 ;;
aarch64) arch=arm64 ;;
armv7l) arch=arm ;;
*) isError "Unsupported architecture: $arch"; return 1 ;;
esac
local tmp
tmp=$(mktemp -d)
if curl -sL "https://github.com/restic/restic/releases/latest/download/restic_linux_${arch}.bz2" -o "$tmp/restic.bz2"; then
bunzip2 "$tmp/restic.bz2"
runSystem install -m 0755 "$tmp/restic" /usr/local/bin/restic
rm -rf "$tmp"
checkSuccess "restic installed to /usr/local/bin/restic"
else
rm -rf "$tmp"
isError "Failed to download restic"
return 1
# The privileged install (package manager or signed-release download) runs in
# the root-owned libreportal-bininstall helper so the manager-run runtime
# needs no blanket sudo apt-get/install.
if runBinInstall install restic; then
checkSuccess "restic installed"
return 0
fi
isError "Failed to install restic"
return 1
}

View File

@ -9,7 +9,6 @@ checkRequirements()
checkRootRequirement;
checkCommandRequirement;
checkWireguardRequirement;
checkInstallTypeRequirement;
checkConfigRequirement;
checkPasswordsRequirement;

View File

@ -127,6 +127,14 @@ runSocket() { _runRootHelper libreportal-socket "$@"; }
# config; no caller-supplied content): {install|enable|restart|start|status}
runSvc() { _runRootHelper libreportal-svc "$@"; }
# Backup-engine binary install (restic/kopia) to /usr/local/bin: install <engine>
runBinInstall() { _runRootHelper libreportal-bininstall "$@"; }
# App config-file rewrites owned by in-container uids / root /etc:
# {adguard-auth <user> <bcrypt>|crowdsec-priority|
# owncloud-config <public> <host> <ip> <public_ip>}
runAppCfg() { _runRootHelper libreportal-appcfg "$@"; }
# Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc
# edits). Needs real root in both modes; funnelled through one place so it can
# later be confined to a scoped sudoers allowlist.

View File

@ -29,10 +29,6 @@ mainMenu()
isOption "c. Configs"
isOption "d. Database"
isOption "s. Setup Wizard (re-run)"
status=$(dockerCheckAppInstalled "wireguard" "linux")
if [ "$status" == "installed" ]; then
isOption "w. WireGuard"
fi
status=$(dockerCheckAppInstalled "ufw" "linux")
if [ "$status" == "installed" ]; then
isOption "f. Firewall"
@ -161,10 +157,6 @@ mainMenu()
startOther;
;;
w)
wireguardManageMenu;
;;
l)
viewLogs;

View File

@ -32,5 +32,4 @@ files_libreportal_app=(
"${update_scripts[@]}"
"${user_scripts[@]}"
"${webui_scripts[@]}"
"${wireguard_scripts[@]}"
)

View File

@ -30,6 +30,5 @@ checks_scripts=(
"checks/requirements/check_webui_app.sh"
"checks/requirements/check_webui_image.sh"
"checks/requirements/check_webui_systemd.sh"
"checks/requirements/check_wireguard.sh"
)

View File

@ -21,6 +21,5 @@ menu_scripts=(
"menu/tools/manage_linkding.sh"
"menu/tools/manage_main.sh"
"menu/tools/manage_mattermost.sh"
"menu/tools/manage_wireguard.sh"
)

View File

@ -28,6 +28,5 @@ source_scripts=(
"source/files/arrays/files_start.sh"
"source/files/arrays/files_update.sh"
"source/files/arrays/files_webui.sh"
"source/files/arrays/files_wireguard.sh"
)

View File

@ -1,13 +0,0 @@
#!/bin/bash
# This file is auto-generated by generate_arrays.sh
# Do not edit manually - run './scripts/source/files/generate_arrays.sh run' to regenerate
wireguard_scripts=(
"wireguard/client/check_clients.sh"
"wireguard/client/list_clients.sh"
"wireguard/client/revoke_client.sh"
"wireguard/install_standalone.sh"
"wireguard/uninstall_standalone.sh"
)

View File

@ -32,5 +32,4 @@ files_libreportal_cli=(
"${update_scripts[@]}"
"${user_scripts[@]}"
"${webui_scripts[@]}"
"${wireguard_scripts[@]}"
)

View File

@ -65,8 +65,10 @@ for folder in "$SCRIPTS_DIR"/*; do
if [ -d "$folder" ]; then
folder_name=$(basename "$folder")
# Skip unused folder
if [ "$folder_name" = "unused" ]; then
# Skip folders that aren't sourced function libraries: the dead-code
# graveyard, and system/ (standalone root-owned helpers installed to
# /usr/local/sbin and invoked via sudo — never sourced into the runtime).
if [ "$folder_name" = "unused" ] || [ "$folder_name" = "system" ]; then
isNotice "Skipping $folder_name/"
continue
fi

View File

@ -13,7 +13,6 @@ startPreInstall()
installDebianUbuntu;
installArch;
installStandaloneWireGuard;
#######################################################
### Install Docker ###

View File

@ -0,0 +1,129 @@
#!/bin/bash
# LibrePortal app-config helper — root-privileged edits of specific app config
# files owned by in-container UIDs (AdGuard yaml, ownCloud config.php) or host
# /etc (CrowdSec bouncer). Installed root:root 0755 to /usr/local/sbin by
# init.sh. Self-contained; each action edits a FIXED path with strictly-validated
# arguments, so the scoped sudoers needn't grant blanket sudo awk/sed/tee/cp/mv
# on those trees. Faithful ports of the original transforms.
set -u
[[ $EUID -eq 0 ]] || { echo "libreportal-appcfg: must run as root" >&2; exit 1; }
CONTAINERS_DIR="/docker/containers"
DB_CFG="/docker/configs/general/general_docker_install"
_install_user() {
local u
u=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$DB_CFG" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}')
[[ -n "$u" ]] && id -u "$u" >/dev/null 2>&1 && { echo "$u"; return; }
echo "dockerinstall"
}
# --- AdGuard: set users[0].name + password (bcrypt) in AdGuardHome.yaml --------
adguard_auth() {
local user="$1" bcrypt="$2"
[[ "$user" =~ ^[A-Za-z0-9._@-]+$ ]] || { echo "libreportal-appcfg: invalid adguard user" >&2; return 1; }
[[ "$bcrypt" =~ ^[A-Za-z0-9./\$]+$ ]] || { echo "libreportal-appcfg: invalid bcrypt" >&2; return 1; }
local yaml="$CONTAINERS_DIR/adguard/conf/AdGuardHome.yaml"
[[ -f "$yaml" ]] || { echo "libreportal-appcfg: $yaml not found" >&2; return 1; }
local tmp; tmp=$(mktemp)
if ! awk -v u="$user" -v pw="$bcrypt" '
/^users:/ { in_users=1; print; next }
in_users && /^[^[:space:]-]/ { in_users=0 }
in_users && /^[[:space:]]+name:/ && !done_user {
match($0, /^[[:space:]]+/); print substr($0, RSTART, RLENGTH) "name: " u; done_user=1; next
}
in_users && /^[[:space:]]+password:/ && !done_pw {
match($0, /^[[:space:]]+/); print substr($0, RSTART, RLENGTH) "password: " pw; done_pw=1; next
}
{ print }
END { exit (done_pw ? 0 : 1) }
' "$yaml" > "$tmp"; then
rm -f "$tmp"
echo "libreportal-appcfg: AdGuardHome.yaml has no users password line" >&2
return 1
fi
cp "$tmp" "$yaml" # overwrite content, preserve the file's existing owner
rm -f "$tmp"
}
# --- CrowdSec: set nftables ipv4/ipv6 priority to -100 in the bouncer yaml ------
crowdsec_priority() {
local cfg="/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml"
[[ -f "$cfg" ]] || { echo "libreportal-appcfg: $cfg not found" >&2; return 1; }
cp "$cfg" "${cfg}.bak.$(date +%Y%m%d-%H%M%S)"
awk -v p="-100" '
BEGIN { in_v4=0; in_v6=0 }
/^[[:space:]]*ipv4:/ { in_v4=1; in_v6=0; print; next }
/^[[:space:]]*ipv6:/ { in_v6=1; in_v4=0; print; next }
/^[a-zA-Z]/ { in_v4=0; in_v6=0 }
in_v4 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p) }
in_v6 && /^[[:space:]]+priority:/ { sub(/priority:.*/, "priority: " p) }
{ print }
' "$cfg" > "${cfg}.new" && mv "${cfg}.new" "$cfg"
}
# --- ownCloud: normalise trusted_domains + overwrite.cli.url in config.php ------
owncloud_config() {
local public="$1" host_setup="$2" ip_setup="$3" public_ip="$4"
[[ "$public" =~ ^(true|false)$ ]] || { echo "libreportal-appcfg: public must be true|false" >&2; return 1; }
local v
for v in "$host_setup" "$ip_setup" "$public_ip"; do
[[ -z "$v" || "$v" =~ ^[A-Za-z0-9._:-]+$ ]] || { echo "libreportal-appcfg: invalid host/ip arg" >&2; return 1; }
done
local cfg_folder="$CONTAINERS_DIR/owncloud/files/config"
local cfg="$cfg_folder/config.php"
[[ -d "$cfg_folder" ]] || { echo "libreportal-appcfg: $cfg_folder not found" >&2; return 1; }
# ownCloud writes its real config.php after first boot. Drop the default and
# wait (as root — these files are owned by the in-container uid) for the
# objectstore + a non-empty config.php to appear before transforming.
rm -f "$cfg"
local i
for i in $(seq 1 60); do [[ -f "$cfg_folder/objectstore.config.php" ]] && break; sleep 5; done
for i in $(seq 1 60); do [[ -f "$cfg" && -s "$cfg" ]] && break; sleep 5; done
[[ -f "$cfg" && -s "$cfg" ]] || { echo "libreportal-appcfg: config.php never appeared" >&2; return 1; }
local tmp; tmp=$(mktemp -d)
local out="$tmp/config.php.new"
cp "$cfg" "$cfg_folder/config.php.backup"
awk '/'"'"'trusted_domains'"'"'/,/\),/{next} {print}' "$cfg" > "$out"
sed -i '/overwrite\.cli\.url/d' "$out"
sed -i '$s/);//' "$out"
sed -i '/^ *$/d' "$out"
if [[ "$public" == "true" ]]; then
cat >> "$out" <<EOL
'overwrite.cli.url' => 'https://$host_setup/',
'Overwriteprotocol' => 'https',
'trusted_domains' =>
array(
0 => '$host_setup',
1 => '$ip_setup',
2 => '$public_ip',
),
);
EOL
else
cat >> "$out" <<EOL
'trusted_domains' =>
array(
0 => '$ip_setup',
),
);
EOL
fi
mv "$out" "$cfg"
chown "165568:$(_install_user)" "$cfg"
rm -rf "$tmp"
}
action="${1:-}"; shift 2>/dev/null || true
case "$action" in
adguard-auth) adguard_auth "${1:-}" "${2:-}" ;;
crowdsec-priority) crowdsec_priority ;;
owncloud-config) owncloud_config "${1:-}" "${2:-}" "${3:-}" "${4:-}" ;;
*) echo "usage: libreportal-appcfg {adguard-auth <user> <bcrypt>|crowdsec-priority|owncloud-config <public> <host> <ip> <public_ip>}" >&2; exit 2 ;;
esac

View File

@ -0,0 +1,76 @@
#!/bin/bash
# LibrePortal backup-engine installer helper — the only root-privileged install of
# the restic/kopia binaries the manager may trigger (they're installed on demand
# when a backup location is first set up). Installed root:root 0755 to
# /usr/local/sbin by init.sh. Self-contained: it does the WHOLE install itself
# (package manager or signed-release download) for a FIXED, validated engine name,
# so the scoped sudoers needn't grant blanket `sudo apt-get`/`sudo install`
# (both root-equivalent — install writes anywhere, apt runs maintainer scripts).
set -u
[[ $EUID -eq 0 ]] || { echo "libreportal-bininstall: must run as root" >&2; exit 1; }
action="${1:-}"
engine="${2:-}"
[[ "$action" == "install" ]] || { echo "usage: libreportal-bininstall install <restic|kopia>" >&2; exit 2; }
case "$engine" in
restic|kopia) ;;
*) echo "libreportal-bininstall: unknown engine '$engine'" >&2; exit 2 ;;
esac
arch=$(uname -m)
install_restic() {
command -v restic >/dev/null 2>&1 && return 0
if command -v apt-get >/dev/null 2>&1; then
apt-get update -qq >/dev/null 2>&1
if apt-get install -y restic >/dev/null 2>&1; then
restic self-update >/dev/null 2>&1 || true
return 0
fi
elif command -v dnf >/dev/null 2>&1; then
dnf install -y restic >/dev/null 2>&1 && return 0
elif command -v pacman >/dev/null 2>&1; then
pacman -S --noconfirm restic >/dev/null 2>&1 && return 0
fi
local a
case "$arch" in
x86_64) a=amd64 ;; aarch64) a=arm64 ;; armv7l) a=arm ;;
*) echo "libreportal-bininstall: unsupported arch '$arch'" >&2; return 1 ;;
esac
local tmp; tmp=$(mktemp -d)
if curl -sL "https://github.com/restic/restic/releases/latest/download/restic_linux_${a}.bz2" -o "$tmp/restic.bz2" \
&& bunzip2 "$tmp/restic.bz2" \
&& install -m 0755 "$tmp/restic" /usr/local/bin/restic; then
rm -rf "$tmp"; return 0
fi
rm -rf "$tmp"; echo "libreportal-bininstall: restic download failed" >&2; return 1
}
install_kopia() {
command -v kopia >/dev/null 2>&1 && return 0
local a
case "$arch" in
x86_64) a=x64 ;; aarch64) a=arm64 ;; armv7l) a=arm ;;
*) echo "libreportal-bininstall: unsupported arch '$arch'" >&2; return 1 ;;
esac
local version
version=$(curl -sL https://api.github.com/repos/kopia/kopia/releases/latest 2>/dev/null \
| grep -oE '"tag_name":[[:space:]]*"v[0-9.]+"' | head -1 | grep -oE '[0-9.]+')
[[ -n "$version" ]] || { echo "libreportal-bininstall: kopia version lookup failed" >&2; return 1; }
local url="https://github.com/kopia/kopia/releases/download/v${version}/kopia-${version}-linux-${a}.tar.gz"
local tmp; tmp=$(mktemp -d)
if curl -sL "$url" -o "$tmp/kopia.tgz" && tar xzf "$tmp/kopia.tgz" -C "$tmp"; then
local bin; bin=$(find "$tmp" -name kopia -type f -executable | head -1)
if [[ -n "$bin" ]] && install -m 0755 "$bin" /usr/local/bin/kopia; then
rm -rf "$tmp"; return 0
fi
fi
rm -rf "$tmp"; echo "libreportal-bininstall: kopia install failed" >&2; return 1
}
case "$engine" in
restic) install_restic ;;
kopia) install_kopia ;;
esac

View File

@ -33,76 +33,16 @@ installStandaloneWireGuard()
# Install WireGuard tools and module
if [[ "$OS_TYPE" == "Ubuntu" || "$OS_TYPE" == "Debian" ]]; then
runSystem apt-get install -y wireguard iptables resolvconf qrencode
# All the privileged work (apt, /etc/wireguard keys + server
# config, sysctl ip_forward, wg-quick service) runs in the
# root-owned libreportal-wg helper, which reads + validates the
# CFG_WG_* settings itself.
runWg install "$server_nic" "$public_ip_v4" >/dev/null
checkSuccess "Installed standalone WireGuard server (/etc/wireguard/${CFG_WG_SERVER_NIC}.conf)"
# Update DNS after installing resolvconf
# Update DNS after the helper apt-installed resolvconf
updateDNS "" standalonewireguard;
# Check if the directory exists; if not, create it
if [ ! -d "/etc/wireguard" ]; then
result=$(runSystem mkdir /etc/wireguard)
checkSuccess "Created the WireGuard folder"
fi
result=$(runSystem chmod 600 -R /etc/wireguard/)
checkSuccess "Updated permissions for /etc/wireguard"
local SERVER_PRIV_KEY=$(wg genkey)
local SERVER_PUB_KEY=$(echo "${SERVER_PRIV_KEY}" | wg pubkey)
# Save WireGuard settings
echo "SERVER_PUB_IP=${public_ip_v4}
SERVER_PUB_NIC=${server_nic}
SERVER_WG_NIC=${CFG_WG_SERVER_NIC}
SERVER_WG_IPV4=${CFG_WG_SERVER_IPV4}
SERVER_PORT=${CFG_WG_SERVER_PORT}
SERVER_PRIV_KEY=${SERVER_PRIV_KEY}
SERVER_PUB_KEY=${SERVER_PUB_KEY}
CLIENT_DNS_1=${CFG_DNS_SERVER_1}
CLIENT_DNS_2=${CFG_DNS_SERVER_2}
ALLOWED_IPS=${CFG_WG_ALLOWED_IPS}" | runSystem tee /etc/wireguard/params >/dev/null
result=$(runSystem chmod 644 /etc/wireguard/params)
checkSuccess "Updating permissions for /etc/wireguard/params"
# Add server interface
echo "[Interface]
Address = ${CFG_WG_SERVER_IPV4}/32
ListenPort = ${CFG_WG_SERVER_PORT}
PrivateKey = ${SERVER_PRIV_KEY}" | runSystem tee "/etc/wireguard/${CFG_WG_SERVER_NIC}.conf" >/dev/null
echo "PostUp = iptables -I INPUT -p udp --dport ${CFG_WG_SERVER_PORT} -j ACCEPT
PostUp = iptables -I FORWARD -i ${server_nic} -o ${CFG_WG_SERVER_NIC} -j ACCEPT
PostUp = iptables -I FORWARD -i ${CFG_WG_SERVER_NIC} -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o ${server_nic} -j MASQUERADE
PostDown = iptables -D INPUT -p udp --dport ${CFG_WG_SERVER_PORT} -j ACCEPT
PostDown = iptables -D FORWARD -i ${server_nic} -o ${CFG_WG_SERVER_NIC} -j ACCEPT
PostDown = iptables -D FORWARD -i ${CFG_WG_SERVER_NIC} -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o ${server_nic} -j MASQUERADE" | runSystem tee -a "/etc/wireguard/${CFG_WG_SERVER_NIC}.conf" >/dev/null
result=$(runSystem chmod 644 /etc/wireguard/${CFG_WG_SERVER_NIC}.conf)
checkSuccess "Updating permissions for /etc/wireguard/${CFG_WG_SERVER_NIC}.conf"
result=$(runSystem sed -i '/^net.ipv4.ip_forward/d' /etc/sysctl.conf)
checkSuccess "Removing all instances of net.ipv4.ip_forward from sysctl.conf"
local result=$(echo '# WIREGUARD START' | runSystem tee -a "$sysctl" > /dev/null)
checkSuccess "Adding wireguard header to sysctl"
result=$(echo "net.ipv4.ip_forward = 1" | runSystem tee -a $sysctl)
checkSuccess "Add the configuration for IPv4 IP forwarding"
local result=$(echo '# WIREGUARD END' | runSystem tee -a "$sysctl" > /dev/null)
checkSuccess "Adding wireguard header to sysctl"
result=$(runSystem systemctl start "wg-quick@${CFG_WG_SERVER_NIC}")
checkSuccess "Started wg-quick@${CFG_WG_SERVER_NIC} service."
result=$(runSystem systemctl enable "wg-quick@${CFG_WG_SERVER_NIC}")
checkSuccess "Enabled wg-quick@${CFG_WG_SERVER_NIC} service."
result=$(runSystem sysctl --system)
checkSuccess "Reloaded sysctl"
portUse wireguardstandalone $CFG_WG_SERVER_PORT install;
portOpenwireguardstandalone $CFG_WG_SERVER_PORT/udp install;