193 lines
4.1 KiB
Bash
Executable file
193 lines
4.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Common utilities for shepherd scripts
|
|
# Logging, error handling, and sudo helpers
|
|
|
|
set -euo pipefail
|
|
|
|
# Colors for terminal output (disabled if not a tty)
|
|
if [[ -t 1 ]]; then
|
|
RED='\033[0;31m'
|
|
YELLOW='\033[0;33m'
|
|
GREEN='\033[0;32m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
else
|
|
RED=''
|
|
YELLOW=''
|
|
GREEN=''
|
|
BLUE=''
|
|
NC=''
|
|
fi
|
|
|
|
# Logging functions
|
|
info() {
|
|
echo -e "${BLUE}[INFO]${NC} $*" >&2
|
|
}
|
|
|
|
warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $*" >&2
|
|
}
|
|
|
|
error() {
|
|
echo -e "${RED}[ERROR]${NC} $*" >&2
|
|
}
|
|
|
|
success() {
|
|
echo -e "${GREEN}[OK]${NC} $*" >&2
|
|
}
|
|
|
|
die() {
|
|
error "$@"
|
|
exit 1
|
|
}
|
|
|
|
# Check if running as root
|
|
is_root() {
|
|
[[ $EUID -eq 0 ]]
|
|
}
|
|
|
|
# Require root or exit
|
|
require_root() {
|
|
if ! is_root; then
|
|
die "This command must be run as root (use sudo)"
|
|
fi
|
|
}
|
|
|
|
# Run a command with sudo if not already root
|
|
maybe_sudo() {
|
|
if is_root; then
|
|
"$@"
|
|
else
|
|
sudo "$@"
|
|
fi
|
|
}
|
|
|
|
# Check if a command exists
|
|
command_exists() {
|
|
command -v "$1" &>/dev/null
|
|
}
|
|
|
|
# Require a command or exit with helpful message
|
|
require_command() {
|
|
local cmd="$1"
|
|
local pkg="${2:-$1}"
|
|
if ! command_exists "$cmd"; then
|
|
die "Required command '$cmd' not found. Install it with: apt install $pkg"
|
|
fi
|
|
}
|
|
|
|
# Get the repository root directory
|
|
get_repo_root() {
|
|
local script_dir
|
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
(cd "$script_dir/../.." && pwd)
|
|
}
|
|
|
|
# Verify we're in the shepherd repository
|
|
verify_repo() {
|
|
local repo_root
|
|
repo_root="$(get_repo_root)"
|
|
if [[ ! -f "$repo_root/Cargo.toml" ]]; then
|
|
die "Not in shepherd repository (Cargo.toml not found at $repo_root)"
|
|
fi
|
|
# Check it's the right project
|
|
if ! grep -q 'shepherd-launcher-ui' "$repo_root/Cargo.toml" 2>/dev/null; then
|
|
die "This doesn't appear to be the shepherd-launcher repository"
|
|
fi
|
|
}
|
|
|
|
# Check Ubuntu version and warn if not supported
|
|
check_ubuntu_version() {
|
|
local min_version="${1:-25.10}"
|
|
|
|
if [[ ! -f /etc/os-release ]]; then
|
|
warn "Cannot determine OS version (not Linux or missing /etc/os-release)"
|
|
return 0
|
|
fi
|
|
|
|
# shellcheck source=/dev/null
|
|
source /etc/os-release
|
|
|
|
if [[ "${ID:-}" != "ubuntu" ]]; then
|
|
warn "This system is not Ubuntu (detected: ${ID:-unknown}). Some features may not work."
|
|
return 0
|
|
fi
|
|
|
|
local version="${VERSION_ID:-0}"
|
|
if [[ "$(printf '%s\n' "$min_version" "$version" | sort -V | head -n1)" != "$min_version" ]]; then
|
|
warn "Ubuntu version $version detected. Recommended: $min_version or higher."
|
|
fi
|
|
}
|
|
|
|
# Safe file backup - creates timestamped backup
|
|
backup_file() {
|
|
local file="$1"
|
|
local backup_dir="${2:-}"
|
|
|
|
if [[ ! -e "$file" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
local timestamp
|
|
timestamp="$(date +%Y%m%d_%H%M%S)"
|
|
local backup_name
|
|
|
|
if [[ -n "$backup_dir" ]]; then
|
|
mkdir -p "$backup_dir"
|
|
backup_name="$backup_dir/$(basename "$file").$timestamp"
|
|
else
|
|
backup_name="$file.$timestamp.bak"
|
|
fi
|
|
|
|
cp -a "$file" "$backup_name"
|
|
echo "$backup_name"
|
|
}
|
|
|
|
# Create directory with proper permissions
|
|
ensure_dir() {
|
|
local dir="$1"
|
|
local mode="${2:-0755}"
|
|
local owner="${3:-}"
|
|
|
|
if [[ ! -d "$dir" ]]; then
|
|
mkdir -p "$dir"
|
|
chmod "$mode" "$dir"
|
|
if [[ -n "$owner" ]]; then
|
|
chown "$owner" "$dir"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Validate username exists
|
|
validate_user() {
|
|
local user="$1"
|
|
if ! id "$user" &>/dev/null; then
|
|
die "User '$user' does not exist"
|
|
fi
|
|
}
|
|
|
|
# Get user's home directory
|
|
get_user_home() {
|
|
local user="$1"
|
|
getent passwd "$user" | cut -d: -f6
|
|
}
|
|
|
|
# Kill processes matching a pattern (silent if none found)
|
|
kill_matching() {
|
|
local pattern="$1"
|
|
pkill -f "$pattern" 2>/dev/null || true
|
|
}
|
|
|
|
# Wait for a file/socket to appear
|
|
wait_for_file() {
|
|
local file="$1"
|
|
local timeout="${2:-30}"
|
|
local elapsed=0
|
|
|
|
while [[ ! -e "$file" ]] && [[ $elapsed -lt $timeout ]]; do
|
|
sleep 0.5
|
|
elapsed=$((elapsed + 1))
|
|
done
|
|
|
|
[[ -e "$file" ]]
|
|
}
|