1206 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			1206 lines
		
	
	
		
			41 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
| #!/bin/bash
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs Node.js and optional global modules.
 | |
| #
 | |
| # Description:
 | |
| #   - Installs specified Node.js version using NodeSource APT repo
 | |
| #   - Optionally installs or updates global npm modules
 | |
| #
 | |
| # Variables:
 | |
| #   NODE_VERSION   - Node.js version to install (default: 22)
 | |
| #   NODE_MODULE    - Comma-separated list of global modules (e.g. "yarn,@vue/cli@5.0.0")
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_node_and_modules() {
 | |
|   local NODE_VERSION="${NODE_VERSION:-22}"
 | |
|   local NODE_MODULE="${NODE_MODULE:-}"
 | |
|   local CURRENT_NODE_VERSION=""
 | |
|   local NEED_NODE_INSTALL=false
 | |
| 
 | |
|   # Check if Node.js is already installed
 | |
|   if command -v node >/dev/null; then
 | |
|     CURRENT_NODE_VERSION="$(node -v | grep -oP '^v\K[0-9]+')"
 | |
|     if [[ "$CURRENT_NODE_VERSION" != "$NODE_VERSION" ]]; then
 | |
|       msg_info "Node.js version $CURRENT_NODE_VERSION found, replacing with $NODE_VERSION"
 | |
|       NEED_NODE_INSTALL=true
 | |
|     else
 | |
|       msg_ok "Node.js $NODE_VERSION already installed"
 | |
|     fi
 | |
|   else
 | |
|     msg_info "Node.js not found, installing version $NODE_VERSION"
 | |
|     NEED_NODE_INSTALL=true
 | |
|   fi
 | |
| 
 | |
|   # Install Node.js if required
 | |
|   if [[ "$NEED_NODE_INSTALL" == true ]]; then
 | |
|     $STD apt-get purge -y nodejs
 | |
|     rm -f /etc/apt/sources.list.d/nodesource.list /etc/apt/keyrings/nodesource.gpg
 | |
| 
 | |
|     mkdir -p /etc/apt/keyrings
 | |
| 
 | |
|     if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key |
 | |
|       gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; then
 | |
|       msg_error "Failed to download or import NodeSource GPG key"
 | |
|       exit 1
 | |
|     fi
 | |
| 
 | |
|     echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_${NODE_VERSION}.x nodistro main" \
 | |
|       >/etc/apt/sources.list.d/nodesource.list
 | |
| 
 | |
|     if ! apt-get update >/dev/null 2>&1; then
 | |
|       msg_error "Failed to update APT repositories after adding NodeSource"
 | |
|       exit 1
 | |
|     fi
 | |
| 
 | |
|     if ! apt-get install -y nodejs >/dev/null 2>&1; then
 | |
|       msg_error "Failed to install Node.js ${NODE_VERSION} from NodeSource"
 | |
|       exit 1
 | |
|     fi
 | |
| 
 | |
|     msg_ok "Installed Node.js ${NODE_VERSION}"
 | |
|   fi
 | |
| 
 | |
|   export NODE_OPTIONS="--max-old-space-size=4096"
 | |
| 
 | |
|   # Install global Node modules
 | |
|   if [[ -n "$NODE_MODULE" ]]; then
 | |
|     IFS=',' read -ra MODULES <<<"$NODE_MODULE"
 | |
|     for mod in "${MODULES[@]}"; do
 | |
|       local MODULE_NAME MODULE_REQ_VERSION MODULE_INSTALLED_VERSION
 | |
|       if [[ "$mod" == @*/*@* ]]; then
 | |
|         # Scoped package with version, e.g. @vue/cli-service@latest
 | |
|         MODULE_NAME="${mod%@*}"
 | |
|         MODULE_REQ_VERSION="${mod##*@}"
 | |
|       elif [[ "$mod" == *"@"* ]]; then
 | |
|         # Unscoped package with version, e.g. yarn@latest
 | |
|         MODULE_NAME="${mod%@*}"
 | |
|         MODULE_REQ_VERSION="${mod##*@}"
 | |
|       else
 | |
|         # No version specified
 | |
|         MODULE_NAME="$mod"
 | |
|         MODULE_REQ_VERSION="latest"
 | |
|       fi
 | |
| 
 | |
|       # Check if the module is already installed
 | |
|       if npm list -g --depth=0 "$MODULE_NAME" >/dev/null 2>&1; then
 | |
|         MODULE_INSTALLED_VERSION="$(npm list -g --depth=0 "$MODULE_NAME" | grep "$MODULE_NAME@" | awk -F@ '{print $2}' | tr -d '[:space:]')"
 | |
|         if [[ "$MODULE_REQ_VERSION" != "latest" && "$MODULE_REQ_VERSION" != "$MODULE_INSTALLED_VERSION" ]]; then
 | |
|           msg_info "Updating $MODULE_NAME from v$MODULE_INSTALLED_VERSION to v$MODULE_REQ_VERSION"
 | |
|           if ! $STD npm install -g "${MODULE_NAME}@${MODULE_REQ_VERSION}"; then
 | |
|             msg_error "Failed to update $MODULE_NAME to version $MODULE_REQ_VERSION"
 | |
|             exit 1
 | |
|           fi
 | |
|         elif [[ "$MODULE_REQ_VERSION" == "latest" ]]; then
 | |
|           msg_info "Updating $MODULE_NAME to latest version"
 | |
|           if ! $STD npm install -g "${MODULE_NAME}@latest"; then
 | |
|             msg_error "Failed to update $MODULE_NAME to latest version"
 | |
|             exit 1
 | |
|           fi
 | |
|         else
 | |
|           msg_ok "$MODULE_NAME@$MODULE_INSTALLED_VERSION already installed"
 | |
|         fi
 | |
|       else
 | |
|         msg_info "Installing $MODULE_NAME@$MODULE_REQ_VERSION"
 | |
|         if ! $STD npm install -g "${MODULE_NAME}@${MODULE_REQ_VERSION}"; then
 | |
|           msg_error "Failed to install $MODULE_NAME@$MODULE_REQ_VERSION"
 | |
|           exit 1
 | |
|         fi
 | |
|       fi
 | |
|     done
 | |
|     msg_ok "All requested Node modules have been processed"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs or upgrades PostgreSQL and performs data migration.
 | |
| #
 | |
| # Description:
 | |
| #   - Detects existing PostgreSQL version
 | |
| #   - Dumps all databases before upgrade
 | |
| #   - Adds PGDG repo and installs specified version
 | |
| #   - Restores dumped data post-upgrade
 | |
| #
 | |
| # Variables:
 | |
| #   PG_VERSION     - Major PostgreSQL version (e.g. 15, 16) (default: 16)
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_postgresql() {
 | |
|   local PG_VERSION="${PG_VERSION:-16}"
 | |
|   local CURRENT_PG_VERSION=""
 | |
|   local DISTRO
 | |
|   local NEED_PG_INSTALL=false
 | |
|   DISTRO="$(awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release)"
 | |
| 
 | |
|   if command -v psql >/dev/null; then
 | |
|     CURRENT_PG_VERSION="$(psql -V | awk '{print $3}' | cut -d. -f1)"
 | |
|     if [[ "$CURRENT_PG_VERSION" == "$PG_VERSION" ]]; then
 | |
|       msg_ok "PostgreSQL $PG_VERSION is already installed"
 | |
|       return
 | |
|     fi
 | |
|     msg_info "Detected PostgreSQL $CURRENT_PG_VERSION, preparing upgrade to $PG_VERSION"
 | |
|     NEED_PG_INSTALL=true
 | |
|   else
 | |
|     msg_info "Setup PostgreSQL $PG_VERSION"
 | |
|     NEED_PG_INSTALL=true
 | |
|   fi
 | |
| 
 | |
|   if [[ "$NEED_PG_INSTALL" == true ]]; then
 | |
|     if [[ -n "$CURRENT_PG_VERSION" ]]; then
 | |
|       msg_info "Dumping all PostgreSQL data from version $CURRENT_PG_VERSION"
 | |
|       su - postgres -c "pg_dumpall > /var/lib/postgresql/backup_$(date +%F)_v${CURRENT_PG_VERSION}.sql"
 | |
| 
 | |
|       msg_info "Stopping PostgreSQL service"
 | |
|       systemctl stop postgresql
 | |
|     fi
 | |
| 
 | |
|     msg_info "Removing pgdg repo and old GPG key"
 | |
|     rm -f /etc/apt/sources.list.d/pgdg.list /etc/apt/trusted.gpg.d/postgresql.gpg
 | |
| 
 | |
|     msg_info "Adding PostgreSQL PGDG repository"
 | |
|     curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc |
 | |
|       gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
 | |
| 
 | |
|     echo "deb https://apt.postgresql.org/pub/repos/apt ${DISTRO}-pgdg main" \
 | |
|       >/etc/apt/sources.list.d/pgdg.list
 | |
| 
 | |
|     $STD apt-get update
 | |
| 
 | |
|     msg_info "Installing PostgreSQL $PG_VERSION"
 | |
|     $STD apt-get install -y "postgresql-${PG_VERSION}" "postgresql-client-${PG_VERSION}"
 | |
| 
 | |
|     if [[ -n "$CURRENT_PG_VERSION" ]]; then
 | |
|       $STD msg_info "Removing old PostgreSQL $CURRENT_PG_VERSION packages"
 | |
|       $STD apt-get purge -y "postgresql-${CURRENT_PG_VERSION}" "postgresql-client-${CURRENT_PG_VERSION}" || true
 | |
|     fi
 | |
| 
 | |
|     $STD msg_info "Starting PostgreSQL $PG_VERSION"
 | |
|     systemctl enable -q --now postgresql
 | |
| 
 | |
|     if [[ -n "$CURRENT_PG_VERSION" ]]; then
 | |
|       $STD msg_info "Restoring dumped data"
 | |
|       su - postgres -c "psql < /var/lib/postgresql/backup_$(date +%F)_v${CURRENT_PG_VERSION}.sql"
 | |
|     fi
 | |
| 
 | |
|     msg_ok "PostgreSQL $PG_VERSION installed"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs or updates MariaDB from official repo.
 | |
| #
 | |
| # Description:
 | |
| #   - Detects current MariaDB version and replaces it if necessary
 | |
| #   - Preserves existing database data
 | |
| #   - Dynamically determines latest GA version if "latest" is given
 | |
| #
 | |
| # Variables:
 | |
| #   MARIADB_VERSION - MariaDB version to install (e.g. 10.11, latest) (default: latest)
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_mariadb() {
 | |
|   local MARIADB_VERSION="${MARIADB_VERSION:-latest}"
 | |
|   local DISTRO_CODENAME
 | |
|   DISTRO_CODENAME="$(awk -F= '/^VERSION_CODENAME=/{print $2}' /etc/os-release)"
 | |
| 
 | |
|   # grab dynamic latest LTS version
 | |
|   if [[ "$MARIADB_VERSION" == "latest" ]]; then
 | |
|     $STD msg_info "Resolving latest GA MariaDB version"
 | |
|     MARIADB_VERSION=$(curl -fsSL http://mirror.mariadb.org/repo/ |
 | |
|       grep -Eo '[0-9]+\.[0-9]+\.[0-9]+/' |
 | |
|       grep -vE 'rc/|rolling/' |
 | |
|       sed 's|/||' |
 | |
|       sort -Vr |
 | |
|       head -n1)
 | |
|     if [[ -z "$MARIADB_VERSION" ]]; then
 | |
|       msg_error "Could not determine latest GA MariaDB version"
 | |
|       return 1
 | |
|     fi
 | |
|     $STD msg_ok "Latest GA MariaDB version is $MARIADB_VERSION"
 | |
|   fi
 | |
| 
 | |
|   local CURRENT_VERSION=""
 | |
|   if command -v mariadb >/dev/null; then
 | |
|     CURRENT_VERSION="$(mariadb --version | grep -oP 'Ver\s+\K[0-9]+\.[0-9]+')"
 | |
|   fi
 | |
| 
 | |
|   if [[ "$CURRENT_VERSION" == "$MARIADB_VERSION" ]]; then
 | |
|     $STD msg_info "MariaDB $MARIADB_VERSION, upgrading"
 | |
|     $STD apt-get update
 | |
|     $STD apt-get install --only-upgrade -y mariadb-server mariadb-client
 | |
|     $STD msg_ok "MariaDB upgraded to $MARIADB_VERSION"
 | |
|     return 0
 | |
|   fi
 | |
| 
 | |
|   if [[ -n "$CURRENT_VERSION" ]]; then
 | |
|     $STD msg_info "Replacing MariaDB $CURRENT_VERSION with $MARIADB_VERSION (data will be preserved)"
 | |
|     $STD systemctl stop mariadb >/dev/null 2>&1 || true
 | |
|     $STD apt-get purge -y 'mariadb*' || true
 | |
|     rm -f /etc/apt/sources.list.d/mariadb.list /etc/apt/trusted.gpg.d/mariadb.gpg
 | |
|   else
 | |
|     msg_info "Setup MariaDB $MARIADB_VERSION"
 | |
|   fi
 | |
| 
 | |
|   $STD msg_info "Setting up MariaDB Repository"
 | |
|   curl -fsSL "https://mariadb.org/mariadb_release_signing_key.asc" |
 | |
|     gpg --dearmor -o /etc/apt/trusted.gpg.d/mariadb.gpg
 | |
| 
 | |
|   echo "deb [signed-by=/etc/apt/trusted.gpg.d/mariadb.gpg] http://mirror.mariadb.org/repo/${MARIADB_VERSION}/debian ${DISTRO_CODENAME} main" \
 | |
|     >/etc/apt/sources.list.d/mariadb.list
 | |
| 
 | |
|   $STD apt-get update
 | |
|   $STD apt-get install -y mariadb-server mariadb-client
 | |
| 
 | |
|   msg_ok "Setup MariaDB $MARIADB_VERSION"
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs or upgrades MySQL and configures APT repo.
 | |
| #
 | |
| # Description:
 | |
| #   - Detects existing MySQL installation
 | |
| #   - Purges conflicting packages before installation
 | |
| #   - Supports clean upgrade
 | |
| #
 | |
| # Variables:
 | |
| #   MYSQL_VERSION  - MySQL version to install (e.g. 5.7, 8.0) (default: 8.0)
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_mysql() {
 | |
|   local MYSQL_VERSION="${MYSQL_VERSION:-8.0}"
 | |
|   local CURRENT_VERSION=""
 | |
|   local NEED_INSTALL=false
 | |
| 
 | |
|   if command -v mysql >/dev/null; then
 | |
|     CURRENT_VERSION="$(mysql --version | grep -oP 'Distrib\s+\K[0-9]+\.[0-9]+')"
 | |
|     if [[ "$CURRENT_VERSION" != "$MYSQL_VERSION" ]]; then
 | |
|       msg_info "MySQL $CURRENT_VERSION found, replacing with $MYSQL_VERSION"
 | |
|       NEED_INSTALL=true
 | |
|     else
 | |
|       msg_ok "MySQL $MYSQL_VERSION already installed"
 | |
|     fi
 | |
|   else
 | |
|     msg_info "MySQL not found, installing version $MYSQL_VERSION"
 | |
|     NEED_INSTALL=true
 | |
|   fi
 | |
| 
 | |
|   if [[ "$NEED_INSTALL" == true ]]; then
 | |
|     msg_info "Removing conflicting MySQL packages"
 | |
|     $STD systemctl stop mysql >/dev/null 2>&1 || true
 | |
|     $STD apt-get purge -y 'mysql*'
 | |
|     rm -f /etc/apt/sources.list.d/mysql.list /etc/apt/trusted.gpg.d/mysql.gpg
 | |
| 
 | |
|     msg_info "Setting up MySQL APT Repository"
 | |
|     DISTRO_CODENAME="$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)"
 | |
|     curl -fsSL https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 | gpg --dearmor -o /etc/apt/trusted.gpg.d/mysql.gpg
 | |
|     echo "deb [signed-by=/etc/apt/trusted.gpg.d/mysql.gpg] https://repo.mysql.com/apt/debian/ ${DISTRO_CODENAME} mysql-${MYSQL_VERSION}" \
 | |
|       >/etc/apt/sources.list.d/mysql.list
 | |
| 
 | |
|     $STD apt-get update
 | |
|     $STD apt-get install -y mysql-server
 | |
| 
 | |
|     msg_ok "Installed MySQL $MYSQL_VERSION"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs PHP with selected modules and configures Apache/FPM support.
 | |
| #
 | |
| # Description:
 | |
| #   - Adds Sury PHP repo if needed
 | |
| #   - Installs default and user-defined modules
 | |
| #   - Patches php.ini for CLI, Apache, and FPM as needed
 | |
| #
 | |
| # Variables:
 | |
| #   PHP_VERSION                - PHP version to install (default: 8.4)
 | |
| #   PHP_MODULE                 - Additional comma-separated modules
 | |
| #   PHP_APACHE                 - Set YES to enable PHP with Apache
 | |
| #   PHP_FPM                    - Set YES to enable PHP-FPM
 | |
| #   PHP_MEMORY_LIMIT           - (default: 512M)
 | |
| #   PHP_UPLOAD_MAX_FILESIZE    - (default: 128M)
 | |
| #   PHP_POST_MAX_SIZE          - (default: 128M)
 | |
| #   PHP_MAX_EXECUTION_TIME     - (default: 300)
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_php() {
 | |
|   local PHP_VERSION="${PHP_VERSION:-8.4}"
 | |
|   local PHP_MODULE="${PHP_MODULE:-}"
 | |
|   local PHP_APACHE="${PHP_APACHE:-NO}"
 | |
|   local PHP_FPM="${PHP_FPM:-NO}"
 | |
|   local DISTRO_CODENAME
 | |
|   DISTRO_CODENAME=$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)
 | |
| 
 | |
|   local DEFAULT_MODULES="bcmath,cli,curl,gd,intl,mbstring,opcache,readline,xml,zip"
 | |
|   local COMBINED_MODULES
 | |
| 
 | |
|   local PHP_MEMORY_LIMIT="${PHP_MEMORY_LIMIT:-512M}"
 | |
|   local PHP_UPLOAD_MAX_FILESIZE="${PHP_UPLOAD_MAX_FILESIZE:-128M}"
 | |
|   local PHP_POST_MAX_SIZE="${PHP_POST_MAX_SIZE:-128M}"
 | |
|   local PHP_MAX_EXECUTION_TIME="${PHP_MAX_EXECUTION_TIME:-300}"
 | |
| 
 | |
|   # Merge default + user-defined modules
 | |
|   if [[ -n "$PHP_MODULE" ]]; then
 | |
|     COMBINED_MODULES="${DEFAULT_MODULES},${PHP_MODULE}"
 | |
|   else
 | |
|     COMBINED_MODULES="${DEFAULT_MODULES}"
 | |
|   fi
 | |
| 
 | |
|   # Deduplicate modules
 | |
|   COMBINED_MODULES=$(echo "$COMBINED_MODULES" | tr ',' '\n' | awk '!seen[$0]++' | paste -sd, -)
 | |
| 
 | |
|   local CURRENT_PHP
 | |
|   if command -v php >/dev/null 2>&1; then
 | |
|     CURRENT_PHP=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2)
 | |
|   else
 | |
|     CURRENT_PHP=""
 | |
|   fi
 | |
| 
 | |
|   if [[ "$CURRENT_PHP" != "$PHP_VERSION" ]]; then
 | |
|     $STD msg_info "PHP $CURRENT_PHP detected, migrating to PHP $PHP_VERSION"
 | |
|     if [[ ! -f /etc/apt/sources.list.d/php.list ]]; then
 | |
|       $STD curl -fsSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb
 | |
|       $STD dpkg -i /tmp/debsuryorg-archive-keyring.deb
 | |
|       echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ ${DISTRO_CODENAME} main" \
 | |
|         >/etc/apt/sources.list.d/php.list
 | |
|       $STD apt-get update
 | |
|     fi
 | |
| 
 | |
|     $STD apt-get purge -y "php${CURRENT_PHP//./}"* || true
 | |
|   fi
 | |
| 
 | |
|   local MODULE_LIST="php${PHP_VERSION}"
 | |
|   IFS=',' read -ra MODULES <<<"$COMBINED_MODULES"
 | |
|   for mod in "${MODULES[@]}"; do
 | |
|     MODULE_LIST+=" php${PHP_VERSION}-${mod}"
 | |
|   done
 | |
| 
 | |
|   if [[ "$PHP_APACHE" == "YES" ]]; then
 | |
|     # Optionally disable old Apache PHP module
 | |
|     if [[ -f /etc/apache2/mods-enabled/php${CURRENT_PHP}.load ]]; then
 | |
|       $STD a2dismod php${CURRENT_PHP} || true
 | |
|     fi
 | |
|   fi
 | |
| 
 | |
|   if [[ "$PHP_FPM" == "YES" ]]; then
 | |
|     $STD systemctl stop php${CURRENT_PHP}-fpm || true
 | |
|     $STD systemctl disable php${CURRENT_PHP}-fpm || true
 | |
|   fi
 | |
| 
 | |
|   $STD apt-get install -y $MODULE_LIST
 | |
|   msg_ok "Installed PHP $PHP_VERSION with selected modules"
 | |
| 
 | |
|   if [[ "$PHP_APACHE" == "YES" ]]; then
 | |
|     $STD systemctl restart apache2 || true
 | |
|   fi
 | |
| 
 | |
|   if [[ "$PHP_FPM" == "YES" ]]; then
 | |
|     $STD systemctl enable php${PHP_VERSION}-fpm
 | |
|     $STD systemctl restart php${PHP_VERSION}-fpm
 | |
|   fi
 | |
| 
 | |
|   # Patch all relevant php.ini files
 | |
|   local PHP_INI_PATHS=()
 | |
|   PHP_INI_PATHS+=("/etc/php/${PHP_VERSION}/cli/php.ini")
 | |
|   [[ "$PHP_FPM" == "YES" ]] && PHP_INI_PATHS+=("/etc/php/${PHP_VERSION}/fpm/php.ini")
 | |
|   [[ "$PHP_APACHE" == "YES" ]] && PHP_INI_PATHS+=("/etc/php/${PHP_VERSION}/apache2/php.ini")
 | |
| 
 | |
|   for ini in "${PHP_INI_PATHS[@]}"; do
 | |
|     if [[ -f "$ini" ]]; then
 | |
|       $STD msg_info "Patching $ini"
 | |
|       sed -i "s|^memory_limit = .*|memory_limit = ${PHP_MEMORY_LIMIT}|" "$ini"
 | |
|       sed -i "s|^upload_max_filesize = .*|upload_max_filesize = ${PHP_UPLOAD_MAX_FILESIZE}|" "$ini"
 | |
|       sed -i "s|^post_max_size = .*|post_max_size = ${PHP_POST_MAX_SIZE}|" "$ini"
 | |
|       sed -i "s|^max_execution_time = .*|max_execution_time = ${PHP_MAX_EXECUTION_TIME}|" "$ini"
 | |
|       $STD msg_ok "Patched $ini"
 | |
|     fi
 | |
|   done
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs or updates Composer globally.
 | |
| #
 | |
| # Description:
 | |
| #   - Downloads latest version from getcomposer.org
 | |
| #   - Installs to /usr/local/bin/composer
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_composer() {
 | |
|   local COMPOSER_BIN="/usr/local/bin/composer"
 | |
|   export COMPOSER_ALLOW_SUPERUSER=1
 | |
| 
 | |
|   # Check if composer is already installed
 | |
|   if [[ -x "$COMPOSER_BIN" ]]; then
 | |
|     local CURRENT_VERSION
 | |
|     CURRENT_VERSION=$("$COMPOSER_BIN" --version | awk '{print $3}')
 | |
|     $STD msg_info "Composer $CURRENT_VERSION found, updating to latest"
 | |
|   else
 | |
|     msg_info "Setup Composer"
 | |
|   fi
 | |
| 
 | |
|   # Download and install latest composer
 | |
|   curl -fsSL https://getcomposer.org/installer -o /tmp/composer-setup.php
 | |
|   php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer &>/dev/null
 | |
| 
 | |
|   if [[ $? -ne 0 ]]; then
 | |
|     msg_error "Failed to install Composer"
 | |
|     return 1
 | |
|   fi
 | |
| 
 | |
|   chmod +x "$COMPOSER_BIN"
 | |
|   msg_ok "Installed Composer $($COMPOSER_BIN --version | awk '{print $3}')"
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs Go (Golang) from official tarball.
 | |
| #
 | |
| # Description:
 | |
| #   - Determines system architecture
 | |
| #   - Downloads latest version if GO_VERSION not set
 | |
| #
 | |
| # Variables:
 | |
| #   GO_VERSION     - Version to install (e.g. 1.22.2 or latest)
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_go() {
 | |
|   local ARCH
 | |
|   case "$(uname -m)" in
 | |
|   x86_64) ARCH="amd64" ;;
 | |
|   aarch64) ARCH="arm64" ;;
 | |
|   *)
 | |
|     msg_error "Unsupported architecture: $(uname -m)"
 | |
|     return 1
 | |
|     ;;
 | |
|   esac
 | |
| 
 | |
|   # Determine version
 | |
|   if [[ -z "${GO_VERSION:-}" || "${GO_VERSION}" == "latest" ]]; then
 | |
|     GO_VERSION=$(curl -fsSL https://go.dev/VERSION?m=text | head -n1 | sed 's/^go//')
 | |
|     if [[ -z "$GO_VERSION" ]]; then
 | |
|       msg_error "Could not determine latest Go version"
 | |
|       return 1
 | |
|     fi
 | |
|     $STD msg_info "Detected latest Go version: $GO_VERSION"
 | |
|   fi
 | |
| 
 | |
|   local GO_BIN="/usr/local/bin/go"
 | |
|   local GO_INSTALL_DIR="/usr/local/go"
 | |
| 
 | |
|   if [[ -x "$GO_BIN" ]]; then
 | |
|     local CURRENT_VERSION
 | |
|     CURRENT_VERSION=$("$GO_BIN" version | awk '{print $3}' | sed 's/go//')
 | |
|     if [[ "$CURRENT_VERSION" == "$GO_VERSION" ]]; then
 | |
|       $STD msg_ok "Go $GO_VERSION already installed"
 | |
|       return 0
 | |
|     else
 | |
|       $STD msg_info "Go $CURRENT_VERSION found, upgrading to $GO_VERSION"
 | |
|       rm -rf "$GO_INSTALL_DIR"
 | |
|     fi
 | |
|   else
 | |
|     msg_info "Installing Go $GO_VERSION"
 | |
|   fi
 | |
| 
 | |
|   local TARBALL="go${GO_VERSION}.linux-${ARCH}.tar.gz"
 | |
|   local URL="https://go.dev/dl/${TARBALL}"
 | |
|   local TMP_TAR=$(mktemp)
 | |
| 
 | |
|   curl -fsSL "$URL" -o "$TMP_TAR" || {
 | |
|     msg_error "Failed to download $TARBALL"
 | |
|     return 1
 | |
|   }
 | |
| 
 | |
|   tar -C /usr/local -xzf "$TMP_TAR"
 | |
|   ln -sf /usr/local/go/bin/go /usr/local/bin/go
 | |
|   ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt
 | |
|   rm -f "$TMP_TAR"
 | |
| 
 | |
|   msg_ok "Installed Go $GO_VERSION"
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs Temurin JDK via Adoptium APT repository.
 | |
| #
 | |
| # Description:
 | |
| #   - Removes previous JDK if version mismatch
 | |
| #   - Installs or upgrades to specified JAVA_VERSION
 | |
| #
 | |
| # Variables:
 | |
| #   JAVA_VERSION   - Temurin JDK version to install (e.g. 17, 21)
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_java() {
 | |
|   local JAVA_VERSION="${JAVA_VERSION:-21}"
 | |
|   local DISTRO_CODENAME
 | |
|   DISTRO_CODENAME=$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)
 | |
|   local DESIRED_PACKAGE="temurin-${JAVA_VERSION}-jdk"
 | |
| 
 | |
|   # Add Adoptium repo if missing
 | |
|   if [[ ! -f /etc/apt/sources.list.d/adoptium.list ]]; then
 | |
|     msg_info "Setting up Adoptium Repository"
 | |
|     mkdir -p /etc/apt/keyrings
 | |
|     curl -fsSL "https://packages.adoptium.net/artifactory/api/gpg/key/public" | gpg --dearmor -o /etc/apt/trusted.gpg.d/adoptium.gpg
 | |
|     echo "deb [signed-by=/etc/apt/trusted.gpg.d/adoptium.gpg] https://packages.adoptium.net/artifactory/deb ${DISTRO_CODENAME} main" \
 | |
|       >/etc/apt/sources.list.d/adoptium.list
 | |
|     $STD apt-get update
 | |
|     msg_ok "Set up Adoptium Repository"
 | |
|   fi
 | |
| 
 | |
|   # Detect currently installed temurin version
 | |
|   local INSTALLED_VERSION=""
 | |
|   if dpkg -l | grep -q "temurin-.*-jdk"; then
 | |
|     INSTALLED_VERSION=$(dpkg -l | awk '/temurin-.*-jdk/{print $2}' | grep -oP 'temurin-\K[0-9]+')
 | |
|   fi
 | |
| 
 | |
|   if [[ "$INSTALLED_VERSION" == "$JAVA_VERSION" ]]; then
 | |
|     msg_info "Temurin JDK $JAVA_VERSION already installed, updating if needed"
 | |
|     $STD apt-get update
 | |
|     $STD apt-get install --only-upgrade -y "$DESIRED_PACKAGE"
 | |
|     msg_ok "Updated Temurin JDK $JAVA_VERSION (if applicable)"
 | |
|   else
 | |
|     if [[ -n "$INSTALLED_VERSION" ]]; then
 | |
|       msg_info "Removing Temurin JDK $INSTALLED_VERSION"
 | |
|       $STD apt-get purge -y "temurin-${INSTALLED_VERSION}-jdk"
 | |
|     fi
 | |
| 
 | |
|     msg_info "Installing Temurin JDK $JAVA_VERSION"
 | |
|     $STD apt-get install -y "$DESIRED_PACKAGE"
 | |
|     msg_ok "Installed Temurin JDK $JAVA_VERSION"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs or updates MongoDB to specified major version.
 | |
| #
 | |
| # Description:
 | |
| #   - Preserves data across installations
 | |
| #   - Adds official MongoDB repo
 | |
| #
 | |
| # Variables:
 | |
| #   MONGO_VERSION  - MongoDB major version to install (e.g. 7.0, 8.0)
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| install_mongodb() {
 | |
|   local MONGO_VERSION="${MONGO_VERSION:-8.0}"
 | |
|   local DISTRO_CODENAME
 | |
|   DISTRO_CODENAME=$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)
 | |
|   local REPO_LIST="/etc/apt/sources.list.d/mongodb-org-${MONGO_VERSION}.list"
 | |
| 
 | |
|   # Aktuell installierte Major-Version ermitteln
 | |
|   local INSTALLED_VERSION=""
 | |
|   if command -v mongod >/dev/null; then
 | |
|     INSTALLED_VERSION=$(mongod --version | awk '/db version/{print $3}' | cut -d. -f1,2)
 | |
|   fi
 | |
| 
 | |
|   if [[ "$INSTALLED_VERSION" == "$MONGO_VERSION" ]]; then
 | |
|     msg_info "MongoDB $MONGO_VERSION already installed, checking for upgrade"
 | |
|     $STD apt-get update
 | |
|     $STD apt-get install --only-upgrade -y mongodb-org
 | |
|     msg_ok "MongoDB $MONGO_VERSION upgraded if needed"
 | |
|     return 0
 | |
|   fi
 | |
| 
 | |
|   # Ältere Version entfernen (nur Packages, nicht Daten!)
 | |
|   if [[ -n "$INSTALLED_VERSION" ]]; then
 | |
|     msg_info "Replacing MongoDB $INSTALLED_VERSION with $MONGO_VERSION (data will be preserved)"
 | |
|     $STD systemctl stop mongod || true
 | |
|     $STD apt-get purge -y mongodb-org || true
 | |
|     rm -f /etc/apt/sources.list.d/mongodb-org-*.list
 | |
|     rm -f /etc/apt/trusted.gpg.d/mongodb-*.gpg
 | |
|   else
 | |
|     msg_info "Installing MongoDB $MONGO_VERSION"
 | |
|   fi
 | |
| 
 | |
|   # MongoDB Repo hinzufügen
 | |
|   curl -fsSL "https://pgp.mongodb.com/server-${MONGO_VERSION}.asc" | gpg --dearmor -o "/etc/apt/trusted.gpg.d/mongodb-${MONGO_VERSION}.gpg"
 | |
|   echo "deb [signed-by=/etc/apt/trusted.gpg.d/mongodb-${MONGO_VERSION}.gpg] https://repo.mongodb.org/apt/debian ${DISTRO_CODENAME}/mongodb-org/${MONGO_VERSION} main" \
 | |
|     >"$REPO_LIST"
 | |
| 
 | |
|   $STD apt-get update
 | |
|   $STD apt-get install -y mongodb-org
 | |
| 
 | |
|   # Sicherstellen, dass Datenverzeichnis intakt bleibt
 | |
|   mkdir -p /var/lib/mongodb
 | |
|   chown -R mongodb:mongodb /var/lib/mongodb
 | |
| 
 | |
|   $STD systemctl enable mongod
 | |
|   $STD systemctl start mongod
 | |
|   msg_ok "MongoDB $MONGO_VERSION installed and started"
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Downloads and deploys latest GitHub release tarball.
 | |
| #
 | |
| # Description:
 | |
| #   - Fetches latest release from GitHub API
 | |
| #   - Detects matching asset by architecture
 | |
| #   - Extracts to /opt/<app> and saves version
 | |
| #
 | |
| # Variables:
 | |
| #   APP            - Override default application name (optional)
 | |
| #   GITHUB_TOKEN   - (optional) GitHub token for private rate limits
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| fetch_and_deploy_gh_release() {
 | |
|   local repo="$1"
 | |
|   local app=${APP:-$(echo "${APPLICATION,,}" | tr -d ' ')}
 | |
|   local api_url="https://api.github.com/repos/$repo/releases/latest"
 | |
|   local header=()
 | |
|   local attempt=0
 | |
|   local max_attempts=3
 | |
|   local api_response tag http_code
 | |
|   local current_version=""
 | |
|   local curl_timeout="--connect-timeout 10 --max-time 30"
 | |
|   # Check if the app directory exists and if there's a version file
 | |
|   if [[ -f "/opt/${app}_version.txt" ]]; then
 | |
|     current_version=$(cat "/opt/${app}_version.txt")
 | |
|     $STD msg_info "Current version: $current_version"
 | |
|   fi
 | |
|   # ensure that jq is installed
 | |
|   if ! command -v jq &>/dev/null; then
 | |
|     $STD msg_info "Installing jq..."
 | |
|     $STD apt-get update -qq &>/dev/null
 | |
|     $STD apt-get install -y jq &>/dev/null || {
 | |
|       msg_error "Failed to install jq"
 | |
|       return 1
 | |
|     }
 | |
|   fi
 | |
|   [[ -n "${GITHUB_TOKEN:-}" ]] && header=(-H "Authorization: token $GITHUB_TOKEN")
 | |
|   until [[ $attempt -ge $max_attempts ]]; do
 | |
|     ((attempt++)) || true
 | |
|     $STD msg_info "[$attempt/$max_attempts] Fetching GitHub release for $repo...\n"
 | |
|     api_response=$(curl $curl_timeout -fsSL -w "%{http_code}" -o /tmp/gh_resp.json "${header[@]}" "$api_url")
 | |
|     http_code="${api_response:(-3)}"
 | |
|     if [[ "$http_code" == "404" ]]; then
 | |
|       msg_error "Repository $repo has no Release candidate (404)"
 | |
|       return 1
 | |
|     fi
 | |
|     if [[ "$http_code" != "200" ]]; then
 | |
|       $STD msg_info "Request failed with HTTP $http_code, retrying...\n"
 | |
|       sleep $((attempt * 2))
 | |
|       continue
 | |
|     fi
 | |
|     api_response=$(</tmp/gh_resp.json)
 | |
|     if echo "$api_response" | grep -q "API rate limit exceeded"; then
 | |
|       msg_error "GitHub API rate limit exceeded."
 | |
|       return 1
 | |
|     fi
 | |
|     if echo "$api_response" | jq -e '.message == "Not Found"' &>/dev/null; then
 | |
|       msg_error "Repository not found: $repo"
 | |
|       return 1
 | |
|     fi
 | |
|     tag=$(echo "$api_response" | jq -r '.tag_name // .name // empty')
 | |
|     [[ "$tag" =~ ^v[0-9] ]] && tag="${tag:1}"
 | |
|     version="${tag#v}"
 | |
|     if [[ -z "$tag" ]]; then
 | |
|       $STD msg_info "Empty tag received, retrying...\n"
 | |
|       sleep $((attempt * 2))
 | |
|       continue
 | |
|     fi
 | |
|     $STD msg_ok "Found release: $tag for $repo"
 | |
|     break
 | |
|   done
 | |
|   if [[ -z "$tag" ]]; then
 | |
|     msg_error "Failed to fetch release for $repo after $max_attempts attempts."
 | |
|     exit 1
 | |
|   fi
 | |
|   # Version comparison (if we already have this version, skip)
 | |
|   if [[ "$current_version" == "$tag" ]]; then
 | |
|     $STD msg_info "Already running the latest version ($tag). Skipping update."
 | |
|     return 0
 | |
|   fi
 | |
| 
 | |
|   local base_url="https://github.com/$repo/releases/download/v$tag"
 | |
|   local tmpdir
 | |
|   tmpdir=$(mktemp -d) || return 1
 | |
|   # Extract list of assets from the Release API
 | |
|   local assets urls
 | |
|   assets=$(echo "$api_response" | jq -r '.assets[].browser_download_url') || true
 | |
|   # Detect current architecture
 | |
|   local arch
 | |
|   if command -v dpkg &>/dev/null; then
 | |
|     arch=$(dpkg --print-architecture)
 | |
|   elif command -v uname &>/dev/null; then
 | |
|     case "$(uname -m)" in
 | |
|     x86_64) arch="amd64" ;;
 | |
|     aarch64) arch="arm64" ;;
 | |
|     armv7l) arch="armv7" ;;
 | |
|     armv6l) arch="armv6" ;;
 | |
|     *) arch="unknown" ;;
 | |
|     esac
 | |
|   else
 | |
|     arch="unknown"
 | |
|   fi
 | |
|   $STD msg_info "Detected system architecture: $arch"
 | |
|   # Try to find a matching asset for our architecture
 | |
|   local url=""
 | |
|   for u in $assets; do
 | |
|     if [[ "$u" =~ $arch.*\.tar\.gz$ ]]; then
 | |
|       url="$u"
 | |
|       $STD msg_info "Found matching architecture asset: $url"
 | |
|       break
 | |
|     fi
 | |
|   done
 | |
|   # Fallback to other architectures if our specific one isn't found
 | |
|   if [[ -z "$url" ]]; then
 | |
|     for u in $assets; do
 | |
|       if [[ "$u" =~ (x86_64|amd64|arm64|armv7|armv6).*\.tar\.gz$ ]]; then
 | |
|         url="$u"
 | |
|         $STD msg_info "Architecture-specific asset not found, using: $url"
 | |
|         break
 | |
|       fi
 | |
|     done
 | |
|   fi
 | |
|   # Fallback to any tar.gz
 | |
|   if [[ -z "$url" ]]; then
 | |
|     for u in $assets; do
 | |
|       if [[ "$u" =~ \.tar\.gz$ ]]; then
 | |
|         url="$u"
 | |
|         $STD msg_info "Using generic tarball: $url"
 | |
|         break
 | |
|       fi
 | |
|     done
 | |
|   fi
 | |
|   # Final fallback to GitHub source tarball
 | |
|   if [[ -z "$url" ]]; then
 | |
|     # Use tarball_url directly from API response instead of constructing our own URL
 | |
|     url=$(echo "$api_response" | jq -r '.tarball_url // empty')
 | |
| 
 | |
|     # If tarball_url is empty for some reason, fall back to a constructed URL as before
 | |
|     if [[ -z "$url" ]]; then
 | |
|       url="https://github.com/$repo/archive/refs/tags/v$version.tar.gz"
 | |
|     fi
 | |
| 
 | |
|     $STD msg_info "Using GitHub source tarball: $url"
 | |
|   fi
 | |
|   local filename="${url##*/}"
 | |
|   $STD msg_info "Downloading $url"
 | |
|   if ! curl $curl_timeout -fsSL -o "$tmpdir/$filename" "$url"; then
 | |
|     msg_error "Failed to download release asset from $url"
 | |
|     rm -rf "$tmpdir"
 | |
|     return 1
 | |
|   fi
 | |
|   mkdir -p "/opt/$app"
 | |
|   tar -xzf "$tmpdir/$filename" -C "$tmpdir"
 | |
|   local content_root
 | |
|   content_root=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d)
 | |
|   if [[ $(echo "$content_root" | wc -l) -eq 1 ]]; then
 | |
|     shopt -s dotglob nullglob
 | |
|     cp -r "$content_root"/* "/opt/$app/"
 | |
|     shopt -u dotglob nullglob
 | |
|   else
 | |
|     shopt -s dotglob nullglob
 | |
|     cp -r "$tmpdir"/* "/opt/$app/"
 | |
|     shopt -u dotglob nullglob
 | |
|   fi
 | |
|   echo "$version" >"/opt/${app}_version.txt"
 | |
|   $STD msg_ok "Deployed $app v$version to /opt/$app"
 | |
|   rm -rf "$tmpdir"
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs a local IP updater script using networkd-dispatcher.
 | |
| #
 | |
| # Description:
 | |
| #   - Stores current IP in /run/local-ip.env
 | |
| #   - Automatically runs on network changes
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| setup_local_ip_helper() {
 | |
|   local BASE_DIR="/usr/local/community-scripts/ip-management"
 | |
|   local SCRIPT_PATH="$BASE_DIR/update_local_ip.sh"
 | |
|   local IP_FILE="/run/local-ip.env"
 | |
|   local DISPATCHER_SCRIPT="/etc/networkd-dispatcher/routable.d/10-update-local-ip.sh"
 | |
| 
 | |
|   mkdir -p "$BASE_DIR"
 | |
| 
 | |
|   # Install networkd-dispatcher if not present
 | |
|   if ! dpkg -s networkd-dispatcher >/dev/null 2>&1; then
 | |
|     $STD apt-get update -qq
 | |
|     $STD apt-get install -yq networkd-dispatcher
 | |
|   fi
 | |
| 
 | |
|   # Write update_local_ip.sh
 | |
|   cat <<'EOF' >"$SCRIPT_PATH"
 | |
| #!/bin/bash
 | |
| set -euo pipefail
 | |
| 
 | |
| IP_FILE="/run/local-ip.env"
 | |
| mkdir -p "$(dirname "$IP_FILE")"
 | |
| 
 | |
| get_current_ip() {
 | |
|     local targets=("8.8.8.8" "1.1.1.1" "192.168.1.1" "10.0.0.1" "172.16.0.1" "default")
 | |
|     local ip
 | |
| 
 | |
|     for target in "${targets[@]}"; do
 | |
|         if [[ "$target" == "default" ]]; then
 | |
|             ip=$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") print $(i+1)}')
 | |
|         else
 | |
|             ip=$(ip route get "$target" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") print $(i+1)}')
 | |
|         fi
 | |
|         if [[ -n "$ip" ]]; then
 | |
|             echo "$ip"
 | |
|             return 0
 | |
|         fi
 | |
|     done
 | |
| 
 | |
|     return 1
 | |
| }
 | |
| 
 | |
| current_ip="$(get_current_ip)"
 | |
| 
 | |
| if [[ -z "$current_ip" ]]; then
 | |
|     echo "[ERROR] Could not detect local IP" >&2
 | |
|     exit 1
 | |
| fi
 | |
| 
 | |
| if [[ -f "$IP_FILE" ]]; then
 | |
|     source "$IP_FILE"
 | |
|     [[ "$LOCAL_IP" == "$current_ip" ]] && exit 0
 | |
| fi
 | |
| 
 | |
| echo "LOCAL_IP=$current_ip" > "$IP_FILE"
 | |
| echo "[INFO] LOCAL_IP updated to $current_ip"
 | |
| EOF
 | |
| 
 | |
|   chmod +x "$SCRIPT_PATH"
 | |
| 
 | |
|   # Install dispatcher hook
 | |
|   mkdir -p "$(dirname "$DISPATCHER_SCRIPT")"
 | |
|   cat <<EOF >"$DISPATCHER_SCRIPT"
 | |
| #!/bin/bash
 | |
| $SCRIPT_PATH
 | |
| EOF
 | |
| 
 | |
|   chmod +x "$DISPATCHER_SCRIPT"
 | |
|   systemctl enable -q --now networkd-dispatcher.service
 | |
| 
 | |
|   $STD msg_ok "LOCAL_IP helper installed using networkd-dispatcher"
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Loads LOCAL_IP from persistent store or detects if missing.
 | |
| #
 | |
| # Description:
 | |
| #   - Loads from /run/local-ip.env or performs runtime lookup
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| import_local_ip() {
 | |
|   local IP_FILE="/run/local-ip.env"
 | |
|   if [[ -f "$IP_FILE" ]]; then
 | |
|     # shellcheck disable=SC1090
 | |
|     source "$IP_FILE"
 | |
|   fi
 | |
| 
 | |
|   if [[ -z "${LOCAL_IP:-}" ]]; then
 | |
|     get_current_ip() {
 | |
|       local targets=("8.8.8.8" "1.1.1.1" "192.168.1.1" "10.0.0.1" "172.16.0.1" "default")
 | |
|       local ip
 | |
| 
 | |
|       for target in "${targets[@]}"; do
 | |
|         if [[ "$target" == "default" ]]; then
 | |
|           ip=$(ip route get 1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") print $(i+1)}')
 | |
|         else
 | |
|           ip=$(ip route get "$target" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if ($i=="src") print $(i+1)}')
 | |
|         fi
 | |
|         if [[ -n "$ip" ]]; then
 | |
|           echo "$ip"
 | |
|           return 0
 | |
|         fi
 | |
|       done
 | |
| 
 | |
|       return 1
 | |
|     }
 | |
| 
 | |
|     LOCAL_IP="$(get_current_ip || true)"
 | |
|     if [[ -z "$LOCAL_IP" ]]; then
 | |
|       msg_error "Could not determine LOCAL_IP"
 | |
|       return 1
 | |
|     fi
 | |
|   fi
 | |
| 
 | |
|   export LOCAL_IP
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Downloads file with optional progress indicator using pv.
 | |
| #
 | |
| # Arguments:
 | |
| #   $1 - URL
 | |
| #   $2 - Destination path
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| function download_with_progress() {
 | |
|   local url="$1"
 | |
|   local output="$2"
 | |
|   if [ -n "$SPINNER_PID" ] && ps -p "$SPINNER_PID" >/dev/null; then kill "$SPINNER_PID" >/dev/null; fi
 | |
| 
 | |
|   if ! command -v pv &>/dev/null; then
 | |
|     $STD apt-get install -y pv
 | |
|   fi
 | |
|   set -o pipefail
 | |
| 
 | |
|   # Content-Length aus HTTP-Header holen
 | |
|   local content_length
 | |
|   content_length=$(curl -fsSLI "$url" | awk '/Content-Length/ {print $2}' | tr -d '\r' || true)
 | |
| 
 | |
|   if [[ -z "$content_length" ]]; then
 | |
|     #msg_warn "Content-Length not available, falling back to plain download"
 | |
|     if ! curl -fL# -o "$output" "$url"; then
 | |
|       msg_error "Download failed"
 | |
|       return 1
 | |
|     fi
 | |
|   else
 | |
|     if ! curl -fsSL "$url" | pv -s "$content_length" >"$output"; then
 | |
|       msg_error "Download failed"
 | |
|       return 1
 | |
|     fi
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs or upgrades uv (Python package manager) from GitHub releases.
 | |
| #
 | |
| # Description:
 | |
| #   - Downloads architecture-specific tarball
 | |
| #   - Places binary in /usr/local/bin
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| function setup_uv() {
 | |
|   $STD msg_info "Checking uv installation..."
 | |
|   UV_BIN="/usr/local/bin/uv"
 | |
|   TMP_DIR=$(mktemp -d)
 | |
|   ARCH=$(uname -m)
 | |
| 
 | |
|   if [[ "$ARCH" == "x86_64" ]]; then
 | |
|     UV_TAR="uv-x86_64-unknown-linux-gnu.tar.gz"
 | |
|   elif [[ "$ARCH" == "aarch64" ]]; then
 | |
|     UV_TAR="uv-aarch64-unknown-linux-gnu.tar.gz"
 | |
|   else
 | |
|     msg_error "Unsupported architecture: $ARCH"
 | |
|     rm -rf "$TMP_DIR"
 | |
|     return 1
 | |
|   fi
 | |
| 
 | |
|   # get current github version
 | |
|   LATEST_VERSION=$(curl -s https://api.github.com/repos/astral-sh/uv/releases/latest | grep '"tag_name":' | cut -d '"' -f4 | sed 's/^v//')
 | |
|   if [[ -z "$LATEST_VERSION" ]]; then
 | |
|     msg_error "Could not fetch latest uv version from GitHub."
 | |
|     rm -rf "$TMP_DIR"
 | |
|     return 1
 | |
|   fi
 | |
| 
 | |
|   # check if uv exists
 | |
|   if [[ -x "$UV_BIN" ]]; then
 | |
|     INSTALLED_VERSION=$($UV_BIN -V | awk '{print $2}')
 | |
|     if [[ "$INSTALLED_VERSION" == "$LATEST_VERSION" ]]; then
 | |
|       $STD msg_ok "uv is already at the latest version ($INSTALLED_VERSION)"
 | |
|       rm -rf "$TMP_DIR"
 | |
|       # set path
 | |
|       if [[ ":$PATH:" != *":/usr/local/bin:"* ]]; then
 | |
|         export PATH="/usr/local/bin:$PATH"
 | |
|       fi
 | |
|       return 0
 | |
|     else
 | |
|       $STD msg_info "Updating uv from $INSTALLED_VERSION to $LATEST_VERSION"
 | |
|     fi
 | |
|   else
 | |
|     $STD msg_info "uv not found. Installing version $LATEST_VERSION"
 | |
|   fi
 | |
| 
 | |
|   # install or update uv
 | |
|   curl -fsSL "https://github.com/astral-sh/uv/releases/latest/download/${UV_TAR}" -o "$TMP_DIR/uv.tar.gz"
 | |
|   tar -xzf "$TMP_DIR/uv.tar.gz" -C "$TMP_DIR"
 | |
|   install -m 755 "$TMP_DIR"/*/uv "$UV_BIN"
 | |
|   rm -rf "$TMP_DIR"
 | |
| 
 | |
|   # set path
 | |
|   ensure_usr_local_bin_persist
 | |
|   msg_ok "uv installed/updated to $LATEST_VERSION"
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Ensures /usr/local/bin is permanently in system PATH.
 | |
| #
 | |
| # Description:
 | |
| #   - Adds to /etc/profile.d if not present
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| function ensure_usr_local_bin_persist() {
 | |
|   local PROFILE_FILE="/etc/profile.d/custom_path.sh"
 | |
| 
 | |
|   if [[ ! -f "$PROFILE_FILE" ]] && ! command -v pveversion &>/dev/null; then
 | |
|     echo 'export PATH="/usr/local/bin:$PATH"' >"$PROFILE_FILE"
 | |
|     chmod +x "$PROFILE_FILE"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs or updates Ghostscript (gs) from source.
 | |
| #
 | |
| # Description:
 | |
| #   - Fetches latest release
 | |
| #   - Builds and installs system-wide
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| function setup_gs() {
 | |
|   msg_info "Setup Ghostscript"
 | |
|   mkdir -p /tmp
 | |
|   TMP_DIR=$(mktemp -d)
 | |
|   CURRENT_VERSION=$(gs --version 2>/dev/null || echo "0")
 | |
| 
 | |
|   RELEASE_JSON=$(curl -fsSL https://api.github.com/repos/ArtifexSoftware/ghostpdl-downloads/releases/latest)
 | |
|   LATEST_VERSION=$(echo "$RELEASE_JSON" | grep '"tag_name":' | head -n1 | cut -d '"' -f4 | sed 's/^gs//')
 | |
|   LATEST_VERSION_DOTTED=$(echo "$RELEASE_JSON" | grep '"name":' | head -n1 | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+')
 | |
| 
 | |
|   if [[ -z "$LATEST_VERSION" ]]; then
 | |
|     msg_error "Could not determine latest Ghostscript version from GitHub."
 | |
|     rm -rf "$TMP_DIR"
 | |
|     return
 | |
|   fi
 | |
| 
 | |
|   if dpkg --compare-versions "$CURRENT_VERSION" ge "$LATEST_VERSION_DOTTED"; then
 | |
|     msg_ok "Ghostscript is already at version $CURRENT_VERSION"
 | |
|     rm -rf "$TMP_DIR"
 | |
|     return
 | |
|   fi
 | |
| 
 | |
|   msg_info "Installing/Updating Ghostscript to $LATEST_VERSION_DOTTED"
 | |
|   curl -fsSL "https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs${LATEST_VERSION}/ghostscript-${LATEST_VERSION_DOTTED}.tar.gz" -o "$TMP_DIR/ghostscript.tar.gz"
 | |
| 
 | |
|   if ! tar -xzf "$TMP_DIR/ghostscript.tar.gz" -C "$TMP_DIR"; then
 | |
|     msg_error "Failed to extract Ghostscript archive."
 | |
|     rm -rf "$TMP_DIR"
 | |
|     return
 | |
|   fi
 | |
| 
 | |
|   cd "$TMP_DIR/ghostscript-${LATEST_VERSION_DOTTED}" || {
 | |
|     msg_error "Failed to enter Ghostscript source directory."
 | |
|     rm -rf "$TMP_DIR"
 | |
|   }
 | |
|   $STD apt-get install -y build-essential libpng-dev zlib1g-dev
 | |
|   ./configure >/dev/null && make && sudo make install >/dev/null
 | |
|   local EXIT_CODE=$?
 | |
|   hash -r
 | |
|   if [[ ! -x "$(command -v gs)" ]]; then
 | |
|     if [[ -x /usr/local/bin/gs ]]; then
 | |
|       ln -sf /usr/local/bin/gs /usr/bin/gs
 | |
|     fi
 | |
|   fi
 | |
| 
 | |
|   rm -rf "$TMP_DIR"
 | |
| 
 | |
|   if [[ $EXIT_CODE -eq 0 ]]; then
 | |
|     msg_ok "Ghostscript installed/updated to version $LATEST_VERSION_DOTTED"
 | |
|   else
 | |
|     msg_error "Ghostscript installation failed"
 | |
|   fi
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Installs rbenv and ruby-build, installs Ruby and optionally Rails.
 | |
| #
 | |
| # Description:
 | |
| #   - Downloads rbenv and ruby-build from GitHub
 | |
| #   - Compiles and installs target Ruby version
 | |
| #   - Optionally installs Rails via gem
 | |
| #
 | |
| # Variables:
 | |
| #   RUBY_VERSION         - Ruby version to install (default: 3.4.4)
 | |
| #   RUBY_INSTALL_RAILS   - true/false to install Rails (default: true)
 | |
| # ------------------------------------------------------------------------------
 | |
| 
 | |
| setup_rbenv_stack() {
 | |
|   local RUBY_VERSION="${RUBY_VERSION:-3.4.4}"
 | |
|   local RUBY_INSTALL_RAILS="${RUBY_INSTALL_RAILS:-true}"
 | |
| 
 | |
|   local RBENV_DIR="$HOME/.rbenv"
 | |
|   local RBENV_BIN="$RBENV_DIR/bin/rbenv"
 | |
|   local PROFILE_FILE="$HOME/.profile"
 | |
|   local TMP_DIR
 | |
|   TMP_DIR=$(mktemp -d)
 | |
| 
 | |
|   $STD msg_info "Installing rbenv + ruby-build + Ruby $RUBY_VERSION"
 | |
| 
 | |
|   # Fetch latest rbenv release tag from GitHub (e.g. v1.3.2 → 1.3.2)
 | |
|   local RBENV_RELEASE
 | |
|   RBENV_RELEASE=$(curl -fsSL https://api.github.com/repos/rbenv/rbenv/releases/latest | grep '"tag_name":' | cut -d '"' -f4 | sed 's/^v//')
 | |
|   if [[ -z "$RBENV_RELEASE" ]]; then
 | |
|     msg_error "Failed to fetch latest rbenv version"
 | |
|     rm -rf "$TMP_DIR"
 | |
|     return 1
 | |
|   fi
 | |
| 
 | |
|   # Download and extract rbenv release
 | |
|   curl -fsSL "https://github.com/rbenv/rbenv/archive/refs/tags/v${RBENV_RELEASE}.tar.gz" -o "$TMP_DIR/rbenv.tar.gz"
 | |
|   tar -xzf "$TMP_DIR/rbenv.tar.gz" -C "$TMP_DIR"
 | |
|   mkdir -p "$RBENV_DIR"
 | |
|   cp -r "$TMP_DIR/rbenv-${RBENV_RELEASE}/." "$RBENV_DIR/"
 | |
|   cd "$RBENV_DIR" && src/configure && make -C src
 | |
| 
 | |
|   # Fetch latest ruby-build plugin release tag (e.g. v20250507 → 20250507)
 | |
|   local RUBY_BUILD_RELEASE
 | |
|   RUBY_BUILD_RELEASE=$(curl -fsSL https://api.github.com/repos/rbenv/ruby-build/releases/latest | grep '"tag_name":' | cut -d '"' -f4 | sed 's/^v//')
 | |
|   if [[ -z "$RUBY_BUILD_RELEASE" ]]; then
 | |
|     msg_error "Failed to fetch latest ruby-build version"
 | |
|     rm -rf "$TMP_DIR"
 | |
|     return 1
 | |
|   fi
 | |
| 
 | |
|   # Download and install ruby-build plugin
 | |
|   curl -fsSL "https://github.com/rbenv/ruby-build/archive/refs/tags/v${RUBY_BUILD_RELEASE}.tar.gz" -o "$TMP_DIR/ruby-build.tar.gz"
 | |
|   tar -xzf "$TMP_DIR/ruby-build.tar.gz" -C "$TMP_DIR"
 | |
|   mkdir -p "$RBENV_DIR/plugins/ruby-build"
 | |
|   cp -r "$TMP_DIR/ruby-build-${RUBY_BUILD_RELEASE}/." "$RBENV_DIR/plugins/ruby-build/"
 | |
|   echo "$RUBY_BUILD_RELEASE" >"$RBENV_DIR/plugins/ruby-build/RUBY_BUILD_version.txt"
 | |
| 
 | |
|   # Persist rbenv init to user's profile
 | |
|   if ! grep -q 'rbenv init' "$PROFILE_FILE"; then
 | |
|     echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >>"$PROFILE_FILE"
 | |
|     echo 'eval "$(rbenv init -)"' >>"$PROFILE_FILE"
 | |
|   fi
 | |
| 
 | |
|   # Activate rbenv in current shell
 | |
|   export PATH="$RBENV_DIR/bin:$PATH"
 | |
|   eval "$("$RBENV_BIN" init - bash)"
 | |
| 
 | |
|   # Install Ruby version if not already present
 | |
|   if "$RBENV_BIN" versions --bare | grep -qx "$RUBY_VERSION"; then
 | |
|     msg_ok "Ruby $RUBY_VERSION already installed"
 | |
|   else
 | |
|     $STD msg_info "Installing Ruby $RUBY_VERSION"
 | |
|     $STD "$RBENV_BIN" install "$RUBY_VERSION"
 | |
|   fi
 | |
| 
 | |
|   # Set Ruby version globally
 | |
|   "$RBENV_BIN" global "$RUBY_VERSION"
 | |
|   hash -r
 | |
| 
 | |
|   # Optionally install Rails via gem
 | |
|   if [[ "$RUBY_INSTALL_RAILS" == "true" ]]; then
 | |
|     $STD msg_info "Installing latest Rails via gem"
 | |
|     gem install rails
 | |
|     msg_ok "Rails $(rails -v) installed"
 | |
|   fi
 | |
| 
 | |
|   rm -rf "$TMP_DIR"
 | |
|   msg_ok "rbenv stack ready (Ruby $RUBY_VERSION)"
 | |
| }
 | |
| 
 | |
| # ------------------------------------------------------------------------------
 | |
| # Creates and installs self-signed certificates.
 | |
| #
 | |
| # Description:
 | |
| #   - Create a self-signed certificate with option to override application name
 | |
| #
 | |
| # Variables:
 | |
| #   APP   - Application name (default: $APPLICATION variable)
 | |
| # ------------------------------------------------------------------------------
 | |
| create_selfsigned_certs() {
 | |
|   local app=${APP:-$(echo "${APPLICATION,,}" | tr -d ' ')}
 | |
|   $STD msg_info "Creating Self-Signed Certificate"
 | |
|   $STD openssl req -x509 -nodes -days 365 -newkey rsa:4096 \
 | |
|     -keyout /etc/ssl/private/"$app"-selfsigned.key \
 | |
|     -out /etc/ssl/certs/"$app"-selfsigned.crt \
 | |
|     -subj "/C=US/O=$app/OU=Domain Control Validated/CN=localhost"
 | |
|   $STD msg_ok "Created Self-Signed Certificate"
 | |
| }
 | 
