Initial commit: networktuning.sh, gitlab-upgrade.sh, CI pipeline
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:
@@ -0,0 +1,95 @@
|
||||
###############################################################################
|
||||
# .gitlab-ci.yml — CI pipeline for bash script testing
|
||||
#
|
||||
# Stages:
|
||||
# 1. lint — ShellCheck static analysis + bash syntax check
|
||||
# 2. test — Run --help and --dry-run in Ubuntu and RHEL containers
|
||||
###############################################################################
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
|
||||
variables:
|
||||
SHELLCHECK_SEVERITY: "warning"
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Lint Stage
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
shellcheck:
|
||||
stage: lint
|
||||
image: koalaman/shellcheck-alpine:stable
|
||||
script:
|
||||
- echo "Running ShellCheck on all .sh files..."
|
||||
- find . -name "*.sh" -not -path "./.git/*" -print0 |
|
||||
xargs -0 -r shellcheck --severity="$SHELLCHECK_SEVERITY" --format=tty
|
||||
tags:
|
||||
- docker
|
||||
|
||||
bash-syntax:
|
||||
stage: lint
|
||||
image: bash:5
|
||||
script:
|
||||
- echo "Checking bash syntax (bash -n)..."
|
||||
- |
|
||||
errors=0
|
||||
for script in $(find . -name "*.sh" -not -path "./.git/*"); do
|
||||
if ! bash -n "$script" 2>&1; then
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
done
|
||||
if [ "$errors" -gt 0 ]; then
|
||||
echo "FAILED: $errors script(s) have syntax errors"
|
||||
exit 1
|
||||
fi
|
||||
echo "All scripts pass syntax check"
|
||||
tags:
|
||||
- docker
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Test Stage — Ubuntu
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
test-ubuntu:
|
||||
stage: test
|
||||
image: ubuntu:24.04
|
||||
before_script:
|
||||
- apt-get update -qq
|
||||
- apt-get install -y -qq procps iproute2 kmod >/dev/null 2>&1
|
||||
script:
|
||||
- echo "=== Testing on Ubuntu 24.04 ==="
|
||||
- |
|
||||
for script in $(find . -maxdepth 1 -name "*.sh" -not -path "./.git/*"); do
|
||||
echo ""
|
||||
echo "--- $(basename "$script") --help ---"
|
||||
bash "$script" --help 2>&1 || true
|
||||
done
|
||||
- echo ""
|
||||
- echo "--- networktuning.sh --dry-run ---"
|
||||
- bash networktuning.sh --dry-run 2>&1 || true
|
||||
tags:
|
||||
- docker
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Test Stage — RHEL
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
test-rhel:
|
||||
stage: test
|
||||
image: rockylinux:9
|
||||
before_script:
|
||||
- dnf install -y -q procps iproute kmod >/dev/null 2>&1
|
||||
script:
|
||||
- echo "=== Testing on Rocky Linux 9 ==="
|
||||
- |
|
||||
for script in $(find . -maxdepth 1 -name "*.sh" -not -path "./.git/*"); do
|
||||
echo ""
|
||||
echo "--- $(basename "$script") --help ---"
|
||||
bash "$script" --help 2>&1 || true
|
||||
done
|
||||
- echo ""
|
||||
- echo "--- networktuning.sh --dry-run ---"
|
||||
- bash networktuning.sh --dry-run 2>&1 || true
|
||||
tags:
|
||||
- docker
|
||||
@@ -0,0 +1,760 @@
|
||||
#!/bin/bash
|
||||
|
||||
################################################
|
||||
#### GitLab Upgrade Automation ####
|
||||
#### Multi-stop upgrade with air-gapped ####
|
||||
#### environment support ####
|
||||
#### ####
|
||||
#### Author: Phil Connor ####
|
||||
#### Contact: contact@mylinux.work ####
|
||||
#### Version: 1.00-030526 ####
|
||||
################################################
|
||||
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
readonly SCRIPT_NAME
|
||||
|
||||
# Required version stops (as of 2026)
|
||||
readonly VERSION_STOPS=(
|
||||
"14.0.12"
|
||||
"14.3.6"
|
||||
"14.9.5"
|
||||
"14.10.5"
|
||||
"15.0.5"
|
||||
"15.4.6"
|
||||
"15.11.13"
|
||||
"16.0.9"
|
||||
"16.3.8"
|
||||
"16.7.9"
|
||||
"16.11.10"
|
||||
"17.0.8"
|
||||
"17.3.7"
|
||||
"17.5.5"
|
||||
"17.8.7"
|
||||
"18.0.1"
|
||||
)
|
||||
|
||||
# Default configuration
|
||||
readonly DEFAULT_PACKAGE_DIR="/var/opt/gitlab/upgrade-packages"
|
||||
readonly DEFAULT_BACKUP_DIR="/var/opt/gitlab/backups"
|
||||
readonly DEFAULT_MIN_DISK_GB=5
|
||||
readonly DEFAULT_MIGRATION_TIMEOUT=3600
|
||||
readonly DEFAULT_MIGRATION_POLL_INTERVAL=30
|
||||
|
||||
# Configuration variables (can be overridden by environment)
|
||||
PACKAGE_DIR=${PACKAGE_DIR:-$DEFAULT_PACKAGE_DIR}
|
||||
BACKUP_DIR=${BACKUP_DIR:-$DEFAULT_BACKUP_DIR}
|
||||
MIN_DISK_GB=${MIN_DISK_GB:-$DEFAULT_MIN_DISK_GB}
|
||||
MIGRATION_TIMEOUT=${MIGRATION_TIMEOUT:-$DEFAULT_MIGRATION_TIMEOUT}
|
||||
MIGRATION_POLL_INTERVAL=${MIGRATION_POLL_INTERVAL:-$DEFAULT_MIGRATION_POLL_INTERVAL}
|
||||
DEBUG=${DEBUG:-}
|
||||
|
||||
# Runtime flags
|
||||
EDITION=""
|
||||
MODE="upgrade"
|
||||
AUTO_YES=false
|
||||
SKIP_BACKUP=false
|
||||
TARGET_VERSION=""
|
||||
PKG_MANAGER=""
|
||||
OS_FAMILY=""
|
||||
CURRENT_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 multi-stop GitLab upgrades with air-gapped environment support.
|
||||
|
||||
Handles required version stops from 14.x through 18.x, background migration
|
||||
checks, backups, health verification, and supports both online and offline
|
||||
(air-gapped) installations.
|
||||
|
||||
MODES:
|
||||
(default) Perform the upgrade (online or offline)
|
||||
--download-only Download all packages for the upgrade path to PACKAGE_DIR
|
||||
--list-stops Show the upgrade path for your current version and exit
|
||||
|
||||
OPTIONS:
|
||||
--target VERSION Target GitLab version (default: latest in stop list)
|
||||
--edition ce|ee GitLab edition (default: auto-detect from installed package)
|
||||
--offline Install from local packages in PACKAGE_DIR instead of repos
|
||||
--yes Skip confirmation prompts between stops
|
||||
--skip-backup Skip the pre-upgrade backup (not recommended)
|
||||
--help, -h Show this help message
|
||||
|
||||
ENVIRONMENT VARIABLES:
|
||||
PACKAGE_DIR Directory for downloaded/offline packages (default: $DEFAULT_PACKAGE_DIR)
|
||||
BACKUP_DIR Backup directory (default: $DEFAULT_BACKUP_DIR)
|
||||
MIN_DISK_GB Minimum free disk space in GB (default: $DEFAULT_MIN_DISK_GB)
|
||||
MIGRATION_TIMEOUT Max seconds to wait for background migrations (default: $DEFAULT_MIGRATION_TIMEOUT)
|
||||
MIGRATION_POLL_INTERVAL Seconds between migration status checks (default: $DEFAULT_MIGRATION_POLL_INTERVAL)
|
||||
DEBUG Enable debug output
|
||||
|
||||
EXAMPLES:
|
||||
# Show what stops are needed
|
||||
$SCRIPT_NAME --list-stops
|
||||
|
||||
# Upgrade to latest (online)
|
||||
sudo $SCRIPT_NAME --yes
|
||||
|
||||
# Upgrade to a specific version
|
||||
sudo $SCRIPT_NAME --target 17.5.5
|
||||
|
||||
# Download packages for air-gapped transfer
|
||||
sudo $SCRIPT_NAME --download-only --target 17.5.5
|
||||
|
||||
# Install from downloaded packages (air-gapped)
|
||||
sudo $SCRIPT_NAME --offline --yes
|
||||
|
||||
# Force EE edition
|
||||
sudo $SCRIPT_NAME --edition ee --yes
|
||||
EOF
|
||||
}
|
||||
|
||||
detect_os() {
|
||||
if [[ -f /etc/os-release ]]; then
|
||||
# shellcheck disable=SC1091
|
||||
source /etc/os-release
|
||||
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)"
|
||||
}
|
||||
|
||||
detect_edition() {
|
||||
if [[ -n "$EDITION" ]]; then
|
||||
debug_echo "Edition set by flag: $EDITION"
|
||||
return
|
||||
fi
|
||||
|
||||
if command -v dpkg >/dev/null 2>&1; then
|
||||
if dpkg -l gitlab-ee 2>/dev/null | grep -q "^ii"; then
|
||||
EDITION="ee"
|
||||
elif dpkg -l gitlab-ce 2>/dev/null | grep -q "^ii"; then
|
||||
EDITION="ce"
|
||||
fi
|
||||
elif command -v rpm >/dev/null 2>&1; then
|
||||
if rpm -q gitlab-ee >/dev/null 2>&1; then
|
||||
EDITION="ee"
|
||||
elif rpm -q gitlab-ce >/dev/null 2>&1; then
|
||||
EDITION="ce"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$EDITION" ]]; then
|
||||
log_error "Cannot detect GitLab edition. Use --edition ce|ee"
|
||||
exit 1
|
||||
fi
|
||||
debug_echo "Detected edition: $EDITION"
|
||||
}
|
||||
|
||||
get_current_version() {
|
||||
local version_file="/opt/gitlab/embedded/service/gitlab-rails/VERSION"
|
||||
if [[ -f "$version_file" ]]; then
|
||||
CURRENT_VERSION=$(cat "$version_file")
|
||||
else
|
||||
log_error "GitLab does not appear to be installed ($version_file not found)"
|
||||
exit 1
|
||||
fi
|
||||
debug_echo "Current version: $CURRENT_VERSION"
|
||||
}
|
||||
|
||||
version_compare() {
|
||||
# Returns 0 if $1 < $2, 1 if equal, 2 if $1 > $2
|
||||
if [[ "$1" == "$2" ]]; then
|
||||
echo 1
|
||||
return
|
||||
fi
|
||||
|
||||
local IFS=.
|
||||
local i
|
||||
read -ra ver1 <<< "$1"
|
||||
read -ra ver2 <<< "$2"
|
||||
|
||||
for ((i = 0; i < ${#ver1[@]}; i++)); do
|
||||
local v1=${ver1[i]:-0}
|
||||
local v2=${ver2[i]:-0}
|
||||
if ((v1 < v2)); then
|
||||
echo 0
|
||||
return
|
||||
elif ((v1 > v2)); then
|
||||
echo 2
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo 1
|
||||
}
|
||||
|
||||
get_upgrade_path() {
|
||||
local current="$1"
|
||||
local target="$2"
|
||||
local path=()
|
||||
|
||||
for stop in "${VERSION_STOPS[@]}"; do
|
||||
local cmp_current
|
||||
cmp_current=$(version_compare "$current" "$stop")
|
||||
if [[ "$cmp_current" == "0" ]]; then
|
||||
# Current < stop, so this stop is needed
|
||||
if [[ -n "$target" ]]; then
|
||||
local cmp_target
|
||||
cmp_target=$(version_compare "$stop" "$target")
|
||||
if [[ "$cmp_target" == "0" || "$cmp_target" == "1" ]]; then
|
||||
path+=("$stop")
|
||||
fi
|
||||
else
|
||||
path+=("$stop")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# If target is specified and not already in the path, add it
|
||||
if [[ -n "$target" ]]; then
|
||||
local last="${path[*]: -1}"
|
||||
if [[ "$last" != "$target" ]]; then
|
||||
local in_stops=false
|
||||
for stop in "${VERSION_STOPS[@]}"; do
|
||||
if [[ "$stop" == "$target" ]]; then
|
||||
in_stops=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [[ "$in_stops" == false ]]; then
|
||||
path+=("$target")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "${path[@]}"
|
||||
}
|
||||
|
||||
format_package_name() {
|
||||
local version="$1"
|
||||
if [[ "$OS_FAMILY" == "debian" ]]; then
|
||||
echo "gitlab-${EDITION}=${version}-${EDITION}.0"
|
||||
else
|
||||
echo "gitlab-${EDITION}-${version}-${EDITION}.0"
|
||||
fi
|
||||
}
|
||||
|
||||
format_package_filename() {
|
||||
local version="$1"
|
||||
if [[ "$OS_FAMILY" == "debian" ]]; then
|
||||
echo "gitlab-${EDITION}_${version}-${EDITION}.0_amd64.deb"
|
||||
else
|
||||
echo "gitlab-${EDITION}-${version}-${EDITION}.0.el*.x86_64.rpm"
|
||||
fi
|
||||
}
|
||||
|
||||
check_disk_space() {
|
||||
local check_path="$1"
|
||||
local required_gb="$2"
|
||||
|
||||
local available_kb
|
||||
available_kb=$(df --output=avail "$check_path" 2>/dev/null | tail -1 | tr -d ' ')
|
||||
local available_gb=$((available_kb / 1024 / 1024))
|
||||
|
||||
if ((available_gb < required_gb)); then
|
||||
log_error "Insufficient disk space on $check_path: ${available_gb}GB available, ${required_gb}GB required"
|
||||
return 1
|
||||
fi
|
||||
debug_echo "Disk space on $check_path: ${available_gb}GB available (${required_gb}GB required)"
|
||||
return 0
|
||||
}
|
||||
|
||||
check_background_migrations() {
|
||||
log_info "Checking background migrations..."
|
||||
|
||||
local pending
|
||||
pending=$(sudo gitlab-rails runner -e production \
|
||||
'puts Gitlab::Database::BackgroundMigration::BatchedMigration.queued.count' 2>/dev/null) || {
|
||||
log_warn "Could not check background migrations via Rails runner"
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "$pending" != "0" ]]; then
|
||||
log_warn "$pending background migration(s) still pending"
|
||||
log_info "Attempting to finalize background migrations..."
|
||||
sudo gitlab-rake db:background_migrations:finalize
|
||||
|
||||
local elapsed=0
|
||||
while ((elapsed < MIGRATION_TIMEOUT)); do
|
||||
pending=$(sudo gitlab-rails runner -e production \
|
||||
'puts Gitlab::Database::BackgroundMigration::BatchedMigration.queued.count' 2>/dev/null) || break
|
||||
if [[ "$pending" == "0" ]]; then
|
||||
log_info "All background migrations complete"
|
||||
return 0
|
||||
fi
|
||||
log_info "Waiting for $pending migration(s)... (${elapsed}s / ${MIGRATION_TIMEOUT}s)"
|
||||
sleep "$MIGRATION_POLL_INTERVAL"
|
||||
elapsed=$((elapsed + MIGRATION_POLL_INTERVAL))
|
||||
done
|
||||
|
||||
if [[ "$pending" != "0" ]]; then
|
||||
log_error "Background migrations did not complete within ${MIGRATION_TIMEOUT}s"
|
||||
log_error "Run: sudo gitlab-rake db:background_migrations:status"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
log_info "No pending background migrations"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
create_backup() {
|
||||
if [[ "$SKIP_BACKUP" == true ]]; then
|
||||
log_warn "Skipping backup (--skip-backup specified)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Creating GitLab backup..."
|
||||
local backup_name
|
||||
backup_name="pre-upgrade-$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
check_disk_space "$BACKUP_DIR" "$MIN_DISK_GB" || return 1
|
||||
|
||||
sudo gitlab-backup create BACKUP="$backup_name" || {
|
||||
log_error "Backup failed"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Back up config files
|
||||
log_info "Backing up configuration files..."
|
||||
sudo cp /etc/gitlab/gitlab.rb "${BACKUP_DIR}/gitlab.rb.pre-upgrade.bak"
|
||||
sudo cp /etc/gitlab/gitlab-secrets.json "${BACKUP_DIR}/gitlab-secrets.json.pre-upgrade.bak"
|
||||
|
||||
log_info "Backup complete: $backup_name"
|
||||
return 0
|
||||
}
|
||||
|
||||
verify_health() {
|
||||
local version="$1"
|
||||
log_info "Verifying GitLab health after upgrade to $version..."
|
||||
|
||||
# Check service status
|
||||
if ! sudo gitlab-ctl status >/dev/null 2>&1; then
|
||||
log_error "gitlab-ctl status reports unhealthy services"
|
||||
sudo gitlab-ctl status
|
||||
return 1
|
||||
fi
|
||||
log_info "All services running"
|
||||
|
||||
# Verify installed version
|
||||
local installed
|
||||
installed=$(cat /opt/gitlab/embedded/service/gitlab-rails/VERSION 2>/dev/null)
|
||||
if [[ "$installed" != "$version" ]]; then
|
||||
log_warn "Expected version $version but found $installed"
|
||||
else
|
||||
log_info "Version confirmed: $installed"
|
||||
fi
|
||||
|
||||
# Run health check
|
||||
log_info "Running gitlab:check..."
|
||||
if ! sudo gitlab-rake gitlab:check SANITIZE=true >/dev/null 2>&1; then
|
||||
log_warn "gitlab:check reported issues — review manually"
|
||||
else
|
||||
log_info "Health check passed"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
install_package_online() {
|
||||
local version="$1"
|
||||
local pkg_name
|
||||
pkg_name=$(format_package_name "$version")
|
||||
|
||||
log_info "Installing $pkg_name (online)..."
|
||||
|
||||
case "$PKG_MANAGER" in
|
||||
apt)
|
||||
sudo apt-get update -qq || true
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y "$pkg_name" || return 1
|
||||
;;
|
||||
dnf)
|
||||
sudo dnf install -y "$pkg_name" || return 1
|
||||
;;
|
||||
yum)
|
||||
sudo yum install -y "$pkg_name" || return 1
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
install_package_offline() {
|
||||
local version="$1"
|
||||
local pattern
|
||||
pattern=$(format_package_filename "$version")
|
||||
|
||||
# Find the package file
|
||||
local pkg_file
|
||||
pkg_file=$(find "$PACKAGE_DIR" -name "$pattern" -type f 2>/dev/null | head -1)
|
||||
|
||||
if [[ -z "$pkg_file" ]]; then
|
||||
log_error "Package not found in $PACKAGE_DIR matching: $pattern"
|
||||
log_error "Run --download-only first to fetch packages"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Installing from local package: $pkg_file"
|
||||
|
||||
case "$OS_FAMILY" in
|
||||
debian)
|
||||
sudo DEBIAN_FRONTEND=noninteractive dpkg -i "$pkg_file" || {
|
||||
log_info "Resolving dependencies..."
|
||||
sudo apt-get install -f -y || return 1
|
||||
}
|
||||
;;
|
||||
rhel)
|
||||
if [[ "$PKG_MANAGER" == "dnf" ]]; then
|
||||
sudo dnf install -y "$pkg_file" || return 1
|
||||
else
|
||||
sudo yum localinstall -y "$pkg_file" || return 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
download_packages() {
|
||||
local -a path
|
||||
IFS=' ' read -ra path <<< "$(get_upgrade_path "$CURRENT_VERSION" "$TARGET_VERSION")"
|
||||
|
||||
if [[ ${#path[@]} -eq 0 ]]; then
|
||||
log_info "No upgrades needed — already at or above target version"
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$PACKAGE_DIR"
|
||||
|
||||
log_info "Downloading ${#path[@]} package(s) to $PACKAGE_DIR"
|
||||
log_info "Upgrade path: ${path[*]}"
|
||||
|
||||
local failed=0
|
||||
for version in "${path[@]}"; do
|
||||
local pkg_name
|
||||
pkg_name=$(format_package_name "$version")
|
||||
log_info "Downloading: $pkg_name"
|
||||
|
||||
case "$PKG_MANAGER" in
|
||||
apt)
|
||||
(cd "$PACKAGE_DIR" && sudo apt-get download "$pkg_name" 2>/dev/null) || {
|
||||
# Try apt-get with download option
|
||||
sudo apt-get install --download-only -y -o Dir::Cache::Archives="$PACKAGE_DIR" "$pkg_name" 2>/dev/null || {
|
||||
log_error "Failed to download $pkg_name"
|
||||
failed=$((failed + 1))
|
||||
}
|
||||
}
|
||||
;;
|
||||
dnf)
|
||||
sudo dnf download --destdir="$PACKAGE_DIR" "$pkg_name" || {
|
||||
log_error "Failed to download $pkg_name"
|
||||
failed=$((failed + 1))
|
||||
}
|
||||
;;
|
||||
yum)
|
||||
sudo yumdownloader --destdir="$PACKAGE_DIR" "$pkg_name" || {
|
||||
log_error "Failed to download $pkg_name"
|
||||
failed=$((failed + 1))
|
||||
}
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if ((failed > 0)); then
|
||||
log_error "$failed package(s) failed to download"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "All packages downloaded to $PACKAGE_DIR"
|
||||
log_info "Transfer this directory to your air-gapped system, then run:"
|
||||
log_info " PACKAGE_DIR=$PACKAGE_DIR $SCRIPT_NAME --offline"
|
||||
return 0
|
||||
}
|
||||
|
||||
list_stops() {
|
||||
local -a path
|
||||
IFS=' ' read -ra path <<< "$(get_upgrade_path "$CURRENT_VERSION" "$TARGET_VERSION")"
|
||||
|
||||
echo "Current version: $CURRENT_VERSION"
|
||||
echo "Edition: gitlab-$EDITION"
|
||||
echo "OS: $OS_FAMILY ($PKG_MANAGER)"
|
||||
if [[ -n "$TARGET_VERSION" ]]; then
|
||||
echo "Target version: $TARGET_VERSION"
|
||||
else
|
||||
echo "Target version: latest (${VERSION_STOPS[-1]})"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
if [[ ${#path[@]} -eq 0 ]]; then
|
||||
echo "No upgrades needed — already at or above target version."
|
||||
return
|
||||
fi
|
||||
|
||||
echo "Required upgrade path (${#path[@]} stops):"
|
||||
echo ""
|
||||
echo " $CURRENT_VERSION (current)"
|
||||
for stop in "${path[@]}"; do
|
||||
echo " → $stop"
|
||||
done
|
||||
echo ""
|
||||
echo "Estimated time: $((${#path[@]} * 15))-$((${#path[@]} * 30)) minutes (varies by database size)"
|
||||
}
|
||||
|
||||
run_upgrade() {
|
||||
local -a path
|
||||
IFS=' ' read -ra path <<< "$(get_upgrade_path "$CURRENT_VERSION" "$TARGET_VERSION")"
|
||||
|
||||
if [[ ${#path[@]} -eq 0 ]]; then
|
||||
log_info "No upgrades needed — already at or above target version"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local offline=false
|
||||
if [[ "$MODE" == "offline" ]]; then
|
||||
offline=true
|
||||
fi
|
||||
|
||||
echo "============================================"
|
||||
echo " GitLab Upgrade: $CURRENT_VERSION → ${path[-1]}"
|
||||
echo " Edition: gitlab-$EDITION"
|
||||
echo " Stops: ${#path[@]}"
|
||||
echo " Mode: $(if $offline; then echo "offline"; else echo "online"; fi)"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
|
||||
if [[ "$AUTO_YES" != true ]]; then
|
||||
echo "Press Enter to start the upgrade, or Ctrl+C to abort..."
|
||||
read -r
|
||||
fi
|
||||
|
||||
# Pre-upgrade checks
|
||||
check_disk_space "/opt/gitlab" "$MIN_DISK_GB" || exit 1
|
||||
check_disk_space "/var/opt/gitlab" "$MIN_DISK_GB" || exit 1
|
||||
check_disk_space "/tmp" 1 || exit 1
|
||||
|
||||
# Create backup before starting
|
||||
create_backup || exit 1
|
||||
|
||||
# Check initial background migrations
|
||||
check_background_migrations || exit 1
|
||||
|
||||
local stop_num=0
|
||||
local total=${#path[@]}
|
||||
for version in "${path[@]}"; do
|
||||
stop_num=$((stop_num + 1))
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " Stop $stop_num/$total: Upgrading to $version"
|
||||
echo "============================================"
|
||||
|
||||
# Check background migrations before this stop
|
||||
if ((stop_num > 1)); then
|
||||
check_background_migrations || {
|
||||
log_error "Cannot proceed — background migrations incomplete"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Install the package
|
||||
if $offline; then
|
||||
install_package_offline "$version" || {
|
||||
log_error "Failed to install $version from local packages"
|
||||
exit 1
|
||||
}
|
||||
else
|
||||
install_package_online "$version" || {
|
||||
log_error "Failed to install $version"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Verify health
|
||||
verify_health "$version" || {
|
||||
log_warn "Health verification had issues — review before continuing"
|
||||
}
|
||||
|
||||
# Check for new background migrations
|
||||
log_info "Checking for new background migrations..."
|
||||
sudo gitlab-rake db:background_migrations:status 2>/dev/null || true
|
||||
|
||||
if ((stop_num < total)); then
|
||||
if [[ "$AUTO_YES" != true ]]; then
|
||||
echo ""
|
||||
echo "Stop $stop_num/$total complete. Press Enter for next stop, or Ctrl+C to abort..."
|
||||
read -r
|
||||
else
|
||||
log_info "Stop $stop_num/$total complete — proceeding to next stop"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " Upgrade Complete"
|
||||
echo "============================================"
|
||||
local final_version
|
||||
final_version=$(cat /opt/gitlab/embedded/service/gitlab-rails/VERSION 2>/dev/null)
|
||||
echo " Final version: $final_version"
|
||||
echo ""
|
||||
echo "Post-upgrade tasks:"
|
||||
echo " 1. sudo gitlab-rake gitlab:check SANITIZE=true"
|
||||
echo " 2. sudo gitlab-rake db:background_migrations:status"
|
||||
echo " 3. Verify CI/CD pipelines and runners"
|
||||
echo " 4. Check container registry functionality"
|
||||
echo " 5. Remove maintenance notification"
|
||||
}
|
||||
|
||||
parse_arguments() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--download-only)
|
||||
MODE="download"
|
||||
shift
|
||||
;;
|
||||
--offline)
|
||||
MODE="offline"
|
||||
shift
|
||||
;;
|
||||
--list-stops)
|
||||
MODE="list"
|
||||
shift
|
||||
;;
|
||||
--target)
|
||||
TARGET_VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
--edition)
|
||||
EDITION="$2"
|
||||
if [[ "$EDITION" != "ce" && "$EDITION" != "ee" ]]; then
|
||||
log_error "Edition must be 'ce' or 'ee'"
|
||||
exit 1
|
||||
fi
|
||||
shift 2
|
||||
;;
|
||||
--yes)
|
||||
AUTO_YES=true
|
||||
shift
|
||||
;;
|
||||
--skip-backup)
|
||||
SKIP_BACKUP=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
validate_requirements() {
|
||||
# Must run as root for upgrade/download operations
|
||||
if [[ "$MODE" != "list" && $EUID -ne 0 ]]; then
|
||||
log_error "This script must be run as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
detect_os
|
||||
detect_edition
|
||||
get_current_version
|
||||
|
||||
# Validate target version format if specified
|
||||
if [[ -n "$TARGET_VERSION" ]]; then
|
||||
if ! [[ "$TARGET_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
log_error "Invalid target version format: $TARGET_VERSION (expected: X.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# For offline mode, verify package directory exists
|
||||
if [[ "$MODE" == "offline" ]]; then
|
||||
if [[ ! -d "$PACKAGE_DIR" ]]; then
|
||||
log_error "Package directory not found: $PACKAGE_DIR"
|
||||
log_error "Run --download-only first, then transfer packages to this directory"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
parse_arguments "$@"
|
||||
|
||||
# Allow --list-stops and --help without root
|
||||
if [[ "$MODE" == "list" ]]; then
|
||||
detect_os
|
||||
detect_edition
|
||||
get_current_version
|
||||
list_stops
|
||||
exit 0
|
||||
fi
|
||||
|
||||
validate_requirements
|
||||
|
||||
case "$MODE" in
|
||||
download)
|
||||
download_packages
|
||||
;;
|
||||
upgrade|offline)
|
||||
run_upgrade
|
||||
;;
|
||||
esac
|
||||
|
||||
debug_echo "Script completed successfully"
|
||||
}
|
||||
|
||||
# Execute main function if script is run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
@@ -0,0 +1,398 @@
|
||||
#!/bin/bash
|
||||
|
||||
######################################################################################
|
||||
#### Version 2.00 ####
|
||||
#### For questions or comments contact@mylinux.work ####
|
||||
#### Author : Phil Connor ####
|
||||
#### ####
|
||||
#### Notes : ####
|
||||
#### This script configures sysctl network and system tuning on Linux servers. ####
|
||||
#### It writes an idempotent drop-in file to /etc/sysctl.d/ so it can be run ####
|
||||
#### multiple times safely without duplicating settings. ####
|
||||
#### ####
|
||||
#### Use this script at your OWN risk. There is no guarantee whatsoever. ####
|
||||
#### ####
|
||||
#### Usage: ####
|
||||
#### networktuning.sh - Standard tuning (HDD) ####
|
||||
#### networktuning.sh ssd - SSD-optimized dirty page settings ####
|
||||
#### networktuning.sh nvme - NVMe-optimized dirty page settings ####
|
||||
#### networktuning.sh --bbr - Enable BBR congestion control ####
|
||||
#### networktuning.sh ssd --bbr - SSD + BBR ####
|
||||
#### networktuning.sh --router - Enable IP forwarding ####
|
||||
#### networktuning.sh --dry-run - Show config without applying ####
|
||||
#### networktuning.sh --rollback - Restore previous configuration ####
|
||||
######################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
##########################
|
||||
#### System Variables ####
|
||||
##########################
|
||||
HOST=$(hostname)
|
||||
CONF_FILE="/etc/sysctl.d/99-network-tuning.conf"
|
||||
BACKUP_DIR="/etc/sysctl.d/backups"
|
||||
STORAGE_TYPE="hdd"
|
||||
ENABLE_BBR=false
|
||||
ENABLE_ROUTER=false
|
||||
DRY_RUN=false
|
||||
ROLLBACK=false
|
||||
|
||||
#########################
|
||||
#### Color Variables ####
|
||||
#########################
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
######################
|
||||
#### Root Check ####
|
||||
######################
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo -e "${RED}Error: This script must be run as root${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
##############################
|
||||
#### Parse CLI Arguments ####
|
||||
##############################
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
ssd) STORAGE_TYPE="ssd" ;;
|
||||
nvme) STORAGE_TYPE="nvme" ;;
|
||||
--bbr) ENABLE_BBR=true ;;
|
||||
--router) ENABLE_ROUTER=true ;;
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--rollback) ROLLBACK=true ;;
|
||||
--help|-h)
|
||||
echo "Usage: $(basename "$0") [ssd|nvme] [--bbr] [--router] [--dry-run] [--rollback]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " ssd Optimize dirty page settings for SSD storage"
|
||||
echo " nvme Optimize dirty page settings for NVMe storage"
|
||||
echo " --bbr Enable BBR congestion control (requires kernel 4.9+)"
|
||||
echo " --router Enable IP forwarding (IPv4 and IPv6)"
|
||||
echo " --dry-run Display the configuration without applying it"
|
||||
echo " --rollback Restore the previous configuration backup"
|
||||
echo " --help Show this help message"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown option: $arg${NC}"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
##################################
|
||||
#### Detect OS and OS Version ####
|
||||
##################################
|
||||
if [[ ! -f /etc/os-release ]]; then
|
||||
echo -e "${RED}Error: /etc/os-release not found. Unsupported system.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OS_ID=$(. /etc/os-release && echo "${ID}")
|
||||
OS_VERSION=$(. /etc/os-release && echo "${VERSION_ID}" | cut -d. -f1)
|
||||
|
||||
case "$OS_ID" in
|
||||
ubuntu|debian)
|
||||
PAKMGR="apt-get -y"
|
||||
;;
|
||||
centos|rhel|oracle|rocky|almalinux|fedora)
|
||||
if [[ "$OS_VERSION" -le 7 ]]; then
|
||||
PAKMGR="yum -y"
|
||||
else
|
||||
PAKMGR="dnf -y"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo -e "${YELLOW}Warning: Unrecognized OS '${OS_ID}'. Proceeding without package manager.${NC}"
|
||||
PAKMGR=""
|
||||
;;
|
||||
esac
|
||||
|
||||
################################
|
||||
#### Handle Rollback ####
|
||||
################################
|
||||
if [[ "$ROLLBACK" == true ]]; then
|
||||
LATEST_BACKUP=$(find "$BACKUP_DIR" -name "99-network-tuning.conf.*" -type f 2>/dev/null | sort -r | head -1)
|
||||
if [[ -z "$LATEST_BACKUP" ]]; then
|
||||
echo -e "${RED}Error: No backup found in $BACKUP_DIR${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}Restoring from: $LATEST_BACKUP${NC}"
|
||||
cp "$LATEST_BACKUP" "$CONF_FILE"
|
||||
sysctl --system > /dev/null 2>&1
|
||||
echo -e "${GREEN}Rollback complete. Settings applied.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
##########################
|
||||
#### Sysctl Variables ####
|
||||
##########################
|
||||
MEM_BYTES=$(awk '/MemTotal:/ { printf "%0.f",$2 * 1024}' /proc/meminfo)
|
||||
MEM_KB=$(awk '/MemTotal:/ { printf "%0.f",$2}' /proc/meminfo)
|
||||
MAX_ORPHAN=$((MEM_BYTES / 10 / 65536))
|
||||
FILE_MAX=$((MEM_BYTES / 4194304 * 256))
|
||||
MAX_TW=$((FILE_MAX * 2))
|
||||
MIN_FREE=$((MEM_KB / 100))
|
||||
|
||||
######################################
|
||||
#### Storage-based dirty settings ####
|
||||
######################################
|
||||
case "$STORAGE_TYPE" in
|
||||
hdd)
|
||||
VM_DIRTY_BG_RATIO=5
|
||||
VM_DIRTY_RATIO=15
|
||||
;;
|
||||
ssd)
|
||||
VM_DIRTY_BG_RATIO=3
|
||||
VM_DIRTY_RATIO=5
|
||||
;;
|
||||
nvme)
|
||||
VM_DIRTY_BG_RATIO=1
|
||||
VM_DIRTY_RATIO=3
|
||||
;;
|
||||
esac
|
||||
|
||||
####################################
|
||||
#### BBR availability check ####
|
||||
####################################
|
||||
if [[ "$ENABLE_BBR" == true ]]; then
|
||||
if ! modprobe tcp_bbr 2>/dev/null; then
|
||||
echo -e "${YELLOW}Warning: BBR kernel module not available. Skipping BBR configuration.${NC}"
|
||||
ENABLE_BBR=false
|
||||
fi
|
||||
fi
|
||||
|
||||
#################################
|
||||
#### Generate Configuration ####
|
||||
#################################
|
||||
echo "#######################################"
|
||||
echo "#### Network Tuning for $HOST"
|
||||
echo "#### Storage: $STORAGE_TYPE | BBR: $ENABLE_BBR | Router: $ENABLE_ROUTER"
|
||||
echo "#######################################"
|
||||
|
||||
CONFIG=$(cat << EOF
|
||||
###############################################################################
|
||||
# Network Tuning Configuration
|
||||
# Generated: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
# Host: $HOST
|
||||
# Storage: $STORAGE_TYPE | BBR: $ENABLE_BBR | Router: $ENABLE_ROUTER
|
||||
# Generator: networktuning.sh v2.00
|
||||
###############################################################################
|
||||
|
||||
############################
|
||||
#### Performance Tuning ####
|
||||
############################
|
||||
|
||||
# Enable SYN cookies for SYN flood protection
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
|
||||
# Basic TCP tuning
|
||||
net.ipv4.tcp_keepalive_time = 600
|
||||
net.ipv4.tcp_synack_retries = 3
|
||||
net.ipv4.tcp_syn_retries = 3
|
||||
|
||||
# RFC1337 — handle TIME-WAIT assassination hazards
|
||||
net.ipv4.tcp_rfc1337 = 1
|
||||
|
||||
# Defines the local port range used by TCP and UDP
|
||||
net.ipv4.ip_local_port_range = 1024 65535
|
||||
|
||||
# Log Martian packets with impossible addresses
|
||||
net.ipv4.conf.all.log_martians = 1
|
||||
net.ipv4.conf.default.log_martians = 1
|
||||
|
||||
# Enable window scaling (RFC1323)
|
||||
net.ipv4.tcp_window_scaling = 1
|
||||
|
||||
# Enable timestamps (RFC1323)
|
||||
net.ipv4.tcp_timestamps = 1
|
||||
|
||||
# Enable selective acknowledgments
|
||||
net.ipv4.tcp_sack = 1
|
||||
|
||||
# Allow TCP to send duplicate SACKs
|
||||
net.ipv4.tcp_dsack = 1
|
||||
|
||||
# Loose reverse path filtering (compatible with cloud/multihomed)
|
||||
net.ipv4.conf.default.rp_filter = 2
|
||||
net.ipv4.conf.all.rp_filter = 2
|
||||
|
||||
# Max remembered connection requests in SYN backlog
|
||||
net.ipv4.tcp_max_syn_backlog = 20000
|
||||
|
||||
# Max orphaned TCP sockets (calculated from system memory)
|
||||
net.ipv4.tcp_max_orphans = $MAX_ORPHAN
|
||||
|
||||
# Retries before killing an orphaned TCP connection
|
||||
net.ipv4.tcp_orphan_retries = 1
|
||||
|
||||
# Time to hold sockets in FIN-WAIT-2 state (seconds)
|
||||
net.ipv4.tcp_fin_timeout = 20
|
||||
|
||||
# Maximum TIME-WAIT sockets held simultaneously
|
||||
net.ipv4.tcp_max_tw_buckets = $MAX_TW
|
||||
|
||||
# Don't cache ssthresh from previous connections
|
||||
net.ipv4.tcp_no_metrics_save = 1
|
||||
|
||||
# Enable receive buffer auto-tuning
|
||||
net.ipv4.tcp_moderate_rcvbuf = 1
|
||||
|
||||
# TCP buffer auto-tuning limits (min default max bytes)
|
||||
net.ipv4.tcp_rmem = 4096 87380 16777216
|
||||
net.ipv4.tcp_wmem = 4096 65536 16777216
|
||||
|
||||
# Max socket buffer sizes
|
||||
net.core.rmem_max = 16777216
|
||||
net.core.wmem_max = 16777216
|
||||
|
||||
# Network device backlog queue length
|
||||
net.core.netdev_max_backlog = 2500
|
||||
|
||||
# Max pending connections in listen queue
|
||||
net.core.somaxconn = 65000
|
||||
|
||||
############################
|
||||
#### VM / Storage Tuning ###
|
||||
############################
|
||||
|
||||
# Prefer keeping processes in memory over swapping (1 = minimal swap)
|
||||
vm.swappiness = 1
|
||||
|
||||
# Dirty page writeback ratios (tuned for $STORAGE_TYPE)
|
||||
# Monitor with: grep -A 1 dirty /proc/vmstat
|
||||
vm.dirty_background_ratio = $VM_DIRTY_BG_RATIO
|
||||
vm.dirty_ratio = $VM_DIRTY_RATIO
|
||||
|
||||
# Required free memory (1% of physical RAM)
|
||||
vm.min_free_kbytes = $MIN_FREE
|
||||
|
||||
# System open file limit (calculated from system memory)
|
||||
fs.file-max = $FILE_MAX
|
||||
|
||||
############################
|
||||
#### Kernel Settings ####
|
||||
############################
|
||||
|
||||
# Kernel log levels
|
||||
kernel.printk = 4 4 1 7
|
||||
kernel.core_uses_pid = 1
|
||||
kernel.sysrq = 0
|
||||
|
||||
# Disable core dumps for setuid programs
|
||||
fs.suid_dumpable = 0
|
||||
|
||||
###########################
|
||||
#### Security Settings ####
|
||||
###########################
|
||||
|
||||
# Full ASLR (randomize stack, VDSO, mmap, heap)
|
||||
kernel.randomize_va_space = 2
|
||||
|
||||
# Don't accept ICMP redirects
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
net.ipv6.conf.default.accept_redirects = 0
|
||||
|
||||
# Don't send ICMP redirects
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
|
||||
# Don't accept IP source route packets
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.default.accept_source_route = 0
|
||||
net.ipv6.conf.all.accept_source_route = 0
|
||||
|
||||
# Ignore ICMP broadcasts and bogus error responses
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||||
|
||||
# IPv6 router advertisement hardening
|
||||
net.ipv6.conf.default.accept_ra_rtr_pref = 0
|
||||
net.ipv6.conf.all.accept_ra_rtr_pref = 0
|
||||
net.ipv6.conf.default.accept_ra_pinfo = 0
|
||||
net.ipv6.conf.all.accept_ra_pinfo = 0
|
||||
net.ipv6.conf.default.accept_ra_defrtr = 0
|
||||
net.ipv6.conf.all.accept_ra_defrtr = 0
|
||||
EOF
|
||||
)
|
||||
|
||||
# Append BBR congestion control if enabled
|
||||
if [[ "$ENABLE_BBR" == true ]]; then
|
||||
CONFIG+=$(cat << 'EOF'
|
||||
|
||||
|
||||
###########################
|
||||
#### BBR Congestion ####
|
||||
###########################
|
||||
|
||||
# Use fair queuing packet scheduler (required for BBR)
|
||||
net.core.default_qdisc = fq
|
||||
|
||||
# Enable BBR congestion control algorithm
|
||||
net.ipv4.tcp_congestion_control = bbr
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
# Append router/forwarding settings if enabled
|
||||
if [[ "$ENABLE_ROUTER" == true ]]; then
|
||||
CONFIG+=$(cat << 'EOF'
|
||||
|
||||
|
||||
###########################
|
||||
#### Router / Forward ####
|
||||
###########################
|
||||
|
||||
# Enable IP forwarding (IPv4 and IPv6)
|
||||
net.ipv4.ip_forward = 1
|
||||
net.ipv6.conf.all.forwarding = 1
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
##############################
|
||||
#### Apply Configuration ####
|
||||
##############################
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
echo ""
|
||||
echo -e "${YELLOW}--- Dry Run: Configuration Preview ---${NC}"
|
||||
echo "$CONFIG"
|
||||
echo -e "${YELLOW}--- End Preview ---${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create backup directory
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Backup existing config if present
|
||||
if [[ -f "$CONF_FILE" ]]; then
|
||||
cp "$CONF_FILE" "${BACKUP_DIR}/99-network-tuning.conf.$(date +%Y%m%d-%H%M%S)"
|
||||
echo -e "${GREEN}Backed up existing config to $BACKUP_DIR${NC}"
|
||||
fi
|
||||
|
||||
# Write configuration atomically
|
||||
TEMP_FILE=$(mktemp)
|
||||
echo "$CONFIG" > "$TEMP_FILE"
|
||||
|
||||
# Validate before installing
|
||||
if sysctl -p "$TEMP_FILE" > /dev/null 2>&1; then
|
||||
mv "$TEMP_FILE" "$CONF_FILE"
|
||||
chmod 644 "$CONF_FILE"
|
||||
sysctl --system > /dev/null 2>&1
|
||||
echo -e "${GREEN}Network tuning applied successfully to $CONF_FILE${NC}"
|
||||
else
|
||||
echo -e "${RED}Error: Configuration validation failed. No changes applied.${NC}"
|
||||
echo -e "${RED}Review errors above and check kernel compatibility.${NC}"
|
||||
rm -f "$TEMP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Done. Use 'sysctl --system' to verify or '$(basename "$0") --rollback' to revert.${NC}"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user