ProxmoxVED/misc/tools.func
2025-06-02 15:33:48 +02:00

1331 lines
45 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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
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
# 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 optional extensions/modules.
#
# Description:
# - Detects existing PostgreSQL version
# - Dumps all databases before upgrade
# - Adds PGDG repo and installs specified version
# - Installs optional PG_MODULES (e.g. postgis, contrib)
# - Restores dumped data post-upgrade
#
# Variables:
# PG_VERSION - Major PostgreSQL version (e.g. 15, 16) (default: 16)
# PG_MODULES - Comma-separated list of extensions (e.g. "postgis,contrib")
# ------------------------------------------------------------------------------
install_postgresql() {
local PG_VERSION="${PG_VERSION:-16}"
local PG_MODULES="${PG_MODULES:-}"
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"
else
msg_info "Detected PostgreSQL $CURRENT_PG_VERSION, preparing upgrade to $PG_VERSION"
NEED_PG_INSTALL=true
fi
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
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
msg_info "Starting PostgreSQL $PG_VERSION"
systemctl enable -q --now postgresql
if [[ -n "$CURRENT_PG_VERSION" ]]; then
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
# Install optional PostgreSQL modules
if [[ -n "$PG_MODULES" ]]; then
IFS=',' read -ra MODULES <<<"$PG_MODULES"
for module in "${MODULES[@]}"; do
local pkg="postgresql-${PG_VERSION}-${module}"
msg_info "Installing PostgreSQL module: $pkg"
$STD apt-get install -y "$pkg" || {
msg_error "Failed to install $pkg"
continue
}
done
msg_ok "All requested PostgreSQL modules 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
# Check for patch-level updates
if apt list --upgradable 2>/dev/null | grep -q '^mysql-server/'; then
msg_info "MySQL $CURRENT_VERSION available for upgrade"
$STD apt-get update
$STD apt-get install --only-upgrade -y mysql-server
msg_ok "MySQL upgraded"
fi
return
fi
else
msg_info "Installing MySQL $MYSQL_VERSION"
NEED_INSTALL=true
fi
if [[ "$NEED_INSTALL" == true ]]; then
$STD systemctl stop mysql >/dev/null 2>&1 || true
$STD apt-get purge -y "^mysql-server.*" "^mysql-client.*" "^mysql-common.*" || true
rm -f /etc/apt/sources.list.d/mysql.list /etc/apt/trusted.gpg.d/mysql.gpg
local DISTRO_CODENAME
DISTRO_CODENAME="$(awk -F= '/VERSION_CODENAME/ { print $2 }' /etc/os-release)"
curl -fsSL https://repo.mysql.com/RPM-GPG-KEY-mysql-2023 | 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
export DEBIAN_FRONTEND=noninteractive
$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 [[ -z "$CURRENT_PHP" ]]; then
msg_info "Setup PHP $PHP_VERSION"
elif [[ "$CURRENT_PHP" != "$PHP_VERSION" ]]; then
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_FPM" == "YES" ]]; then
MODULE_LIST+=" php${PHP_VERSION}-fpm"
fi
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 2>&1
if [[ $? -ne 0 ]]; then
msg_error "Failed to install Composer"
return 1
fi
chmod +x "$COMPOSER_BIN"
composer diagnose >/dev/null 2>&1
msg_ok "Setup Composer"
#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_ID DISTRO_CODENAME MONGO_BASE_URL
DISTRO_ID=$(awk -F= '/^ID=/{ gsub(/"/,"",$2); print $2 }' /etc/os-release)
DISTRO_CODENAME=$(awk -F= '/^VERSION_CODENAME=/{ print $2 }' /etc/os-release)
case "$DISTRO_ID" in
ubuntu) MONGO_BASE_URL="https://repo.mongodb.org/apt/ubuntu" ;;
debian) MONGO_BASE_URL="https://repo.mongodb.org/apt/debian" ;;
*)
msg_error "Unsupported distribution: $DISTRO_ID"
return 1
;;
esac
local REPO_LIST="/etc/apt/sources.list.d/mongodb-org-${MONGO_VERSION}.list"
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
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
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] ${MONGO_BASE_URL} ${DISTRO_CODENAME}/mongodb-org/${MONGO_VERSION} main" \
>"$REPO_LIST"
$STD apt-get update || {
msg_error "APT update failed — invalid MongoDB repo for ${DISTRO_ID}-${DISTRO_CODENAME}?"
return 1
}
$STD apt-get install -y mongodb-org
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 app="$1"
local repo="$2"
local mode="${3:-tarball}" # tarball | source | binary
local version="${4:-latest}" # optional, default "latest"
local target="${5:-/opt/$app}" # optional target dir
local app_lc=$(echo "${app,,}" | tr -d ' ')
local version_file="$HOME/.${app_lc}/${app_lc}_version.txt"
local curl_timeout="--connect-timeout 10 --max-time 30"
mkdir -p "$(dirname "$version_file")"
local current_version=""
if [[ -f "$version_file" ]]; then
current_version=$(<"$version_file")
$STD msg_info "Current version: $current_version"
fi
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
local api_url="https://api.github.com/repos/$repo/releases"
[[ "$version" != "latest" ]] && api_url="$api_url/tags/$version" || api_url="$api_url/latest"
local header=()
[[ -n "${GITHUB_TOKEN:-}" ]] && header=(-H "Authorization: token $GITHUB_TOKEN")
$STD msg_info "Fetching release metadata for $repo ($version)..."
local resp http_code
resp=$(curl $curl_timeout -fsSL -w "%{http_code}" -o /tmp/gh_rel.json "${header[@]}" "$api_url")
http_code="${resp:(-3)}"
[[ "$http_code" != "200" ]] && {
msg_error "Failed to fetch release: HTTP $http_code"
return 1
}
local json
json=$(</tmp/gh_rel.json)
local tag_name
tag_name=$(echo "$json" | jq -r '.tag_name // .name // empty')
[[ "$tag_name" =~ ^v ]] && version="${tag_name:1}" || version="$tag_name"
if [[ "$current_version" == "$version" ]]; then
$STD msg_info "Already on latest version ($version)"
return 0
fi
local tmpdir
tmpdir=$(mktemp -d) || return 1
local filename=""
local url=""
if [[ "$mode" == "tarball" || "$mode" == "source" ]]; then
url=$(echo "$json" | jq -r '.tarball_url // empty')
[[ -z "$url" ]] && url="https://github.com/$repo/archive/refs/tags/v$version.tar.gz"
filename="${app_lc}-${version}.tar.gz"
$STD msg_info "Downloading source tarball: $url"
curl $curl_timeout -fsSL -o "$tmpdir/$filename" "$url" || {
msg_error "Download failed: $url"
rm -rf "$tmpdir"
return 1
}
mkdir -p "$target"
tar -xzf "$tmpdir/$filename" -C "$tmpdir"
local unpack_dir
unpack_dir=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d | head -n1)
shopt -s dotglob nullglob
cp -r "$unpack_dir"/* "$target/"
shopt -u dotglob nullglob
elif [[ "$mode" == "binary" ]]; then
local arch
arch=$(dpkg --print-architecture 2>/dev/null || uname -m)
[[ "$arch" == "x86_64" ]] && arch="x86_64"
[[ "$arch" == "aarch64" ]] && arch="arm64"
local assets url_match=""
assets=$(echo "$json" | jq -r '.assets[].browser_download_url')
# 1. Versuche, nach genauer Architektur zu matchen (z.B. x86_64, amd64, arm64)
for u in $assets; do
if [[ "$u" =~ ($arch|amd64|x86_64|aarch64|arm64).*\.deb$ ]]; then
url_match="$u"
break
fi
done
# 2. Wenn keine Arch-Zuordnung, nimm erstes .deb im Asset-Listing
if [[ -z "$url_match" ]]; then
for u in $assets; do
if [[ "$u" =~ \.deb$ ]]; then
url_match="$u"
break
fi
done
fi
if [[ -z "$url_match" ]]; then
msg_error "No suitable .deb asset found for $app"
rm -rf "$tmpdir"
return 1
fi
filename="${url_match##*/}"
$STD msg_info "Downloading binary .deb: $url_match"
curl $curl_timeout -fsSL -o "$tmpdir/$filename" "$url_match" || {
msg_error "Download failed: $url_match"
rm -rf "$tmpdir"
return 1
}
$STD msg_info "Installing $filename via apt"
apt-get install -y "$tmpdir/$filename" || {
msg_error "Failed to install $filename"
rm -rf "$tmpdir"
return 1
}
else
msg_error "Unknown mode: $mode"
rm -rf "$tmpdir"
return 1
fi
echo "$version" >"$version_file"
$STD msg_ok "$app deployed (version: $version)"
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"
}
# ------------------------------------------------------------------------------
# Installs Rust toolchain and optional global crates via cargo.
#
# Description:
# - Installs rustup (if missing)
# - Installs or updates desired Rust toolchain (stable, nightly, or versioned)
# - Installs or updates specified global crates using `cargo install`
#
# Notes:
# - Skips crate install if exact version is already present
# - Updates crate if newer version or different version is requested
#
# Variables:
# RUST_TOOLCHAIN - Rust toolchain to install (default: stable)
# RUST_CRATES - Comma-separated list of crates (e.g. "cargo-edit,wasm-pack@0.12.1")
# ------------------------------------------------------------------------------
install_rust_and_crates() {
local RUST_TOOLCHAIN="${RUST_TOOLCHAIN:-stable}"
local RUST_CRATES="${RUST_CRATES:-}"
local CARGO_BIN="${HOME}/.cargo/bin"
# rustup & toolchain
if ! command -v rustup &>/dev/null; then
msg_info "Installing rustup"
curl -fsSL https://sh.rustup.rs | $STD sh -s -- -y --default-toolchain "$RUST_TOOLCHAIN"
export PATH="$CARGO_BIN:$PATH"
echo 'export PATH="$HOME/.cargo/bin:$PATH"' >>"$HOME/.profile"
msg_ok "Installed rustup with $RUST_TOOLCHAIN"
else
$STD rustup install "$RUST_TOOLCHAIN"
$STD rustup default "$RUST_TOOLCHAIN"
$STD rustup update "$RUST_TOOLCHAIN"
msg_ok "Rust toolchain set to $RUST_TOOLCHAIN"
fi
# install/update crates
if [[ -n "$RUST_CRATES" ]]; then
IFS=',' read -ra CRATES <<<"$RUST_CRATES"
for crate in "${CRATES[@]}"; do
local NAME VER INSTALLED_VER
if [[ "$crate" == *"@"* ]]; then
NAME="${crate%@*}"
VER="${crate##*@}"
else
NAME="$crate"
VER=""
fi
INSTALLED_VER=$(cargo install --list 2>/dev/null | awk "/^$NAME v[0-9]/ {print \$2}" | tr -d 'v')
if [[ -n "$INSTALLED_VER" ]]; then
if [[ -n "$VER" && "$VER" != "$INSTALLED_VER" ]]; then
msg_info "Updating $NAME from $INSTALLED_VER to $VER"
$STD cargo install "$NAME" --version "$VER" --force
elif [[ -z "$VER" ]]; then
msg_info "Updating $NAME to latest"
$STD cargo install "$NAME" --force
else
msg_ok "$NAME@$INSTALLED_VER already up to date"
fi
else
msg_info "Installing $NAME ${VER:+($VER)}"
$STD cargo install "$NAME" ${VER:+--version "$VER"}
fi
done
msg_ok "All requested Rust crates processed"
fi
}
# ------------------------------------------------------------------------------
# Installs Adminer (Debian/Ubuntu via APT, Alpine via direct download).
#
# Description:
# - Adds Adminer to Apache or web root
# - Supports Alpine and Debian-based systems
# ------------------------------------------------------------------------------
install_adminer() {
if grep -qi alpine /etc/os-release; then
msg_info "Installing Adminer (Alpine)"
mkdir -p /var/www/localhost/htdocs/adminer
if ! curl -fsSL https://github.com/vrana/adminer/releases/latest/download/adminer.php \
-o /var/www/localhost/htdocs/adminer/index.php; then
msg_error "Failed to download Adminer"
return 1
fi
msg_ok "Adminer available at /adminer (Alpine)"
else
msg_info "Installing Adminer (Debian/Ubuntu)"
$STD apt-get install -y adminer
$STD a2enconf adminer
$STD systemctl reload apache2
msg_ok "Adminer available at /adminer (Debian/Ubuntu)"
fi
}