LibrePortal/scripts/system/libreportal-appcfg
librelad cd4fd55a6d 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>
2026-05-24 19:22:22 +01:00

130 lines
5.2 KiB
Bash

#!/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