LibrePortal/init.sh
librelad 6a2ba02647 security(init): manage manager-user sudo via validated sudoers.d drop-in
init.sh appended 'libreportal ALL=(ALL) NOPASSWD: ALL' straight to /etc/sudoers
— a malformed line there locks out sudo entirely. Move it to a validated
/etc/sudoers.d/libreportal drop-in (visudo -cf before install, 0440 root:root).
The grant is still broad; this is the single managed file we tighten to a
scoped command allowlist once the runtime no longer needs broad root. Only runs
at install, so existing boxes are untouched.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 20:26:43 +01:00

1224 lines
38 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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"
fqdn_file="/root/libreportal-fqdn.txt"
command_script="/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
if [[ -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
sudo sed -i "s|^$config_option=.*|$config_option=$escaped_value $comment_part|" "$config_file"
else
sudo 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 [[ -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
sudo useradd -s /bin/bash -d "/home/$sudo_user_name" -m -G sudo "$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 lives in a validated /etc/sudoers.d drop-in, not appended
# to /etc/sudoers — a malformed line in the main file locks out sudo entirely.
# The grant is broad for now; this single drop-in is what gets tightened to a
# scoped command allowlist once the runtime no longer needs broad root.
local sudoers_dropin="/etc/sudoers.d/${sudo_user_name}"
local sudoers_tmp
sudoers_tmp=$(mktemp)
printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$sudo_user_name" > "$sudoers_tmp"
if sudo visudo -cf "$sudoers_tmp" >/dev/null 2>&1; then
sudo install -m 0440 -o root -g root "$sudoers_tmp" "$sudoers_dropin"
isSuccessful "Configured 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"
}
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"
if ! sudo cp -a "$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..."
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
sudo sed -i "s|^$config_option=.*|$config_option=$escaped_value $comment_part|" "$config_file"
else
sudo 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
sudo mkdir -p "$dst"
if ! sudo cp -a "$src"/. "$dst"/; then
echo "ERROR: Failed to sync configs from $src to $dst."
return 1
fi
sudo 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() {
sudo 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 sudo -u libreportal 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 sudo -u libreportal 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
sudo chmod 0755 /docker/install/*
cd /docker/install
sudo ./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 $sudo_user_name:$sudo_user_name $command_script
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
}
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