Add all 44 scripts, update CI: error severity baseline, PowerShell validation, multi-distro testing
Amp-Thread-ID: https://ampcode.com/threads/T-019cc404-c628-759e-a50b-f5eeea35b91f Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
+509
@@ -0,0 +1,509 @@
|
||||
#!/bin/bash
|
||||
|
||||
################################################
|
||||
#### Salt Master/Minion Setup Automation ####
|
||||
#### Install and configure SaltStack ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### Version: 1.00-030526 ####
|
||||
################################################
|
||||
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
readonly SCRIPT_NAME
|
||||
|
||||
# Default configuration
|
||||
readonly DEFAULT_SALT_VERSION="latest"
|
||||
readonly DEFAULT_FILE_ROOTS="/srv/salt"
|
||||
readonly DEFAULT_PILLAR_ROOTS="/srv/pillar"
|
||||
readonly DEFAULT_MASTER_INTERFACE="0.0.0.0"
|
||||
readonly DEFAULT_MASTER_PORT_PUB=4505
|
||||
readonly DEFAULT_MASTER_PORT_RET=4506
|
||||
|
||||
# Configuration variables (can be overridden by environment)
|
||||
SALT_VERSION=${SALT_VERSION:-$DEFAULT_SALT_VERSION}
|
||||
FILE_ROOTS=${FILE_ROOTS:-$DEFAULT_FILE_ROOTS}
|
||||
PILLAR_ROOTS=${PILLAR_ROOTS:-$DEFAULT_PILLAR_ROOTS}
|
||||
DEBUG=${DEBUG:-}
|
||||
|
||||
# Runtime flags
|
||||
MODE=""
|
||||
MASTER_IP=""
|
||||
MINION_ID=""
|
||||
AUTO_ACCEPT=false
|
||||
AUTO_YES=false
|
||||
PKG_MANAGER=""
|
||||
OS_FAMILY=""
|
||||
OS_VERSION=""
|
||||
|
||||
handle_error() {
|
||||
local exit_code=$1
|
||||
local line_number=$2
|
||||
echo "Error: $SCRIPT_NAME failed at line $line_number with exit code $exit_code" >&2
|
||||
exit "$exit_code"
|
||||
}
|
||||
|
||||
trap 'handle_error $? $LINENO' ERR
|
||||
|
||||
debug_echo() {
|
||||
if [[ -n "$DEBUG" ]]; then
|
||||
echo "[DEBUG] $*" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo "[INFO] $*"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo "[WARN] $*" >&2
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo "[ERROR] $*" >&2
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << EOF
|
||||
Usage: $SCRIPT_NAME [OPTIONS]
|
||||
|
||||
Automate Salt master and/or minion installation and configuration.
|
||||
|
||||
Supports Ubuntu/Debian and RHEL/AlmaLinux. Adds the Salt Project repository,
|
||||
installs packages, configures services, creates directory structure, and
|
||||
opens firewall ports.
|
||||
|
||||
OPTIONS:
|
||||
--mode master|minion|both What to install (required)
|
||||
--master-ip ADDRESS Salt master IP or hostname (required for minion/both)
|
||||
--minion-id NAME Custom minion ID (default: system hostname)
|
||||
--auto-accept Enable auto_accept on master (NOT for production)
|
||||
--salt-version VERSION Pin Salt version (default: latest)
|
||||
--yes Skip confirmation prompts
|
||||
--help, -h Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
SALT_VERSION Salt version to install (default: $DEFAULT_SALT_VERSION)
|
||||
FILE_ROOTS Master file_roots path (default: $DEFAULT_FILE_ROOTS)
|
||||
PILLAR_ROOTS Master pillar_roots path (default: $DEFAULT_PILLAR_ROOTS)
|
||||
DEBUG Enable debug output
|
||||
|
||||
EXAMPLES:
|
||||
# Install salt-master
|
||||
sudo $SCRIPT_NAME --mode master --yes
|
||||
|
||||
# Install salt-minion pointing to master
|
||||
sudo $SCRIPT_NAME --mode minion --master-ip 10.0.0.1
|
||||
|
||||
# Install both on the same node
|
||||
sudo $SCRIPT_NAME --mode both --master-ip localhost --yes
|
||||
|
||||
# Install with custom minion ID
|
||||
sudo $SCRIPT_NAME --mode minion --master-ip salt.example.com --minion-id web01
|
||||
|
||||
# Install specific Salt version
|
||||
sudo $SCRIPT_NAME --mode master --salt-version 3006 --yes
|
||||
EOF
|
||||
}
|
||||
|
||||
detect_os() {
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/os-release
|
||||
OS_VERSION="$VERSION_ID"
|
||||
case "$ID" in
|
||||
ubuntu|debian)
|
||||
OS_FAMILY="debian"
|
||||
PKG_MANAGER="apt"
|
||||
;;
|
||||
rhel|centos|rocky|almalinux|ol|fedora)
|
||||
OS_FAMILY="rhel"
|
||||
if command -v dnf >/dev/null 2>&1; then
|
||||
PKG_MANAGER="dnf"
|
||||
else
|
||||
PKG_MANAGER="yum"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
log_error "Unsupported OS: $ID"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
log_error "Cannot detect OS — /etc/os-release not found"
|
||||
exit 1
|
||||
fi
|
||||
debug_echo "Detected OS: $OS_FAMILY ($PKG_MANAGER) version $OS_VERSION"
|
||||
}
|
||||
|
||||
get_cpu_count() {
|
||||
nproc 2>/dev/null || echo 2
|
||||
}
|
||||
|
||||
add_salt_repo_debian() {
|
||||
log_info "Adding Salt Project repository (Debian/Ubuntu)..."
|
||||
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl gnupg2 >/dev/null
|
||||
|
||||
local keyring="/etc/apt/keyrings/salt-archive-keyring.gpg"
|
||||
mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL "https://repo.saltproject.io/salt/py3/ubuntu/${OS_VERSION}/amd64/SALT-PROJECT-GPG-PUBKEY-2023.gpg" \
|
||||
-o "$keyring"
|
||||
|
||||
local repo_url="https://repo.saltproject.io/salt/py3/ubuntu/${OS_VERSION}/amd64"
|
||||
if [[ "$SALT_VERSION" != "latest" ]]; then
|
||||
repo_url="${repo_url}/${SALT_VERSION}"
|
||||
fi
|
||||
echo "deb [signed-by=${keyring}] ${repo_url} ${VERSION_CODENAME} main" \
|
||||
> /etc/apt/sources.list.d/salt.list
|
||||
|
||||
apt-get update -qq
|
||||
log_info "Salt repository added"
|
||||
}
|
||||
|
||||
add_salt_repo_rhel() {
|
||||
log_info "Adding Salt Project repository (RHEL)..."
|
||||
|
||||
local major_ver="${OS_VERSION%%.*}"
|
||||
local repo_url="https://repo.saltproject.io/salt/py3/redhat/${major_ver}/x86_64"
|
||||
if [[ "$SALT_VERSION" != "latest" ]]; then
|
||||
repo_url="${repo_url}/${SALT_VERSION}"
|
||||
fi
|
||||
|
||||
cat > /etc/yum.repos.d/salt.repo << REPOEOF
|
||||
[salt]
|
||||
name=Salt Project for RHEL ${major_ver}
|
||||
baseurl=${repo_url}
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
gpgkey=https://repo.saltproject.io/salt/py3/redhat/${major_ver}/x86_64/SALT-PROJECT-GPG-PUBKEY-2023.pub
|
||||
REPOEOF
|
||||
|
||||
"$PKG_MANAGER" clean expire-cache -q
|
||||
log_info "Salt repository added"
|
||||
}
|
||||
|
||||
install_master() {
|
||||
log_info "Installing salt-master..."
|
||||
case "$PKG_MANAGER" in
|
||||
apt)
|
||||
apt-get install -y -qq salt-master >/dev/null
|
||||
;;
|
||||
dnf|yum)
|
||||
"$PKG_MANAGER" install -y -q salt-master
|
||||
;;
|
||||
esac
|
||||
log_info "salt-master installed"
|
||||
}
|
||||
|
||||
install_minion() {
|
||||
log_info "Installing salt-minion..."
|
||||
case "$PKG_MANAGER" in
|
||||
apt)
|
||||
apt-get install -y -qq salt-minion >/dev/null
|
||||
;;
|
||||
dnf|yum)
|
||||
"$PKG_MANAGER" install -y -q salt-minion
|
||||
;;
|
||||
esac
|
||||
log_info "salt-minion installed"
|
||||
}
|
||||
|
||||
configure_master() {
|
||||
log_info "Configuring salt-master..."
|
||||
|
||||
local worker_threads
|
||||
worker_threads=$(get_cpu_count)
|
||||
|
||||
if [[ -f /etc/salt/master ]]; then
|
||||
cp /etc/salt/master /etc/salt/master.bak."$(date +%Y%m%d%H%M%S)"
|
||||
log_info "Backed up existing /etc/salt/master"
|
||||
fi
|
||||
|
||||
cat > /etc/salt/master << MASTEREOF
|
||||
##### Salt Master Configuration #####
|
||||
##### Managed by salt-setup.sh #####
|
||||
|
||||
interface: ${DEFAULT_MASTER_INTERFACE}
|
||||
|
||||
file_roots:
|
||||
base:
|
||||
- ${FILE_ROOTS}
|
||||
|
||||
pillar_roots:
|
||||
base:
|
||||
- ${PILLAR_ROOTS}
|
||||
|
||||
worker_threads: ${worker_threads}
|
||||
timeout: 30
|
||||
state_events: True
|
||||
presence_events: True
|
||||
MASTEREOF
|
||||
|
||||
if [[ "$AUTO_ACCEPT" == true ]]; then
|
||||
{
|
||||
echo ""
|
||||
echo "# WARNING: NOT recommended for production"
|
||||
echo "auto_accept: True"
|
||||
} >> /etc/salt/master
|
||||
log_warn "auto_accept enabled — NOT recommended for production"
|
||||
else
|
||||
{
|
||||
echo ""
|
||||
echo "auto_accept: False"
|
||||
} >> /etc/salt/master
|
||||
fi
|
||||
|
||||
log_info "Master configuration written to /etc/salt/master"
|
||||
}
|
||||
|
||||
configure_minion() {
|
||||
log_info "Configuring salt-minion..."
|
||||
|
||||
local minion_id
|
||||
minion_id="${MINION_ID:-$(hostname -f 2>/dev/null || hostname)}"
|
||||
|
||||
if [[ -f /etc/salt/minion ]]; then
|
||||
cp /etc/salt/minion /etc/salt/minion.bak."$(date +%Y%m%d%H%M%S)"
|
||||
log_info "Backed up existing /etc/salt/minion"
|
||||
fi
|
||||
|
||||
cat > /etc/salt/minion << MINIONEOF
|
||||
##### Salt Minion Configuration #####
|
||||
##### Managed by salt-setup.sh #####
|
||||
|
||||
master: ${MASTER_IP}
|
||||
id: ${minion_id}
|
||||
|
||||
# grains:
|
||||
# role: webserver
|
||||
# environment: production
|
||||
MINIONEOF
|
||||
|
||||
log_info "Minion configured (id: ${minion_id}, master: ${MASTER_IP})"
|
||||
}
|
||||
|
||||
create_directory_structure() {
|
||||
log_info "Creating Salt directory structure..."
|
||||
|
||||
mkdir -p "${FILE_ROOTS}" "${PILLAR_ROOTS}"
|
||||
|
||||
if [[ ! -f "${FILE_ROOTS}/top.sls" ]]; then
|
||||
cat > "${FILE_ROOTS}/top.sls" << 'TOPEOF'
|
||||
base:
|
||||
'*':
|
||||
[]
|
||||
# - common
|
||||
# - packages
|
||||
TOPEOF
|
||||
log_info "Created ${FILE_ROOTS}/top.sls"
|
||||
fi
|
||||
|
||||
if [[ ! -f "${PILLAR_ROOTS}/top.sls" ]]; then
|
||||
cat > "${PILLAR_ROOTS}/top.sls" << 'PTOPEOF'
|
||||
base:
|
||||
'*':
|
||||
[]
|
||||
# - common
|
||||
PTOPEOF
|
||||
log_info "Created ${PILLAR_ROOTS}/top.sls"
|
||||
fi
|
||||
}
|
||||
|
||||
open_firewall_ports() {
|
||||
log_info "Configuring firewall for Salt master ports..."
|
||||
|
||||
if command -v ufw >/dev/null 2>&1; then
|
||||
if ufw status | grep -q "Status: active"; then
|
||||
ufw allow ${DEFAULT_MASTER_PORT_PUB}/tcp >/dev/null
|
||||
ufw allow ${DEFAULT_MASTER_PORT_RET}/tcp >/dev/null
|
||||
log_info "Opened ports ${DEFAULT_MASTER_PORT_PUB}/${DEFAULT_MASTER_PORT_RET} in ufw"
|
||||
else
|
||||
debug_echo "ufw not active — skipping"
|
||||
fi
|
||||
elif command -v firewall-cmd >/dev/null 2>&1; then
|
||||
if firewall-cmd --state >/dev/null 2>&1; then
|
||||
firewall-cmd --permanent --add-port=${DEFAULT_MASTER_PORT_PUB}/tcp >/dev/null
|
||||
firewall-cmd --permanent --add-port=${DEFAULT_MASTER_PORT_RET}/tcp >/dev/null
|
||||
firewall-cmd --reload >/dev/null
|
||||
log_info "Opened ports ${DEFAULT_MASTER_PORT_PUB}/${DEFAULT_MASTER_PORT_RET} in firewalld"
|
||||
else
|
||||
debug_echo "firewalld not running — skipping"
|
||||
fi
|
||||
else
|
||||
log_warn "No supported firewall detected — manually open ports ${DEFAULT_MASTER_PORT_PUB} and ${DEFAULT_MASTER_PORT_RET}"
|
||||
fi
|
||||
}
|
||||
|
||||
start_service() {
|
||||
local service="$1"
|
||||
log_info "Enabling and starting ${service}..."
|
||||
systemctl enable "$service" >/dev/null 2>&1
|
||||
systemctl restart "$service"
|
||||
if systemctl is-active "$service" >/dev/null 2>&1; then
|
||||
log_info "${service} is running"
|
||||
else
|
||||
log_error "${service} failed to start"
|
||||
systemctl status "$service" --no-pager
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
show_summary() {
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " Salt Setup Complete"
|
||||
echo "============================================"
|
||||
|
||||
if [[ "$MODE" == "master" || "$MODE" == "both" ]]; then
|
||||
echo ""
|
||||
echo " Master:"
|
||||
echo " Config: /etc/salt/master"
|
||||
echo " File roots: ${FILE_ROOTS}"
|
||||
echo " Pillar roots: ${PILLAR_ROOTS}"
|
||||
echo " Ports: ${DEFAULT_MASTER_PORT_PUB}, ${DEFAULT_MASTER_PORT_RET}"
|
||||
echo ""
|
||||
echo " Master fingerprint:"
|
||||
salt-key -F master 2>/dev/null | grep -A1 "master.pub" || echo " (not yet generated — restart may be needed)"
|
||||
echo ""
|
||||
echo " Next steps:"
|
||||
echo " salt-key -L # List pending keys"
|
||||
echo " salt-key -a <minion_id> # Accept a minion key"
|
||||
echo " salt '*' test.ping # Test connectivity"
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "minion" || "$MODE" == "both" ]]; then
|
||||
local minion_id
|
||||
minion_id="${MINION_ID:-$(hostname -f 2>/dev/null || hostname)}"
|
||||
echo ""
|
||||
echo " Minion:"
|
||||
echo " Config: /etc/salt/minion"
|
||||
echo " Master: ${MASTER_IP}"
|
||||
echo " Minion ID: ${minion_id}"
|
||||
echo ""
|
||||
echo " Next steps:"
|
||||
echo " salt-call test.ping # Test master connectivity"
|
||||
if [[ "$AUTO_ACCEPT" != true ]]; then
|
||||
echo " (on master) salt-key -a ${minion_id}"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
}
|
||||
|
||||
parse_arguments() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--mode)
|
||||
MODE="$2"
|
||||
if [[ "$MODE" != "master" && "$MODE" != "minion" && "$MODE" != "both" ]]; then
|
||||
log_error "Mode must be 'master', 'minion', or 'both'"
|
||||
exit 1
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
--master-ip)
|
||||
MASTER_IP="$2"
|
||||
shift 2
|
||||
;;
|
||||
--minion-id)
|
||||
MINION_ID="$2"
|
||||
shift 2
|
||||
;;
|
||||
--auto-accept)
|
||||
AUTO_ACCEPT=true
|
||||
shift
|
||||
;;
|
||||
--salt-version)
|
||||
SALT_VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--yes)
|
||||
AUTO_YES=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
validate_requirements() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "This script must be run as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$MODE" ]]; then
|
||||
log_error "--mode is required (master, minion, or both)"
|
||||
show_help >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "minion" || "$MODE" == "both" ]]; then
|
||||
if [[ -z "$MASTER_IP" ]]; then
|
||||
log_error "--master-ip is required for minion/both modes"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
detect_os
|
||||
}
|
||||
|
||||
main() {
|
||||
parse_arguments "$@"
|
||||
validate_requirements
|
||||
|
||||
echo "============================================"
|
||||
echo " Salt Setup"
|
||||
echo " Mode: $MODE"
|
||||
echo " OS: $OS_FAMILY ($PKG_MANAGER)"
|
||||
if [[ -n "$MASTER_IP" ]]; then
|
||||
echo " Master: $MASTER_IP"
|
||||
fi
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
if [[ "$AUTO_YES" != true ]]; then
|
||||
echo "Press Enter to continue, or Ctrl+C to abort..."
|
||||
read -r
|
||||
fi
|
||||
|
||||
case "$OS_FAMILY" in
|
||||
debian) add_salt_repo_debian ;;
|
||||
rhel) add_salt_repo_rhel ;;
|
||||
esac
|
||||
|
||||
if [[ "$MODE" == "master" || "$MODE" == "both" ]]; then
|
||||
install_master
|
||||
configure_master
|
||||
create_directory_structure
|
||||
open_firewall_ports
|
||||
start_service salt-master
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "minion" || "$MODE" == "both" ]]; then
|
||||
install_minion
|
||||
configure_minion
|
||||
start_service salt-minion
|
||||
fi
|
||||
|
||||
show_summary
|
||||
|
||||
debug_echo "Script completed successfully"
|
||||
}
|
||||
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Reference in New Issue
Block a user