Organise the system footprint outside /docker: - All LibrePortal executables now live together in /usr/local/lib/libreportal/ (root:root): the 7 root helpers AND the CLI wrapper. /usr/local/bin/libreportal becomes a symlink onto $PATH. run_privileged._runRootHelper, init.sh (initRootHelpers + scoped-sudoers Cmnd_Alias + command setup) all point there. The wrapper is now root-owned too (manager can't tamper with its entrypoint). - Fix a real bug: rootless sysctl settings were written to /etc/sysctl/99-custom.conf, a dir does NOT read, so net.ipv4.ip_unprivileged_port_start / kernel.unprivileged_userns_clone never persisted across reboot. Moved to /etc/sysctl.d/99-libreportal-rootless.conf (the existing reload now actually applies them). Consistent libreportal* naming. - Drop dead fqdn_file=/root/libreportal-fqdn.txt global (never used). - Add FOOTPRINT.md: a manifest of every file LibrePortal places outside /docker (doubles as an uninstall checklist). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
1315 lines
43 KiB
Bash
Executable File
1315 lines
43 KiB
Bash
Executable File
#!/bin/bash
|
||
#
|
||
# LibrePortal Initialization Script
|
||
#
|
||
# Usage: ./init.sh [OPTIONS] init [password] [git_user] [git_token] [git_url] [unattended] [install_mode]
|
||
#
|
||
# OPTIONS:
|
||
# --random-password Generate a random password automatically
|
||
# --local Use local folder installation automatically
|
||
# --unattended Run in unattended mode (skip confirmations)
|
||
# --skip-os-update Skip operating system update
|
||
# --skip-prereqs Skip installing prerequisite apps
|
||
#
|
||
# Examples:
|
||
# ./init.sh --random-password --local init
|
||
# ./init.sh --random-password --local --unattended init
|
||
# ./init.sh --random-password --local --skip-os-update --skip-prereqs init
|
||
# ./init.sh init mypassword myuser mytoken https://github.com/user/repo.git
|
||
#
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[0;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m'
|
||
|
||
isSuccessful() { echo -e "${GREEN}✓ Success${NC} $1"; }
|
||
isError() { echo -e "${RED}✗ Error${NC} $1"; }
|
||
isNotice() { echo -e "${YELLOW}! Notice${NC} $1"; }
|
||
isQuestion() { echo -e -n "${BLUE}❯ Question${NC} $1 "; }
|
||
|
||
displayLibrePortalLogo() {
|
||
local hbar; hbar=$(printf '═%.0s' $(seq 1 50))
|
||
printf '\n╔%s╗\n' "$hbar"
|
||
printf '║%6s%s%8s║\n' '' '╦ ┬┌┐ ┬─┐┌─┐ ╭─╮ ╔═╗┌─┐┬─┐┌┬┐┌─┐┬' ''
|
||
printf '║%6s%s%8s║\n' '' '║ │├┴┐├┬┘├┤ │◉│ ╠═╝│ │├┬┘ │ ├─┤│' ''
|
||
printf '║%6s%s%6s║\n' '' '╩═╝┴└─┘┴└─└─┘ ╨─╨ ╩ └─┘┴└─ ┴ ┴ ┴┴─┘' ''
|
||
printf '╚%s╝\n\n' "$hbar"
|
||
}
|
||
|
||
init_action="$1"
|
||
for arg in "$@"; do
|
||
case "$arg" in --*) ;; *) init_action="$arg"; break ;;
|
||
esac
|
||
done
|
||
if [[ "${BASH_SOURCE[0]}" == "$0" ]] \
|
||
&& [[ "$init_action" == "init" ]] \
|
||
&& [[ "$LIBREPORTAL_SKIP_LOGO" != "1" ]]; then
|
||
displayLibrePortalLogo
|
||
fi
|
||
unset init_action
|
||
|
||
checkSuccess() {
|
||
if [ $? -eq 0 ]; then
|
||
isSuccessful "$1"
|
||
else
|
||
isError "$1"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
isHeader() {
|
||
local title="$1"
|
||
local width=52
|
||
local inner=$((width - 2))
|
||
local title_len=${#title}
|
||
local total_pad=$((inner - title_len))
|
||
if (( total_pad < 0 )); then total_pad=0; fi
|
||
local left_pad=$((total_pad / 2))
|
||
local right_pad=$((total_pad - left_pad))
|
||
local hbar
|
||
hbar=$(printf '═%.0s' $(seq 1 "$inner"))
|
||
printf '\n╔%s╗\n' "$hbar"
|
||
printf '║%*s%s%*s║\n' "$left_pad" '' "$title" "$right_pad" ''
|
||
printf '╚%s╝\n\n' "$hbar"
|
||
}
|
||
|
||
# Original parameters for backward compatibility
|
||
param1="$1" # init to start script
|
||
param2="$2" # password
|
||
param3="$3" # git user
|
||
param4="$4" # git token
|
||
param5="$5" # git url
|
||
param6="$6" # unattended
|
||
param7="$7" # install mode (git/local)
|
||
|
||
# Parse command line arguments
|
||
init_random_password=false
|
||
init_local_install=false
|
||
init_unattended_mode=false
|
||
init_skip_os_update=false
|
||
init_skip_prereqs=false
|
||
|
||
install_param="init"
|
||
sudo_user_name=libreportal
|
||
sshd_config="/etc/ssh/sshd_config"
|
||
sudo_bashrc="/home/$sudo_user_name/.bashrc"
|
||
hosts_file="/etc/hosts"
|
||
hostname_file="/etc/hostname"
|
||
# All LibrePortal executables installed outside /docker live together here
|
||
# (root-owned). The user-facing CLI is symlinked into $PATH from /usr/local/bin.
|
||
lp_lib_dir="/usr/local/lib/libreportal"
|
||
command_script="$lp_lib_dir/libreportal"
|
||
command_symlink="/usr/local/bin/libreportal"
|
||
|
||
# Directories
|
||
docker_dir="/docker"
|
||
containers_dir="$docker_dir/containers/"
|
||
ssl_dir="$docker_dir/ssl/"
|
||
ssh_dir="$docker_dir/ssh/"
|
||
wireguard_dir="$docker_dir/wireguard/"
|
||
logs_dir="$docker_dir/logs/"
|
||
configs_dir="$docker_dir/configs/"
|
||
backup_dir="$docker_dir/backups"
|
||
restore_dir="$docker_dir/restore"
|
||
migrate_dir="$docker_dir/migrate"
|
||
# Install Scripts
|
||
script_dir="$docker_dir/install"
|
||
install_configs_dir="$script_dir/configs/"
|
||
install_containers_dir="$script_dir/containers/"
|
||
install_scripts_dir="$script_dir/scripts/"
|
||
|
||
# Parse flags
|
||
init_shift_count=0
|
||
for ((i=1; i<=$#; i++)); do
|
||
case "${!i}" in
|
||
--random-password)
|
||
init_random_password=true
|
||
((init_shift_count++))
|
||
;;
|
||
--local)
|
||
init_local_install=true
|
||
((init_shift_count++))
|
||
;;
|
||
--unattended)
|
||
init_unattended_mode=true
|
||
((init_shift_count++))
|
||
;;
|
||
--skip-os-update)
|
||
init_skip_os_update=true
|
||
((init_shift_count++))
|
||
;;
|
||
--skip-prereqs)
|
||
init_skip_prereqs=true
|
||
((init_shift_count++))
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# Shift parsed flags to get positional parameters
|
||
if [ $init_shift_count -gt 0 ]; then
|
||
for ((i=1; i<=init_shift_count; i++)); do
|
||
shift
|
||
done
|
||
# Reset positional parameters after shifting flags
|
||
param1="$1" # init to start script
|
||
param2="$2" # password
|
||
param3="$3" # git user
|
||
param4="$4" # git token
|
||
param5="$5" # git url
|
||
param6="$6" # unattended
|
||
param7="$7" # install mode (git/local)
|
||
fi
|
||
|
||
# Apply flag logic
|
||
if [ "$init_random_password" = true ]; then
|
||
param2=$(openssl rand -base64 12 | tr -d '\n')
|
||
isSuccessful "Generated password: $param2"
|
||
fi
|
||
|
||
if [ "$init_local_install" = true ]; then
|
||
param7="local"
|
||
fi
|
||
|
||
# Auto-detect installation mode based on provided parameters. Only when init.sh
|
||
# is EXECUTED directly (install time) — start.sh sources init.sh for its function
|
||
# definitions at runtime (Model A, as the manager), and this block must not run
|
||
# then (it would print a spurious "Auto-detected ..." and rewrite the config).
|
||
if [[ "${BASH_SOURCE[0]}" == "$0" && -z "$param7" ]]; then
|
||
# A reinstall that doesn't re-pass the git args must not silently
|
||
# downgrade an existing git install to local (that disables the updater
|
||
# and blanks the saved creds). Honor a git URL already saved from a
|
||
# prior install — only fall back to local when there's no git history.
|
||
saved_git_url=$(grep -E '^CFG_GIT_URL=' /docker/configs/general/general_install 2>/dev/null \
|
||
| sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
|
||
if [[ -n "$param3" || -n "$param4" || -n "$param5" ]]; then
|
||
# Git parameters provided, set to git mode
|
||
param7="git"
|
||
isSuccessful "Auto-detected Git installation mode (git args provided)"
|
||
elif [[ -n "$saved_git_url" && "$saved_git_url" != "changeme" && "$saved_git_url" != "empty" ]]; then
|
||
# No git args this run, but a prior git install is on disk — keep it
|
||
param7="git"
|
||
isSuccessful "Auto-detected Git installation mode (existing git config detected)"
|
||
else
|
||
# No git parameters and no prior git config, set to local mode
|
||
param7="local"
|
||
isSuccessful "Auto-detected Local installation mode"
|
||
fi
|
||
fi
|
||
|
||
|
||
initUpdateConfigOption() {
|
||
local config_option="$1"
|
||
local config_value="$2"
|
||
local config_file=""
|
||
|
||
for category_dir in "$configs_dir"/*; do
|
||
if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
|
||
for config_file in "$category_dir"/*; do
|
||
if [ -f "$config_file" ] && [[ ! "$config_file" =~ \.category$ ]]; then
|
||
if grep -q "^$config_option=" "$config_file"; then
|
||
local escaped_value=$(printf '%s\n' "$config_value" | sed -e 's/[\/&]/\\&/g')
|
||
local original_line=$(grep "^$config_option=" "$config_file")
|
||
local comment_part=$(echo "$original_line" | sed -n "s|^$config_option=[^#]*\(#.*\)|\1|p")
|
||
|
||
if [[ -n "$comment_part" ]]; then
|
||
sed -i "s|^$config_option=.*|$config_option=$escaped_value $comment_part|" "$config_file"
|
||
else
|
||
sed -i "s|^$config_option=.*|$config_option=$escaped_value|" "$config_file"
|
||
fi
|
||
source "$config_file"
|
||
return 0
|
||
fi
|
||
fi
|
||
done
|
||
fi
|
||
done
|
||
|
||
return 1
|
||
}
|
||
|
||
if [[ "${BASH_SOURCE[0]}" == "$0" && -n "$param7" && -f "${configs_dir}general/general_install" ]]; then
|
||
initUpdateConfigOption "CFG_INSTALL_MODE" "$param7"
|
||
fi
|
||
|
||
if [ "$init_unattended_mode" = true ]; then
|
||
param6="true"
|
||
fi
|
||
|
||
if [ "$init_skip_os_update" = true ]; then
|
||
isNotice "Skipping operating system update"
|
||
fi
|
||
|
||
if [ "$init_skip_prereqs" = true ]; then
|
||
isNotice "Skipping prerequisite apps installation"
|
||
fi
|
||
|
||
# Get script directory for local installation (only if needed)
|
||
if [ "$init_local_install" = true ]; then
|
||
init_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
isNotice "Using script directory for local installation: $init_script_dir"
|
||
fi
|
||
|
||
detectLocalLibrePortal() {
|
||
local check_dir
|
||
|
||
# If --local flag is used, check script directory, otherwise check current directory
|
||
if [ "$init_local_install" = true ]; then
|
||
check_dir="$init_script_dir"
|
||
echo "Checking script directory for LibrePortal structure..."
|
||
else
|
||
check_dir="$(pwd)"
|
||
echo "Checking current directory for LibrePortal structure..."
|
||
fi
|
||
|
||
local required_dirs=("configs" "scripts" "containers")
|
||
local required_files=("init.sh" "start.sh")
|
||
local missing_count=0
|
||
|
||
# Check for required directories
|
||
for dir in "${required_dirs[@]}"; do
|
||
if [[ ! -d "$check_dir/$dir" ]]; then
|
||
echo " Missing directory: $dir"
|
||
((missing_count++))
|
||
fi
|
||
done
|
||
|
||
# Check for required files
|
||
for file in "${required_files[@]}"; do
|
||
if [[ ! -f "$check_dir/$file" ]]; then
|
||
echo " Missing file: $file"
|
||
((missing_count++))
|
||
fi
|
||
done
|
||
|
||
if [[ $missing_count -eq 0 ]]; then
|
||
echo "Valid LibrePortal structure detected in $check_dir"
|
||
echo ""
|
||
return 0
|
||
else
|
||
isNotice "Warning: $missing_count required items missing. Not a valid LibrePortal source."
|
||
return 1
|
||
fi
|
||
}
|
||
|
||
copyFilesFromLocal() {
|
||
local source_dir
|
||
|
||
# If --local flag is used, use script directory, otherwise use current directory
|
||
if [ "$init_local_install" = true ]; then
|
||
source_dir="$init_script_dir"
|
||
else
|
||
source_dir="$(pwd)"
|
||
fi
|
||
|
||
isHeader "Copying from Local Directory"
|
||
|
||
# Remove existing install directory
|
||
sudo rm -rf "$script_dir"
|
||
|
||
# Create install directory
|
||
sudo mkdir -p "$script_dir"
|
||
|
||
isNotice "Copying files from $source_dir to $script_dir..."
|
||
|
||
# Copy all files while preserving structure
|
||
if sudo cp -r "$source_dir"/* "$script_dir/" 2>/dev/null; then
|
||
sudo cp -f "$script_dir/init.sh" /root/
|
||
setupConfigsFromRepo
|
||
sudo chown -R $sudo_user_name:$sudo_user_name "$script_dir"
|
||
isSuccessful "Files copied from local directory to '$script_dir'."
|
||
else
|
||
isError "Failed to copy files from local directory."
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
resetConfigVars() {
|
||
param2=""
|
||
param3=""
|
||
param4=""
|
||
param5=""
|
||
param7=""
|
||
GIT_USER=""
|
||
GIT_TOKEN=""
|
||
GIT_URL=""
|
||
INSTALL_MODE=""
|
||
}
|
||
|
||
initCheckConfigs() {
|
||
# Check if any config files exist (new structure) or old config files
|
||
local config_found=false
|
||
|
||
# Check for new structure
|
||
if [ -d "$configs_dir" ]; then
|
||
for category_dir in "$configs_dir"/*; do
|
||
if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
|
||
# Load new structure config files
|
||
for config_file in "$category_dir"/*; do
|
||
local should_load=true
|
||
local filename=$(basename "$config_file")
|
||
# Skip .category files and excluded files (hardcoded)
|
||
if [[ "$config_file" =~ \.category$ ]]; then
|
||
should_load=false
|
||
fi
|
||
|
||
if [ "$should_load" = true ]; then
|
||
source "$config_file"
|
||
config_found=true
|
||
fi
|
||
done
|
||
fi
|
||
done
|
||
fi
|
||
|
||
if [ "$config_found" = false ]; then
|
||
isNotice "Configuration files do not exist, skipping init check"
|
||
return
|
||
fi
|
||
|
||
get_cfg() {
|
||
local var_name="$1"
|
||
local var_value="${!var_name}"
|
||
echo "$var_value"
|
||
}
|
||
|
||
[[ -z "$param2" ]] && param2=$(get_cfg CFG_LIBREPORTAL_USER_PASS)
|
||
[[ -z "$param3" ]] && param3=$(get_cfg CFG_GIT_USER)
|
||
[[ -z "$param4" ]] && param4=$(get_cfg CFG_GIT_KEY)
|
||
[[ -z "$param5" ]] && param5=$(get_cfg CFG_GIT_URL)
|
||
[[ -z "$param7" ]] && param7=$(get_cfg CFG_INSTALL_MODE)
|
||
|
||
[[ "$param2" == "changeme" ]] && param2=""
|
||
[[ "$param3" == "changeme" ]] && param3=""
|
||
[[ "$param4" == "changeme" ]] && param4=""
|
||
[[ "$param5" == "changeme" ]] && param5=""
|
||
[[ "$param7" == "changeme" ]] && param7=""
|
||
}
|
||
|
||
validateUnattended() {
|
||
if [[ -z "$param2" ]]; then
|
||
isError "Password is required in unattended mode"
|
||
exit 1
|
||
fi
|
||
|
||
# If install mode is not specified, default to git
|
||
if [[ -z "$param7" ]]; then
|
||
param7="git"
|
||
fi
|
||
|
||
# For git installation, validate git parameters
|
||
if [[ "$param7" == "git" ]]; then
|
||
if [[ -z "$param5" ]]; then
|
||
isError "Git repository URL is required in unattended mode"
|
||
exit 1
|
||
fi
|
||
|
||
if [[ -z "$param3" ]]; then
|
||
param3="empty"
|
||
param4="empty"
|
||
fi
|
||
|
||
if [[ "$param3" != "empty" && -z "$param4" ]]; then
|
||
param4="empty"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
initInputQuestions() {
|
||
### PASSWORD
|
||
if [[ -z "$param2" ]]; then
|
||
while true; do
|
||
echo ""
|
||
echo "LibrePortal User Password"
|
||
read -p "Use custom (c) or randomized (r) password? (c/r): " choice
|
||
|
||
case "$choice" in
|
||
c)
|
||
read -p "Enter custom password: " param2
|
||
[[ -z "$param2" ]] && echo "Password cannot be empty." || break
|
||
;;
|
||
r)
|
||
param2=$(openssl rand -base64 12)
|
||
isSuccessful "Generated password: $param2"
|
||
break
|
||
;;
|
||
*)
|
||
echo "Invalid option. Enter c or r."
|
||
;;
|
||
esac
|
||
done
|
||
fi
|
||
|
||
### INSTALLATION METHOD
|
||
if [[ -z "$param7" ]]; then
|
||
echo ""
|
||
echo "Installation Method:"
|
||
|
||
# Check if local installation is available
|
||
if detectLocalLibrePortal; then
|
||
echo "1) Install from Git repository"
|
||
echo "2) Install from local folder (current directory)"
|
||
echo ""
|
||
while true; do
|
||
read -p "Choose option [1-2]: " install_choice
|
||
case "$install_choice" in
|
||
1)
|
||
param7="git"
|
||
echo "Using Git repository installation."
|
||
break
|
||
;;
|
||
2)
|
||
param7="local"
|
||
echo "Using local folder installation."
|
||
break
|
||
;;
|
||
*)
|
||
echo "Invalid option. Choose 1 or 2."
|
||
;;
|
||
esac
|
||
done
|
||
else
|
||
echo "Local installation not available - missing required files/directories."
|
||
echo "Defaulting to Git repository installation."
|
||
param7="git"
|
||
fi
|
||
fi
|
||
|
||
### GIT USER (only for git installation)
|
||
if [[ "$param7" == "git" && -z "$param3" ]]; then
|
||
echo ""
|
||
echo "Git Authentication Method:"
|
||
echo "1) Login required (username + token)"
|
||
echo "2) Authenticationless (public repos / SSH keys)"
|
||
echo ""
|
||
while true; do
|
||
read -p "Choose option [1-2]: " git_auth_choice
|
||
case "$git_auth_choice" in
|
||
1)
|
||
read -p "Enter Git username: " param3
|
||
if [[ -z "$param3" ]]; then
|
||
echo "Username cannot be empty for login authentication."
|
||
else
|
||
break
|
||
fi
|
||
;;
|
||
2)
|
||
param3="empty"
|
||
param4="empty"
|
||
echo "Using authenticationless Git access."
|
||
break
|
||
;;
|
||
*)
|
||
echo "Invalid option. Choose 1 or 2."
|
||
;;
|
||
esac
|
||
done
|
||
fi
|
||
|
||
### GIT TOKEN (only for git installation)
|
||
if [[ "$param7" == "git" && "$param3" != "empty" && -z "$param4" ]]; then
|
||
read -p "Enter Git token: " param4
|
||
[[ -z "$param4" ]] && param4="empty"
|
||
fi
|
||
|
||
### GIT URL (only for git installation)
|
||
if [[ "$param7" == "git" && -z "$param5" ]]; then
|
||
while true; do
|
||
echo ""
|
||
read -p "Enter Git repository URL: " param5
|
||
[[ -z "$param5" ]] && echo "Git repository URL is required." || {
|
||
# Validate and fix Git URL
|
||
param5=$(validateAndFixGitUrl "$param5")
|
||
echo "Using Git URL: $param5"
|
||
break
|
||
}
|
||
done
|
||
elif [[ "$param7" == "git" ]]; then
|
||
# Also validate URL if provided as parameter
|
||
param5=$(validateAndFixGitUrl "$param5")
|
||
echo "Using Git URL: $param5"
|
||
fi
|
||
}
|
||
|
||
cleanGitUrl() {
|
||
local url="$1"
|
||
url="${url#"${url%%[![:space:]]*}"}"
|
||
url="${url%"${url##*[![:space:]]}"}"
|
||
url="${url#https://}"
|
||
url="${url#http://}"
|
||
while [[ "$url" == */ ]]; do url="${url%/}"; done
|
||
url="${url%.git}"
|
||
echo "$url"
|
||
}
|
||
|
||
validateAndFixGitUrl() {
|
||
local url="$1"
|
||
url="${url#"${url%%[![:space:]]*}"}"
|
||
url="${url%"${url##*[![:space:]]}"}"
|
||
echo "$url"
|
||
}
|
||
|
||
initReloadConfigs() {
|
||
for category_dir in "$configs_dir"/*; do
|
||
if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
|
||
for config_file in "$category_dir"/*; do
|
||
if [ -f "$config_file" ] && [[ ! "$config_file" =~ \.category$ ]]; then
|
||
source "$config_file"
|
||
fi
|
||
done
|
||
fi
|
||
done
|
||
|
||
# Check old config files (backward compatibility)
|
||
for config_file in "$configs_dir"/config_*; do
|
||
if [ -f "$config_file" ]; then
|
||
source "$config_file"
|
||
fi
|
||
done
|
||
}
|
||
|
||
writeConfig()
|
||
{
|
||
# Defer config writes until the repo is cloned and /docker/configs is
|
||
# populated by setupConfigsFromRepo. On a fresh install the file
|
||
# below doesn't exist yet — initUpdateConfigs at the end of init.sh
|
||
# is what actually persists everything.
|
||
if [[ ! -f "${configs_dir}general/general_install" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
[[ -n "$param2" ]] && initUpdateConfigOption "CFG_LIBREPORTAL_USER_PASS" "$param2"
|
||
[[ -n "$param3" ]] && initUpdateConfigOption "CFG_GIT_USER" "$param3"
|
||
[[ -n "$param4" ]] && initUpdateConfigOption "CFG_GIT_KEY" "$param4"
|
||
[[ -n "$param5" ]] && initUpdateConfigOption "CFG_GIT_URL" "$param5"
|
||
[[ -n "$param7" ]] && initUpdateConfigOption "CFG_INSTALL_MODE" "$param7"
|
||
}
|
||
|
||
|
||
initDisplayConfig()
|
||
{
|
||
GIT_USER="$param3"
|
||
GIT_TOKEN="$param4"
|
||
GIT_URL="$param5"
|
||
INSTALL_MODE="$param7"
|
||
|
||
isHeader "Configuration Summary"
|
||
isNotice "LibrePortal User Password: [HIDDEN]"
|
||
|
||
if [[ "$INSTALL_MODE" == "local" ]]; then
|
||
isNotice "Git Username: [NOT APPLICABLE]"
|
||
isNotice "Git Token: [NOT APPLICABLE]"
|
||
else
|
||
[[ "$GIT_USER" == "empty" ]] \
|
||
&& isNotice "Git Username: [DISABLED]" \
|
||
|| isNotice "Git Username: $GIT_USER"
|
||
|
||
[[ "$GIT_TOKEN" == "empty" ]] \
|
||
&& isNotice "Git Token: [DISABLED]" \
|
||
|| isNotice "Git Token: [HIDDEN]"
|
||
fi
|
||
|
||
[[ "$INSTALL_MODE" == "local" ]] \
|
||
&& isNotice "Installation Mode: Local Folder" \
|
||
|| isNotice "Installation Mode: Git Repository"
|
||
|
||
if [[ "$INSTALL_MODE" != "local" ]]; then
|
||
isNotice "Git URL: $GIT_URL"
|
||
fi
|
||
echo ""
|
||
|
||
if [[ "$param6" == "true" || "$param6" == "1" ]]; then
|
||
isNotice "Unattended mode enabled — auto-accepting configuration"
|
||
writeConfig
|
||
fi
|
||
|
||
if [[ "$INSTALL_MODE" != "local" ]]; then
|
||
read -p "Are these details correct? (y/n): " confirm
|
||
else
|
||
confirm=y
|
||
fi
|
||
|
||
if [[ "$confirm" != "y" ]]; then
|
||
echo "Restarting configuration.."
|
||
resetConfigVars
|
||
initCheckConfigs
|
||
initInputQuestions
|
||
initDisplayConfig
|
||
else
|
||
writeConfig
|
||
isSuccessful "Configuration saved."
|
||
fi
|
||
}
|
||
|
||
initOS()
|
||
{
|
||
isHeader "Updating Operating System"
|
||
apt-get install sudo -y
|
||
sudo apt-get update
|
||
sudo apt-get dist-upgrade -y
|
||
echo ""
|
||
isSuccessful "OS Updated"
|
||
}
|
||
|
||
initPrerequires()
|
||
{
|
||
isHeader "Installing Prerequired Apps"
|
||
# apache2-utils → htpasswd, used by hashPassword for fast local bcrypt.
|
||
sudo apt-get install git zip curl sshpass dos2unix dnsutils apt-transport-https ca-certificates software-properties-common uidmap adduser apache2-utils restic -y
|
||
TARGET_PATH="/usr/local/bin"
|
||
CONFIG_FILE="$HOME/.bashrc"
|
||
if ! echo "$PATH" | grep -q "$TARGET_PATH"; then
|
||
echo "Adding $TARGET_PATH to PATH..."
|
||
echo "export PATH=\$PATH:$TARGET_PATH" >> "$CONFIG_FILE"
|
||
source "$CONFIG_FILE"
|
||
echo "PATH updated successfully!"
|
||
else
|
||
echo "$TARGET_PATH is already in PATH."
|
||
fi
|
||
isSuccessful "Prerequisite apps installed."
|
||
}
|
||
|
||
initDocker()
|
||
{
|
||
isHeader "Installing Docker"
|
||
if command -v docker &> /dev/null; then
|
||
isSuccessful "Docker is already installed."
|
||
else
|
||
curl -fsSL https://get.docker.com | sh
|
||
systemctl start docker
|
||
systemctl enable docker
|
||
isSuccessful "Docker has been installed successfully."
|
||
fi
|
||
}
|
||
|
||
initUsers()
|
||
{
|
||
isHeader "Creating User Accounts"
|
||
if id "$sudo_user_name" &>/dev/null; then
|
||
isSuccessful "User $sudo_user_name already exists."
|
||
else
|
||
# No -G sudo: the manager's privileges come from the scoped /etc/sudoers.d
|
||
# drop-in below (user-specific), not blanket sudo-group membership.
|
||
sudo useradd -s /bin/bash -d "/home/$sudo_user_name" -m "$sudo_user_name" 2>/dev/null
|
||
isNotice "Setting password for $sudo_user_name user."
|
||
echo "$sudo_user_name:$param2" | sudo chpasswd
|
||
sudo usermod -aG docker "$sudo_user_name"
|
||
sudo systemctl restart docker
|
||
isSuccessful "User $sudo_user_name created successfully."
|
||
fi
|
||
# Manager-user sudo: a SCOPED, validated /etc/sudoers.d drop-in (NOT
|
||
# NOPASSWD: ALL, and never appended to /etc/sudoers — a malformed main file
|
||
# locks out sudo entirely). Under Model A the runtime runs AS this user and
|
||
# reaches root ONLY via: the unprivileged docker-install user (data plane,
|
||
# confined by rootless), the root-owned /usr/local/lib/libreportal/ helpers
|
||
# (each a fixed, self-validated op the manager can't modify), and a fixed set
|
||
# of system binaries. Deliberately excluded: bash/su and tee/cp/chmod/chown/
|
||
# sed/mv/rm/install (each root-equivalent). See scripts/system/libreportal-*.
|
||
local sudoers_dropin="/etc/sudoers.d/${sudo_user_name}"
|
||
local install_user="${CFG_DOCKER_INSTALL_USER:-dockerinstall}"
|
||
local sudoers_tmp
|
||
sudoers_tmp=$(mktemp)
|
||
cat > "$sudoers_tmp" <<EOF
|
||
# Scoped least-privilege grant for the LibrePortal manager. Generated by init.sh.
|
||
Cmnd_Alias LP_HELPERS = ${lp_lib_dir}/libreportal-ownership, \\
|
||
${lp_lib_dir}/libreportal-dns, \\
|
||
${lp_lib_dir}/libreportal-ssh-access, \\
|
||
${lp_lib_dir}/libreportal-socket, \\
|
||
${lp_lib_dir}/libreportal-svc, \\
|
||
${lp_lib_dir}/libreportal-bininstall, \\
|
||
${lp_lib_dir}/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
|
||
${sudo_user_name} ALL=(${install_user}) NOPASSWD:SETENV: ALL
|
||
${sudo_user_name} ALL=(root) NOPASSWD: LP_HELPERS, LP_SYSTEM
|
||
EOF
|
||
if sudo visudo -cf "$sudoers_tmp" >/dev/null 2>&1; then
|
||
sudo install -m 0440 -o root -g root "$sudoers_tmp" "$sudoers_dropin"
|
||
# Defensive cleanup of legacy broad grants from older installs: a
|
||
# NOPASSWD: ALL line appended to the main /etc/sudoers, plus sudo-group
|
||
# membership (the scoped drop-in is user-specific; the group isn't needed).
|
||
if sudo grep -qE "^${sudo_user_name}[[:space:]]+ALL=\(ALL\)[[:space:]]+NOPASSWD:[[:space:]]+ALL" /etc/sudoers 2>/dev/null; then
|
||
local main_tmp; main_tmp=$(mktemp)
|
||
sudo grep -vE "^${sudo_user_name}[[:space:]]+ALL=\(ALL\)[[:space:]]+NOPASSWD:[[:space:]]+ALL$" /etc/sudoers > "$main_tmp"
|
||
sudo visudo -cf "$main_tmp" >/dev/null 2>&1 && sudo cp "$main_tmp" /etc/sudoers
|
||
rm -f "$main_tmp"
|
||
fi
|
||
sudo gpasswd -d "$sudo_user_name" sudo >/dev/null 2>&1 || true
|
||
isSuccessful "Configured scoped passwordless sudo for $sudo_user_name (/etc/sudoers.d/${sudo_user_name})."
|
||
else
|
||
isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name."
|
||
fi
|
||
rm -f "$sudoers_tmp"
|
||
|
||
initRootHelpers
|
||
}
|
||
|
||
# Install the root-owned privilege helpers. Under Model A the runtime runs AS the
|
||
# manager, so the genuine-root operations it can't drop (the /docker ownership
|
||
# model, /etc/resolv.conf edits, host SSH access) need root — but granting the
|
||
# manager blanket `sudo chown/chmod/tee/sed/cp` would be root-equivalent. Each
|
||
# helper does a FIXED, self-validated set of operations and lives root:root 0755
|
||
# where the manager can't edit it, so the scoped sudoers can allow each wholesale.
|
||
# The manager name is baked in here (the manager can't change it); the sed is a
|
||
# no-op on helpers without the placeholder.
|
||
initRootHelpers()
|
||
{
|
||
# One root-owned home for every LibrePortal executable installed outside
|
||
# /docker (the helpers here; the CLI wrapper lands here too, see the command
|
||
# setup). Root-owned 0755 = the manager can't tamper with the helpers it
|
||
# sudo's (the trust boundary the scoped sudoers relies on).
|
||
sudo install -d -m 0755 -o root -g root "$lp_lib_dir"
|
||
local helper helper_src helper_dst helper_tmp
|
||
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="$lp_lib_dir/$helper"
|
||
if [[ ! -f "$helper_src" ]]; then
|
||
isError "Root helper source missing ($helper_src) — skipping."
|
||
continue
|
||
fi
|
||
helper_tmp=$(mktemp)
|
||
sed "s/__MANAGER__/${sudo_user_name}/g" "$helper_src" > "$helper_tmp"
|
||
if bash -n "$helper_tmp" 2>/dev/null; then
|
||
sudo install -m 0755 -o root -g root "$helper_tmp" "$helper_dst"
|
||
isSuccessful "Installed root-owned helper ($helper_dst)."
|
||
else
|
||
isError "Refusing to install a malformed root helper ($helper)."
|
||
fi
|
||
rm -f "$helper_tmp"
|
||
done
|
||
}
|
||
|
||
initFolders()
|
||
{
|
||
isHeader "LibrePortal Folder Creation"
|
||
folders=("$docker_dir" "$containers_dir" "$ssl_dir" "$ssh_dir" "$wireguard_dir" "$logs_dir" "$configs_dir" "$backup_dir" "$restore_dir" "$migrate_dir" "$script_dir")
|
||
for folder in "${folders[@]}"; do
|
||
if [ ! -d "$folder" ]; then
|
||
sudo mkdir "$folder"
|
||
sudo chown $sudo_user_name:$sudo_user_name "$folder"
|
||
sudo chmod 750 "$folder"
|
||
isSuccessful "Folder '$folder' created."
|
||
fi
|
||
done
|
||
isSuccessful "All folders have been created."
|
||
}
|
||
|
||
setupConfigsFromRepo()
|
||
{
|
||
isNotice "Setting up configuration files from repository..."
|
||
|
||
local src="$script_dir/configs"
|
||
local dst="/docker/configs"
|
||
|
||
if [[ ! -d "$src" ]]; then
|
||
isError "Source configs directory missing: $src"
|
||
isError "The clone in $script_dir didn't include a configs/ tree — aborting."
|
||
exit 1
|
||
fi
|
||
|
||
sudo mkdir -p "$dst"
|
||
|
||
# No-clobber: only seed config files that don't already exist. On a fresh
|
||
# install this copies the whole template; on a re-run/deploy it preserves the
|
||
# user's live values (a plain cp -a here silently reset e.g. the Docker
|
||
# install type back to the template default). New *keys* in existing files
|
||
# are added separately by the add-only reconcile pass.
|
||
if ! sudo cp -an "$src"/. "$dst"/; then
|
||
isError "Failed to copy configs from $src to $dst — aborting."
|
||
exit 1
|
||
fi
|
||
sudo chown -R "$sudo_user_name":"$sudo_user_name" "$dst"
|
||
|
||
if [[ ! -f "$dst/general/general_install" ]]; then
|
||
isError "Configs were copied but $dst/general/general_install is missing."
|
||
isError "Repository layout may have changed — fix and re-run."
|
||
exit 1
|
||
fi
|
||
isSuccessful "Configuration files copied from repository."
|
||
|
||
isNotice "Applying initial configuration values..."
|
||
[[ -n "$param2" ]] && initUpdateConfigOption "CFG_LIBREPORTAL_USER_PASS" "$param2"
|
||
[[ -n "$param3" ]] && initUpdateConfigOption "CFG_GIT_USER" "$param3"
|
||
[[ -n "$param4" ]] && initUpdateConfigOption "CFG_GIT_KEY" "$param4"
|
||
[[ -n "$param5" ]] && initUpdateConfigOption "CFG_GIT_URL" "$param5"
|
||
[[ -n "$param7" ]] && initUpdateConfigOption "CFG_INSTALL_MODE" "$param7"
|
||
|
||
isSuccessful "Configuration setup complete."
|
||
}
|
||
|
||
initGIT()
|
||
{
|
||
isHeader "Git Clone / Update"
|
||
|
||
# Handle local installation
|
||
if [[ "$param7" == "local" ]]; then
|
||
isNotice "Using local folder installation."
|
||
copyFilesFromLocal
|
||
return
|
||
fi
|
||
|
||
GIT_USER="$param3"
|
||
GIT_TOKEN="$param4"
|
||
GIT_URL="$param5"
|
||
|
||
if [[ -z "$GIT_URL" ]]; then
|
||
isError "Git URL is empty. Please provide a valid Git repository URL."
|
||
exit 1
|
||
fi
|
||
|
||
local clean_url
|
||
clean_url=$(cleanGitUrl "$GIT_URL")
|
||
if [[ -z "$clean_url" ]]; then
|
||
isError "Could not normalize Git URL: $GIT_URL"
|
||
exit 1
|
||
fi
|
||
|
||
local auth=""
|
||
if [[ -n "$GIT_USER" && "$GIT_USER" != "empty" ]]; then
|
||
auth="${GIT_USER}:${GIT_TOKEN}@"
|
||
fi
|
||
|
||
isNotice "Cloning $clean_url into $script_dir ..."
|
||
sudo rm -rf "$script_dir"
|
||
|
||
local scheme cloned=false
|
||
for scheme in https http; do
|
||
if sudo -u "$sudo_user_name" git clone -q "${scheme}://${auth}${clean_url}.git" "$script_dir" 2>/dev/null; then
|
||
cloned=true
|
||
isSuccessful "Cloned via ${scheme^^}."
|
||
break
|
||
fi
|
||
done
|
||
|
||
if ! $cloned; then
|
||
isError "Failed to clone $clean_url over HTTPS or HTTP."
|
||
isError "Check the URL, credentials, and that the server is reachable."
|
||
exit 1
|
||
fi
|
||
|
||
sudo cp -f "$script_dir/init.sh" /root/
|
||
setupConfigsFromRepo
|
||
sudo chown -R "$sudo_user_name":"$sudo_user_name" "$script_dir"
|
||
}
|
||
|
||
initLibrePortalCommand()
|
||
{
|
||
isHeader "Custom Command Setup"
|
||
if ! grep -q "LibrePortal Command Start" $sudo_bashrc; then
|
||
isNotice "Command maker not found. Removing old LibrePortal command."
|
||
sed -i '/^libreportal() {$/,/^}$/d' $sudo_bashrc
|
||
else
|
||
isNotice "Command maker found. Removing old LibrePortal command."
|
||
sed -i '/# LibrePortal Command Start/,/# LibrePortal Command End/d' $sudo_bashrc
|
||
fi
|
||
isNotice "Custom command 'libreportal' is not installed. Installing..."
|
||
|
||
# The CLI wrapper lives alongside the root helpers in $lp_lib_dir; a symlink
|
||
# in /usr/local/bin keeps `libreportal` on $PATH.
|
||
sudo install -d -m 0755 -o root -g root "$lp_lib_dir"
|
||
sudo rm -rf $command_script
|
||
sudo tee -a "$command_script" >/dev/null <<'EOF'
|
||
#!/usr/bin/env bash
|
||
# LibrePortal Command Start
|
||
# LibrePortal Command Version 1.4
|
||
|
||
CHECK_USER="libreportal"
|
||
CURRENT_USER=$(whoami)
|
||
|
||
# Check if the script is run by the specified user
|
||
if [ "$CURRENT_USER" != "$CHECK_USER" ]; then
|
||
echo "Script is NOT able to run from this user."
|
||
echo "This script should ONLY be run as: $CHECK_USER"
|
||
exit 1
|
||
fi
|
||
|
||
command1="${1:-empty}"
|
||
command2="${2:-empty}"
|
||
command3="${3:-empty}"
|
||
command4="${4:-empty}"
|
||
command5="${5:-empty}"
|
||
command6="${6:-empty}"
|
||
command7="${7:-empty}"
|
||
command8="${8:-empty}"
|
||
command9="${9:-empty}"
|
||
|
||
path="$PWD"
|
||
|
||
reset_git_config() {
|
||
echo ""
|
||
echo "Resetting Git configuration for re-entry..."
|
||
|
||
# Use dynamic config update
|
||
commandUpdateConfigOption "CFG_GIT_USER" "changeme"
|
||
commandUpdateConfigOption "CFG_GIT_KEY" "changeme"
|
||
commandUpdateConfigOption "CFG_GIT_URL" "changeme"
|
||
}
|
||
|
||
# Helper function to load config files in the libreportal command
|
||
commandReloadConfigs() {
|
||
# Load new structure config files only
|
||
for category_dir in /docker/configs/*; do
|
||
if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
|
||
for config_file in "$category_dir"/*; do
|
||
local should_load=true
|
||
local filename=$(basename "$config_file")
|
||
# Skip .category files and excluded files (hardcoded for now)
|
||
if [[ "$config_file" =~ \.category$ ]]; then
|
||
should_load=false
|
||
fi
|
||
|
||
if [ "$should_load" = true ]; then
|
||
source "$config_file"
|
||
fi
|
||
done
|
||
fi
|
||
done
|
||
}
|
||
|
||
# Helper function to update config files in the libreportal command
|
||
commandUpdateConfigOption() {
|
||
local config_option="$1"
|
||
local config_value="$2"
|
||
for category_dir in /docker/configs/*; do
|
||
if [ -d "$category_dir" ] && [ -f "$category_dir/.category" ]; then
|
||
for config_file in "$category_dir"/*; do
|
||
if [ -f "$config_file" ] && [[ ! "$config_file" =~ \.category$ ]]; then
|
||
if grep -q "^$config_option=" "$config_file"; then
|
||
# Escape special characters in the config value to prevent sed issues
|
||
local escaped_value=$(printf '%s\n' "$config_value" | sed -e 's/[\/&]/\\&/g')
|
||
|
||
# Extract the comment part first (everything after the first #)
|
||
local original_line=$(grep "^$config_option=" "$config_file")
|
||
local comment_part=$(echo "$original_line" | sed -n "s|^$config_option=[^#]*\(#.*\)|\1|p")
|
||
|
||
# Replace the value, preserving comment if it existed
|
||
if [[ -n "$comment_part" ]]; then
|
||
sed -i "s|^$config_option=.*|$config_option=$escaped_value $comment_part|" "$config_file"
|
||
else
|
||
sed -i "s|^$config_option=.*|$config_option=$escaped_value|" "$config_file"
|
||
fi
|
||
source "$config_file"
|
||
return 0
|
||
fi
|
||
fi
|
||
done
|
||
fi
|
||
done
|
||
}
|
||
|
||
update_config_values() {
|
||
# Reload all config files to get current values
|
||
commandReloadConfigs
|
||
|
||
if [[ "$CFG_INSTALL_MODE" == "local" ]]; then
|
||
[[ "$CFG_GIT_USER" != "empty" ]] && commandUpdateConfigOption "CFG_GIT_USER" "empty"
|
||
[[ "$CFG_GIT_KEY" != "empty" ]] && commandUpdateConfigOption "CFG_GIT_KEY" "empty"
|
||
[[ "$CFG_GIT_URL" != "empty" ]] && commandUpdateConfigOption "CFG_GIT_URL" "empty"
|
||
commandReloadConfigs
|
||
return 0
|
||
fi
|
||
|
||
CFG_GIT_USER="${CFG_GIT_USER:-changeme}"
|
||
if [[ -z "$CFG_GIT_USER" ]] || [[ "$CFG_GIT_USER" == "changeme" ]]; then
|
||
while true; do
|
||
echo "Please enter your Git username (Press Enter to set to 'empty'):"
|
||
read -r NEW_GIT_USER
|
||
if [[ -n "$NEW_GIT_USER" || "$NEW_GIT_USER" == "" ]]; then
|
||
if [[ -z "$NEW_GIT_USER" ]]; then
|
||
NEW_GIT_USER="empty"
|
||
fi
|
||
commandUpdateConfigOption "CFG_GIT_USER" "$NEW_GIT_USER"
|
||
commandReloadConfigs
|
||
echo "Updating Git Username to '$NEW_GIT_USER'"
|
||
break
|
||
fi
|
||
done
|
||
fi
|
||
|
||
CFG_GIT_KEY="${CFG_GIT_KEY:-changeme}"
|
||
# If Git user is disabled, force token to empty and skip prompt
|
||
if [[ "$CFG_GIT_USER" == "empty" ]]; then
|
||
commandUpdateConfigOption "CFG_GIT_KEY" "empty"
|
||
commandReloadConfigs
|
||
echo "Git authentication disabled; skipping Git token."
|
||
else
|
||
# Only prompt if token is unset or changeme
|
||
if [[ -z "$CFG_GIT_KEY" || "$CFG_GIT_KEY" == "changeme" ]]; then
|
||
while true; do
|
||
echo "Please enter your Git access token (Press Enter to set to 'empty'):"
|
||
read -rs NEW_GIT_KEY
|
||
echo ""
|
||
|
||
if [[ -z "$NEW_GIT_KEY" ]]; then
|
||
NEW_GIT_KEY="empty"
|
||
fi
|
||
|
||
commandUpdateConfigOption "CFG_GIT_KEY" "$NEW_GIT_KEY"
|
||
commandReloadConfigs
|
||
echo "Git token updated."
|
||
break
|
||
done
|
||
fi
|
||
fi
|
||
|
||
CFG_GIT_URL="${CFG_GIT_URL:-changeme}"
|
||
if [[ -z "$CFG_GIT_URL" ]] || [[ "$CFG_GIT_URL" == "changeme" ]]; then
|
||
while true; do
|
||
echo "Please enter your Git repository URL:"
|
||
read -rs NEW_GIT_URL
|
||
if [[ -n "$NEW_GIT_URL" ]]; then
|
||
commandUpdateConfigOption "CFG_GIT_URL" "$NEW_GIT_URL"
|
||
commandReloadConfigs
|
||
echo "Updating Git URL"
|
||
break
|
||
fi
|
||
echo "Error: Git URL cannot be empty"
|
||
done
|
||
fi
|
||
}
|
||
|
||
commandCleanGitUrl() {
|
||
local url="$1"
|
||
url="${url#"${url%%[![:space:]]*}"}"
|
||
url="${url%"${url##*[![:space:]]}"}"
|
||
url="${url#https://}"
|
||
url="${url#http://}"
|
||
while [[ "$url" == */ ]]; do url="${url%/}"; done
|
||
url="${url%.git}"
|
||
echo "$url"
|
||
}
|
||
|
||
setup_repo() {
|
||
while true; do
|
||
update_config_values
|
||
commandReloadConfigs
|
||
|
||
CLEAN_GIT_URL=$(commandCleanGitUrl "$CFG_GIT_URL")
|
||
|
||
local auth=""
|
||
if [[ "$CFG_GIT_USER" != "empty" && -n "$CFG_GIT_USER" ]]; then
|
||
auth="${CFG_GIT_USER}:${CFG_GIT_KEY}@"
|
||
fi
|
||
AUTH_HTTPS_REPO_URL="https://${auth}${CLEAN_GIT_URL}.git"
|
||
AUTH_HTTP_REPO_URL="http://${auth}${CLEAN_GIT_URL}.git"
|
||
|
||
echo ""
|
||
echo "Configuration Summary:"
|
||
echo ""
|
||
|
||
if [[ "$CFG_INSTALL_MODE" == "local" ]]; then
|
||
echo "Git Username: [NOT APPLICABLE]"
|
||
echo "Git Token: [NOT APPLICABLE]"
|
||
echo "Git URL: [NOT APPLICABLE]"
|
||
else
|
||
echo "Git Username: $CFG_GIT_USER"
|
||
echo "Git Token: [HIDDEN]"
|
||
echo "Git URL: $CLEAN_GIT_URL"
|
||
fi
|
||
echo ""
|
||
|
||
read -p "Are these details correct? (y/n): " confirm_config
|
||
|
||
if [[ "$confirm_config" == "y" ]]; then
|
||
echo "Configuration confirmed."
|
||
break
|
||
else
|
||
reset_git_config
|
||
fi
|
||
done
|
||
}
|
||
|
||
sync_configs_from_install() {
|
||
local src="/docker/install/configs"
|
||
local dst="/docker/configs"
|
||
if [ ! -d "$src" ]; then
|
||
echo "ERROR: $src missing — clone broken."
|
||
return 1
|
||
fi
|
||
mkdir -p "$dst"
|
||
# No-clobber: preserve the user's live config values; only add new files.
|
||
if ! cp -an "$src"/. "$dst"/; then
|
||
echo "ERROR: Failed to sync configs from $src to $dst."
|
||
return 1
|
||
fi
|
||
chown -R libreportal:libreportal "$dst"
|
||
if [ ! -f "$dst/general/general_install" ]; then
|
||
echo "ERROR: $dst/general/general_install missing after sync."
|
||
return 1
|
||
fi
|
||
[ -n "$CFG_GIT_USER" ] && commandUpdateConfigOption "CFG_GIT_USER" "$CFG_GIT_USER"
|
||
[ -n "$CFG_GIT_KEY" ] && commandUpdateConfigOption "CFG_GIT_KEY" "$CFG_GIT_KEY"
|
||
[ -n "$CFG_GIT_URL" ] && commandUpdateConfigOption "CFG_GIT_URL" "$CFG_GIT_URL"
|
||
commandUpdateConfigOption "CFG_INSTALL_MODE" "git"
|
||
echo "SUCCESS: Configs synced and credentials re-applied."
|
||
}
|
||
|
||
clone_repo() {
|
||
rm -rf /docker/install
|
||
local clone_url
|
||
if [ "$CFG_GIT_USER" != "empty" ]; then
|
||
for clone_url in "$AUTH_HTTPS_REPO_URL" "$AUTH_HTTP_REPO_URL"; do
|
||
if git clone -q "$clone_url" "/docker/install" 2>/dev/null; then
|
||
sudo cp -f /docker/install/init.sh /root/
|
||
sync_configs_from_install || return 1
|
||
echo "SUCCESS: Clone complete. Run 'libreportal run' to continue."
|
||
return 0
|
||
fi
|
||
done
|
||
echo "ERROR: Authentication failed. Please check your credentials."
|
||
return 1
|
||
fi
|
||
for clone_url in "https://${CLEAN_GIT_URL}.git" "http://${CLEAN_GIT_URL}.git"; do
|
||
if git clone -q "$clone_url" "/docker/install" 2>/dev/null; then
|
||
sudo cp -f /docker/install/init.sh /root/
|
||
sync_configs_from_install || return 1
|
||
echo "SUCCESS: Clone complete. Run 'libreportal run' to continue."
|
||
return 0
|
||
fi
|
||
done
|
||
echo "ERROR: Anonymous clone failed."
|
||
return 1
|
||
}
|
||
|
||
clone_and_install() {
|
||
commandReloadConfigs
|
||
if [[ "$CFG_INSTALL_MODE" == "local" ]]; then
|
||
echo "NOTICE: Local install detected — no Git remote to clone."
|
||
return 0
|
||
fi
|
||
update_config_values;
|
||
setup_repo;
|
||
clone_repo;
|
||
}
|
||
|
||
cd /docker/
|
||
if [[ $command1 == "reset" ]]; then
|
||
clone_and_install
|
||
elif [ -f "/docker/install/start.sh" ]; then
|
||
chmod 0755 /docker/install/*
|
||
cd /docker/install
|
||
./start.sh "$command1" "$command2" "$command3" "$command4" "$command5" "$command6" "$command7" "$command8" "$command9"
|
||
else
|
||
clone_and_install
|
||
fi
|
||
# LibrePortal Command End
|
||
EOF
|
||
sudo chmod +x $command_script
|
||
sudo chown root:root $command_script
|
||
# Put it on $PATH via a symlink (replaces any older real file at this path).
|
||
sudo ln -sfn "$command_script" "$command_symlink"
|
||
source $sudo_bashrc
|
||
}
|
||
|
||
initUpdateConfigs()
|
||
{
|
||
isHeader "Updating Configs"
|
||
|
||
initUpdateConfigOption "CFG_LIBREPORTAL_USER_PASS" "$param2" && isSuccessful "Updated Docker user password"
|
||
initUpdateConfigOption "CFG_GIT_USER" "$param3" && isSuccessful "Updated Git Username"
|
||
initUpdateConfigOption "CFG_GIT_KEY" "$param4" && isSuccessful "Updated Git Token"
|
||
initUpdateConfigOption "CFG_GIT_URL" "$param5" && isSuccessful "Updated Git URL"
|
||
initUpdateConfigOption "CFG_INSTALL_MODE" "$param7" && isSuccessful "Updated Installation Mode"
|
||
|
||
isHeader "Verifying Saved Configuration"
|
||
local cfg_file="/docker/configs/general/general_install"
|
||
if [[ ! -f "$cfg_file" ]]; then
|
||
isError "Expected $cfg_file is missing — install cannot proceed."
|
||
exit 1
|
||
fi
|
||
|
||
local saved_mode saved_user saved_url saved_key
|
||
saved_mode=$(grep -E '^CFG_INSTALL_MODE=' "$cfg_file" | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
|
||
saved_user=$(grep -E '^CFG_GIT_USER=' "$cfg_file" | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
|
||
saved_url=$(grep -E '^CFG_GIT_URL=' "$cfg_file" | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
|
||
saved_key=$(grep -E '^CFG_GIT_KEY=' "$cfg_file" | sed -E 's/^[^=]+=([^[:space:]#]*).*/\1/')
|
||
|
||
isNotice "Mode: $saved_mode"
|
||
isNotice "User: $saved_user"
|
||
isNotice "URL: $saved_url"
|
||
[[ -n "$saved_key" && "$saved_key" != "changeme" ]] \
|
||
&& isNotice "Token: [SET]" \
|
||
|| isNotice "Token: [EMPTY]"
|
||
|
||
if [[ "$saved_mode" == "git" ]]; then
|
||
local fail=0
|
||
if [[ -z "$saved_url" || "$saved_url" == "changeme" ]]; then
|
||
isError "CFG_GIT_URL didn't persist — installer will re-prompt."
|
||
fail=1
|
||
fi
|
||
if [[ "$saved_user" != "empty" && ( -z "$saved_user" || "$saved_user" == "changeme" ) ]]; then
|
||
isError "CFG_GIT_USER didn't persist — installer will re-prompt."
|
||
fail=1
|
||
fi
|
||
if (( fail )); then
|
||
isError "Aborting before handoff so you can fix this once instead of retyping every install run."
|
||
exit 1
|
||
fi
|
||
fi
|
||
isSuccessful "Configuration verified."
|
||
}
|
||
|
||
completeInitMessage()
|
||
{
|
||
isHeader "LibrePortal Initilization Complete"
|
||
|
||
# Run LibrePortal install as the libreportal user
|
||
isNotice "Starting LibrePortal installation as $sudo_user_name user..."
|
||
|
||
# Switch to libreportal user and run the install command
|
||
if sudo -u "$sudo_user_name" LIBREPORTAL_SKIP_LOGO=1 bash -c "libreportal run install"; then
|
||
:
|
||
else
|
||
echo ""
|
||
echo "⚠️ LibrePortal installation encountered issues."
|
||
echo " You can manually run the installation with:"
|
||
echo " sudo -u $sudo_user_name bash -c 'libreportal run install'"
|
||
echo ""
|
||
fi
|
||
}
|
||
|
||
# Only run the installer entrypoint (root check + init flow) when init.sh is
|
||
# EXECUTED directly. When it's SOURCED — start.sh loads init.sh for its function
|
||
# defs at runtime, and under Model A start.sh runs as the manager, not root — the
|
||
# defs above are all that's wanted and this root check must NOT fire.
|
||
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && return 0 2>/dev/null
|
||
|
||
if [[ $EUID -ne 0 ]]; then
|
||
echo "This script must be run as root."
|
||
exit 1
|
||
else
|
||
if [[ "$param1" == "init" ]]; then
|
||
# Always check existing config first
|
||
initCheckConfigs
|
||
|
||
if [[ "$param6" == "true" || "$param6" == "1" ]]; then
|
||
# Validate unattended params
|
||
validateUnattended
|
||
initDisplayConfig # in unattended mode, it will auto-accept
|
||
else
|
||
# Interactive mode
|
||
initInputQuestions
|
||
initDisplayConfig
|
||
fi
|
||
|
||
# Common steps (run in both interactive and unattended after confirmation)
|
||
if [ "$init_skip_os_update" != true ]; then
|
||
initOS
|
||
fi
|
||
|
||
if [ "$init_skip_prereqs" != true ]; then
|
||
initPrerequires
|
||
fi
|
||
|
||
initDocker
|
||
initUsers
|
||
initFolders
|
||
initGIT
|
||
initLibrePortalCommand
|
||
initUpdateConfigs
|
||
completeInitMessage
|
||
fi
|
||
fi
|