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:
parent
048e967ec1
commit
cd4fd55a6d
6
init.sh
6
init.sh
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@ checkRequirements()
|
||||
|
||||
checkRootRequirement;
|
||||
checkCommandRequirement;
|
||||
checkWireguardRequirement;
|
||||
checkInstallTypeRequirement;
|
||||
checkConfigRequirement;
|
||||
checkPasswordsRequirement;
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -32,5 +32,4 @@ files_libreportal_app=(
|
||||
"${update_scripts[@]}"
|
||||
"${user_scripts[@]}"
|
||||
"${webui_scripts[@]}"
|
||||
"${wireguard_scripts[@]}"
|
||||
)
|
||||
|
||||
@ -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"
|
||||
|
||||
)
|
||||
|
||||
@ -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"
|
||||
|
||||
)
|
||||
|
||||
@ -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"
|
||||
|
||||
)
|
||||
|
||||
@ -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"
|
||||
|
||||
)
|
||||
@ -32,5 +32,4 @@ files_libreportal_cli=(
|
||||
"${update_scripts[@]}"
|
||||
"${user_scripts[@]}"
|
||||
"${webui_scripts[@]}"
|
||||
"${wireguard_scripts[@]}"
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -13,7 +13,6 @@ startPreInstall()
|
||||
|
||||
installDebianUbuntu;
|
||||
installArch;
|
||||
installStandaloneWireGuard;
|
||||
|
||||
#######################################################
|
||||
### Install Docker ###
|
||||
|
||||
129
scripts/system/libreportal-appcfg
Normal file
129
scripts/system/libreportal-appcfg
Normal 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
|
||||
76
scripts/system/libreportal-bininstall
Normal file
76
scripts/system/libreportal-bininstall
Normal 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
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user