From cd4fd55a6df1c40eff4c85f5b09701e52f22f9d9 Mon Sep 17 00:00:00 2001 From: librelad Date: Sun, 24 May 2026 19:22:22 +0100 Subject: [PATCH] feat(desudo): helper-ize backup-engine + app-config installs; retire standalone WireGuard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 — 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 | crowdsec-priority|owncloud-config } — 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 Signed-off-by: librelad --- init.sh | 6 +- .../app/containers/adguard/adguard_auth.sh | 22 +-- .../crowdsec/crowdsec_fix_priority.sh | 28 +--- .../owncloud/owncloud_setup_config.sh | 123 ++--------------- scripts/backup/engine/kopia_install.sh | 43 ++---- scripts/backup/engine/restic_install.sh | 42 ++---- scripts/checks/check_requirements.sh | 1 - scripts/docker/command/run_privileged.sh | 8 ++ scripts/menu/menu_main.sh | 8 -- scripts/source/files/app_files.sh | 1 - scripts/source/files/arrays/files_checks.sh | 1 - scripts/source/files/arrays/files_menu.sh | 1 - scripts/source/files/arrays/files_source.sh | 1 - .../source/files/arrays/files_wireguard.sh | 13 -- scripts/source/files/cli_files.sh | 1 - scripts/source/files/generate_arrays.sh | 6 +- scripts/start/start_preinstall.sh | 1 - scripts/system/libreportal-appcfg | 129 ++++++++++++++++++ scripts/system/libreportal-bininstall | 76 +++++++++++ .../check_wireguard.sh | 0 .../tools => unused}/manage_wireguard.sh | 0 .../wireguard/client/check_clients.sh | 0 .../wireguard/client/list_clients.sh | 0 .../wireguard/client/revoke_client.sh | 0 .../{ => unused}/wireguard/config_wireguard | 0 .../wireguard/install_standalone.sh | 74 +--------- .../wireguard/uninstall_standalone.sh | 0 27 files changed, 261 insertions(+), 324 deletions(-) delete mode 100755 scripts/source/files/arrays/files_wireguard.sh create mode 100644 scripts/system/libreportal-appcfg create mode 100644 scripts/system/libreportal-bininstall rename scripts/{checks/requirements => unused}/check_wireguard.sh (100%) rename scripts/{menu/tools => unused}/manage_wireguard.sh (100%) rename scripts/{ => unused}/wireguard/client/check_clients.sh (100%) rename scripts/{ => unused}/wireguard/client/list_clients.sh (100%) rename scripts/{ => unused}/wireguard/client/revoke_client.sh (100%) rename scripts/{ => unused}/wireguard/config_wireguard (100%) rename scripts/{ => unused}/wireguard/install_standalone.sh (74%) rename scripts/{ => unused}/wireguard/uninstall_standalone.sh (100%) diff --git a/init.sh b/init.sh index 853ff5f..f7b779d 100755 --- a/init.sh +++ b/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 diff --git a/scripts/app/containers/adguard/adguard_auth.sh b/scripts/app/containers/adguard/adguard_auth.sh index 6cbaf27..4679324 100644 --- a/scripts/app/containers/adguard/adguard_auth.sh +++ b/scripts/app/containers/adguard/adguard_auth.sh @@ -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" diff --git a/scripts/app/containers/crowdsec/crowdsec_fix_priority.sh b/scripts/app/containers/crowdsec/crowdsec_fix_priority.sh index 18d4591..67d7868 100644 --- a/scripts/app/containers/crowdsec/crowdsec_fix_priority.sh +++ b/scripts/app/containers/crowdsec/crowdsec_fix_priority.sh @@ -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" diff --git a/scripts/app/containers/owncloud/owncloud_setup_config.sh b/scripts/app/containers/owncloud/owncloud_setup_config.sh index 839822f..25c4782 100755 --- a/scripts/app/containers/owncloud/owncloud_setup_config.sh +++ b/scripts/app/containers/owncloud/owncloud_setup_config.sh @@ -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 < '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 < -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 diff --git a/scripts/backup/engine/kopia_install.sh b/scripts/backup/engine/kopia_install.sh index 3d98cbd..37b7054 100644 --- a/scripts/backup/engine/kopia_install.sh +++ b/scripts/backup/engine/kopia_install.sh @@ -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 } diff --git a/scripts/backup/engine/restic_install.sh b/scripts/backup/engine/restic_install.sh index 10e4955..3a8b849 100644 --- a/scripts/backup/engine/restic_install.sh +++ b/scripts/backup/engine/restic_install.sh @@ -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 } diff --git a/scripts/checks/check_requirements.sh b/scripts/checks/check_requirements.sh index 2187509..8b5ec05 100755 --- a/scripts/checks/check_requirements.sh +++ b/scripts/checks/check_requirements.sh @@ -9,7 +9,6 @@ checkRequirements() checkRootRequirement; checkCommandRequirement; - checkWireguardRequirement; checkInstallTypeRequirement; checkConfigRequirement; checkPasswordsRequirement; diff --git a/scripts/docker/command/run_privileged.sh b/scripts/docker/command/run_privileged.sh index 110d632..81d18d4 100644 --- a/scripts/docker/command/run_privileged.sh +++ b/scripts/docker/command/run_privileged.sh @@ -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 +runBinInstall() { _runRootHelper libreportal-bininstall "$@"; } + +# App config-file rewrites owned by in-container uids / root /etc: +# {adguard-auth |crowdsec-priority| +# owncloud-config } +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. diff --git a/scripts/menu/menu_main.sh b/scripts/menu/menu_main.sh index 340eb7e..0d01b20 100755 --- a/scripts/menu/menu_main.sh +++ b/scripts/menu/menu_main.sh @@ -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; diff --git a/scripts/source/files/app_files.sh b/scripts/source/files/app_files.sh index 1d4c72a..e803c21 100755 --- a/scripts/source/files/app_files.sh +++ b/scripts/source/files/app_files.sh @@ -32,5 +32,4 @@ files_libreportal_app=( "${update_scripts[@]}" "${user_scripts[@]}" "${webui_scripts[@]}" - "${wireguard_scripts[@]}" ) diff --git a/scripts/source/files/arrays/files_checks.sh b/scripts/source/files/arrays/files_checks.sh index 230dd14..d641b45 100755 --- a/scripts/source/files/arrays/files_checks.sh +++ b/scripts/source/files/arrays/files_checks.sh @@ -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" ) diff --git a/scripts/source/files/arrays/files_menu.sh b/scripts/source/files/arrays/files_menu.sh index e690158..851c8a2 100755 --- a/scripts/source/files/arrays/files_menu.sh +++ b/scripts/source/files/arrays/files_menu.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" ) diff --git a/scripts/source/files/arrays/files_source.sh b/scripts/source/files/arrays/files_source.sh index 279b95a..14277fd 100755 --- a/scripts/source/files/arrays/files_source.sh +++ b/scripts/source/files/arrays/files_source.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" ) diff --git a/scripts/source/files/arrays/files_wireguard.sh b/scripts/source/files/arrays/files_wireguard.sh deleted file mode 100755 index 7bcd6ca..0000000 --- a/scripts/source/files/arrays/files_wireguard.sh +++ /dev/null @@ -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" - -) diff --git a/scripts/source/files/cli_files.sh b/scripts/source/files/cli_files.sh index 82f6c46..65f0e7e 100755 --- a/scripts/source/files/cli_files.sh +++ b/scripts/source/files/cli_files.sh @@ -32,5 +32,4 @@ files_libreportal_cli=( "${update_scripts[@]}" "${user_scripts[@]}" "${webui_scripts[@]}" - "${wireguard_scripts[@]}" ) diff --git a/scripts/source/files/generate_arrays.sh b/scripts/source/files/generate_arrays.sh index cd513ef..0ed4cee 100755 --- a/scripts/source/files/generate_arrays.sh +++ b/scripts/source/files/generate_arrays.sh @@ -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 diff --git a/scripts/start/start_preinstall.sh b/scripts/start/start_preinstall.sh index 750085f..60c4d6e 100755 --- a/scripts/start/start_preinstall.sh +++ b/scripts/start/start_preinstall.sh @@ -13,7 +13,6 @@ startPreInstall() installDebianUbuntu; installArch; - installStandaloneWireGuard; ####################################################### ### Install Docker ### diff --git a/scripts/system/libreportal-appcfg b/scripts/system/libreportal-appcfg new file mode 100644 index 0000000..95592ae --- /dev/null +++ b/scripts/system/libreportal-appcfg @@ -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" < 'https://$host_setup/', +'Overwriteprotocol' => 'https', +'trusted_domains' => +array( + 0 => '$host_setup', + 1 => '$ip_setup', + 2 => '$public_ip', +), +); + +EOL + else + cat >> "$out" < +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 |crowdsec-priority|owncloud-config }" >&2; exit 2 ;; +esac diff --git a/scripts/system/libreportal-bininstall b/scripts/system/libreportal-bininstall new file mode 100644 index 0000000..0cc250e --- /dev/null +++ b/scripts/system/libreportal-bininstall @@ -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 " >&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 diff --git a/scripts/checks/requirements/check_wireguard.sh b/scripts/unused/check_wireguard.sh similarity index 100% rename from scripts/checks/requirements/check_wireguard.sh rename to scripts/unused/check_wireguard.sh diff --git a/scripts/menu/tools/manage_wireguard.sh b/scripts/unused/manage_wireguard.sh similarity index 100% rename from scripts/menu/tools/manage_wireguard.sh rename to scripts/unused/manage_wireguard.sh diff --git a/scripts/wireguard/client/check_clients.sh b/scripts/unused/wireguard/client/check_clients.sh similarity index 100% rename from scripts/wireguard/client/check_clients.sh rename to scripts/unused/wireguard/client/check_clients.sh diff --git a/scripts/wireguard/client/list_clients.sh b/scripts/unused/wireguard/client/list_clients.sh similarity index 100% rename from scripts/wireguard/client/list_clients.sh rename to scripts/unused/wireguard/client/list_clients.sh diff --git a/scripts/wireguard/client/revoke_client.sh b/scripts/unused/wireguard/client/revoke_client.sh similarity index 100% rename from scripts/wireguard/client/revoke_client.sh rename to scripts/unused/wireguard/client/revoke_client.sh diff --git a/scripts/wireguard/config_wireguard b/scripts/unused/wireguard/config_wireguard similarity index 100% rename from scripts/wireguard/config_wireguard rename to scripts/unused/wireguard/config_wireguard diff --git a/scripts/wireguard/install_standalone.sh b/scripts/unused/wireguard/install_standalone.sh similarity index 74% rename from scripts/wireguard/install_standalone.sh rename to scripts/unused/wireguard/install_standalone.sh index afd0451..2f6f17a 100755 --- a/scripts/wireguard/install_standalone.sh +++ b/scripts/unused/wireguard/install_standalone.sh @@ -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; diff --git a/scripts/wireguard/uninstall_standalone.sh b/scripts/unused/wireguard/uninstall_standalone.sh similarity index 100% rename from scripts/wireguard/uninstall_standalone.sh rename to scripts/unused/wireguard/uninstall_standalone.sh